{
    "schema": "vulnhost-lab-export",
    "version": 1,
    "generated_at": "2026-09-28T14:55:50+00:00",
    "target": {
        "url": "https://testi.compla.nl",
        "last_run": {
            "at": "2026-09-28T13:43:25Z",
            "url": "https://testi.compla.nl",
            "source": "python-lab-runner-remote"
        }
    },
    "summary": {
        "total": 38,
        "exploitable": 26,
        "blocked": 12,
        "pending": 0
    },
    "findings": [
        {
            "id": "server_connectivity",
            "title": "Panel reachable from runner",
            "category": "Diagnostics",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nVerify the target panel URL responds.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker opens a browser (or scanner) and navigates to the site root URL with no login.\n2. The VulnHost dashboard HTML loads with HTTP 200 — the application is exposed to the network.\n\nWhat happened on this run\n----------------\nThe panel homepage loaded without error; the target is reachable from the internet or test network.\n\nTechnical observations\n----------------\n• HTTP status: 200\n• URL: https://testi.compla.nl\n\nSecurity impact\n----------------\nExposure increases attack surface if the app is not meant to be public.\n\nRecommended fix\n----------------\nRestrict access by VPN, firewall, or the lab PIN gate; monitor external reachability.",
            "teaser": "Attacker opens a browser (or scanner) and navigates to the site root URL with no login.",
            "tested_at": "2026-09-28T13:42:10Z",
            "endpoint": "/",
            "url": "https://testi.compla.nl/",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/server_connectivity-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/server_connectivity-render.html",
                "attack_url": "https://testi.compla.nl/",
                "screenshot": "https://testi.compla.nl/lab-evidence/server_connectivity.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "default_login",
            "title": "Default credentials",
            "category": "Authentication",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nCheck whether default administrator credentials still work.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker opens /login.php in the browser.\n2. They enter username admin and password admin123 (documented lab defaults) and submit the form.\n3. They are redirected to the dashboard; the header shows Logout and the username admin — full admin session without guessing.\n\nWhat happened on this run\n----------------\nLogin succeeded with factory credentials; no MFA or lockout stopped the attempt.\n\nTechnical observations\n----------------\n• Credentials tried: admin / admin123\n\nSecurity impact\n----------------\nImmediate admin compromise for anyone who knows or tries default creds.\n\nRecommended fix\n----------------\nForce password change on install, disable default accounts, enable MFA, alert on default-login patterns.",
            "teaser": "Attacker opens /login.php in the browser.",
            "tested_at": "2026-09-28T13:42:12Z",
            "endpoint": "/login.php",
            "url": "https://testi.compla.nl/login.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/default_login-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/default_login-render.html",
                "attack_url": "https://testi.compla.nl/login.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/default_login.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "sqli_login",
            "title": "SQL injection login bypass",
            "category": "Authentication",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nCheck SQL injection on the login form that bypasses authentication.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker opens /login.php.\n2. In both username and password fields they submit the payload: ' OR '1'='1\n3. The application logs them in without a valid password because the SQL WHERE clause becomes always true.\n\nWhat happened on this run\n----------------\nA session was created using only malicious SQL in the login fields.\n\nTechnical observations\n----------------\n• Payload (user & pass): ' OR '1'='1\n\nSecurity impact\n----------------\nUnauthenticated login as an arbitrary user, often the first DB row (admin).\n\nRecommended fix\n----------------\nUse prepared statements only; never concatenate user input into SQL.",
            "teaser": "Attacker opens /login.php.",
            "tested_at": "2026-09-28T13:42:13Z",
            "endpoint": "/login.php",
            "url": "https://testi.compla.nl/login.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/sqli_login-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/sqli_login-render.html",
                "attack_url": "https://testi.compla.nl/login.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/sqli_login.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "php_files_forbidden",
            "title": "PHP runtime + .php files Forbidden",
            "category": "Disclosure",
            "status": "blocked",
            "exploitable": false,
            "detail": "What we tested\n----------------\nDetect whether PHP runs on the host, then verify .php files that must not be public return Forbidden (not raw source or accidental execution).\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Step 1 — Detect PHP: open /login.php. If you see a normal HTML login form, PHP is executing. If you see raw <?php source or a file download, PHP is not running (static hosting).\n2. Step 2a — If PHP is NOT running: try /pin.php, /index.php, /router.php in the browser. Each should return 403 Forbidden (or 404). If the server serves PHP source code or runs the script, that is a critical misconfiguration.\n3. Step 2b — If PHP IS running: try /bootstrap.php and /lib/pin_gate.php. These must never be web-readable — expect 403/404. If you see PHP source or secrets, the document root is wrong or directory protection failed.\n\nWhat happened on this run\n----------------\nPolicy looks correct: PHP runtime was classified accurately and probed .php paths returned Forbidden or were not exposed (no source leak).\n\nTechnical observations\n----------------\n• PHP runtime active: yes\n• Check mode: PHP is executing — app .php outside public/ must not be web-readable\n• Protected responses: GET /bootstrap.php → HTTP 200; GET /lib/pin_gate.php → HTTP 200; GET /config/database.php → HTTP 200\n\nSecurity impact\n----------------\nRaw .php on static hosts leaks credentials in source; exposed bootstrap/lib files leak app secrets and aid full compromise.\n\nRecommended fix\n----------------\nStatic hosts: block *.php in nginx/Apache or remove PHP files from the web tree. PHP hosts: document root = public/ only; deny access to lib/, config/, bootstrap.php; return 403 for dotfiles and parent paths.",
            "teaser": "Step 1 — Detect PHP: open /login.php. If you see a normal HTML login form, PHP is executing. If you see raw <?php source or a file download, PHP is not run...",
            "tested_at": "2026-09-28T13:42:16Z",
            "endpoint": "/pin.php",
            "url": "https://testi.compla.nl/pin.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/php_files_forbidden-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/php_files_forbidden-render.html",
                "attack_url": "https://testi.compla.nl/pin.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/php_files_forbidden.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "direct_env",
            "title": "Direct HTTP access to /.env",
            "category": "Disclosure",
            "status": "blocked",
            "exploitable": false,
            "detail": "What we tested\n----------------\nDetect if .env is exposed at the web root (common when document root is the upload folder, not public/).\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker opens https://your-domain/.env directly in the browser address bar — no login, no tricks.\n2. If the server document root includes the project folder (bootstrap.php level), Apache/nginx may serve the raw .env file.\n3. The page shows plaintext lines such as PANEL_SECRET=, API_KEY=, and ADMIN_PASS= — the easiest possible secret leak.\n\nWhat happened on this run\n----------------\nGET /.env did not return .env contents (404/403, blocked by server rules, or docroot correctly set to public/ only).\n\nTechnical observations\n----------------\n• HTTP status: 403\n• Request: GET https://testi.compla.nl/.env (no session)\n• Marker: not found\n\nSecurity impact\n----------------\nInstant full credential and API key theft with a single URL; scanners hit this in seconds.\n\nRecommended fix\n----------------\nSet document root to public/ only; deny dotfiles in nginx/Apache; never deploy .env inside the web tree; use env vars on the host instead.",
            "teaser": "Attacker opens https://your-domain/.env directly in the browser address bar — no login, no tricks.",
            "tested_at": "2026-09-28T13:42:17Z",
            "endpoint": "/.env",
            "url": "https://testi.compla.nl/.env",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/direct_env-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/direct_env-render.html",
                "attack_url": "https://testi.compla.nl/.env",
                "screenshot": "https://testi.compla.nl/lab-evidence/direct_env.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "read_env",
            "title": "Read .env via view.php",
            "category": "Path traversal",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nRead the secret .env file via path traversal in the file viewer.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker logs in (e.g. admin / admin123).\n2. In the browser they open /view.php?file=../../.env (or use the View file UI and change the file parameter to ../../.env).\n3. The page renders the contents of .env in the browser, including lines like PANEL_SECRET= and API_KEY=.\n\nWhat happened on this run\n----------------\nSecrets from the server filesystem appeared in the HTTP response body.\n\nTechnical observations\n----------------\n• HTTP status: 200\n• Request: GET https://testi.compla.nl/view.php?file=../../.env\n• Marker: PANEL_SECRET= in body\n\nSecurity impact\n----------------\nTheft of API keys, panel secrets, and credentials for further attacks.\n\nRecommended fix\n----------------\nMap files to an allow-list under storage/; reject ..; use realpath() checks; keep .env outside web-readable paths.",
            "teaser": "Attacker logs in (e.g. admin / admin123).",
            "tested_at": "2026-09-28T13:42:19Z",
            "endpoint": "/view.php?file=../../.env",
            "url": "https://testi.compla.nl/view.php?file=../../.env",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/read_env-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/read_env-render.html",
                "attack_url": "https://testi.compla.nl/view.php?file=../../.env",
                "screenshot": "https://testi.compla.nl/lab-evidence/read_env.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "read_config",
            "title": "Read config/database.php",
            "category": "Path traversal",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nLeak PHP config that contains hardcoded secrets.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker logs in, then visits /view.php?file=../../config/database.php in the address bar.\n2. The response shows PHP source including fallback_password and other secrets.\n\nWhat happened on this run\n----------------\nConfig source code was displayed in the browser.\n\nTechnical observations\n----------------\n• HTTP status: 200\n• Request: GET https://testi.compla.nl/view.php?file=../../config/database.php\n\nSecurity impact\n----------------\nBackup passwords and DB settings aid credential stuffing and pivoting.\n\nRecommended fix\n----------------\nSame as path traversal fixes; never store secrets in web-accessible trees.",
            "teaser": "Attacker logs in, then visits /view.php?file=../../config/database.php in the address bar.",
            "tested_at": "2026-09-28T13:42:22Z",
            "endpoint": "/view.php?file=../../config/database.php",
            "url": "https://testi.compla.nl/view.php?file=../../config/database.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/read_config-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/read_config-render.html",
                "attack_url": "https://testi.compla.nl/view.php?file=../../config/database.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/read_config.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "traversal_bypass",
            "title": "....// filter bypass",
            "category": "Path traversal",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nBypass weak ../ filters using encoded traversal.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker logs in, then opens /view.php?file=....//....//.env in the browser.\n2. The doubled-dot pattern evades naive filters but still resolves to .env on the server.\n3. Sensitive .env lines (e.g. API_KEY=) appear in the page.\n\nWhat happened on this run\n----------------\nFilter bypass returned .env content.\n\nTechnical observations\n----------------\n• HTTP status: 200\n• Request: GET https://testi.compla.nl/view.php?file=....//....//.env\n\nSecurity impact\n----------------\nWAF/filter evasion plus arbitrary file read.\n\nRecommended fix\n----------------\nCanonicalize paths on the real filesystem; deny if outside allowed base directory.",
            "teaser": "Attacker logs in, then opens /view.php?file=....//....//.env in the browser.",
            "tested_at": "2026-09-28T13:42:24Z",
            "endpoint": "/view.php?file=....//....//.env",
            "url": "https://testi.compla.nl/view.php?file=....//....//.env",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/traversal_bypass-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/traversal_bypass-render.html",
                "attack_url": "https://testi.compla.nl/view.php?file=....//....//.env",
                "screenshot": "https://testi.compla.nl/lab-evidence/traversal_bypass.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "lfi_env",
            "title": "Include .env via include.php",
            "category": "LFI",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nLocal file inclusion via dynamic include.php.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker logs in and opens /include.php?page=../../.env\n2. The include handler loads ../../.env as the page template and prints secret lines into the HTML.\n\nWhat happened on this run\n----------------\n.env content visible in the included page output.\n\nTechnical observations\n----------------\n• HTTP status: 200\n• Request: GET https://testi.compla.nl/include.php?page=../../.env\n\nSecurity impact\n----------------\nFile read and potential RCE via include tricks or log poisoning.\n\nRecommended fix\n----------------\nNever pass user input to include/require; use a fixed allow-list of template names.",
            "teaser": "Attacker logs in and opens /include.php?page=../../.env",
            "tested_at": "2026-09-28T13:42:26Z",
            "endpoint": "/include.php?page=../../.env",
            "url": "https://testi.compla.nl/include.php?page=../../.env",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/lfi_env-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/lfi_env-render.html",
                "attack_url": "https://testi.compla.nl/include.php?page=../../.env",
                "screenshot": "https://testi.compla.nl/lab-evidence/lfi_env.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "unrestricted_upload",
            "title": "Upload .php without validation",
            "category": "Upload",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nUpload a PHP web shell without type validation.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker logs in and opens /upload.php.\n2. They choose a file named shell.php containing PHP code and submit the upload form.\n3. They browse to /uploads/<filename>.php — if the server executes PHP in uploads/, the shell runs.\n\nWhat happened on this run\n----------------\nA .php file was accepted and stored (and may execute depending on server config).\n\nTechnical observations\n----------------\n• Upload HTTP status: 200\n• Filename: lab_test_1790602947.php\n• Execution: Stored; PHP not executed (common on Apache/nginx)\n\nSecurity impact\n----------------\nPersistent remote code execution on the server.\n\nRecommended fix\n----------------\nAllow-list extensions, scan content, store outside web root, disable PHP in upload directories.",
            "teaser": "Attacker logs in and opens /upload.php.",
            "tested_at": "2026-09-28T13:42:29Z",
            "endpoint": "/upload.php",
            "url": "https://testi.compla.nl/upload.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/unrestricted_upload-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/unrestricted_upload-render.html",
                "attack_url": "https://testi.compla.nl/upload.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/unrestricted_upload.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "eicar_upload",
            "title": "EICAR test file upload (AV bypass)",
            "category": "Upload",
            "status": "blocked",
            "exploitable": false,
            "detail": "What we tested\n----------------\nVerify uploads are scanned for malware using the harmless EICAR test file.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. The lab runner generates the standard EICAR antivirus test string on your PC (not shipped in the repo).\n2. It logs in and POSTs the file to /upload.php (e.g. eicar_<id>.com) like a real user upload.\n3. It then GETs /uploads/<filename> — if the exact EICAR marker is still there, nothing blocked it at upload or storage.\n\nWhat happened on this run\n----------------\nUpload rejected, quarantined, or file stripped before it could be downloaded.\n\nTechnical observations\n----------------\n• Upload HTTP status: 200\n• Filename: eicar_87c1b124.com\n• Result: Upload rejected (AV/policy)\n\nSecurity impact\n----------------\nReal malware would reach disk and backups; hosting panels are common malware distribution points.\n\nRecommended fix\n----------------\nScan uploads with ClamAV or cloud AV; block EICAR in CI; quarantine; never serve untrusted files from the web root.",
            "teaser": "The lab runner generates the standard EICAR antivirus test string on your PC (not shipped in the repo).",
            "tested_at": "2026-09-28T13:42:31Z",
            "endpoint": "/upload.php",
            "url": "https://testi.compla.nl/upload.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/eicar_upload-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/eicar_upload-render.html",
                "attack_url": "https://testi.compla.nl/upload.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/eicar_upload.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "upload_path_traversal",
            "title": "Upload path traversal (path=)",
            "category": "Upload",
            "status": "blocked",
            "exploitable": false,
            "detail": "What we tested\n----------------\nWrite uploaded files outside the intended tenant folder using the path form field.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker logs in and POSTs to /upload.php with path= such as tenant-lab/../../public/uploads.\n2. The server joins path + filename without canonicalization and may place files under public/uploads/ (web-readable).\n\nWhat happened on this run\n----------------\nPath normalized, rejected, or file not web-accessible.\n\nTechnical observations\n----------------\n• Upload HTTP status: 200\n• path field: tenant-lab/../../public/uploads\n• GET /uploads/: 200\n\nSecurity impact\n----------------\nWeb shells or malware planted directly under the document root.\n\nRecommended fix\n----------------\nIgnore client path; derive tenant from session; use realpath() and reject ..; store outside public/.",
            "teaser": "Attacker logs in and POSTs to /upload.php with path= such as tenant-lab/../../public/uploads.",
            "tested_at": "2026-09-28T13:42:33Z",
            "endpoint": "/upload.php",
            "url": "https://testi.compla.nl/upload.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/upload_path_traversal-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/upload_path_traversal-render.html",
                "attack_url": "https://testi.compla.nl/upload.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/upload_path_traversal.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "double_extension_upload",
            "title": "Double extension upload (.php.jpg)",
            "category": "Upload",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nBypass naive extension filters with double extensions (e.g. shell.php.jpg).\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker uploads a file named something.php.jpg containing PHP code.\n2. Weak panels only check the last extension or MIME sniffing — file may be stored and sometimes executed.\n\nWhat happened on this run\n----------------\nFile stored and/or PHP marker executes when requesting /uploads/<name>.\n\nTechnical observations\n----------------\n• Upload HTTP status: 200\n• Filename: shell_VULNHOST_DOUBLEEXT_5ea9667a.php.jpg\n• PHP executed: no\n\nSecurity impact\n----------------\nRemote code execution if the server maps .php.jpg to PHP.\n\nRecommended fix\n----------------\nAllow-list safe extensions; inspect content; randomize stored names; disable PHP in upload dirs.",
            "teaser": "Attacker uploads a file named something.php.jpg containing PHP code.",
            "tested_at": "2026-09-28T13:42:35Z",
            "endpoint": "/upload.php",
            "url": "https://testi.compla.nl/upload.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/double_extension_upload-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/double_extension_upload-render.html",
                "attack_url": "https://testi.compla.nl/upload.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/double_extension_upload.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "zip_slip",
            "title": "Zip Slip on import extract",
            "category": "Upload",
            "status": "blocked",
            "exploitable": false,
            "detail": "What we tested\n----------------\nEscape the extract directory by uploading a zip whose entries contain ../ paths (Zip Slip).\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker crafts a .zip with an entry like ../../../public/uploads/evil.txt (or a .php).\n2. They POST the archive to /tools/import.php; extractTo() runs without sanitizing entry paths.\n\nWhat happened on this run\n----------------\nPaths sanitized, extraction disabled, or zip rejected.\n\nTechnical observations\n----------------\n• Upload HTTP status: 200\n• Zip entry: ../../../public/uploads/zipslip_8173e8d4.txt\n• GET proof: 200\n\nSecurity impact\n----------------\nArbitrary file write leading to RCE or overwrite of config.\n\nRecommended fix\n----------------\nResolve each entry with realpath(); reject ..; extract only basenames into a chrooted dir.",
            "teaser": "Attacker crafts a .zip with an entry like ../../../public/uploads/evil.txt (or a .php).",
            "tested_at": "2026-09-28T13:42:37Z",
            "endpoint": "/tools/import.php",
            "url": "https://testi.compla.nl/tools/import.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/zip_slip-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/zip_slip-render.html",
                "attack_url": "https://testi.compla.nl/tools/import.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/zip_slip.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "xml_xxe",
            "title": "XML external entity (XXE)",
            "category": "Injection",
            "status": "blocked",
            "exploitable": false,
            "detail": "What we tested\n----------------\nRead server files via XML external entities in config import.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker logs in and POSTs malicious XML to /tools/import-xml.php with a DOCTYPE and SYSTEM entity.\n2. Parser expands entities (LIBXML_NOENT) and echoes content — file:// or php://filter may leak .env.\n\nWhat happened on this run\n----------------\nEntities disabled, parser hardened, or input rejected.\n\nTechnical observations\n----------------\n• HTTP status: 200\n• Request: POST /tools/import-xml.php (XXE entity)\n\nSecurity impact\n----------------\nDisclosure of credentials and keys; SSRF in some parsers.\n\nRecommended fix\n----------------\nDisable external entities; use libxml_disable_entity_loader patterns; prefer JSON config.",
            "teaser": "Attacker logs in and POSTs malicious XML to /tools/import-xml.php with a DOCTYPE and SYSTEM entity.",
            "tested_at": "2026-09-28T13:42:39Z",
            "endpoint": "/tools/import-xml.php",
            "url": "https://testi.compla.nl/tools/import-xml.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/xml_xxe-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/xml_xxe-render.html",
                "attack_url": "https://testi.compla.nl/tools/import-xml.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/xml_xxe.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "open_redirect",
            "title": "Open redirect (next=)",
            "category": "Redirect",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nRedirect victims to an attacker site using an unvalidated next/url parameter.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker links to /redirect.php?next=https://evil.example/phish (logout, SSO, or marketing redirect).\n2. Browser follows HTTP 302 Location to the attacker URL — trusted domain in the address bar first.\n\nWhat happened on this run\n----------------\nLocation header points to the external attacker URL unchanged.\n\nTechnical observations\n----------------\n• HTTP status: 302\n• Location: https://evil.vulnhost-lab.example/phish\n• Expected: https://evil.vulnhost-lab.example/phish\n\nSecurity impact\n----------------\nPhishing, OAuth token theft, malware downloads.\n\nRecommended fix\n----------------\nAllow only relative paths or fixed host allow-list; never pass raw user URL to Location.",
            "teaser": "Attacker links to /redirect.php?next=https://evil.example/phish (logout, SSO, or marketing redirect).",
            "tested_at": "2026-09-28T13:42:40Z",
            "endpoint": "/redirect.php",
            "url": "https://testi.compla.nl/redirect.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/open_redirect-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/open_redirect-render.html",
                "attack_url": "https://testi.compla.nl/redirect.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/open_redirect.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "csrf_admin_email",
            "title": "CSRF on admin email update",
            "category": "CSRF",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nChange admin settings with a cross-site form POST (no CSRF token).\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker hosts a page that auto-POSTs email=attacker@evil to /admin.php.\n2. Victim admin visits while logged in — or in this lab, admin.php may accept anonymous POST.\n\nWhat happened on this run\n----------------\nAdmin email updated or probe address visible on /admin.php without anti-CSRF controls.\n\nTechnical observations\n----------------\n• POST status: 200\n• Probe email: csrf-probe-41616026@evil.test\n• GET admin: 200\n\nSecurity impact\n----------------\nAccount takeover flows, password reset poisoning, social engineering.\n\nRecommended fix\n----------------\nCSRF tokens on all state-changing POSTs; SameSite=Strict cookies; re-auth for sensitive actions.",
            "teaser": "Attacker hosts a page that auto-POSTs email=attacker@evil to /admin.php.",
            "tested_at": "2026-09-28T13:42:42Z",
            "endpoint": "/admin.php",
            "url": "https://testi.compla.nl/admin.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/csrf_admin_email-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/csrf_admin_email-render.html",
                "attack_url": "https://testi.compla.nl/admin.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/csrf_admin_email.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "directory_listing_uploads",
            "title": "Directory listing on /uploads/",
            "category": "Disclosure",
            "status": "blocked",
            "exploitable": false,
            "detail": "What we tested\n----------------\nEnumerate uploaded filenames via directory listing on /uploads/.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker opens /uploads/ in the browser (scanner or manual).\n2. Server returns Index of /uploads with file names — no login required.\n\nWhat happened on this run\n----------------\n403/404 or empty index without file names.\n\nTechnical observations\n----------------\n• HTTP status: 200\n• Request: GET /uploads/\n\nSecurity impact\n----------------\nLeak of private filenames, shells, backups; aids further attacks.\n\nRecommended fix\n----------------\nDisable Options +Indexes; serve uploads via controlled download script only.",
            "teaser": "Attacker opens /uploads/ in the browser (scanner or manual).",
            "tested_at": "2026-09-28T13:42:44Z",
            "endpoint": "/uploads/",
            "url": "https://testi.compla.nl/uploads/",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/directory_listing_uploads-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/directory_listing_uploads-render.html",
                "attack_url": "https://testi.compla.nl/uploads/",
                "screenshot": "https://testi.compla.nl/lab-evidence/directory_listing_uploads.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "exposed_git",
            "title": "Exposed .git/HEAD",
            "category": "Disclosure",
            "status": "blocked",
            "exploitable": false,
            "detail": "What we tested\n----------------\nDetect public .git metadata (common deployment mistake).\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker requests /.git/HEAD (automated scanners do this constantly).\n2. If deployed from git without excluding .git, ref: refs/heads/main is returned.\n\nWhat happened on this run\n----------------\n404/403 or no git metadata at web root.\n\nTechnical observations\n----------------\n• HTTP status: 403\n• Body preview: Forbidden\n\nSecurity impact\n----------------\nFull source history recovery via git tools.\n\nRecommended fix\n----------------\nNever deploy .git to web root; use CI artifacts; block /.git in nginx/Apache.",
            "teaser": "Attacker requests /.git/HEAD (automated scanners do this constantly).",
            "tested_at": "2026-09-28T13:42:47Z",
            "endpoint": "/.git/HEAD",
            "url": "https://testi.compla.nl/.git/HEAD",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/exposed_git-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/exposed_git-render.html",
                "attack_url": "https://testi.compla.nl/.git/HEAD",
                "screenshot": "https://testi.compla.nl/lab-evidence/exposed_git.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "ssrf_file_scheme",
            "title": "SSRF file:// local read",
            "category": "SSRF",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nUse SSRF to read local files via file:// URLs.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker logs in and calls /tools/fetch.php?url=file:///etc/passwd (or file:// paths to .env).\n2. Server-side fetch returns file contents in the HTML response.\n\nWhat happened on this run\n----------------\nLocal file content (e.g. root: from /etc/passwd) appeared in the response.\n\nTechnical observations\n----------------\n• HTTP status: 200\n• URL: file:///etc/passwd\n\nSecurity impact\n----------------\nRead secrets and system files from the app server.\n\nRecommended fix\n----------------\nAllow only http(s) to approved hosts; block file:// and private IPs.",
            "teaser": "Attacker logs in and calls /tools/fetch.php?url=file:///etc/passwd (or file:// paths to .env).",
            "tested_at": "2026-09-28T13:42:49Z",
            "endpoint": "/tools/fetch.php",
            "url": "https://testi.compla.nl/tools/fetch.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/ssrf_file_scheme-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/ssrf_file_scheme-render.html",
                "attack_url": "https://testi.compla.nl/tools/fetch.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/ssrf_file_scheme.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "cors_permissive_api",
            "title": "Permissive CORS on API",
            "category": "CORS",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nDetect APIs that reflect Origin with credentials allowed.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker's site sends fetch() to /api/whoami.php with Origin: https://evil.example and cookies.\n2. Response includes Access-Control-Allow-Origin matching evil and Allow-Credentials: true.\n\nWhat happened on this run\n----------------\nPermissive CORS headers allow cross-origin authenticated reads.\n\nTechnical observations\n----------------\n• Origin: https://evil.vulnhost-lab.example\n• ACAO: https://evil.vulnhost-lab.example\n• Credentials: true\n\nSecurity impact\n----------------\nSteal JSON session data from victims who visit attacker pages while logged in.\n\nRecommended fix\n----------------\nNever use * with credentials; strict Origin allow-list; use same-site cookies.",
            "teaser": "Attacker's site sends fetch() to /api/whoami.php with Origin: https://evil.example and cookies.",
            "tested_at": "2026-09-28T13:42:50Z",
            "endpoint": "/api/whoami.php",
            "url": "https://testi.compla.nl/api/whoami.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/cors_permissive_api-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/cors_permissive_api-render.html",
                "attack_url": "https://testi.compla.nl/api/whoami.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/cors_permissive_api.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "host_header_reset",
            "title": "Host header in password-reset link",
            "category": "Header injection",
            "status": "blocked",
            "exploitable": false,
            "detail": "What we tested\n----------------\nPoison password-reset links using Host / X-Forwarded-Host.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker POSTs /tools/forgot.php with X-Forwarded-Host: attacker.example.\n2. Reset URL in the response uses the attacker host — victim clicks and leaks tokens.\n\nWhat happened on this run\n----------------\nCanonical host hard-coded; forwarded headers ignored.\n\nTechnical observations\n----------------\n• X-Forwarded-Host: attacker-cache.vulnhost-lab.example\n\nSecurity impact\n----------------\nAccount takeover via phishing reset links on a trusted domain name in email.\n\nRecommended fix\n----------------\nBuild URLs from configured APP_URL only; strip untrusted X-Forwarded-*.",
            "teaser": "Attacker POSTs /tools/forgot.php with X-Forwarded-Host: attacker.example.",
            "tested_at": "2026-09-28T13:42:52Z",
            "endpoint": "/tools/forgot.php",
            "url": "https://testi.compla.nl/tools/forgot.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/host_header_reset-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/host_header_reset-render.html",
                "attack_url": "https://testi.compla.nl/tools/forgot.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/host_header_reset.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "crlf_content_disposition",
            "title": "CRLF in Content-Disposition filename",
            "category": "Injection",
            "status": "blocked",
            "exploitable": false,
            "detail": "What we tested\n----------------\nInject response headers via CRLF in download filename.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker requests /tools/export.php?filename=lab.txt%0d%0aX-Lab-Injected:%20true.\n2. Unsanitized filename breaks out of Content-Disposition and adds headers.\n\nWhat happened on this run\n----------------\nCRLF stripped, filename encoded, or headers not injectable.\n\nTechnical observations\n----------------\n• X-Lab-Injected header: (absent)\n\nSecurity impact\n----------------\nSession fixation via Set-Cookie injection, XSS via injected headers in proxies.\n\nRecommended fix\n----------------\nStrip \\r\\n from header values; use RFC 5987 filename* encoding only.",
            "teaser": "Attacker requests /tools/export.php?filename=lab.txt%0d%0aX-Lab-Injected:%20true.",
            "tested_at": "2026-09-28T13:42:55Z",
            "endpoint": "/tools/export.php",
            "url": "https://testi.compla.nl/tools/export.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/crlf_content_disposition-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/crlf_content_disposition-render.html",
                "attack_url": "https://testi.compla.nl/tools/export.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/crlf_content_disposition.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "login_no_rate_limit",
            "title": "No login rate limiting",
            "category": "Authentication",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nCheck whether failed logins are throttled or locked out.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker script sends many POST /login.php attempts with wrong passwords.\n2. No HTTP 429, captcha, or lockout message appears within the burst.\n\nWhat happened on this run\n----------------\nAll attempts processed without rate limiting (brute force feasible).\n\nTechnical observations\n----------------\n• Attempts: 8\n• Rate limited: no\n• Last status: 200\n\nSecurity impact\n----------------\nOffline-quality guessing against live accounts.\n\nRecommended fix\n----------------\nExponential backoff, CAPTCHA, account lockout, WAF rate limits.",
            "teaser": "Attacker script sends many POST /login.php attempts with wrong passwords.",
            "tested_at": "2026-09-28T13:42:57Z",
            "endpoint": "/login.php",
            "url": "https://testi.compla.nl/login.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/login_no_rate_limit-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/login_no_rate_limit-render.html",
                "attack_url": "https://testi.compla.nl/login.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/login_no_rate_limit.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "session_cookie_insecure",
            "title": "Session cookie missing HttpOnly / Secure",
            "category": "Session",
            "status": "blocked",
            "exploitable": false,
            "detail": "What we tested\n----------------\nSession cookies should use HttpOnly and Secure flags.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. After successful login, inspect Set-Cookie for PHPSESSID.\n2. Flags HttpOnly and/or Secure are missing on HTTPS deployments.\n\nWhat happened on this run\n----------------\nBoth flags present on session cookie.\n\nTechnical observations\n----------------\n• Set-Cookie (trunc): \n• Missing HttpOnly: yes\n• Missing Secure: yes\n\nSecurity impact\n----------------\nSession hijack via XSS or network sniffing.\n\nRecommended fix\n----------------\nsession.cookie_httponly=1, session.cookie_secure=1 on HTTPS, SameSite=Lax/Strict.",
            "teaser": "After successful login, inspect Set-Cookie for PHPSESSID.",
            "tested_at": "2026-09-28T13:42:59Z",
            "endpoint": "/login.php",
            "url": "https://testi.compla.nl/login.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/session_cookie_insecure-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/session_cookie_insecure-render.html",
                "attack_url": "https://testi.compla.nl/login.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/session_cookie_insecure.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "session_fixation",
            "title": "Session fixation (no rotation on login)",
            "category": "Session",
            "status": "blocked",
            "exploitable": false,
            "detail": "What we tested\n----------------\nSession ID must change after authentication.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker sets victim's PHPSESSID, victim logs in, same id remains active.\n2. Attacker reuses that id to hijack the authenticated session.\n\nWhat happened on this run\n----------------\nsession_regenerate_id(true) on login.\n\nTechnical observations\n----------------\n• Before: bef019cf3c1c2bfafa00697c28f7efd1\n• After: 5b1f7388d68631e01699ccd0c0af51bf\n\nSecurity impact\n----------------\nAccount takeover without stealing password.\n\nRecommended fix\n----------------\nRegenerate session id on privilege change; invalidate old sessions.",
            "teaser": "Attacker sets victim's PHPSESSID, victim logs in, same id remains active.",
            "tested_at": "2026-09-28T13:43:01Z",
            "endpoint": "/login.php",
            "url": "https://testi.compla.nl/login.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/session_fixation-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/session_fixation-render.html",
                "attack_url": "https://testi.compla.nl/login.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/session_fixation.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "mass_assignment_role",
            "title": "Mass assignment (role=admin)",
            "category": "Access control",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nPrevent users from setting privileged fields via profile forms.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Low-privilege user alice POSTs /profile.php with role=admin.\n2. Database role column updates without server-side allow-list.\n\nWhat happened on this run\n----------------\nDashboard shows alice as admin after mass assignment.\n\nTechnical observations\n----------------\n• User: alice\n• POST role: admin\n\nSecurity impact\n----------------\nVertical privilege escalation to administrator.\n\nRecommended fix\n----------------\nBind only whitelisted fields; never trust role/is_admin from POST.",
            "teaser": "Low-privilege user alice POSTs /profile.php with role=admin.",
            "tested_at": "2026-09-28T13:43:03Z",
            "endpoint": "/profile.php",
            "url": "https://testi.compla.nl/profile.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/mass_assignment_role-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/mass_assignment_role-render.html",
                "attack_url": "https://testi.compla.nl/profile.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/mass_assignment_role.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "exposed_backup_zip",
            "title": "Exposed backup.zip at web root",
            "category": "Disclosure",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nFind downloadable backup archives in the web root.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Scanner requests /backup.zip, /site.zip, /db.sql.gz, etc.\n2. HTTP 200 returns a zip file (PK signature).\n\nWhat happened on this run\n----------------\nbackup.zip (or similar) downloadable without auth.\n\nTechnical observations\n----------------\n• HTTP status: 200\n\nSecurity impact\n----------------\nFull site + database leak from one URL.\n\nRecommended fix\n----------------\nNever place backups under public/; use off-site encrypted storage.",
            "teaser": "Scanner requests /backup.zip, /site.zip, /db.sql.gz, etc.",
            "tested_at": "2026-09-28T13:43:05Z",
            "endpoint": "/backup.zip",
            "url": "https://testi.compla.nl/backup.zip",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/exposed_backup_zip-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/exposed_backup_zip-render.html",
                "attack_url": "https://testi.compla.nl/backup.zip",
                "screenshot": "https://testi.compla.nl/lab-evidence/exposed_backup_zip.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "missing_security_headers",
            "title": "Missing baseline security headers",
            "category": "Headers",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nBaseline protective headers on HTML pages.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker loads / and inspects response headers.\n2. X-Frame-Options and Content-Security-Policy are both absent.\n\nWhat happened on this run\n----------------\nNeither X-Frame-Options nor CSP is set (clickjacking / XSS impact worse).\n\nTechnical observations\n----------------\n• X-Frame-Options: (missing)\n• CSP: (missing)\n\nSecurity impact\n----------------\nEasier clickjacking and inline script abuse.\n\nRecommended fix\n----------------\nAdd CSP, X-Frame-Options or frame-ancestors, HSTS, Referrer-Policy.",
            "teaser": "Attacker loads / and inspects response headers.",
            "tested_at": "2026-09-28T13:43:06Z",
            "endpoint": "/",
            "url": "https://testi.compla.nl/",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/missing_security_headers-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/missing_security_headers-render.html",
                "attack_url": "https://testi.compla.nl/",
                "screenshot": "https://testi.compla.nl/lab-evidence/missing_security_headers.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "idor_files",
            "title": "IDOR list another user files",
            "category": "Access control",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nList another tenant's files by changing a user ID parameter.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker logs in as one user, then changes the URL to /files.php?user=2\n2. The page lists filenames from tenant 2 (e.g. idor_probe.txt) without verifying the session may access that tenant.\n\nWhat happened on this run\n----------------\nAnother user's file list appeared in the browser.\n\nTechnical observations\n----------------\n• HTTP status: 200\n• Request: GET https://testi.compla.nl/files.php?user=2\n\nSecurity impact\n----------------\nHorizontal privilege escalation and data enumeration.\n\nRecommended fix\n----------------\nDerive tenant/user ID from the session only; ignore client-supplied user= parameters.",
            "teaser": "Attacker logs in as one user, then changes the URL to /files.php?user=2",
            "tested_at": "2026-09-28T13:43:09Z",
            "endpoint": "/files.php?user=2",
            "url": "https://testi.compla.nl/files.php?user=2",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/idor_files-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/idor_files-render.html",
                "attack_url": "https://testi.compla.nl/files.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/idor_files.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "download_traversal",
            "title": "Insecure download path (any uploads path)",
            "category": "Path traversal",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nDownload files across tenants via predictable paths.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker logs in and requests /download.php?file=tenant-2/idor_probe.txt in the browser.\n2. The file body (e.g. idor-ok) downloads or displays — no check that the session owns tenant-2.\n\nWhat happened on this run\n----------------\nCross-tenant file content was returned.\n\nTechnical observations\n----------------\n• HTTP status: 200\n• Request: GET https://testi.compla.nl/download.php?file=tenant-2/idor_probe.txt\n\nSecurity impact\n----------------\nConfidential file exfiltration by path guessing.\n\nRecommended fix\n----------------\nAuthorize downloads against DB records tied to the current user; never use raw client paths.",
            "teaser": "Attacker logs in and requests /download.php?file=tenant-2/idor_probe.txt in the browser.",
            "tested_at": "2026-09-28T13:43:11Z",
            "endpoint": "/download.php?file=tenant-2/idor_probe.txt",
            "url": "https://testi.compla.nl/download.php?file=tenant-2/idor_probe.txt",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/download_traversal-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/download_traversal-render.html",
                "attack_url": "https://testi.compla.nl/download.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/download_traversal.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "admin_no_auth",
            "title": "Admin panel without login",
            "category": "Access control",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nAccess admin functions without logging in.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker opens /admin.php in a private/incognito window with no session cookie.\n2. The full administration UI loads, including user delete forms — no redirect to login.\n\nWhat happened on this run\n----------------\nAdmin pages rendered anonymously.\n\nTechnical observations\n----------------\n• HTTP status: 200\n• Request: GET https://testi.compla.nl/admin.php (no cookie)\n\nSecurity impact\n----------------\nFull administrative control without credentials.\n\nRecommended fix\n----------------\nRequire auth + role middleware on every admin route.",
            "teaser": "Attacker opens /admin.php in a private/incognito window with no session cookie.",
            "tested_at": "2026-09-28T13:43:13Z",
            "endpoint": "/admin.php",
            "url": "https://testi.compla.nl/admin.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/admin_no_auth-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/admin_no_auth-render.html",
                "attack_url": "https://testi.compla.nl/admin.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/admin_no_auth.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "cmd_injection",
            "title": "Ping command injection",
            "category": "Command injection",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nExecute OS commands via the ping tool.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker logs in and opens the Ping tool at /tools/ping.php.\n2. In the host field they enter: 127.0.0.1; echo VULNHOST_CMDI_OK (Unix) or use & on Windows.\n3. Submit — the page shows ping output plus VULNHOST_CMDI_OK, proving shell metacharacters ran a second command.\n\nWhat happened on this run\n----------------\nInjected command output appeared in the tool response.\n\nTechnical observations\n----------------\n• HTTP status: 200\n• Host parameter: 127.0.0.1; echo VULNHOST_CMDI_OK\n\nSecurity impact\n----------------\nRemote code execution as the web server OS user.\n\nRecommended fix\n----------------\nDo not call shell with user input; validate hostnames with strict allow-list regex.",
            "teaser": "Attacker logs in and opens the Ping tool at /tools/ping.php.",
            "tested_at": "2026-09-28T13:43:14Z",
            "endpoint": "/tools/ping.php",
            "url": "https://testi.compla.nl/tools/ping.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/cmd_injection-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/cmd_injection-render.html",
                "attack_url": "https://testi.compla.nl/tools/ping.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/cmd_injection.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "ssrf_local",
            "title": "SSRF read local file (file://)",
            "category": "SSRF",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nForce the server to fetch attacker-chosen URLs (SSRF).\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker logs in and opens /tools/fetch.php?url= with a target URL (e.g. same-site /robots.txt or file:///.env on some runners).\n2. The server fetches that URL server-side and returns the body in the browser — attacker reads internal or local content.\n\nWhat happened on this run\n----------------\nServer-side fetch returned the requested resource content to the attacker.\n\nTechnical observations\n----------------\n• HTTP status: 200\n• Fetched URL: https://testi.compla.nl/robots.txt\n• Note: Python runner uses HTTP SSRF to same-origin robots.txt\n\nSecurity impact\n----------------\nInternal network probing, cloud metadata theft, local file read via file://.\n\nRecommended fix\n----------------\nBlock private IPs, metadata IPs, and file://; use an allow-list of outbound hosts.",
            "teaser": "Attacker logs in and opens /tools/fetch.php?url= with a target URL (e.g. same-site /robots.txt or file:///.env on some runners).",
            "tested_at": "2026-09-28T13:43:18Z",
            "endpoint": "/tools/fetch.php",
            "url": "https://testi.compla.nl/tools/fetch.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/ssrf_local-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/ssrf_local-render.html",
                "attack_url": "https://testi.compla.nl/tools/fetch.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/ssrf_local.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "reflected_xss",
            "title": "Reflected XSS on search",
            "category": "XSS",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nReflected XSS via search query echoed in HTML.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker crafts a link: /search.php?q=<vulnhost-xss-probe> (or a script tag) and opens it in the browser.\n2. The search page prints the query unescaped inside HTML (e.g. Results for: <tag>), so script can run in a victim's browser.\n\nWhat happened on this run\n----------------\nRaw attacker HTML appeared in the document source.\n\nTechnical observations\n----------------\n• HTTP status: 200\n• Query probe: <vulnhost-xss-probe>\n\nSecurity impact\n----------------\nSession hijack, phishing, actions as the victim.\n\nRecommended fix\n----------------\nHTML-encode all dynamic output; deploy Content-Security-Policy.",
            "teaser": "Attacker crafts a link: /search.php?q=<vulnhost-xss-probe> (or a script tag) and opens it in the browser.",
            "tested_at": "2026-09-28T13:43:19Z",
            "endpoint": "/search.php",
            "url": "https://testi.compla.nl/search.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/reflected_xss-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/reflected_xss-render.html",
                "attack_url": "https://testi.compla.nl/search.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/reflected_xss.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "stored_xss",
            "title": "Stored XSS on tickets",
            "category": "XSS",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nPersistent XSS in ticket bodies.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker logs in, opens /tickets.php, and submits a new ticket whose body contains HTML/script markup.\n2. On reload, the ticket list renders that body as raw HTML — any user viewing tickets executes the payload.\n\nWhat happened on this run\n----------------\nStored markup appeared unescaped in the ticket view.\n\nTechnical observations\n----------------\n• HTTP status: 200\n• Stored probe: <vulnhost-stored-04ebc8>\n\nSecurity impact\n----------------\nPersistent compromise of every user who views tickets.\n\nRecommended fix\n----------------\nEncode on output, CSP, sanitize rich text with a safe library.",
            "teaser": "Attacker logs in, opens /tickets.php, and submits a new ticket whose body contains HTML/script markup.",
            "tested_at": "2026-09-28T13:43:22Z",
            "endpoint": "/tickets.php",
            "url": "https://testi.compla.nl/tickets.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/stored_xss-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/stored_xss-render.html",
                "attack_url": "https://testi.compla.nl/tickets.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/stored_xss.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "debug_phpinfo",
            "title": "Exposed phpinfo",
            "category": "Disclosure",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nUnauthenticated access to phpinfo().\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker opens /debug.php directly in the browser without logging in.\n2. Full PHP configuration, extensions, and paths are displayed.\n\nWhat happened on this run\n----------------\nphpinfo() output was publicly visible.\n\nTechnical observations\n----------------\n• HTTP status: 200\n• Request: GET https://testi.compla.nl/debug.php\n\nSecurity impact\n----------------\nReconnaissance for version-specific exploits.\n\nRecommended fix\n----------------\nRemove debug routes in production; restrict to localhost if needed.",
            "teaser": "Attacker opens /debug.php directly in the browser without logging in.",
            "tested_at": "2026-09-28T13:43:23Z",
            "endpoint": "/debug.php",
            "url": "https://testi.compla.nl/debug.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/debug_phpinfo-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/debug_phpinfo-render.html",
                "attack_url": "https://testi.compla.nl/debug.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/debug_phpinfo.png"
            },
            "override": null,
            "note": ""
        },
        {
            "id": "unsafe_unserialize",
            "title": "Unsafe unserialize endpoint",
            "category": "Deserialization",
            "status": "exploitable",
            "exploitable": true,
            "detail": "What we tested\n----------------\nUnsafe PHP unserialize on attacker-controlled input.\n\nHow the attacker reproduced this (browser steps)\n----------------\n1. Attacker sends POST /api/legacy.php with form field data= containing a serialized PHP object string.\n2. The API unserializes the blob and echoes object details in the response — gadget chains may lead to RCE.\n\nWhat happened on this run\n----------------\nResponse shows deserialized object (e.g. object(stdClass)).\n\nTechnical observations\n----------------\n• HTTP status: 200\n• POST data: O:8:\"stdClass\":0:{}\n• Request: POST https://testi.compla.nl/api/legacy.php\n\nSecurity impact\n----------------\nObject injection and potential remote code execution.\n\nRecommended fix\n----------------\nUse JSON instead of serialize(); never unserialize untrusted data.",
            "teaser": "Attacker sends POST /api/legacy.php with form field data= containing a serialized PHP object string.",
            "tested_at": "2026-09-28T13:43:25Z",
            "endpoint": "/api/legacy.php",
            "url": "https://testi.compla.nl/api/legacy.php",
            "evidence": {
                "render": "https://testi.compla.nl/lab-evidence/unsafe_unserialize-render.html",
                "proof": "https://testi.compla.nl/lab-evidence/unsafe_unserialize-render.html",
                "attack_url": "https://testi.compla.nl/api/legacy.php",
                "screenshot": "https://testi.compla.nl/lab-evidence/unsafe_unserialize.png"
            },
            "override": null,
            "note": ""
        }
    ]
}