sicua
Version:
A tool for analyzing project structure and dependencies
337 lines (336 loc) • 14.1 kB
JavaScript
;
/**
* Detector for dangerous eval() usage and similar code execution vulnerabilities
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DangerousEvalDetector = void 0;
const typescript_1 = __importDefault(require("typescript"));
const BaseDetector_1 = require("./BaseDetector");
const ASTTraverser_1 = require("../utils/ASTTraverser");
class DangerousEvalDetector extends BaseDetector_1.BaseDetector {
constructor() {
super("DangerousEvalDetector", "dangerous-eval", "critical", DangerousEvalDetector.EVAL_PATTERNS);
}
async detect(scanResult) {
const vulnerabilities = [];
// Filter relevant files
// TODO: MOVE TO CONSTANTS
const relevantFiles = this.filterRelevantFiles(scanResult, [".ts", ".tsx", ".js", ".jsx"], [
"node_modules",
"dist",
"build",
".git",
"coverage",
"__tests__",
".test.",
".spec.",
]);
for (const filePath of relevantFiles) {
const content = scanResult.fileContents.get(filePath);
if (!content)
continue;
// Apply pattern matching for basic detection
const patternResults = this.applyPatternMatching(content, filePath);
const patternVulnerabilities = this.convertPatternMatchesToVulnerabilities(patternResults, (match) => this.validateEvalMatch(match));
// Apply AST-based analysis for more sophisticated detection
const sourceFile = scanResult.sourceFiles.get(filePath);
if (sourceFile) {
const astVulnerabilities = this.applyASTAnalysis(sourceFile, filePath, (sf, fp) => this.analyzeASTForDangerousEval(sf, fp));
vulnerabilities.push(...astVulnerabilities);
}
// Adjust confidence based on file context
const fileContext = this.getFileContext(filePath, content);
for (const vuln of patternVulnerabilities) {
vuln.confidence = this.adjustConfidenceBasedOnContext(vuln, fileContext);
if (this.validateVulnerability(vuln)) {
vulnerabilities.push(vuln);
}
}
}
return vulnerabilities;
}
/**
* Validate if a pattern match is actually dangerous eval usage
*/
validateEvalMatch(matchResult) {
const match = matchResult.matches[0];
if (!match)
return false;
// Check if it's in a comment
if (this.isInComment(match.context || "", match.match)) {
return false;
}
// Check if it's in a string literal (not actual code)
if (this.isInStringLiteral(match.context || "", match.match)) {
return false;
}
// Check if it's in test files or documentation
if (this.isInTestContext(match.context || "")) {
return false;
}
return true;
}
/**
* AST-based analysis for sophisticated eval detection
*/
analyzeASTForDangerousEval(sourceFile, filePath) {
const vulnerabilities = [];
// Find all call expressions
const callExpressions = ASTTraverser_1.ASTTraverser.findNodesByKind(sourceFile, typescript_1.default.SyntaxKind.CallExpression);
const newExpressions = ASTTraverser_1.ASTTraverser.findNodesByKind(sourceFile, typescript_1.default.SyntaxKind.NewExpression);
for (const callExpr of callExpressions) {
const vulnInfo = this.analyzeCallExpression(callExpr, sourceFile);
if (vulnInfo) {
const location = ASTTraverser_1.ASTTraverser.getNodeLocation(callExpr, sourceFile);
const context = ASTTraverser_1.ASTTraverser.getNodeContext(callExpr, sourceFile);
const vulnerability = this.createVulnerability(filePath, {
line: location.line,
column: location.column,
endLine: location.line,
endColumn: location.column + vulnInfo.codeSnippet.length,
}, {
code: vulnInfo.codeSnippet,
surroundingContext: context,
functionName: this.extractFunctionFromAST(callExpr),
componentName: this.extractComponentName(filePath),
}, vulnInfo.description, "critical", vulnInfo.confidence, {
functionName: vulnInfo.functionName,
argumentType: vulnInfo.argumentType,
detectionMethod: "ast-analysis",
});
vulnerabilities.push(vulnerability);
}
}
for (const newExpr of newExpressions) {
const vulnInfo = this.analyzeNewExpression(newExpr, sourceFile);
if (vulnInfo) {
const location = ASTTraverser_1.ASTTraverser.getNodeLocation(newExpr, sourceFile);
const context = ASTTraverser_1.ASTTraverser.getNodeContext(newExpr, sourceFile);
const vulnerability = this.createVulnerability(filePath, {
line: location.line,
column: location.column,
endLine: location.line,
endColumn: location.column + vulnInfo.codeSnippet.length,
}, {
code: vulnInfo.codeSnippet,
surroundingContext: context,
functionName: this.extractFunctionFromAST(newExpr),
componentName: this.extractComponentName(filePath),
}, vulnInfo.description, "critical", vulnInfo.confidence, {
functionName: vulnInfo.functionName,
argumentType: vulnInfo.argumentType,
detectionMethod: "ast-analysis",
});
vulnerabilities.push(vulnerability);
}
}
return vulnerabilities;
}
/**
* Fixed analyzeCallExpression function - properly handles CallExpression vs NewExpression
*/
analyzeCallExpression(callExpr, sourceFile) {
const callText = ASTTraverser_1.ASTTraverser.getNodeText(callExpr, sourceFile);
// Check for direct eval() calls
if (typescript_1.default.isIdentifier(callExpr.expression) &&
callExpr.expression.text === "eval") {
return {
description: "Direct eval() call detected - this allows arbitrary code execution",
confidence: "high",
codeSnippet: callText,
functionName: "eval",
argumentType: this.getArgumentType(callExpr.arguments[0]),
};
}
// Check for setTimeout/setInterval with string arguments
if (typescript_1.default.isIdentifier(callExpr.expression)) {
const funcName = callExpr.expression.text;
if ((funcName === "setTimeout" || funcName === "setInterval") &&
callExpr.arguments.length > 0) {
const firstArg = callExpr.arguments[0];
if (typescript_1.default.isStringLiteral(firstArg) || this.isStringVariable(firstArg)) {
return {
description: `${funcName}() with string argument detected - use function instead to avoid code injection`,
confidence: "medium",
codeSnippet: callText,
functionName: funcName,
argumentType: "string",
};
}
}
}
// Check for execScript (IE-specific)
if (typescript_1.default.isIdentifier(callExpr.expression) &&
callExpr.expression.text === "execScript") {
return {
description: "execScript() usage detected - this is deprecated and dangerous",
confidence: "high",
codeSnippet: callText,
functionName: "execScript",
argumentType: this.getArgumentType(callExpr.arguments[0]),
};
}
return null;
}
/**
* Separate function to analyze NewExpression for Function constructor
*/
analyzeNewExpression(newExpr, sourceFile) {
const callText = ASTTraverser_1.ASTTraverser.getNodeText(newExpr, sourceFile);
// Check for Function constructor
if (typescript_1.default.isIdentifier(newExpr.expression) &&
newExpr.expression.text === "Function") {
return {
description: "Function() constructor usage detected - this allows arbitrary code execution",
confidence: "high",
codeSnippet: callText,
functionName: "Function",
argumentType: this.getArgumentType(newExpr.arguments?.[0]),
};
}
return null;
}
/**
* Get the type of an argument for better context
*/
getArgumentType(arg) {
if (!arg)
return "unknown";
if (typescript_1.default.isStringLiteral(arg))
return "string-literal";
if (typescript_1.default.isIdentifier(arg))
return "identifier";
if (typescript_1.default.isCallExpression(arg))
return "function-call";
if (typescript_1.default.isBinaryExpression(arg))
return "expression";
if (typescript_1.default.isTemplateExpression(arg))
return "template-literal";
return "unknown";
}
/**
* Check if an expression represents a string variable
*/
isStringVariable(expr) {
// This is a simplified check - in a real implementation,
// you might want to use the TypeChecker for more accurate type information
if (typescript_1.default.isIdentifier(expr)) {
const name = expr.text.toLowerCase();
return (name.includes("code") ||
name.includes("script") ||
name.includes("eval"));
}
return false;
}
/**
* Extract function name from AST node context
*/
extractFunctionFromAST(node) {
let current = node.parent;
while (current) {
if (typescript_1.default.isFunctionDeclaration(current) && current.name) {
return current.name.text;
}
if (typescript_1.default.isMethodDeclaration(current) && typescript_1.default.isIdentifier(current.name)) {
return current.name.text;
}
if (typescript_1.default.isVariableDeclaration(current) &&
typescript_1.default.isIdentifier(current.name) &&
current.initializer &&
(typescript_1.default.isFunctionExpression(current.initializer) ||
typescript_1.default.isArrowFunction(current.initializer))) {
return current.name.text;
}
current = current.parent;
}
return undefined;
}
/**
* Check if text is inside a string literal
*/
isInStringLiteral(context, text) {
const textIndex = context.indexOf(text);
if (textIndex === -1)
return false;
const beforeText = context.substring(0, textIndex);
const quotes = (beforeText.match(/["'`]/g) || []).length;
// If odd number of quotes before the text, it's inside a string
return quotes % 2 === 1;
}
}
exports.DangerousEvalDetector = DangerousEvalDetector;
DangerousEvalDetector.EVAL_PATTERNS = [
{
id: "direct-eval",
name: "Direct eval() usage",
description: "Direct use of eval() function detected - this can lead to code injection vulnerabilities",
pattern: {
type: "regex",
expression: /\beval\s*\(/g,
},
vulnerabilityType: "dangerous-eval",
severity: "critical",
confidence: "high",
fileTypes: [".ts", ".tsx", ".js", ".jsx"],
enabled: true,
},
{
id: "function-constructor",
name: "Function constructor usage",
description: "Use of Function() constructor detected - this can lead to code injection vulnerabilities",
pattern: {
type: "regex",
expression: /\bnew\s+Function\s*\(/g,
},
vulnerabilityType: "dangerous-eval",
severity: "critical",
confidence: "high",
fileTypes: [".ts", ".tsx", ".js", ".jsx"],
enabled: true,
},
{
id: "settimeout-string",
name: "setTimeout with string argument",
description: "setTimeout() with string argument detected - use function instead to avoid code injection",
pattern: {
type: "regex",
expression: /setTimeout\s*\(\s*['"`]/g,
},
vulnerabilityType: "dangerous-eval",
severity: "critical",
confidence: "medium",
fileTypes: [".ts", ".tsx", ".js", ".jsx"],
enabled: true,
},
{
id: "setinterval-string",
name: "setInterval with string argument",
description: "setInterval() with string argument detected - use function instead to avoid code injection",
pattern: {
type: "regex",
expression: /setInterval\s*\(\s*['"`]/g,
},
vulnerabilityType: "dangerous-eval",
severity: "critical",
confidence: "medium",
fileTypes: [".ts", ".tsx", ".js", ".jsx"],
enabled: true,
},
{
id: "execscript",
name: "execScript usage",
description: "execScript() usage detected - this is deprecated and dangerous",
pattern: {
type: "regex",
expression: /\bexecScript\s*\(/g,
},
vulnerabilityType: "dangerous-eval",
severity: "critical",
confidence: "high",
fileTypes: [".ts", ".tsx", ".js", ".jsx"],
enabled: true,
},
];