Skip to content

Repository files navigation

MediTrace Health - Deliberately Vulnerable Web Lab

MediTrace is a working hospital platform that is broken on purpose. It is a companion lab for the CyberSec University SQL Injection (SQLi) Training Platform at https://cybersec-app.fly.dev. Students learn the theory there and practice the attacks here.

Every feature is genuinely functional (accounts, patient registry, search, lab results, notes, admin tools) - and every OWASP Top 10 flaw is a real, deliberate anti-pattern that works against the app exactly as documented, both by hand and with sqlmap.

Scope: fictional data only, no real patients. Use this only on systems you own. Never run automated scanners against third parties.


Quickstart

Everything runs through one standardized npm interface (Windows, macOS, Linux):

# one-time setup
python -m pip install -r backend/requirements-dev.txt
npm install --prefix frontend

# serve the built app on :8000 and open the browser
npm start

# or: live-reload dev mode (Vite on :5173 proxying /api -> :8000)
npm run start:dev

# run everything in Docker instead
npm run docker:up            # http://localhost:8000

npm start builds the SPA if needed, starts the FastAPI backend (reusing one already running on :8000), waits until it is healthy, and opens the browser. Flags: npm start -- --build forces a rebuild; npm start -- --no-open skips the browser.

Other scripts: npm run build, npm run lint, npm test, npm run test:security, npm run test:sqlmap, npm run db:reset, npm run docker:down.

Default accounts

Username Password Role
admin admin123 Admin
doctor doctor123 Doctor
nurse nurse123 Staff
receptionist welcome123 Staff
dr_lee lee123 Doctor
dr_chen chen123 Doctor
grace grace123 Patient (Grace Otieno)
sofia sofia123 Patient (Sofia Mendez)
hana hana123 Patient (Hana Kobayashi)

Reset the database any time with POST /api/lab/reset (or the Lab page). Resetting invalidates every session, so sign back in afterwards.

The 60-second investor demo

  1. Log in as doctor/doctor123 - the header shows who you are and your role.
  2. Type ' OR 1=1-- into the patient search and search - the whole registry appears.
  3. Open Profile, change the staff ID to 1 - the admin's record and SSN.
  4. (Closer) In Admin > Network diagnostics, run 127.0.0.1 | whoami - a shell command executes on the server.

Patient flow: sign in as grace/grace123 to land on My health (own record, lab results, appointments) and request an appointment with another doctor.

Each step takes one line of input and visibly breaks a "real" system.

Vulnerabilities (full catalog: backend/app/vuln/README.md)

OWASP 2021 Surface One-liner
A03 Injection login, patient id, search, lab results, appointments SQLi in 5 flavors: auth bypass, UNION, boolean, error, time
A03 Injection admin diagnostics `
A01 Broken Access /api/users/{id}, admin panel, notes IDOR + missing role checks + SSN leaks
A03 XSS notes (stored), search (reflected) unsanitized HTML rendering
A10 SSRF /api/tools/fetch fetch any URL, reach internal endpoints
A02 Crypto passwords, sessions MD5 hashes, predictable tokens, stealable cookie
A05 Misconfig error handler, defaults verbose SQL errors, documented default creds
A04/A09 login endpoint no rate limiting, no audit logging

Each vulnerability is pinned by an automated test that proves it is still exploitable (and therefore cannot be accidentally "fixed" later).

Architecture

backend/            FastAPI + raw SQLite3
  app/main.py       app factory, verbose SQL error handler, SPA static serving
  app/db.py         per-request sqlite3 connections, schema (WAL mode)
  app/seed.py       deterministic fictional clinic data (incl. patient accounts)
  app/vuln/         every deliberate flaw, documented in vuln/README.md
  app/routes/       auth, patients, lab, notes, users, portal, admin, tools
  tests/
    functional/     the app really works
    security/       every vulnerability is still exploitable (incl. sqlmap)
frontend/           React + Vite + Tailwind SPA (mobile-first), served by FastAPI
  src/auth/         global auth state (AuthProvider + useAuth), RequireAuth guard
  src/components/ui/  Button, Card, Badge, Skeleton, Spinner, Alert, EmptyState
  src/pages/        registry, patient detail, patient portal, admin, tools, lab
  src/**/*.test.tsx Vitest + React Testing Library component tests
package.json        root npm scripts - single cross-platform interface (npm start)
scripts/start.mjs   zero-dependency launcher: build, port reuse, health poll, browser
Dockerfile          multi-stage: node build -> python runtime
docker-compose.yml  local lab
fly.toml            Fly.io deployment
.github/workflows/  CI: backend tests + sqlmap integration + frontend test/lint/build

Key design decisions:

  • Raw SQL, not an ORM - ORMs parameterize by default and would silently "fix" the SQLi. Vulnerable queries live in app/vuln/sqli.py; the safe counterparts elsewhere use ? placeholders, so the fix is a visible diff.
  • Auth-gated data surfaces - patient/lab data now require a session; the auth-bypass login payload is the zero-knowledge way in. See the cheat sheet.
  • sqlmap compatibility - injectable params are typed str, error messages leak the executed SQL (error-based), result counts are a stable boolean truth signal, and the canonical SQLite heavy-query time payload is documented.
  • Two test suites - functional tests prove the app works; security regression tests prove the vulns still work. pytest tests/security gates the lab. The frontend is covered by Vitest component tests.

Testing

npm test                        # backend tests + frontend unit tests + build
npm run test:security           # vuln regression only
npm run test:sqlmap             # needs sqlmap installed

Equivalently from backend/: python -m pytest (everything), python -m pytest tests/security (vuln regression), and python -m pytest tests/security/test_sqlmap.py -m sqlmap. Two sqlmap tests pin the student path: one runs stock sqlmap against the public login endpoint with no cookie (--level=2 --tables, the beginner command) and asserts the tables are enumerated; the other logs in via requests, passes sqlmap --cookie=meditrace_session=..., and dumps the UNION surface.

The beginner one-liner (no setup, matches the browser bypass):

sqlmap -u "http://localhost:8000/api/auth/login?username=admin&password=x" \
  --level=2 --batch --tables

That URL is the GET debug variant of the login endpoint: it takes credentials as query params and returns 200 with {"error": ...} on failure instead of 401, so sqlmap does not abort. --level=2 is required because the login query has a trailing AND password_hash='...' clause, and the payload + boundary sqlmap uses to neutralize it (SQLite JSON boolean-CASE + a string-closing AND 'x'='x') are both tagged level 2 in sqlmap's data. The full catalog and every working command live in backend/app/vuln/README.md.

From frontend/: npm test runs the Vitest + React Testing Library suite (auth state, layout identity, page loading/error/empty states).

Deployment

Fly.io (matches the companion app's host):

fly launch --no-deploy
fly deploy

The Dockerfile builds the SPA and serves it from FastAPI as one artifact, so / and /patients/1 both work with no CORS setup. SQLite persists on the meditrace_data volume; without a volume it simply reseeds on every deploy (which is fine for a lab).

Roadmap

  • Backend: SQLi core (auth, UNION, boolean, error, time) + tests
  • Backend: XSS, IDOR, command injection, SSRF + tests
  • Frontend: responsive SPA
  • Frontend: Vitest + React Testing Library component tests (15 passing)
  • Frontend: role-aware UI, auth guard, patient portal ("My health")
  • Backend: auth-gated data surfaces + patient BOLA (appointment booking)
  • Deployment: Docker, compose, Fly.io, CI
  • Audit challenge tracker (gamified "find all N findings")
  • SAFE_MODE toggle (same app, fixed queries) for before/after teaching

About

World's most vulnerable hospital system

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages