From 5d21574c816e15179f6b27573cde31083eb41a1a Mon Sep 17 00:00:00 2001 From: duffyduck Date: Mon, 1 Jun 2026 13:01:44 +0200 Subject: [PATCH] Pentest 48.3 MEDIUM + 48.4 INFO: Rate-Limit + Token-Invalidierung beim Staff-Passwort-Reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 48.3 (Rate-Limit fehlt): POST /api/users/:id/password verlangt seit 47.3 die Eingabe des eigenen Admin-Passworts. Ohne Throttle könnte ein Angreifer mit gestohlenem JWT die Re-Auth per Brute-Force aushebeln. - Neuer staffPasswordReAuthLimiter (5 Versuche / 10 min, bucket: IP + target-user-id, skipSuccessfulRequests: true) - emit SecurityEvent RATE_LIMIT_HIT severity HIGH - Vor authenticate gemounted, damit auch unauth-Spamming begrenzt wird 48.4 (Alter Token überlebt Self-Reset): Nach erfolgreichem Setzen wird tokenInvalidatedAt des Ziel-Users auf jetzt gesetzt. Greift besonders bei Self-Reset (Admin setzt sich selbst zurück) – ein zuvor gestohlenes Token wird sofort ungültig, statt bis zum natürlichen Ablauf (15 min) brauchbar zu bleiben. Die bestehende Auth-Middleware liest tokenInvalidatedAt bereits. Co-Authored-By: Claude Opus 4.7 (1M context) --- backend/src/controllers/user.controller.ts | 12 +++++++- backend/src/middleware/rateLimit.ts | 35 ++++++++++++++++++++++ backend/src/routes/user.routes.ts | 7 +++-- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/backend/src/controllers/user.controller.ts b/backend/src/controllers/user.controller.ts index ce816349..6a0490cb 100644 --- a/backend/src/controllers/user.controller.ts +++ b/backend/src/controllers/user.controller.ts @@ -241,10 +241,20 @@ export async function setUserPassword(req: Request, res: Response): Promise { + const ip = req.ip || 'unknown'; + const targetUserId = (req.params?.id ?? '').toString(); + return `${ip}|staff-pw|${targetUserId}`; + }, + handler: (req, res, _next, options) => { + onLimitReached('staff-password-set', 'HIGH')(req, res); + res.status(options.statusCode).json(options.message); + }, +}); + /** * Public-Consent-Endpoints (/api/public/consent/:hash[/grant|/pdf]) sind * unauthenticated. Der hash ist 128-bit-UUID → kein Brute-Force-Risk, diff --git a/backend/src/routes/user.routes.ts b/backend/src/routes/user.routes.ts index dbb57ffd..d394366e 100644 --- a/backend/src/routes/user.routes.ts +++ b/backend/src/routes/user.routes.ts @@ -1,6 +1,7 @@ import { Router } from 'express'; import * as userController from '../controllers/user.controller.js'; import { authenticate, requirePermission } from '../middleware/auth.js'; +import { staffPasswordReAuthLimiter } from '../middleware/rateLimit.js'; const router = Router(); @@ -10,8 +11,10 @@ router.post('/', authenticate, requirePermission('users:create'), userController router.get('/:id', authenticate, requirePermission('users:read'), userController.getUser); router.put('/:id', authenticate, requirePermission('users:update'), userController.updateUser); router.delete('/:id', authenticate, requirePermission('users:delete'), userController.deleteUser); -// Passwort-Reset durch Admin – dedizierter Endpoint (Pentest Runde 12) -router.post('/:id/password', authenticate, requirePermission('users:update'), userController.setUserPassword); +// Passwort-Reset durch Admin – dedizierter Endpoint (Pentest Runde 12). +// 47.3 verlangt Re-Auth (currentPassword), 48.3 wirft einen Rate-Limit +// davor, damit ein gestohlener JWT das Admin-Passwort nicht brute-forcen kann. +router.post('/:id/password', staffPasswordReAuthLimiter, authenticate, requirePermission('users:update'), userController.setUserPassword); // Roles router.get('/roles/list', authenticate, requirePermission('users:read'), userController.getRoles);