← Back to Feed CACHED · 2026-06-30 00:15:36 · CACHE_KEY CVE-2026-13485
CVE-2026-13485 · CWE-74 · Disclosed 2026-06-28

SourceCodester Class and Exam Timetabling System 1.0 — SQL injection in /preview.php course_year_section parameter

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

A free school-project PHP app dropped on GitHub with a textbook SQL injection, dressed up with a 7.3

SourceCodester's *Class and Exam Timetabling System* v1.0 is a free PHP/MySQL learning project distributed on sourcecodester.com — the kind of codebase students download to learn CRUD. CVE-2026-13485 is an unauthenticated SQL injection in /preview.php via the course_year_section parameter, which is concatenated directly into a query with no sanitization. The bug class is canonical CWE-89 (the CWE-74 label is the parent injection bucket). The same researcher set has filed near-identical SQLi CVEs against preview6.php, preview7.php, archive.php, and others in the same codebase (CVE-2026-13486, -13488, etc.) — the entire project is riddled with the same anti-pattern.

Vendor severity of 7.3 (HIGH) is a mechanical CVSS output, not a reflection of risk. The vector AV:N/AC:L/PR:N/UI:N is technically accurate — anyone who can reach the page can dump the DB — but the population that actually runs this software in production is a rounding error. There is no enterprise install base, no vendor patch pipeline, and no support contract. Treating this like an Oracle or SAP SQLi distorts the patch queue. The honest call is LOW: real, exploitable, public PoC, but blast radius bounded to a handful of student / hobbyist deployments.

"SourceCodester student-project SQLi in a free PHP timetabling demo — vendor 7.3 ignores that this isn't enterprise software."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Locate an exposed instance

Attacker fingerprints a deployed Class and Exam Timetabling System instance — typically via Google dork (inurl:preview.php course_year_section) or Shodan/FOFA HTML title match. The application has no telemetry; instances are usually on shared LAMP hosting or a lab VM.
Conditions required:
  • Instance is reachable from attacker's network position
  • Default PHP routing exposes /preview.php
Where this breaks in practice:
  • Effectively zero enterprise deployment footprint
  • Most installs are on localhost / classroom LAN, not internet-exposed
Detection/coverage: Shodan/Censys do not have a dedicated fingerprint; discovery is opportunistic via search engines.
STEP 02

Inject into course_year_section

Attacker issues GET /preview.php?course_year_section=1' UNION SELECT ...-- - using sqlmap (sqlmap -u 'http://target/preview.php?course_year_section=1' --dbs). The parameter is concatenated unsafely into a MySQL query, so UNION-based and boolean-based payloads both work. PoC is public on VulDB and GitHub issue trackers.
Conditions required:
  • No WAF in front of the app
  • MySQL backend with default error verbosity
Where this breaks in practice:
  • Any generic WAF (ModSecurity CRS, Cloudflare free tier) catches the canonical sqlmap signatures
  • Hosting providers commonly block UNION SELECT patterns at the edge
Detection/coverage: Trivial signature hit in any IDS/WAF — sqlmap user-agent and payload shape are well-known.
STEP 03

Exfiltrate schema and data

Attacker enumerates information_schema.tables, dumps user/account tables, and harvests credentials. If MySQL is running as a privileged account with FILE privilege and secure_file_priv is unset, sqlmap's --os-shell can write a PHP webshell into the docroot.
Conditions required:
  • DB user has SELECT on target tables (default in this app)
  • FILE privilege + writable webroot for shell upload
Where this breaks in practice:
  • Modern MySQL defaults restrict secure_file_priv, blocking file write
  • Shared hosting separates DB user from webroot ownership
Detection/coverage: Outbound large response sizes and INFORMATION_SCHEMA queries are detectable in MySQL general/slow logs if enabled.
STEP 04

Impact bounded to one tenant

Worst case: full read/write of the timetabling database (student names, schedules, faculty accounts) and potential webshell on the single host. There is no federation, no SSO integration, and no lateral pivot story baked into the product itself.
Conditions required:
  • Successful DB compromise from step 3
Where this breaks in practice:
  • No corporate identity integration to abuse
  • Database is single-app, single-host by design
Detection/coverage: Defacement / data dump usually surfaces only post-incident.
03 · Compensating Control

1
Inventory: confirm you do not run any SourceCodester PHP app — Grep your asset inventory and webserver vhost configs for sourcecodester, preview.php, class-and-exam-timetabling. If zero hits, close the ticket. If hits exist, proceed. There is no mitigation SLA at LOW severity — handle within the 365-day remediation window.
2
If found, take the instance off the public internet immediately — Bind to localhost or put behind VPN / authenticated reverse proxy. Public exposure of this codebase is indefensible regardless of CVE — it has at least five sibling SQLi CVEs in the same project.
3
Drop a WAF rule blocking SQLi payloads on /preview.php and siblings — ModSecurity CRS paranoia level 2+ or any commercial WAF will catch sqlmap signatures. Apply to /preview*.php, /archive.php at minimum.
4
Replace the application — The entire codebase is unmaintained and bug-saturated. Swap for a maintained scheduling tool (e.g., FET, Untis, or any institutional LMS module). This is the only durable fix.
What doesn't work
  • Waiting for a vendor patch — SourceCodester does not ship CVE-keyed patches for community uploads.
  • Input validation in the calling page alone — sibling CVEs show the same anti-pattern across the whole codebase, so per-file fixes leave you exposed.
  • Database least-privilege as a primary control — it reduces blast radius but the C:L/I:L/A:L impact still lands; do it, but don't call it remediation.
