CVE-2024-35581 CVE-2024-35582 CVE-2024-35583 Severity: High CWE-79

Three Stored XSS in SourceCodester Laboratory Management System

Stored Cross-Site Scripting (CWE-79) — three instances in the Add Borrow workflow of the SourceCodester Computer Laboratory Management System (PHP/MySQL).

Author: Rohit (@r04i7)  ·  Status: Reported — assigned CVE-2024-35581 / 35582 / 35583

TL;DR

The Add Borrow workflow in this application stores three separate user-supplied fields — Borrower Name, Department, and Remarks — without sanitizing input or encoding output. Each is echoed back unescaped on the borrow view page, so JavaScript placed in any of the three is stored and then executed in the browser of every user who later opens that record, including administrators. Each field received its own CVE:

CVEFieldParameter
CVE-2024-35581Borrower Namelname
CVE-2024-35582Departmentdepartment
CVE-2024-35583Remarksremarks

Individually, each is a textbook stored XSS. Together, they show what happens when an application trusts an entire form instead of an individual field — and why methodical source→sink enumeration finds every instance, not just the first.

Background

SourceCodester's Laboratory Management System is a small PHP/MySQL app for managing lab equipment. Its Borrowing feature records who borrowed an item, from which department, with free-text remarks. The record is created via a POST request and later rendered in the admin panel at:

/php-lms/admin/?page=borrow/view_borrow&id=<record_id>

All three fields — Borrower Name, Department, Remarks — are written to the database on create and echoed back into that view page on read. None is encoded at output. That single missing control, repeated across three fields, is three vulnerabilities.

The vulnerability

The create-borrow handler takes each field from the POST body and:

  1. Stores it without input sanitization, and
  2. Renders it without output encoding on the borrow view page.

At render time the stored value is treated as trusted HTML. Supplying markup instead of a plain value causes the browser to parse and execute it — every time the record is viewed. Because the values persist, all three are stored (persistent) XSS, not reflected: the victim doesn't need to follow a crafted link; simply opening the record in the normal admin workflow triggers execution.

Endpoint
POST → create-borrow handler
Parameters
lname · department · remarks
Render sink
?page=borrow/view_borrow&id=
Root cause
no input validation + no output encoding

Proof of concept

The same technique confirms all three; only the target field changes.

  1. Configure your browser to proxy through Burp Suite (or any intercepting proxy).
  2. In the admin panel, begin creating a Borrow record and fill in the fields.
  3. Intercept the create request and set the target parameter to the payload below:
payload
"><img src=x onerror=alert(document.cookie)>
  1. Forward the request to store the record.
  2. Open the borrow view page:
/php-lms/admin/?page=borrow/view_borrow&id=<id>

The img element fails to load src=x, firing the onerror handler, which runs the JavaScript. alert(document.cookie) demonstrates read access to the session cookie; a real attacker would exfiltrate it to an attacker-controlled endpoint. Because each value persists, the payload re-fires for any user — including higher-privileged accounts — who opens the record.

Note on screenshots Host PoC screenshots — one per field showing the alert firing — directly in the repository rather than linking to external drives, so the writeup stays self-contained and permanent.

Impact

Stored XSS executing in an authenticated admin context allows an attacker to:

The persistence plus the privileged rendering context are what make these High severity: no social engineering is required, and the code runs wherever an admin reviews borrow records. Three injectable fields simply give an attacker three independent ways in.

The hunt — why all three, not just one

The interesting part isn't the payload; it's the coverage. Finding one stored XSS and stopping is a missed opportunity — applications that fail to encode one field usually fail to encode its neighbors, because the flaw is architectural (no output-encoding layer), not a one-off typo.

  1. Enumerate source→sink pairs, not fields at random. For a CRUD app, list every input that gets stored and later displayed. The Add Borrow form has three free-text inputs, all rendered back on one view page — three candidate pairs.
  2. Test every candidate, not just the first hit. Once Borrower Name proved injectable, Department and Remarks were tested with the same payload rather than assumed safe. All three failed identically — confirming a systemic missing control.
  3. Tamper below the UI. Client-side field types and length limits are irrelevant once you intercept the POST; the payload goes straight onto the parameter.
  4. Confirm at the sink. The test is "does it execute when rendered," not "did it save." Loading the view page and watching each onerror fire is the confirmation.
Transferable lesson When one field is unencoded, audit the whole form and the whole render path. The discipline that turned one finding into three here is what turns one finding into a systemic report in enterprise applications — and a systemic report is far more valuable to a defender than a single trophy.

Remediation

Standard XSS defense, applied at both ends and to all user-controlled fields — not just the one that was reported:

example fix (php, at the output point)
// before — value echoed raw into the page
echo $row['lname'];

// after — entity-encoded, payload rendered inert
echo htmlspecialchars($row['lname'], ENT_QUOTES, 'UTF-8');
Load-bearing control Output encoding is the durable fix. Because the root cause is a missing encoding layer, centralize output encoding rather than patching three fields individually — input filtering alone is bypassable and should never be the only defense.

Disclosure timeline

(Replace with exact dates: found → reported → acknowledged → CVEs assigned.)

References

Part of a series documenting my published CVEs. More of my work and tooling at github.com/r04i7.