aimless-security
Version:
Enhanced Runtime Application Self-Protection (RASP) and API Fuzzing Engine with advanced threat detection, behavioral analysis, and intelligent response scoring for Node.js applications
250 lines • 8.49 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.CSRFDetector = void 0;
const types_1 = require("../types");
const crypto = __importStar(require("crypto"));
class CSRFDetector {
constructor(trustedOrigins = []) {
this.defaultExpiry = 3600000; // 1 hour
this.cleanupInterval = 300000; // 5 minutes
this.trustedOrigins = new Set(trustedOrigins);
this.tokenStore = new Map();
this.startCleanup();
}
/**
* Auto-cleanup expired tokens
*/
startCleanup() {
this.cleanupTimer = setInterval(() => {
this.cleanupExpiredTokens();
}, this.cleanupInterval);
}
/**
* Clean up expired tokens to prevent memory leaks
*/
cleanupExpiredTokens() {
const now = Date.now();
for (const [sessionId, data] of this.tokenStore.entries()) {
if (data.expires < now) {
this.tokenStore.delete(sessionId);
}
}
}
/**
* Stop cleanup timer (call when shutting down)
*/
destroy() {
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
}
}
/**
* Generate a cryptographically secure CSRF token
*/
generateToken(sessionId, expiryMs) {
const token = crypto.randomBytes(32).toString('hex');
const expires = Date.now() + (expiryMs || this.defaultExpiry);
const createdAt = Date.now();
this.tokenStore.set(sessionId, { token, expires, createdAt, used: false });
return token;
}
/**
* Validate CSRF token with one-time use option
*/
validateToken(sessionId, token, oneTimeUse = false) {
const stored = this.tokenStore.get(sessionId);
if (!stored)
return false;
// Check expiry
if (stored.expires < Date.now()) {
this.tokenStore.delete(sessionId);
return false;
}
// Check if already used (for one-time tokens)
if (oneTimeUse && stored.used) {
return false;
}
// Validate token using timing-safe comparison
const isValid = this.timingSafeEqual(stored.token, token);
if (isValid && oneTimeUse) {
stored.used = true;
}
return isValid;
}
/**
* Timing-safe comparison to prevent timing attacks
*/
timingSafeEqual(a, b) {
if (a.length !== b.length)
return false;
const bufA = Buffer.from(a);
const bufB = Buffer.from(b);
return crypto.timingSafeEqual(bufA, bufB);
}
/**
* Enhanced CSRF detection with better origin validation
*/
detect(method, origin, referer, csrfToken, sessionId, cookies) {
// Only check state-changing methods
if (!['POST', 'PUT', 'DELETE', 'PATCH'].includes(method.toUpperCase())) {
return null;
}
// Check Origin header
if (origin) {
try {
const originUrl = new URL(origin);
if (!this.isTrustedOrigin(originUrl.origin)) {
return {
type: types_1.ThreatType.CSRF,
severity: 'critical',
description: 'CSRF attack detected: Untrusted origin',
payload: origin,
timestamp: new Date(),
blocked: true,
metadata: { method, origin, check: 'origin-header' }
};
}
}
catch (e) {
// Invalid origin URL
return {
type: types_1.ThreatType.CSRF,
severity: 'high',
description: 'CSRF attack detected: Invalid origin header',
payload: origin,
timestamp: new Date(),
blocked: true,
metadata: { method, origin, check: 'origin-invalid' }
};
}
}
// Check Referer header as fallback
if (!origin && referer) {
try {
const refererUrl = new URL(referer);
if (!this.isTrustedOrigin(refererUrl.origin)) {
return {
type: types_1.ThreatType.CSRF,
severity: 'high',
description: 'CSRF attack detected: Untrusted referer',
payload: referer,
timestamp: new Date(),
blocked: true,
metadata: { method, referer, check: 'referer-header' }
};
}
}
catch (e) {
// Invalid referer - might be stripped, don't block
}
}
// Double-submit cookie check
if (cookies && cookies['csrf-token']) {
if (csrfToken !== cookies['csrf-token']) {
return {
type: types_1.ThreatType.CSRF,
severity: 'high',
description: 'CSRF attack detected: Cookie mismatch',
timestamp: new Date(),
blocked: true,
metadata: { method, check: 'double-submit-cookie' }
};
}
}
// Check CSRF token (synchronizer token pattern)
if (sessionId && !this.validateToken(sessionId, csrfToken || '')) {
return {
type: types_1.ThreatType.CSRF,
severity: 'high',
description: 'CSRF attack detected: Invalid or missing token',
timestamp: new Date(),
blocked: true,
metadata: { method, hasToken: !!csrfToken, check: 'synchronizer-token' }
};
}
return null;
}
/**
* Check if origin is trusted
*/
isTrustedOrigin(origin) {
if (this.trustedOrigins.size === 0)
return true;
return this.trustedOrigins.has(origin);
}
/**
* Add a trusted origin
*/
addTrustedOrigin(origin) {
this.trustedOrigins.add(origin);
}
/**
* Remove a trusted origin
*/
removeTrustedOrigin(origin) {
this.trustedOrigins.delete(origin);
}
/**
* Get all trusted origins
*/
getTrustedOrigins() {
return Array.from(this.trustedOrigins);
}
/**
* Revoke a specific token
*/
revokeToken(sessionId) {
return this.tokenStore.delete(sessionId);
}
/**
* Get token info (for debugging/monitoring)
*/
getTokenInfo(sessionId) {
const stored = this.tokenStore.get(sessionId);
if (!stored) {
return { valid: false };
}
const now = Date.now();
const valid = stored.expires > now;
return {
valid,
expiresIn: valid ? stored.expires - now : 0,
used: stored.used
};
}
}
exports.CSRFDetector = CSRFDetector;
//# sourceMappingURL=csrf-detector.js.map