aimless-sdk
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
329 lines • 11.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.XSSDetector = void 0;
const types_1 = require("../types");
class XSSDetector {
constructor() {
// Enhanced XSS patterns - more comprehensive detection
this.xssPatterns = [
// Script tags
/<script[^>]*>.*?<\/script>/is,
/<script[^>]*>/i,
// Iframe and embed
/<iframe[^>]*>/i,
/<embed[^>]*>/i,
/<object[^>]*>/i,
/<applet[^>]*>/i,
// JavaScript protocols
/javascript:/i,
/vbscript:/i,
/data:text\/html/i,
/data:text\/javascript/i,
// Event handlers (comprehensive list)
/on(load|error|click|mouseover|mouseout|mouseenter|mouseleave|focus|blur|change|submit|keydown|keyup|keypress|dblclick|contextmenu|drag|drop|copy|paste|cut|resize|scroll|wheel|touchstart|touchend|touchmove|animationstart|animationend|transitionend)\s*=/i,
// Dangerous attributes
/<img[^>]+src\s*=\s*["']?javascript:/i,
/<img[^>]+src\s*=\s*["']?data:/i,
/<img[^>]*onerror/i,
// JavaScript execution
/eval\s*\(/i,
/expression\s*\(/i,
/setTimeout\s*\(/i,
/setInterval\s*\(/i,
/Function\s*\(/i,
// SVG-based XSS
/<svg[^>]*onload/i,
/<svg[^>]*>.*?<script/is,
/<animatetransform[^>]*onbegin/i,
// Form-based XSS
/<form[^>]*action\s*=\s*["']?javascript:/i,
/<button[^>]*formaction\s*=\s*["']?javascript:/i,
// Meta refresh
/<meta[^>]*http-equiv\s*=\s*["']?refresh/i,
// Link href
/<link[^>]*href\s*=\s*["']?javascript:/i,
// Base tag
/<base[^>]*href/i,
// Style-based XSS
/<style[^>]*>.*?(expression|behavior|binding|import|@import)/is,
/style\s*=\s*["'][^"']*expression\s*\(/i,
/style\s*=\s*["'][^"']*behavior:/i,
// DOM-based XSS indicators
/document\.(write|writeln|cookie|location|domain)/i,
/window\.(location|name|open)/i,
/innerHTML\s*=/i,
/outerHTML\s*=/i,
// Template injection
/\{\{.*\}\}/,
/\$\{.*\}/,
/<\%.*\%>/,
// AngularJS/Vue/React XSS
/ng-bind-html/i,
/v-html/i,
/dangerouslySetInnerHTML/i
];
// Enhanced HTML entity encoded patterns
this.encodedPatterns = [
// Decimal entities
/&#\d+;/,
// Hex entities
/&#x[0-9a-f]+;/i,
// URL encoding
/%3C/i, // <
/%3E/i, // >
/%22/i, // "
/%27/i, // '
/%2F/i, // /
/%3D/i, // =
// Unicode encoding
/\\u[0-9a-f]{4}/i,
/\\x[0-9a-f]{2}/i,
// Double encoding
/%253C/i,
/%253E/i,
// Overlong UTF-8
/%c0%bc/i,
/%e0%80%bc/i,
// HTML5 entities
/</i,
/>/i,
/"/i,
/'/i,
///i
];
// Context-aware sanitization rules
this.contextRules = {
html: ['<', '>', '&', '"', "'", '/'],
attribute: ['"', "'", '<', '>', '&'],
javascript: ['<', '>', '&', '"', "'", '/', '\\', '\n', '\r'],
css: ['<', '>', '"', "'", '&', '(', ')', '{', '}'],
url: ['javascript:', 'data:', 'vbscript:', '<', '>']
};
}
detect(input, context = 'unknown') {
const threats = [];
if (!input)
return threats;
const inputs = this.extractInputs(input);
for (const value of inputs) {
if (typeof value !== 'string')
continue;
// Direct XSS detection with confidence scoring
const directMatches = this.xssPatterns.filter(pattern => pattern.test(value));
if (directMatches.length > 0) {
threats.push({
type: types_1.ThreatType.XSS,
severity: directMatches.length >= 3 ? 'critical' : 'high',
description: `Potential XSS attack detected (confidence: ${this.calculateConfidence(directMatches.length)})`,
payload: value,
timestamp: new Date(),
blocked: true,
metadata: {
context,
type: 'direct',
matchCount: directMatches.length,
confidence: this.calculateConfidence(directMatches.length)
}
});
}
// Encoded XSS detection (multi-layer decoding)
const decoded = this.decodeInputMultiLayer(value);
if (decoded !== value) {
const encodedMatches = this.xssPatterns.filter(pattern => pattern.test(decoded));
if (encodedMatches.length > 0) {
threats.push({
type: types_1.ThreatType.XSS,
severity: encodedMatches.length >= 3 ? 'critical' : 'high',
description: `Potential encoded XSS attack detected (confidence: ${this.calculateConfidence(encodedMatches.length)})`,
payload: value,
timestamp: new Date(),
blocked: true,
metadata: {
context,
type: 'encoded',
decoded,
matchCount: encodedMatches.length,
confidence: this.calculateConfidence(encodedMatches.length)
}
});
}
}
// Mutation XSS detection (mXSS)
if (this.detectMutationXSS(value)) {
threats.push({
type: types_1.ThreatType.XSS,
severity: 'high',
description: 'Potential mutation XSS (mXSS) detected',
payload: value,
timestamp: new Date(),
blocked: true,
metadata: { context, type: 'mutation' }
});
}
}
return threats;
}
/**
* Multi-layer decoding to catch deeply encoded attacks
*/
decodeInputMultiLayer(input, maxDepth = 3) {
let decoded = input;
let previousDecoded = '';
let depth = 0;
while (decoded !== previousDecoded && depth < maxDepth) {
previousDecoded = decoded;
decoded = this.decodeInput(decoded);
depth++;
}
return decoded;
}
decodeInput(input) {
let decoded = input;
try {
// HTML entity decoding
decoded = decoded.replace(/&#(\d+);/g, (_, num) => String.fromCharCode(parseInt(num)));
decoded = decoded.replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
// Named HTML entities
decoded = decoded.replace(/</gi, '<');
decoded = decoded.replace(/>/gi, '>');
decoded = decoded.replace(/"/gi, '"');
decoded = decoded.replace(/'/gi, "'");
decoded = decoded.replace(/&/gi, '&');
decoded = decoded.replace(///gi, '/');
// URL decoding
decoded = decodeURIComponent(decoded);
// Unicode decoding
decoded = decoded.replace(/\\u([0-9a-f]{4})/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
decoded = decoded.replace(/\\x([0-9a-f]{2})/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
// Overlong UTF-8
decoded = decoded.replace(/%c0%bc/gi, '<');
decoded = decoded.replace(/%e0%80%bc/gi, '<');
}
catch (e) {
// Decoding failed, return original
}
return decoded;
}
/**
* Detect mutation XSS (mXSS) attempts
*/
detectMutationXSS(input) {
const mxssPatterns = [
// Backtick-based mXSS
/<[^>]*`[^>]*>/,
// SVG mXSS
/<svg><style>.*@import/i,
// Namespace confusion
/<math><mi xlink:href/i,
// Form mXSS
/<form><math><mtext><\/form><form>/i,
// Select mXSS
/<select><\/select><img src=x onerror/i,
// Noscript mXSS
/<noscript><style>.*<\/style><\/noscript>/i
];
return mxssPatterns.some(pattern => pattern.test(input));
}
extractInputs(input) {
const inputs = [];
if (typeof input === 'string') {
inputs.push(input);
}
else if (Array.isArray(input)) {
for (const item of input) {
inputs.push(...this.extractInputs(item));
}
}
else if (typeof input === 'object' && input !== null) {
for (const key in input) {
inputs.push(...this.extractInputs(input[key]));
}
}
return inputs;
}
/**
* Calculate confidence score for XSS detection
*/
calculateConfidence(matches) {
const percentage = Math.min((matches / 3) * 100, 100);
return `${Math.round(percentage)}%`;
}
/**
* Context-aware sanitization
*/
sanitize(input, context = 'html') {
if (context === 'html') {
return this.sanitizeHTML(input);
}
else if (context === 'attribute') {
return this.sanitizeAttribute(input);
}
else if (context === 'javascript') {
return this.sanitizeJavaScript(input);
}
else if (context === 'css') {
return this.sanitizeCSS(input);
}
else if (context === 'url') {
return this.sanitizeURL(input);
}
return this.sanitizeHTML(input);
}
/**
* Basic HTML sanitization
*/
sanitizeHTML(input) {
return input
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/\//g, '/');
}
/**
* Sanitize for HTML attributes
*/
sanitizeAttribute(input) {
return input
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/&/g, '&');
}
/**
* Sanitize for JavaScript contexts
*/
sanitizeJavaScript(input) {
return input
.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
.replace(/"/g, '\\"')
.replace(/</g, '\\x3C')
.replace(/>/g, '\\x3E')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t');
}
/**
* Sanitize for CSS contexts
*/
sanitizeCSS(input) {
// Only allow alphanumeric and safe characters
return input.replace(/[^a-zA-Z0-9\s\-_#.]/g, '');
}
/**
* Sanitize URLs
*/
sanitizeURL(input) {
const dangerous = ['javascript:', 'data:', 'vbscript:', 'file:', 'about:'];
const lower = input.toLowerCase();
if (dangerous.some(protocol => lower.includes(protocol))) {
return '';
}
return encodeURI(input);
}
}
exports.XSSDetector = XSSDetector;
//# sourceMappingURL=xss-detector.js.map