Skip to main content

Free 30-min security demo Book Now

Offensive360 Offensive360
ZeroDays CVE-2026-19231
High CVE-2026-19231 CVSS 7.3 SourceCodester Simple Doctors Appointment System PHP

SQL Injection in Doctors Appointment Admin

CVE-2026-19231 exposes a critical SQL injection flaw in SourceCodester Simple Doctors Appointment System 1.0, enabling remote database compromise via an unsanitized ID parameter.

Offensive360 Research Team
Affects: 1.0
Source Code

Overview

CVE-2026-19231 is a classic, unauthenticated SQL injection vulnerability residing in the administrative backend of SourceCodester Simple Doctors Appointment System version 1.0. The vulnerable endpoint is /admin/ajax.php, specifically when invoked with the action=delete_appointment parameter. The id argument passed to this action is interpolated directly into a SQL query without any sanitization, parameterization, or type enforcement, giving a remote attacker full read and write access to the underlying database.

SourceCodester publishes open-source PHP web applications targeted primarily at students and small organizations. Their projects are widely deployed in developing regions for clinic management, school administration, and small business operations — often by administrators with limited security expertise and no dedicated DevSecOps function. This demographic makes these applications an attractive target: they are internet-exposed, infrequently patched, and process sensitive personal and medical data.

The vulnerability was publicly disclosed by security researchers with a working proof-of-concept published to GitHub. With a CVSS score of 7.3 (High) and a publicly available exploit, any unpatched deployment of this application should be considered actively at risk. The combination of remote exploitability, low attack complexity, and no authentication requirement makes this a straightforward target for opportunistic attackers.

Technical Analysis

The root cause is textbook: unsanitized user input concatenated directly into a SQL query. In a typical SourceCodester AJAX handler pattern, the ajax.php dispatcher reads the action query parameter and routes control to the appropriate logic block. For delete_appointment, the code retrieves the id parameter from the HTTP request and embeds it into a DELETE or SELECT statement without any escaping or parameterization.

A representative vulnerable code pattern looks like this:

<?php
// /admin/ajax.php (vulnerable pattern)

if (isset($_GET['action'])) {
    $action = $_GET['action'];

    if ($action === 'delete_appointment') {
        // VULNERABILITY: $id is taken directly from user input, never validated or escaped
        $id = $_REQUEST['id'];

        $sql = "DELETE FROM appointments WHERE id = " . $id;
        $result = mysqli_query($conn, $sql);

        if ($result) {
            echo json_encode(['status' => 'success']);
        } else {
            echo json_encode(['status' => 'error', 'message' => mysqli_error($conn)]);
        }
    }
}
?>

Because $id is never cast to an integer, validated against an expected format, or passed through a prepared statement, an attacker can substitute any arbitrary SQL fragment in its place. A basic exploitation request looks like:

GET /admin/ajax.php?action=delete_appointment&id=1+OR+1=1 HTTP/1.1
Host: target.example.com

More sophisticated payloads can use UNION SELECT to exfiltrate arbitrary table data, leverage INTO OUTFILE to write web shells to the filesystem if the MySQL user has FILE privileges, or use time-based blind injection (SLEEP(), BENCHMARK()) to enumerate data out-of-band when error output is suppressed. Tools like sqlmap can automate full database enumeration against this endpoint in minutes with no credentials required, making exploitation trivially accessible to low-skilled attackers.

The dispatcher pattern used in SourceCodester applications — a single ajax.php file routing on a string action parameter — also makes it easy to enumerate other potentially vulnerable actions within the same file, compounding the attack surface beyond just appointment deletion.

Impact

An attacker who successfully exploits this vulnerability gains the ability to interact with the backend MySQL database as the application’s configured database user. Depending on the privilege level of that user, the consequences range from severe to catastrophic:

  • Data exfiltration: Patient names, contact details, appointment histories, and any credentials stored in the database are accessible via UNION-based or blind injection techniques. In a healthcare context, this constitutes a potential breach of sensitive personal health information.
  • Authentication bypass: Administrator credentials (typically stored as MD5 or bcrypt hashes, or in poorly secured deployments as plaintext) can be extracted and cracked offline, giving the attacker full administrative access to the application.
  • Data manipulation or destruction: The attacker can insert, update, or delete arbitrary records — disrupting appointment scheduling, corrupting patient records, or rendering the system inoperable.
  • Remote code execution (conditional): If the MySQL process runs with FILE privileges and the web server’s document root is writable, an attacker can write a PHP web shell using SELECT ... INTO OUTFILE, escalating from SQL injection to full server compromise.

The CVSS 7.3 score reflects the AV:N/AC:L/PR:N/UI:N impact profile — network-accessible, low complexity, no privileges required, no user interaction needed. The confidentiality and integrity impacts are rated High; availability impact is also a concern given the ability to truncate or corrupt tables.

