UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

337 lines (336 loc) 14.1 kB
"use strict"; /** * 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, }, ];