diff --git a/security/pentest-2026-06-19.md b/security/pentest-2026-06-19.md new file mode 100644 index 0000000..a2ee3e9 --- /dev/null +++ b/security/pentest-2026-06-19.md @@ -0,0 +1,77 @@ +# Pentest mini-report — versila22/lima-app — 2026-06-19 + +**Probed URL:** https://limaimpro.duckdns.org/ +**Stack:** React 18 / Vite + FastAPI (Python) / Nginx / No PWA +**Counts:** Critical=0 High=4 Medium=4 Low=1 Info=1 + +## Findings + +| Sev | Cat | Title | Location | +|---|---|---|---| +| High | SAST | Hardcoded plaintext admin+member passwords in seed data | backend/app/main.py:49–57 | +| High | SCA | serialize-javascript RCE via crafted RegExp/Date objects | package.json → @rollup/plugin-terser | +| High | SCA | undici TLS certificate validation bypass via SOCKS5 ProxyAgent | package.json (transitive) | +| High | SCA | fast-uri path traversal via percent-encoded dot segments | package.json (transitive) | +| Medium | DAST | Unauthenticated /health/db & /health/migrations expose DB URL prefix | backend/app/main.py:202–237 | +| Medium | SAST | User enumeration via distinct auth error messages | backend/app/routers/auth.py:130–131 | +| Medium | DAST | CSP unsafe-inline in style-src weakens XSS protection | nginx.conf:14 | +| Medium | SCA | react-router open redirect via protocol-relative path (//) | package.json | +| Low | SAST | Default CORS_ORIGINS includes localhost origins (no env override) | backend/app/config.py:13 | +| Info | SCA | vitest GHSA-5xrq-8626-4rwp arbitrary file read/exec — devDep only | devDependencies | + +## Top 3 fixes +1. **Hardcoded seed passwords** — replace with env vars or randomly-generated one-time passwords; never commit plaintext creds, especially for `admin@lima-impro.fr` +2. **Unauthenticated health endpoints** — add `_: Member = Depends(require_admin)` to `/health/db` and `/health/migrations` routes in `main.py` +3. **User enumeration** — unify login error to `"Identifiants invalides."` regardless of whether email is absent or password is wrong; keep per-reason logging server-side only + +## Evidence (High only) + +**Hardcoded seed passwords** — `backend/app/main.py:49–57` +```python +{"email": "admin@lima-impro.fr", "app_role": "admin", "password": "Admin1234!", ...}, +{"email": "marie.leroy@exemple.fr", "password": "Password1!", ...}, +``` +Impact: Anyone with repo access knows the admin password; if not rotated post-deploy, full account takeover. +Fix: Seed passwords via env var `SEED_ADMIN_PASSWORD` or use a generated token sent by email on first run. + +**DB URL prefix leak** — `backend/app/main.py:202–237` +```python +"async_url_prefix": settings.async_database_url[:60], # unauthenticated GET /health/db +``` +Impact: 60-char prefix of DB connection string (host, port, user, start of password) exposed publicly. +Fix: Gate `/health/db` and `/health/migrations` behind `require_admin` dependency. + +**User enumeration** — `backend/app/routers/auth.py:130–131` +```python +"email_not_found": "Aucun compte trouvé pour cet email.", +"wrong_password": "Mot de passe incorrect.", +``` +Impact: Attacker can enumerate valid email addresses, reducing brute-force search space. +Fix: Return identical `"Identifiants invalides."` for both cases at HTTP response level. + +**serialize-javascript RCE** — `npm audit GHSA-5c6j-r48x-rmvq / GHSA-qj8w-gfj5-8c6v` +Impact: Malicious input to bundler pipeline can trigger RCE during build/CI. +Fix: `npm update serialize-javascript` (or update @rollup/plugin-terser to latest). + +## Verified safe +- No hardcoded secrets in source files (API keys, bearer tokens, private keys) +- Admin routes protected by `require_admin` dependency (proper role check) +- JWT secret has validator that blocks default in non-dev mode +- Rate limiting on auth endpoints (5/min login, 3/min forgot-password) +- HSTS `max-age=31536000; includeSubDomains` set in nginx.conf +- X-Content-Type-Options, X-Frame-Options DENY set in nginx.conf +- Sensitive file paths (.env, .git) return 403 (CDN layer) +- TLS 1.0/1.1 disabled; TLS 1.2+1.3 enabled +- TLS certificate valid (expires 2026-07-19) +- No XSS sinks (innerHTML, dangerouslySetInnerHTML) found in src/ +- No localStorage/sessionStorage token storage found +- Vitest critical (GHSA-5xrq-8626-4rwp) is devDependency only — not in production bundle + +## Needs server-side verification +- Full HTTP response headers from live URL (CDN blocked DAST from remote env) +- CORS origin validation on the Railway-deployed API endpoint +- Actual DB URL length — 60-char prefix may or may not include credentials depending on URL format +- Whether SEED_MEMBERS admin password has been rotated in production DB + +## Tools +ran=npm-audit, grep-secrets, curl (TLS/headers), openssl; skipped=pnpm-audit (not available in repo)