Leveling up with osquery for Workloads: Find PowerShell Remoting Enabled

May 31, 2022

Audit and Remediation provide direct access to osquery functionality within the VMware Carbon Black Cloud console to enable security, compliance, and IT teams to query over 2,000 individual attributes across endpoints and workloads. osquery can help teams with gathering information at scale across environments for IT and help desk operations, compliance and M&A reporting, incident response, and security investigations.  

Audit and Remediation allow administrators to ask questions about the environment across hardware, software, and network variables at scale. VMware Carbon Black Cloud Workload customers have access to the full Audit and Remediation capabilities. In this blog series, we’ll be laying out relevant queries for VMware Carbon Black Cloud Workload customers to use to achieve a variety of use cases. All queries are available in the VMware Carbon Black Cloud console, or in the VMware Carbon Black User Exchange.  

Requirements:  

This blog series is intended for readers that have a basic understanding of SQLite and have an osquery test environment. If neither of these things is true for you, please take a moment to read the Audit and Remediation Best Practices Guide before reading the rest of the blog.  

Resources  

PowerShell is an incredibly powerful script interpreter that is on every modern Windows operating system. Because of this, malicious attackers leverage PowerShell to achieve their goals. One of the ways they may use maliciously is to enable “PowerShell remoting,” which is described in Microsoft documentation as: 

The Enable-PSRemoting cmdlet configures the computer to receive PowerShell remote commands that are sent by using the WS-Management technology. WS-Management-based PowerShell remoting is currently supported only on the Windows platform. 

PowerShell remoting is enabled by default on Windows Server platforms [Server 2012 and newer]. You can use Enable-PSRemoting to enable PowerShell remoting on other supported versions of Windows and to re-enable remoting if it becomes disabled. 

As stated, PowerShell remoting is enabled by default on Windows server operating systems, but only for connections on the local subnet. An unsuspecting admin might enable it to be accessible from the Internet, which could create a large security hole for attackers to try to exploit.  

While PowerShell remoting is not enabled by default on Windows desktop operating systems, it can be enabled very easily. Therefore, both desktop and server operating systems should be monitored for the status of PowerShell Remoting being enabled and accessible. 

The Microsoft documentation further explains that when enabled with the PowerShell Enable-PSRemoting cmdlet, it: 

  • Starts the WinRM service. 

  • Sets the startup type on the WinRM service to Automatic. 

  • Creates a listener to accept requests on any IP address. 

  • Enables a firewall exception for WS-Management communications. 

  • Creates the simple and long name session endpoint configurations if needed. 

  • Enables all session configurations. 

  • Changes the security descriptor of all session configurations to allow remote access. 

  • Restarts the WinRM service to make the preceding changes effective. 

With this information in mind, we need to make a query that looks for the WinRM service enabled and set to start automatically and for firewall rules that allow communication from anywhere. The easy part is identifying the service: 

osquery> select name,status,pid,start_type from services where name like '%winrm%'; 
+-------+---------+------+------------+ 
| name  | status  | pid  | start_type | 
+-------+---------+------+------------+ 
| WinRM | RUNNING | 9636 | AUTO_START | 
+-------+---------+------+------------+ 

The only time we care about this value is if it is set to AUTO_START, so let’s add that to the query: 

osquery> select name,status,pid,start_type from services where name like '%winrm%' and start_type = 'AUTO_START'; 
+-------+---------+------+------------+ 
| name  | status  | pid  | start_type | 
+-------+---------+------+------------+ 
| WinRM | RUNNING | 9636 | AUTO_START | 
+-------+---------+------+------------+ 

 

Now that we have the services portion of the query, let’s work on the firewall rules. Windows firewall rules are stored in the registry in this key: 

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\FirewallRules  

For WinRM there are a few rules that we need to inspect: 

  1. WINRM-HTTP-In-TCP 

  1. WINRM-HTTP-In-TCP-Noscope 

  1. WINRM-HTTP-Compat-In-TCP 

  1. WINRM-HTTP-Compat-In-TCP-Noscope 

