Sicherheit: Allowlist anhand des echten Socket-Peers statt X-Forwarded-For
Die Allowlist-Pruefung nutzte extractDebugClientIp, das X-Forwarded-For zuerst auswertet - ein angreiferkontrollierter Header. Damit konnte ein entfernter Client auf einem 0.0.0.0-Bind die Allowlist komplett umgehen (X-Forwarded-For: 127.0.0.1 -> als Loopback gewertet). Die Allowlist bot so keinerlei Schutz; nur das Token blieb. Fix: Enforcement liest jetzt ausschliesslich req.socket.remoteAddress (getPeerIp), das vom Kernel aus der TCP-Verbindung gesetzt wird und nicht per Header faelschbar ist. X-Forwarded-For wird nur noch fuer das Trace-Log (Beobachtbarkeit) verwendet, nie fuer die Zugriffsentscheidung. Reine, exportierte Matcher-Funktion evaluateClientAllowed. Tests umgebaut auf den Threat-Model statt den faelschbaren Kanal: Unit-Tests fuer Loopback/exakt/CIDR/fail-closed + expliziter Forged-XFF-Test (socket.remoteAddress 8.8.8.8 + X-Forwarded-For 127.0.0.1 -> verweigert). Integration: Loopback verbindet, gespooftes X-Forwarded-For wird ignoriert, Auth bleibt erzwungen, Live-Reload.
This commit is contained in:
@@ -219,15 +219,23 @@ function matchIpRule(clientIp: string, rule: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
function isClientAllowed(clientIp: string): boolean {
|
||||
export function evaluateClientAllowed(clientIp: string, rules: string[]): boolean {
|
||||
const client = normalizeIp(clientIp);
|
||||
if (isLoopbackIp(client) || client === "") {
|
||||
return true;
|
||||
}
|
||||
if (allowlist.length === 0) {
|
||||
if (rules.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return allowlist.some((rule) => matchIpRule(client, rule));
|
||||
return rules.some((rule) => matchIpRule(client, rule));
|
||||
}
|
||||
|
||||
export function getPeerIp(req: http.IncomingMessage): string {
|
||||
return normalizeIp(req.socket?.remoteAddress || "");
|
||||
}
|
||||
|
||||
function isClientAllowed(clientIp: string): boolean {
|
||||
return evaluateClientAllowed(clientIp, allowlist);
|
||||
}
|
||||
|
||||
function checkAuth(req: http.IncomingMessage): boolean {
|
||||
@@ -551,15 +559,16 @@ function handleRequest(req: http.IncomingMessage, res: http.ServerResponse): voi
|
||||
return;
|
||||
}
|
||||
|
||||
const clientIp = extractDebugClientIp(req);
|
||||
if (!isClientAllowed(clientIp)) {
|
||||
const peerIp = getPeerIp(req);
|
||||
if (!isClientAllowed(peerIp)) {
|
||||
if (traceConfig.enabled && traceConfig.logDebugRequests) {
|
||||
logTraceEvent("WARN", "debug-http", "Durch Allowlist blockiert", {
|
||||
clientIp,
|
||||
peerIp,
|
||||
forwardedFor: extractDebugClientIp(req),
|
||||
url: sanitizeRequestUrlForTrace(req.url || "/")
|
||||
});
|
||||
}
|
||||
jsonResponse(res, 403, { error: "Forbidden", reason: "Client-IP nicht in Allowlist", clientIp });
|
||||
jsonResponse(res, 403, { error: "Forbidden", reason: "Client-IP nicht in Allowlist", clientIp: peerIp });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user