04 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNone observed. No campaigns tracked. EPSS 0.00412 (≈0.4%) reflects negligible predicted exploitation.
Proof-of-conceptPublic PoC on VulDB and researcher GitHub issues (lffaker/cybersec, yan-124/yan). Trivial sqlmap one-liner.
EPSS0.00412 — bottom of the distribution. Realistic given the audience.
KEV statusNot listed. Will not be — CISA does not catalog SourceCodester student-project bugs.
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L → 7.3 HIGH. CVSS v4.0 reassessed to 6.9 MEDIUM, closer to reality.
CWECWE-89 (SQL Injection) — parent label CWE-74 used in NVD entry. Classic concatenation flaw.
Affected versionsSourceCodester Class and Exam Timetabling System v1.0 (only published version). Codebase has sibling CVEs: -13484, -13486, -13487, -13488.
Fixed versionNone. SourceCodester does not maintain a patch pipeline for community-uploaded projects.
Exposure dataShodan/Censys: no meaningful fingerprintable footprint. Effectively zero enterprise install base.
Disclosure2026-06-28 via VulDB. Researcher credit per VulDB entry; coordinated only in the loosest sense.

Sources.

  1. VulDB — CVE-2026-13485
  2. Vulnerability-Lookup CIRCL — CVE-2026-13485
  3. GHSA-Q3VP-2MQC-929R (CIRCL mirror)
  4. Sibling CVE-2026-13488 (preview7.php SQLi)
  5. Researcher PoC — lffaker/cybersec issue #6 (preview.php)
  6. Researcher PoC — lffaker/cybersec issue #4 (archive.php)
  7. Researcher PoC — yan-124/yan issue #4 (preview7.php)
  8. SourceCodester project home
05 · The Call

Final Verdict
↓ DOWNGRADED to LOW (3.1/10)

Why this verdict

  • Installed base near zero in enterprise: SourceCodester is a free code-sample repository, not an enterprise product. If you don't run it (and you almost certainly don't), this CVE is noise.
  • Role multiplier — low-value role only: affected component is a standalone PHP class-scheduling demo. It is never deployed as IdP, hypervisor, CA, backup, EDR, or any high-value role. Blast radius caps at one app database on one host — there is no fleet, domain, or supply-chain pivot path. Floor stays at LOW.
  • Friction — discovery is the bottleneck: there is no Shodan/Censys fingerprint for this product, no fleet scanning, and EPSS predicts <0.5% exploitation probability. Even motivated attackers ignore it because the loot tables are student schedules.
  • No vendor patch pipeline: SourceCodester does not maintain CVE-keyed patches for community uploads, so the realistic remediation is *remove the app* or *replace with a maintained scheduler*, not 'apply update'.

Why not higher?

MEDIUM and above would imply this belongs in a normal patch queue with real ticket velocity. It does not — the product has no enterprise footprint, no patch supply chain, and the only realistic victim population (student-project deployers) is not the audience defending a 10,000-host fleet. Calling this HIGH inflates the queue and dilutes signal on bugs that matter.

Why not lower?

IGNORE would be wrong because the vulnerability IS trivially exploitable with public PoC, and a handful of organizations *might* genuinely run a SourceCodester app inside a lab or staging environment. If your asset inventory surfaces one, you need to act — just at a backlog-hygiene priority, not at incident pace.

06 · Verification

Crowdsourced verification payload.

Run on any auditor workstation with network reach to the suspected host. Requires curl only; no privileges needed beyond outbound HTTP. Invoke as ./check_cve_2026_13485.sh https://target.example.com — script returns VULNERABLE if the app fingerprints as the SourceCodester scheduler and the vulnerable parameter responds with SQL-error-shaped output.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# noisgate verifier — CVE-2026-13485
# SourceCodester Class and Exam Timetabling System 1.0 — SQLi in /preview.php
# Usage: ./check_cve_2026_13485.sh <base_url>
set -u

BASE="${1:-}"
if [[ -z "$BASE" ]]; then
  echo "Usage: $0 <base_url>  e.g. https://timetable.lab.local" >&2
  exit 2
fi
BASE="${BASE%/}"

UA="noisgate-verifier/1.0"
TMP="$(mktemp)"
trap 'rm -f "$TMP"' EXIT

# Step 1: fingerprint the app at all
FP=$(curl -sk -A "$UA" --max-time 10 -o "$TMP" -w "%{http_code}" "$BASE/preview.php?course_year_section=1")
if [[ "$FP" == "000" || "$FP" == "404" ]]; then
  echo "UNKNOWN — /preview.php not reachable (HTTP $FP). Asset may not run this product."
  exit 0
fi

# Step 2: probe with a benign single-quote — a vulnerable build returns a MySQL error
PROBE=$(curl -sk -A "$UA" --max-time 10 "$BASE/preview.php?course_year_section=1%27")
if echo "$PROBE" | grep -qiE 'you have an error in your sql syntax|mysql_fetch|mysqli_|sqlstate|warning: mysql'; then
  echo "VULNERABLE — /preview.php course_year_section emits SQL error on single quote."
  exit 1
fi

# Step 3: boolean differential — same length response for 1=1 vs 1=2 means likely sanitized
TRUE_LEN=$(curl -sk -A "$UA" --max-time 10 "$BASE/preview.php?course_year_section=1%20AND%201%3D1" | wc -c)
FALSE_LEN=$(curl -sk -A "$UA" --max-time 10 "$BASE/preview.php?course_year_section=1%20AND%201%3D2" | wc -c)
DIFF=$(( TRUE_LEN > FALSE_LEN ? TRUE_LEN - FALSE_LEN : FALSE_LEN - TRUE_LEN ))
if (( DIFF > 50 )); then
  echo "VULNERABLE — boolean-based differential detected (delta=$DIFF bytes)."
  exit 1
fi

echo "PATCHED or NOT-AFFECTED — no SQL error, no boolean differential on /preview.php."
exit 0