fixing some seucirty issues
Deploy / deploy (push) Successful in 11s

This commit is contained in:
2026-05-16 11:49:16 +02:00
parent 201c786ae8
commit b63f462cc8
6 changed files with 133 additions and 39 deletions
+14
View File
@@ -20,6 +20,7 @@ class AuthMiddleware
'lifetime' => 86400 * 7,
'path' => '/',
'httponly' => true,
'secure' => true,
'samesite' => 'Lax',
]);
session_start();
@@ -29,6 +30,10 @@ class AuthMiddleware
return null;
}
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$allowedTokens = $this->repo->getAllowedUserTokens();
$userToken = $_SESSION['user_token'] ?? '';
@@ -41,6 +46,15 @@ class AuthMiddleware
'username' => $_SESSION['username'] ?? 'unknown',
'user_token' => $userToken,
'email' => $_SESSION['email'] ?? '',
'csrf_token' => $_SESSION['csrf_token'] ?? '',
];
}
public function validateCsrf(): bool
{
if (empty($_SESSION['csrf_token'])) return false;
$body = json_decode(file_get_contents('php://input'), true);
$token = $body['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
return hash_equals($_SESSION['csrf_token'], $token);
}
}
+36 -14
View File
@@ -33,7 +33,7 @@ class Router
header('Content-Type: application/json');
try {
$publicPaths = ['/health', '/oauth', '/auth/me', '/auth/logout', '/ingest'];
$publicPaths = ['/health', '/oauth', '/auth/me', '/auth/logout', '/auth/csrf', '/ingest'];
$isPublic = false;
foreach ($publicPaths as $pp) {
if ($path === $pp || str_starts_with($path, $pp . '/')) {
@@ -54,31 +54,33 @@ class Router
$result = match (true) {
$path === '/health' && $method === 'GET' => ['status' => 'ok'],
$path === '/auth/csrf' && $method === 'GET' => $this->csrfToken(),
$path === '/ingest' && $method === 'POST' => $this->ingest(),
$path === '/auth/me' && $method === 'GET' => $this->getMe(),
$path === '/auth/logout' && $method === 'POST' => $this->logout(),
$path === '/sources' && $method === 'GET' => $this->repo->getSources(),
$path === '/sources' && $method === 'POST' => $this->createSource(),
$path === '/sources' && $method === 'POST' => $this->requireCsrf(fn() => $this->createSource()),
preg_match('#^/sources/(\d+)$#', $path, $m) && $method === 'DELETE'
=> $this->deleteEntity('source', (int) $m[1]),
=> $this->requireCsrf(fn() => $this->deleteEntity('source', (int) $m[1])),
preg_match('#^/sources/(\d+)$#', $path, $m) && $method === 'PUT'
=> $this->updateSource((int) $m[1]),
=> $this->requireCsrf(fn() => $this->updateSource((int) $m[1])),
$path === '/rules' && $method === 'GET' => $this->repo->getRules(),
$path === '/rules' && $method === 'POST' => $this->createRule(),
$path === '/rules' && $method === 'POST' => $this->requireCsrf(fn() => $this->createRule()),
preg_match('#^/rules/(\d+)$#', $path, $m) && $method === 'DELETE'
=> $this->deleteEntity('rule', (int) $m[1]),
=> $this->requireCsrf(fn() => $this->deleteEntity('rule', (int) $m[1])),
preg_match('#^/rules/(\d+)$#', $path, $m) && $method === 'PUT'
=> $this->updateRule((int) $m[1]),
=> $this->requireCsrf(fn() => $this->updateRule((int) $m[1])),
$path === '/alerts' && $method === 'GET' => $this->getAlerts(),
$path === '/alerts/search' && $method === 'GET' => $this->searchAlerts(),
preg_match('#^/alerts/(\d+)/ack$#', $path, $m) && $method === 'POST'
=> $this->ackAlert((int) $m[1]),
=> $this->requireCsrf(fn() => $this->ackAlert((int) $m[1])),
preg_match('#^/alerts/(\d+)/status$#', $path, $m) && $method === 'POST'
=> $this->updateAlertStatus((int) $m[1]),
=> $this->requireCsrf(fn() => $this->updateAlertStatus((int) $m[1])),
preg_match('#^/alerts/counts$#', $path) && $method === 'GET'
=> $this->repo->getAlertCounts(),
@@ -87,19 +89,19 @@ class Router
$path === '/config/allowed_tokens' && $method === 'GET'
=> ['tokens' => $this->repo->getAllowedUserTokens()],
$path === '/config/allowed_tokens' && $method === 'PUT'
=> $this->updateAllowedTokens(),
=> $this->requireCsrf(fn() => $this->updateAllowedTokens()),
$path === '/config/telegram' && $method === 'GET'
=> $this->getTelegramConfig(),
$path === '/config/telegram' && $method === 'PUT'
=> $this->updateTelegramConfig(),
=> $this->requireCsrf(fn() => $this->updateTelegramConfig()),
$path === '/false-positives' && $method === 'GET'
=> $this->getFalsePositives(),
$path === '/false-positives' && $method === 'POST'
=> $this->createFalsePositive(),
=> $this->requireCsrf(fn() => $this->createFalsePositive()),
preg_match('#^/false-positives/(\d+)$#', $path, $m) && $method === 'DELETE'
=> $this->deleteFalsePositive((int) $m[1]),
=> $this->requireCsrf(fn() => $this->deleteFalsePositive((int) $m[1])),
default => throw new \RuntimeException('Not found', 404),
};
@@ -309,8 +311,13 @@ class Router
private function getTelegramConfig(): array
{
$token = $this->repo->getConfig('telegram_bot_token', '');
$masked = strlen($token) > 8
? substr($token, 0, 8) . '...'
: ($token ? substr($token, 0, 3) . '...' : '');
return [
'bot_token' => $this->repo->getConfig('telegram_bot_token', ''),
'bot_token' => $token,
'bot_token_masked' => $masked,
'chat_id' => $this->repo->getConfig('telegram_chat_id', ''),
];
}
@@ -347,4 +354,19 @@ class Router
$this->repo->deleteFalsePositive($id);
return ['status' => 'deleted', 'id' => $id];
}
private function csrfToken(): array
{
$this->auth->requireAuth();
return ['csrf_token' => $_SESSION['csrf_token'] ?? ''];
}
private function requireCsrf(callable $fn): mixed
{
if (!$this->auth->validateCsrf()) {
http_response_code(403);
return ['error' => 'Invalid or missing CSRF token'];
}
return $fn();
}
}