Files
vulncheck/app/auth
vulncheck 9c84cf2939 fix(security): validate token purpose, verify Wazuh TLS, gate two asset routes
Aus dem Security-Review dieser Branch. Vier Findings, alle mit einem
gemeinsamen Muster: eine Kontrolle existiert, greift aber nicht.

MFA war mit dem Passwort allein umgehbar. _issue_mfa_challenge hat das
Challenge-Token mit create_access_token gebaut — ein regulaerer,
signierter Access-JWT. get_current_user prueft Signatur, Ablauf, JTI,
sub und is_active, aber nie den mfa_pending-Claim; den liest nur
/auth/mfa/verify. Wer das Passwort hat, ruft /auth/login, nimmt das
mfa_token aus der Antwort und schickt es als Bearer an jeden geschuetzten
Endpoint — mit der vollen Rolle aus der DB-Zeile, weil PermissionChecker
die Rolle nicht aus dem Token liest. Dasselbe galt fuer das
Forced-Setup-Token, das in den SSO-Pfaden zusaetzlich als URL-Parameter
uebergeben wird, und fuer Refresh-Tokens: /auth/refresh prueft
type == "refresh", die Gegenrichtung war offen, also war ein
Refresh-Token ein 7-Tage-Bearer und die Rotation umgehbar.

Alle Tokens tragen jetzt einen type-Claim, und get_current_user laesst
nur "access" durch — positiv allowlisten, damit ein neuer Token-Typ per
Default abgelehnt statt per Default gueltig ist. Nebenwirkung: bestehende
Access-Tokens ohne type-Claim werden ungueltig. Refresh-Tokens tragen den
Claim schon, Clients holen sich also im Hintergrund ein neues
Access-Token; wer keins hat, loggt sich einmal neu ein.

Wazuh lief durchgaengig mit verify=False. 15 Call-Sites bauten den
Client mit cfg.get("verify_ssl", False), und das war kein Edge Case: der
wazuhConfig-State im Frontend hatte gar kein verify_ssl-Feld, der Save
schrieb den Key also nie, und der Settings-PUT ersetzt den ganzen Wert.
Der Fallback war der einzig erreichbare Zustand. Ueber diese Verbindung
gehen Manager- und Indexer-Credentials als HTTP Basic. Jetzt
bool(cfg.get("verify_ssl", True)) wie bei Nessus/vCenter/Intune, plus
eine Verify-SSL-Checkbox im Wazuh-Formular, damit ein self-signed Zert
bewusst statt stillschweigend akzeptiert wird.

setup.sh hat einen JWT-Key generiert und ihn nirgends hingeschrieben:
das sed suchte einen Platzhalter, den .env.example seit Laengerem nicht
mehr enthaelt, traf nichts, exit 0, und die naechste Zeile meldete
Erfolg. Die Instanz signierte also mit dem oeffentlich bekannten
CHANGE-ME-String — offline faelschbare Admin-Tokens. Das sed ersetzt
jetzt die ganze Zeile und verifiziert danach; wichtiger ist der
App-seitige Guard in jwt_handler, der Platzhalter und Keys unter 32
Zeichen beim Start ablehnt, so wie totp.py es fuer
AUTH_PROVIDER_CRYPTO_KEY schon tut. Damit ist auch der Operator
abgefangen, der .env.example kopiert und Schritt 2 vergisst.

Beide Setup-Skripte legten ausserdem einen Admin mit hardcodiertem
"changeme" an und umgingen damit die Staerke-Pruefung in db_init. Der
Block ist raus, create_initial_data ist der einzige Bootstrap-Pfad.

Dazu RequireEditor auf /assets/reconcile-lifecycle und
/assets/refresh-exposure — beide hatten nur get_current_user, waehrend
jede andere mutierende Route derselben Datei RequireEditor verlangt.

Nicht gefixt, bewusst: JIT-Provisioning verlinkt eine SSO-Identitaet per
unverifizierter E-Mail auf ein bestehendes lokales Konto
(jit_provisioner.py:60). Kein Privilege Gain, weil _refresh_role die
Rolle neu berechnet, aber ein Lockout des legitimen Admins ist moeglich.
Braucht eine Design-Entscheidung, kein Einzeiler.

tests/test_token_purpose.py deckt die Typ-Logik ab. 92 Tests gruen.
2026-08-26 16:12:34 +02:00
..
2026-02-08 10:15:20 +01:00