@sun-asterisk/sunlint 1.3.4 → 1.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/config/presets/all.json +49 -48
- package/config/presets/beginner.json +7 -18
- package/config/presets/ci.json +63 -27
- package/config/presets/maintainability.json +6 -4
- package/config/presets/performance.json +4 -3
- package/config/presets/quality.json +11 -50
- package/config/presets/recommended.json +83 -10
- package/config/presets/security.json +20 -19
- package/config/presets/strict.json +6 -13
- package/config/rule-analysis-strategies.js +5 -0
- package/config/rules/enhanced-rules-registry.json +87 -7
- package/core/config-preset-resolver.js +7 -2
- package/package.json +1 -1
- package/rules/common/C067_no_hardcoded_config/analyzer.js +95 -0
- package/rules/common/C067_no_hardcoded_config/config.json +81 -0
- package/rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js +1034 -0
- package/rules/common/C070_no_real_time_tests/analyzer.js +320 -0
- package/rules/common/C070_no_real_time_tests/config.json +78 -0
- package/rules/common/C070_no_real_time_tests/regex-analyzer.js +424 -0
- package/rules/security/S024_xpath_xxe_protection/analyzer.js +242 -0
- package/rules/security/S024_xpath_xxe_protection/config.json +152 -0
- package/rules/security/S024_xpath_xxe_protection/regex-based-analyzer.js +338 -0
- package/rules/security/S024_xpath_xxe_protection/symbol-based-analyzer.js +474 -0
- package/rules/security/S025_server_side_validation/README.md +179 -0
- package/rules/security/S025_server_side_validation/analyzer.js +242 -0
- package/rules/security/S025_server_side_validation/config.json +111 -0
- package/rules/security/S025_server_side_validation/regex-based-analyzer.js +388 -0
- package/rules/security/S025_server_side_validation/symbol-based-analyzer.js +523 -0
- package/scripts/README.md +83 -0
- package/scripts/analyze-core-rules.js +151 -0
- package/scripts/generate-presets.js +202 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
{
|
|
2
|
+
"rule": {
|
|
3
|
+
"id": "S024",
|
|
4
|
+
"name": "Protect against XPath Injection and XML External Entity (XXE)",
|
|
5
|
+
"description": "Protect against XPath Injection and XML External Entity (XXE) attacks. XPath injection occurs when user input is used to construct XPath queries without proper sanitization. XXE attacks exploit XML parsers that process external entities, potentially leading to data disclosure, server-side request forgery, or denial of service.",
|
|
6
|
+
"category": "security",
|
|
7
|
+
"severity": "error",
|
|
8
|
+
"languages": ["typescript", "javascript"],
|
|
9
|
+
"frameworks": ["express", "nestjs", "node"],
|
|
10
|
+
"version": "1.0.0",
|
|
11
|
+
"status": "stable",
|
|
12
|
+
"tags": ["security", "xpath", "xxe", "xml", "injection", "owasp"],
|
|
13
|
+
"references": [
|
|
14
|
+
"https://owasp.org/www-community/attacks/XPATH_Injection",
|
|
15
|
+
"https://owasp.org/www-community/vulnerabilities/XML_External_Entity_(XXE)_Processing",
|
|
16
|
+
"https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html",
|
|
17
|
+
"https://portswigger.net/web-security/xpath-injection",
|
|
18
|
+
"https://portswigger.net/web-security/xxe"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
"configuration": {
|
|
22
|
+
"enableXPathInjectionDetection": true,
|
|
23
|
+
"enableXXEDetection": true,
|
|
24
|
+
"checkUserInputSources": [
|
|
25
|
+
"req",
|
|
26
|
+
"request",
|
|
27
|
+
"params",
|
|
28
|
+
"query",
|
|
29
|
+
"body",
|
|
30
|
+
"headers",
|
|
31
|
+
"cookies"
|
|
32
|
+
],
|
|
33
|
+
"vulnerableXPathMethods": [
|
|
34
|
+
"evaluate",
|
|
35
|
+
"select",
|
|
36
|
+
"selectText",
|
|
37
|
+
"selectValue",
|
|
38
|
+
"selectNodes",
|
|
39
|
+
"selectSingleNode"
|
|
40
|
+
],
|
|
41
|
+
"vulnerableXMLMethods": [
|
|
42
|
+
"parseString",
|
|
43
|
+
"parseXml",
|
|
44
|
+
"parseFromString",
|
|
45
|
+
"parse",
|
|
46
|
+
"parseXmlString",
|
|
47
|
+
"transform"
|
|
48
|
+
],
|
|
49
|
+
"vulnerableXMLConstructors": [
|
|
50
|
+
"DOMParser",
|
|
51
|
+
"XSLTProcessor",
|
|
52
|
+
"SAXParser"
|
|
53
|
+
],
|
|
54
|
+
"vulnerableXMLLibraries": [
|
|
55
|
+
"xml2js",
|
|
56
|
+
"libxmljs",
|
|
57
|
+
"xmldom",
|
|
58
|
+
"fast-xml-parser",
|
|
59
|
+
"node-xml2js",
|
|
60
|
+
"xml-parser",
|
|
61
|
+
"xmldoc",
|
|
62
|
+
"xpath"
|
|
63
|
+
],
|
|
64
|
+
"xxeProtectionPatterns": [
|
|
65
|
+
"resolveExternalEntities\\s*:\\s*false",
|
|
66
|
+
"setFeature.*disallow-doctype-decl.*true",
|
|
67
|
+
"setFeature.*external-general-entities.*false",
|
|
68
|
+
"setFeature.*external-parameter-entities.*false",
|
|
69
|
+
"explicitChildren\\s*:\\s*false",
|
|
70
|
+
"ignoreAttrs\\s*:\\s*true",
|
|
71
|
+
"parseDoctype\\s*:\\s*false"
|
|
72
|
+
],
|
|
73
|
+
"secureXPathPatterns": [
|
|
74
|
+
"parameterized",
|
|
75
|
+
"escaped",
|
|
76
|
+
"sanitized",
|
|
77
|
+
"validate"
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
"examples": {
|
|
81
|
+
"violations": [
|
|
82
|
+
{
|
|
83
|
+
"description": "XPath injection via direct user input",
|
|
84
|
+
"code": "const result = xpath.evaluate(req.query.expression, doc);"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"description": "XPath injection via string concatenation",
|
|
88
|
+
"code": "const query = \"//user[@name='\" + req.body.username + \"']\";\nconst result = xpath.select(query, doc);"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"description": "XXE vulnerability in XML parsing",
|
|
92
|
+
"code": "const parser = new DOMParser();\nconst doc = parser.parseFromString(req.body.xml, 'text/xml');"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"description": "XXE vulnerability with xml2js",
|
|
96
|
+
"code": "xml2js.parseString(req.body.xml, (err, result) => { /* ... */ });"
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
"fixes": [
|
|
100
|
+
{
|
|
101
|
+
"description": "Use parameterized XPath with validation",
|
|
102
|
+
"code": "const sanitizedInput = sanitize(req.query.expression);\nconst result = xpath.evaluate(sanitizedInput, doc);"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"description": "Disable external entities in XML parsing",
|
|
106
|
+
"code": "const parser = new DOMParser();\nparser.setFeature('http://apache.org/xml/features/disallow-doctype-decl', true);\nparser.setFeature('http://xml.org/sax/features/external-general-entities', false);\nparser.setFeature('http://xml.org/sax/features/external-parameter-entities', false);"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"description": "Secure xml2js configuration",
|
|
110
|
+
"code": "xml2js.parseString(req.body.xml, {\n explicitChildren: false,\n ignoreAttrs: true\n}, (err, result) => { /* ... */ });"
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
"testing": {
|
|
115
|
+
"testCases": [
|
|
116
|
+
{
|
|
117
|
+
"name": "xpath_injection_direct_input",
|
|
118
|
+
"type": "violation",
|
|
119
|
+
"description": "Direct user input in XPath query"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"name": "xpath_injection_concatenation",
|
|
123
|
+
"type": "violation",
|
|
124
|
+
"description": "String concatenation with user input"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"name": "xxe_domparser",
|
|
128
|
+
"type": "violation",
|
|
129
|
+
"description": "DOMParser without XXE protection"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"name": "xxe_xml2js",
|
|
133
|
+
"type": "violation",
|
|
134
|
+
"description": "xml2js without XXE protection"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"name": "secure_xpath_parameterized",
|
|
138
|
+
"type": "clean",
|
|
139
|
+
"description": "Parameterized XPath query"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"name": "secure_xml_xxe_protected",
|
|
143
|
+
"type": "clean",
|
|
144
|
+
"description": "XML parsing with XXE protection"
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
},
|
|
148
|
+
"performance": {
|
|
149
|
+
"complexity": "O(n)",
|
|
150
|
+
"description": "Linear complexity based on number of function calls and expressions in the source code"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S024 Regex-Based Analyzer - Protect against XPath Injection and XML External Entity (XXE)
|
|
3
|
+
* Fallback analysis using regex patterns for Express.js, NestJS, and TypeScript
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
|
|
8
|
+
class S024RegexBasedAnalyzer {
|
|
9
|
+
constructor(semanticEngine = null) {
|
|
10
|
+
this.semanticEngine = semanticEngine;
|
|
11
|
+
this.ruleId = "S024";
|
|
12
|
+
this.category = "security";
|
|
13
|
+
|
|
14
|
+
// XPath injection patterns - user input in XPath queries
|
|
15
|
+
this.xpathInjectionPatterns = [
|
|
16
|
+
// XPath evaluate methods with user input
|
|
17
|
+
/\.evaluate\s*\(\s*[^,]*(?:req\.|request\.|params\.|query\.|body\.)[^,]*\s*,/gi,
|
|
18
|
+
/xpath\s*\.\s*(?:evaluate|select|selectText|selectValue)\s*\(\s*[^,]*(?:req\.|request\.|params\.|query\.|body\.)[^,]*\s*[,)]/gi,
|
|
19
|
+
|
|
20
|
+
// XPath string concatenation with user input
|
|
21
|
+
/['"`][^'"`]*\+\s*(?:req\.|request\.|params\.|query\.|body\.)[^'"`]*['"`]/gi,
|
|
22
|
+
/(?:req\.|request\.|params\.|query\.|body\.)[^+]*\+\s*['"`][^'"`]*['"`]/gi,
|
|
23
|
+
|
|
24
|
+
// XPath libraries with direct user input
|
|
25
|
+
/xpath\s*\(\s*[^,]*(?:req\.|request\.|params\.|query\.|body\.)[^)]*\)/gi,
|
|
26
|
+
/xmldom\s*\.\s*XPath\s*\(\s*[^,]*(?:req\.|request\.|params\.|query\.|body\.)[^)]*\)/gi,
|
|
27
|
+
|
|
28
|
+
// Template literals with user input in XPath
|
|
29
|
+
/`[^`]*\$\{[^}]*(?:req\.|request\.|params\.|query\.|body\.)[^}]*\}[^`]*`/gi
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
// XXE vulnerability patterns - insecure XML parsing
|
|
33
|
+
this.xxeVulnerabilityPatterns = [
|
|
34
|
+
// XML parsers without XXE protection
|
|
35
|
+
/new\s+DOMParser\s*\(\s*\)/gi,
|
|
36
|
+
/libxmljs\s*\.\s*parseXml\s*\(/gi,
|
|
37
|
+
/xml2js\s*\.\s*parseString\s*\(/gi,
|
|
38
|
+
/xmldom\s*\.\s*DOMParser\s*\(/gi,
|
|
39
|
+
/fast-xml-parser/gi,
|
|
40
|
+
|
|
41
|
+
// XML parsing without entity resolution disabled
|
|
42
|
+
/\.parseFromString\s*\(/gi,
|
|
43
|
+
/XMLHttpRequest\s*\(\s*\)/gi,
|
|
44
|
+
|
|
45
|
+
// XSLT processors without XXE protection
|
|
46
|
+
/new\s+XSLTProcessor\s*\(\s*\)/gi,
|
|
47
|
+
/xslt\s*\.\s*transform\s*\(/gi,
|
|
48
|
+
|
|
49
|
+
// SAX parsers without XXE protection
|
|
50
|
+
/sax\s*\.\s*(?:parser|createStream)\s*\(/gi,
|
|
51
|
+
/new\s+sax\s*\.\s*SAXParser\s*\(/gi
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
// Secure patterns that should NOT be flagged
|
|
55
|
+
this.securePatterns = [
|
|
56
|
+
// XPath with proper parameterization/escaping
|
|
57
|
+
/xpath\s*\.\s*(?:evaluate|select)\s*\(\s*['"`][^'"`${}]*['"`]\s*,/gi,
|
|
58
|
+
/\.evaluate\s*\(\s*['"`][^'"`${}]*['"`]\s*,/gi,
|
|
59
|
+
|
|
60
|
+
// XML parsers with XXE protection explicitly disabled
|
|
61
|
+
/resolveExternalEntities\s*:\s*false/gi,
|
|
62
|
+
/\.setFeature\s*\(\s*['"`]http:\/\/apache\.org\/xml\/features\/disallow-doctype-decl['"`]\s*,\s*true\s*\)/gi,
|
|
63
|
+
/\.setFeature\s*\(\s*['"`]http:\/\/xml\.org\/sax\/features\/external-general-entities['"`]\s*,\s*false\s*\)/gi,
|
|
64
|
+
/\.setFeature\s*\(\s*['"`]http:\/\/xml\.org\/sax\/features\/external-parameter-entities['"`]\s*,\s*false\s*\)/gi
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
// XPath functions that are commonly misused
|
|
68
|
+
this.vulnerableXPathFunctions = [
|
|
69
|
+
"evaluate",
|
|
70
|
+
"select",
|
|
71
|
+
"selectText",
|
|
72
|
+
"selectValue",
|
|
73
|
+
"selectNodes",
|
|
74
|
+
"selectSingleNode"
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
// XML parsing libraries that need XXE protection
|
|
78
|
+
this.vulnerableXMLLibraries = [
|
|
79
|
+
"xml2js",
|
|
80
|
+
"libxmljs",
|
|
81
|
+
"xmldom",
|
|
82
|
+
"fast-xml-parser",
|
|
83
|
+
"node-xml2js",
|
|
84
|
+
"xml-parser",
|
|
85
|
+
"xmldoc",
|
|
86
|
+
"xpath"
|
|
87
|
+
];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async analyze(filePath) {
|
|
91
|
+
if (this.verbose) {
|
|
92
|
+
console.log(`🔍 [${this.ruleId}] Regex-based analysis for: ${filePath}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
97
|
+
return this.analyzeContent(content, filePath);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (this.verbose) {
|
|
100
|
+
console.log(
|
|
101
|
+
`🔍 [${this.ruleId}] Regex: Error reading file:`,
|
|
102
|
+
error.message
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
analyzeContent(content, filePath) {
|
|
110
|
+
const violations = [];
|
|
111
|
+
const lines = content.split("\n");
|
|
112
|
+
|
|
113
|
+
// Remove comments to avoid false positives
|
|
114
|
+
const cleanContent = this.removeComments(content);
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
// Pattern 1: XPath Injection vulnerabilities
|
|
118
|
+
violations.push(
|
|
119
|
+
...this.analyzeXPathInjection(cleanContent, lines, filePath)
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Pattern 2: XXE vulnerabilities in XML parsing
|
|
123
|
+
violations.push(
|
|
124
|
+
...this.analyzeXXEVulnerabilities(cleanContent, lines, filePath)
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// Pattern 3: Insecure XPath query construction
|
|
128
|
+
violations.push(
|
|
129
|
+
...this.analyzeInsecureXPathConstruction(cleanContent, lines, filePath)
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// Pattern 4: XML libraries without XXE protection
|
|
133
|
+
violations.push(
|
|
134
|
+
...this.analyzeXMLLibraryUsage(cleanContent, lines, filePath)
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (this.verbose) {
|
|
139
|
+
console.log(
|
|
140
|
+
`🔍 [${this.ruleId}] Regex: Error in analysis:`,
|
|
141
|
+
error.message
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return violations;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
analyzeXPathInjection(content, lines, filePath) {
|
|
150
|
+
const violations = [];
|
|
151
|
+
|
|
152
|
+
// Check for XPath injection patterns
|
|
153
|
+
for (const pattern of this.xpathInjectionPatterns) {
|
|
154
|
+
let match;
|
|
155
|
+
pattern.lastIndex = 0; // Reset regex state
|
|
156
|
+
|
|
157
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
158
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
159
|
+
const lineContent = lines[lineNumber - 1];
|
|
160
|
+
|
|
161
|
+
if (this.verbose) {
|
|
162
|
+
console.log(
|
|
163
|
+
`🔍 [${this.ruleId}] Regex: XPath injection pattern found at line ${lineNumber}: ${match[0]}`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Skip if this is a secure pattern
|
|
168
|
+
if (this.isSecureXPathPattern(lineContent)) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
violations.push({
|
|
173
|
+
rule: this.ruleId,
|
|
174
|
+
source: filePath,
|
|
175
|
+
category: this.category,
|
|
176
|
+
line: lineNumber,
|
|
177
|
+
column: 1,
|
|
178
|
+
message: `XPath Injection vulnerability: User input used directly in XPath query without proper sanitization`,
|
|
179
|
+
severity: "error",
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return violations;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
analyzeXXEVulnerabilities(content, lines, filePath) {
|
|
188
|
+
const violations = [];
|
|
189
|
+
|
|
190
|
+
// Check for XXE vulnerability patterns
|
|
191
|
+
for (const pattern of this.xxeVulnerabilityPatterns) {
|
|
192
|
+
let match;
|
|
193
|
+
pattern.lastIndex = 0; // Reset regex state
|
|
194
|
+
|
|
195
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
196
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
197
|
+
const lineContent = lines[lineNumber - 1];
|
|
198
|
+
const contextLines = this.getContextLines(lines, lineNumber - 1, 5);
|
|
199
|
+
|
|
200
|
+
if (this.verbose) {
|
|
201
|
+
console.log(
|
|
202
|
+
`🔍 [${this.ruleId}] Regex: XXE vulnerability pattern found at line ${lineNumber}: ${match[0]}`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Skip if XXE protection is already implemented in context
|
|
207
|
+
if (this.hasXXEProtection(contextLines.join('\n'))) {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
violations.push({
|
|
212
|
+
rule: this.ruleId,
|
|
213
|
+
source: filePath,
|
|
214
|
+
category: this.category,
|
|
215
|
+
line: lineNumber,
|
|
216
|
+
column: 1,
|
|
217
|
+
message: `XXE vulnerability: XML parser used without disabling external entity processing`,
|
|
218
|
+
severity: "error",
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return violations;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
analyzeInsecureXPathConstruction(content, lines, filePath) {
|
|
227
|
+
const violations = [];
|
|
228
|
+
|
|
229
|
+
// Look for string concatenation or template literals with user input in XPath context
|
|
230
|
+
const xpathContextPattern = /(?:xpath|XPath|XPATH).*(?:\+|`.*\$\{).*(?:req\.|request\.|params\.|query\.|body\.)/gi;
|
|
231
|
+
let match;
|
|
232
|
+
|
|
233
|
+
while ((match = xpathContextPattern.exec(content)) !== null) {
|
|
234
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
235
|
+
|
|
236
|
+
if (this.verbose) {
|
|
237
|
+
console.log(
|
|
238
|
+
`🔍 [${this.ruleId}] Regex: Insecure XPath construction at line ${lineNumber}: ${match[0]}`
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
violations.push({
|
|
243
|
+
rule: this.ruleId,
|
|
244
|
+
source: filePath,
|
|
245
|
+
category: this.category,
|
|
246
|
+
line: lineNumber,
|
|
247
|
+
column: 1,
|
|
248
|
+
message: `XPath Injection vulnerability: XPath query constructed using string concatenation with user input`,
|
|
249
|
+
severity: "error",
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return violations;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
analyzeXMLLibraryUsage(content, lines, filePath) {
|
|
257
|
+
const violations = [];
|
|
258
|
+
|
|
259
|
+
// Check imports of vulnerable XML libraries
|
|
260
|
+
const importPattern = /(?:import|require)\s*.*(?:xml2js|libxmljs|xmldom|fast-xml-parser|xpath)/gi;
|
|
261
|
+
let match;
|
|
262
|
+
|
|
263
|
+
while ((match = importPattern.exec(content)) !== null) {
|
|
264
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
265
|
+
const contextLines = this.getContextLines(lines, lineNumber - 1, 10);
|
|
266
|
+
const contextContent = contextLines.join('\n');
|
|
267
|
+
|
|
268
|
+
if (this.verbose) {
|
|
269
|
+
console.log(
|
|
270
|
+
`🔍 [${this.ruleId}] Regex: XML library import found at line ${lineNumber}: ${match[0]}`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Check if XXE protection is implemented in the context
|
|
275
|
+
if (!this.hasXXEProtection(contextContent)) {
|
|
276
|
+
violations.push({
|
|
277
|
+
rule: this.ruleId,
|
|
278
|
+
source: filePath,
|
|
279
|
+
category: this.category,
|
|
280
|
+
line: lineNumber,
|
|
281
|
+
column: 1,
|
|
282
|
+
message: `XXE vulnerability: XML parsing library imported without implementing XXE protection measures`,
|
|
283
|
+
severity: "warning",
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return violations;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
isSecureXPathPattern(lineContent) {
|
|
292
|
+
// Check if the line contains secure XPath patterns
|
|
293
|
+
return this.securePatterns.some(pattern => {
|
|
294
|
+
pattern.lastIndex = 0;
|
|
295
|
+
return pattern.test(lineContent);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
hasXXEProtection(content) {
|
|
300
|
+
// Check if content contains XXE protection mechanisms
|
|
301
|
+
const protectionPatterns = [
|
|
302
|
+
/resolveExternalEntities\s*:\s*false/i,
|
|
303
|
+
/setFeature.*disallow-doctype-decl.*true/i,
|
|
304
|
+
/setFeature.*external-general-entities.*false/i,
|
|
305
|
+
/setFeature.*external-parameter-entities.*false/i,
|
|
306
|
+
/explicitChildren\s*:\s*false/i,
|
|
307
|
+
/ignoreAttrs\s*:\s*true/i,
|
|
308
|
+
/parseDoctype\s*:\s*false/i,
|
|
309
|
+
/replaceEntities\s*:\s*false/i,
|
|
310
|
+
/recover\s*:\s*false/i
|
|
311
|
+
];
|
|
312
|
+
|
|
313
|
+
return protectionPatterns.some(pattern => {
|
|
314
|
+
pattern.lastIndex = 0;
|
|
315
|
+
return pattern.test(content);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
getContextLines(lines, centerIndex, contextSize) {
|
|
320
|
+
const start = Math.max(0, centerIndex - contextSize);
|
|
321
|
+
const end = Math.min(lines.length, centerIndex + contextSize + 1);
|
|
322
|
+
return lines.slice(start, end);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
removeComments(content) {
|
|
326
|
+
// Remove single-line comments
|
|
327
|
+
content = content.replace(/\/\/.*$/gm, "");
|
|
328
|
+
// Remove multi-line comments
|
|
329
|
+
content = content.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
330
|
+
return content;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
getLineNumber(content, index) {
|
|
334
|
+
return content.substring(0, index).split("\n").length;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
module.exports = S024RegexBasedAnalyzer;
|