How to Fix It

The fix is straightforward and should be applied immediately. Replace all dynamic SQL string concatenation with prepared statements using parameterized queries. PHP’s mysqli and PDO extensions both support this natively.

<?php
// /admin/ajax.php (FIXED)

if (isset($_GET['action'])) {
    $action = $_GET['action'];

    if ($action === 'delete_appointment') {
        // FIXED: Validate input type first
        $id = filter_input(INPUT_REQUEST, 'id', FILTER_VALIDATE_INT);

        if ($id === false || $id === null) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'Invalid appointment ID.']);
            exit;
        }

        // FIXED: Use a prepared statement with a bound parameter
        $stmt = mysqli_prepare($conn, "DELETE FROM appointments WHERE id = ?");
        mysqli_stmt_bind_param($stmt, "i", $id);
        $result = mysqli_stmt_execute($stmt);
        mysqli_stmt_close($stmt);

        if ($result) {
            echo json_encode(['status' => 'success']);
        } else {
            echo json_encode(['status' => 'error', 'message' => 'Operation failed.']);
        }
    }
}
?>

Key remediation steps:

  1. Parameterize every query: Never interpolate request parameters into SQL strings. Use ? placeholders with mysqli_prepare / mysqli_stmt_bind_param, or PDO’s bindValue / bindParam.
  2. Enforce strict type validation: For numeric identifiers, use FILTER_VALIDATE_INT before the query even reaches the database layer. Reject and log any input that fails validation.
  3. Apply least privilege to the database user: The application’s MySQL account should have only SELECT, INSERT, UPDATE, and DELETE on the specific application schema — never FILE, SUPER, or GRANT privileges.
  4. Audit all AJAX action handlers: The ajax.php dispatch pattern likely contains multiple similar vulnerabilities. Audit every action branch for the same interpolation pattern and remediate them uniformly.
  5. Deploy a WAF as a short-term mitigation: While not a substitute for code-level fixes, a web application firewall configured to detect SQL injection patterns can reduce exposure during the remediation window.

Our Take

SQL injection in PHP applications is one of the oldest vulnerability classes in web security, yet it continues to appear in new code in 2026. The pattern here — raw $_REQUEST values dropped into query strings — is something developers have been warned against for over two decades. Its persistence in SourceCodester projects reflects a broader problem: educational codebases and rapid-development frameworks optimize for demonstrating features, not security properties, and those habits propagate into production deployments.

For enterprises conducting SAST and DAST programs, this vulnerability class serves as a calibration benchmark. If your tooling cannot detect unparameterized queries in PHP with high confidence, something is wrong with your rule configuration. It also highlights why DAST is essential alongside SAST: the vulnerable endpoint is only reachable at runtime through a specific action dispatch path that static analysis may not fully trace without solid taint-tracking across the request lifecycle.

The healthcare context amplifies the risk considerably. Any organization deploying open-source clinic management software in production owes its patients a security review before go-live — not after a breach notification.

Detection with SAST

This vulnerability maps to CWE-89: Improper Neutralization of Special Elements used in an SQL Command. SAST detection relies on taint analysis: tracking user-controlled data from its source (HTTP request parameters such as $_GET, $_POST, $_REQUEST, $_COOKIE) through the application’s control flow to a sensitive sink (any function that executes a SQL query, including mysqli_query, mysql_query, PDO::query, PDO::exec).

Offensive360’s SAST engine flags this pattern by:

  • Source identification: Marking all superglobal array reads ($_REQUEST['id'], $_GET['action'], etc.) as tainted data sources.
  • Propagation tracking: Following taint through variable assignments, string concatenation operators (.), interpolation inside double-quoted strings, and function calls that return tainted values.
  • Sink detection: Raising a HIGH-severity finding when tainted data reaches a SQL execution sink without passing through an approved sanitization function (intval, FILTER_VALIDATE_INT) or a parameterization boundary (mysqli_prepare with bound parameters).
  • Context-aware suppression: Avoiding false positives when the input is demonstrably cast to an integer type before use, since integer casting eliminates the injection vector for numeric ID parameters.

In DAST mode, this class of vulnerability is detected by injecting canonical SQL injection payloads (', 1 OR 1=1, 1; SELECT SLEEP(5)--) into every discovered parameter across every endpoint, then observing response differentials — error messages containing SQL syntax, unexpected data leakage, or measurable response time delays indicating blind injection.

References

#sql-injection #php #healthcare #remote-exploit

Detect this vulnerability class in your codebase

Offensive360 SAST scans your source code for CVE-2026-19231-class vulnerabilities and thousands of other patterns — across 60+ languages.