When I enabled PowerShell remoting on my test system, only access to the local subnet #2 was changed. When I made it publicly accessible, #1 was changed. The Microsoft documentation states that any of them could be modified, so you will need to test this in your own environment. To make it easier for us we will look at them all by using regex_match():  
(If you need a refresher on this function, please see this previous blog

osquery> select name, mtime, data 
    ...> from registry 
    ...> where path like "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\FirewallRules\%" 
    ...>     and regex_match(name,'WINRM-HTTP[-Compat]*-In-TCP',0) is not null; 
name = WINRM-HTTP-In-TCP-NoScope 
mtime = 1652148598 
 data = v2.30|Action=Allow|Active=TRUE|Dir=In|Protocol=6|Profile=Domain|Profile=Private|LPort=5985|RA4=LocalSubnet|RA6=LocalSubnet|App=System|Name=@FirewallAPI.dll,-30253|Desc=@FirewallAPI.dll,-30256|EmbedCtxt=@FirewallAPI.dll,-30267|

I have found in my testing that RA4 and RA6 are only present when the rule is restricted by IP address ranges. With this data in mind, we will need to find cases when Active=TRUE and RA4 and RA6 don’t exist. I have also found that the mtime is accurate to the last change made on the rule, so we can convert that too. 

osquery> select name, datetime(mtime,'unixepoch','localtime') as mtime, data 
    ...> from registry 
    ...> where path like "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\FirewallRules\%" 
    ...>     and regex_match(name,'WINRM-HTTP[-Compat]*-In-TCP',0) is not null 
    ...>     and data like '%Active=TRUE%' 
    ...>     and regex_match(data, 'RA[46]+=',0) is null; 

name = WINRM-HTTP-In-TCP 
mtime = 2022-05-09 19:09:58 
 data = v2.30|Action=Allow|Active=TRUE|Dir=In|Protocol=6|Profile=Public|LPort=5985|App=System|Name=@FirewallAPI.dll,-30253|Desc=@FirewallAPI.dll,-30256|EmbedCtxt=@FirewallAPI.dll,-30267| 

name = WINRM-HTTP-Compat-In-TCP-NoScope 
mtime = 2022-05-09 19:09:58 
 data = v2.30|Action=Allow|Active=TRUE|Dir=In|Protocol=6|Profile=Domain|Profile=Public|LPort=80|App=System|Name=@FirewallAPI.dll,-35001|Desc=@FirewallAPI.dll,-35002|EmbedCtxt=@FirewallAPI.dll,-30252| 

 

Notice in the second regex_match() I used is null to find where the match doesn’t occur. 

Now that we have these two queries, it would be great if we could do a JOIN so that we have just one query, but we do not have any common columns that we could use. In cases like these I will use a trick by adding a placeholder column to use in the JOIN. Here it is in its simplest form: 

osquery> select 1 as 'one'; 
+-----+ 
| one | 
+-----+ 
| 1   | 
+-----+  

 

Now if I add this to the select statements of both queries then I can use a JOIN. When I am doing these types of JOINs and the subqueries are a little more complex I like to use WITH. We are also only looking for a case where both queries have at least one result, so we will use a couple of CASE statements to make our results easily understandable: 

osquery> with s as ( 
    ...>     select count(*) as 'cnt', 1 as one 
    ...>     from services 
    ...>     where name like '%winrm%' 
    ...>         and start_type = 'AUTO_START' 
    ...> ), 
    ...> r as ( 
    ...>     select count(*) as 'cnt', datetime(mtime,'unixepoch','localtime') as mtime, 1 as 'one' 
    ...>     from registry 
    ...>     where path like "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\FirewallRules\%" 
    ...>         and regex_match(name,'WINRM-HTTP[-Compat]*-In-TCP',0) is not null 
    ...>         and data like '%Active=TRUE%' 
    ...>         and regex_match(data, 'RA[46]+=',0) is null 
    ...> ) 
    ...> select 
    ...>     case 
    ...>         when s.cnt > 0 then 'ENABLED' 
    ...>         else 'DISABLED' 
    ...>     end 'winrm_status', 
    ...>     case 
    ...>         when r.cnt > 0 then 'TRUE' 
    ...>         else 'FALSE' 
    ...>     end 'publically_accessable', 
    ...>     r.mtime as 'last_modified' 
    ...> from s 
    ...> join r using(one); 
+--------------+-----------------------+---------------------+ 
| winrm_status | publically_accessable | last_modified       | 
+--------------+-----------------------+---------------------+ 
| ENABLED      | TRUE                  | 2022-05-09 19:09:58 | 
+--------------+-----------------------+---------------------+ 

This blog has shown how we can take two disparate queries and JOIN them together to add context and create meaningful results. This query can now be used to identify systems that have PowerShell remoting enabled and have host-based firewall rules that make it publicly accessible. Granted, while most corporate firewalls would not allow connection to servers over this port, it is still a security hole that needs to be plugged. If desktops/laptops have it enabled and publicly accessible, then that is a much more serious threat that needs to be mitigated ASAP. 

To learn more about how Audit and Remediation capabilities can help you gather data at scale across your environments, check out the other blogs in this series and the other resources below: 

  

 

Filter Tags

Audit and Remediation Blog EPP