cognium-dev 3.136.0 → 3.138.0
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/dist/cli.js +1074 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -22616,6 +22616,15 @@ class LanguageSourcesPass {
|
|
|
22616
22616
|
for (const finding of findGoMongoNosqlInjectionFindings(code, graph.ir.meta.file)) {
|
|
22617
22617
|
ctx.addFinding(finding);
|
|
22618
22618
|
}
|
|
22619
|
+
for (const finding of findGoLdapInjectionFindings(code, graph.ir.meta.file)) {
|
|
22620
|
+
ctx.addFinding(finding);
|
|
22621
|
+
}
|
|
22622
|
+
for (const finding of findGoGobDeserializationFindings(code, graph.ir.meta.file)) {
|
|
22623
|
+
ctx.addFinding(finding);
|
|
22624
|
+
}
|
|
22625
|
+
for (const finding of findGoXmlDecoderXxeFindings(code, graph.ir.meta.file)) {
|
|
22626
|
+
ctx.addFinding(finding);
|
|
22627
|
+
}
|
|
22619
22628
|
}
|
|
22620
22629
|
if (language === "python") {
|
|
22621
22630
|
additionalSanitizers.push(...findPythonNetlocAllowlistGuardSanitizers(code));
|
|
@@ -22649,6 +22658,9 @@ class LanguageSourcesPass {
|
|
|
22649
22658
|
for (const finding of findPythonHeaderCrlfInjectionFindings(code, graph.ir.meta.file)) {
|
|
22650
22659
|
ctx.addFinding(finding);
|
|
22651
22660
|
}
|
|
22661
|
+
for (const finding of findPythonJinjaTemplateSstiFindings(code, graph.ir.meta.file)) {
|
|
22662
|
+
ctx.addFinding(finding);
|
|
22663
|
+
}
|
|
22652
22664
|
}
|
|
22653
22665
|
if (language === "rust") {
|
|
22654
22666
|
additionalSanitizers.push(...findRustSetAllowlistGuardSanitizers(code));
|
|
@@ -22676,6 +22688,12 @@ class LanguageSourcesPass {
|
|
|
22676
22688
|
for (const finding of findRustEvalCrateCodeInjectionFindings(code, graph.ir.meta.file)) {
|
|
22677
22689
|
ctx.addFinding(finding);
|
|
22678
22690
|
}
|
|
22691
|
+
for (const finding of findRustLdapInjectionFindings(code, graph.ir.meta.file)) {
|
|
22692
|
+
ctx.addFinding(finding);
|
|
22693
|
+
}
|
|
22694
|
+
for (const finding of findRustLogInjectionFindings(code, graph.ir.meta.file)) {
|
|
22695
|
+
ctx.addFinding(finding);
|
|
22696
|
+
}
|
|
22679
22697
|
}
|
|
22680
22698
|
if (language === "javascript" || language === "typescript" || language === "htmljs") {
|
|
22681
22699
|
additionalSanitizers.push(...findJsSafeJsonParseSanitizers(code));
|
|
@@ -22703,6 +22721,18 @@ class LanguageSourcesPass {
|
|
|
22703
22721
|
for (const finding of findJsUtilFormatFormatStringFindings(code, graph.ir.meta.file)) {
|
|
22704
22722
|
ctx.addFinding(finding);
|
|
22705
22723
|
}
|
|
22724
|
+
for (const finding of findJsLdapInjectionFindings(code, graph.ir.meta.file)) {
|
|
22725
|
+
ctx.addFinding(finding);
|
|
22726
|
+
}
|
|
22727
|
+
for (const finding of findJsJsonParseBodyFindings(code, graph.ir.meta.file)) {
|
|
22728
|
+
ctx.addFinding(finding);
|
|
22729
|
+
}
|
|
22730
|
+
for (const finding of findJsDomXpathInjectionFindings(code, graph.ir.meta.file)) {
|
|
22731
|
+
ctx.addFinding(finding);
|
|
22732
|
+
}
|
|
22733
|
+
for (const finding of findJsTemplateInjectionSstiFindings(code, graph.ir.meta.file)) {
|
|
22734
|
+
ctx.addFinding(finding);
|
|
22735
|
+
}
|
|
22706
22736
|
}
|
|
22707
22737
|
if (language === "java") {
|
|
22708
22738
|
additionalSanitizers.push(...findJavaSafeJsonParseSanitizers(code));
|
|
@@ -27291,6 +27321,1049 @@ function findPythonHeaderCrlfInjectionFindings(code, file) {
|
|
|
27291
27321
|
}
|
|
27292
27322
|
return findings;
|
|
27293
27323
|
}
|
|
27324
|
+
function findJsLdapInjectionFindings(code, file) {
|
|
27325
|
+
const findings = [];
|
|
27326
|
+
if (typeof code !== "string" || code.length === 0)
|
|
27327
|
+
return findings;
|
|
27328
|
+
if (!/\.\s*search\s*\(/.test(code))
|
|
27329
|
+
return findings;
|
|
27330
|
+
if (!/\breq\s*\.\s*(?:query|body|params|headers|cookies)\b/.test(code)) {
|
|
27331
|
+
return findings;
|
|
27332
|
+
}
|
|
27333
|
+
if (!/\b(?:ldapjs|ldapts|require\(['"]ldapjs['"]\)|from\s+['"]ldapts['"])\b/.test(code)) {
|
|
27334
|
+
return findings;
|
|
27335
|
+
}
|
|
27336
|
+
const lines = code.split(`
|
|
27337
|
+
`);
|
|
27338
|
+
const reqExtractRe = /\breq\s*\.\s*(?:query|body|params|headers|cookies)\b/;
|
|
27339
|
+
const taintedVars = new Set;
|
|
27340
|
+
for (let pass = 0;pass < 3; pass++) {
|
|
27341
|
+
const before = taintedVars.size;
|
|
27342
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
27343
|
+
const t = lines[i2].trim();
|
|
27344
|
+
if (t.startsWith("//"))
|
|
27345
|
+
continue;
|
|
27346
|
+
const m = t.match(/^(?:const|let|var)\s+(\w+)(?:\s*:\s*[^=]+)?\s*=\s*(.+?);?$/);
|
|
27347
|
+
if (!m)
|
|
27348
|
+
continue;
|
|
27349
|
+
const lhs = m[1];
|
|
27350
|
+
const rhs = m[2];
|
|
27351
|
+
if (taintedVars.has(lhs))
|
|
27352
|
+
continue;
|
|
27353
|
+
if (reqExtractRe.test(rhs)) {
|
|
27354
|
+
taintedVars.add(lhs);
|
|
27355
|
+
continue;
|
|
27356
|
+
}
|
|
27357
|
+
for (const v of taintedVars) {
|
|
27358
|
+
if (new RegExp(`\\b${v}\\b`).test(rhs)) {
|
|
27359
|
+
taintedVars.add(lhs);
|
|
27360
|
+
break;
|
|
27361
|
+
}
|
|
27362
|
+
}
|
|
27363
|
+
}
|
|
27364
|
+
if (taintedVars.size === before)
|
|
27365
|
+
break;
|
|
27366
|
+
}
|
|
27367
|
+
if (taintedVars.size === 0)
|
|
27368
|
+
return findings;
|
|
27369
|
+
const filterPropRe = /\bfilter\s*:\s*([^,}\n]+)/;
|
|
27370
|
+
const filterShorthandRe = /(?:^|[\{,])\s*filter\s*[,}]/;
|
|
27371
|
+
const seen = new Set;
|
|
27372
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
27373
|
+
const line = lines[i2];
|
|
27374
|
+
const fm = line.match(filterPropRe);
|
|
27375
|
+
let expr = null;
|
|
27376
|
+
if (fm) {
|
|
27377
|
+
expr = fm[1].trim();
|
|
27378
|
+
} else if (filterShorthandRe.test(line)) {
|
|
27379
|
+
expr = "filter";
|
|
27380
|
+
} else {
|
|
27381
|
+
continue;
|
|
27382
|
+
}
|
|
27383
|
+
let tainted = false;
|
|
27384
|
+
for (const v of taintedVars) {
|
|
27385
|
+
if (new RegExp(`\\b${v}\\b`).test(expr)) {
|
|
27386
|
+
tainted = true;
|
|
27387
|
+
break;
|
|
27388
|
+
}
|
|
27389
|
+
}
|
|
27390
|
+
if (!tainted)
|
|
27391
|
+
continue;
|
|
27392
|
+
if (seen.has(i2 + 1))
|
|
27393
|
+
continue;
|
|
27394
|
+
seen.add(i2 + 1);
|
|
27395
|
+
findings.push({
|
|
27396
|
+
id: `ldap_injection-${file}-${i2 + 1}-js-ldap-filter`,
|
|
27397
|
+
pass: "language-sources",
|
|
27398
|
+
category: "security",
|
|
27399
|
+
rule_id: "ldap_injection",
|
|
27400
|
+
cwe: "CWE-90",
|
|
27401
|
+
severity: "critical",
|
|
27402
|
+
level: "error",
|
|
27403
|
+
message: "LDAP injection: ldapjs/ldapts `client.search(..., { filter: " + "<tainted>, ... })` lets the attacker break out of the filter " + "expression (e.g. `*)(uid=*`) and enumerate the directory. " + "Escape the user input with a tight allowlist (`[A-Za-z0-9_-]+`) " + "or use a structured filter builder.",
|
|
27404
|
+
file,
|
|
27405
|
+
line: i2 + 1,
|
|
27406
|
+
snippet: line.trim()
|
|
27407
|
+
});
|
|
27408
|
+
}
|
|
27409
|
+
return findings;
|
|
27410
|
+
}
|
|
27411
|
+
function findGoLdapInjectionFindings(code, file) {
|
|
27412
|
+
const findings = [];
|
|
27413
|
+
if (typeof code !== "string" || code.length === 0)
|
|
27414
|
+
return findings;
|
|
27415
|
+
if (!/\bldap\s*\.\s*NewSearchRequest\s*\(/.test(code))
|
|
27416
|
+
return findings;
|
|
27417
|
+
if (!/\br\s*\.\s*(?:URL\s*\.\s*Query\s*\(\s*\)|Form|PostForm|FormValue|PostFormValue|Header)/.test(code)) {
|
|
27418
|
+
return findings;
|
|
27419
|
+
}
|
|
27420
|
+
const lines = code.split(`
|
|
27421
|
+
`);
|
|
27422
|
+
const reqExtractRe = /\br\s*\.\s*(?:URL\s*\.\s*Query\s*\(\s*\)\s*\.\s*Get|Form\s*\.\s*Get|PostForm\s*\.\s*Get|FormValue|PostFormValue|Header\s*\.\s*Get)\s*\(/;
|
|
27423
|
+
const taintedVars = new Set;
|
|
27424
|
+
for (let pass = 0;pass < 3; pass++) {
|
|
27425
|
+
const before = taintedVars.size;
|
|
27426
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
27427
|
+
const t = lines[i2].trim();
|
|
27428
|
+
if (t.startsWith("//"))
|
|
27429
|
+
continue;
|
|
27430
|
+
const m = t.match(/^(\w+)\s*(?::=|=)\s*(.+?)$/);
|
|
27431
|
+
if (!m)
|
|
27432
|
+
continue;
|
|
27433
|
+
const lhs = m[1];
|
|
27434
|
+
const rhs = m[2];
|
|
27435
|
+
if (taintedVars.has(lhs))
|
|
27436
|
+
continue;
|
|
27437
|
+
if (reqExtractRe.test(rhs)) {
|
|
27438
|
+
taintedVars.add(lhs);
|
|
27439
|
+
continue;
|
|
27440
|
+
}
|
|
27441
|
+
for (const v of taintedVars) {
|
|
27442
|
+
if (new RegExp(`\\b${v}\\b`).test(rhs)) {
|
|
27443
|
+
taintedVars.add(lhs);
|
|
27444
|
+
break;
|
|
27445
|
+
}
|
|
27446
|
+
}
|
|
27447
|
+
}
|
|
27448
|
+
if (taintedVars.size === before)
|
|
27449
|
+
break;
|
|
27450
|
+
}
|
|
27451
|
+
if (taintedVars.size === 0)
|
|
27452
|
+
return findings;
|
|
27453
|
+
const seen = new Set;
|
|
27454
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
27455
|
+
const openIdx = lines[i2].indexOf("NewSearchRequest(");
|
|
27456
|
+
if (openIdx === -1)
|
|
27457
|
+
continue;
|
|
27458
|
+
let depth = 0;
|
|
27459
|
+
let buf = "";
|
|
27460
|
+
let endLine = i2;
|
|
27461
|
+
let started = false;
|
|
27462
|
+
for (let j = i2;j < Math.min(i2 + 25, lines.length); j++) {
|
|
27463
|
+
for (const ch of lines[j]) {
|
|
27464
|
+
if (ch === "(") {
|
|
27465
|
+
depth++;
|
|
27466
|
+
started = true;
|
|
27467
|
+
} else if (ch === ")") {
|
|
27468
|
+
depth--;
|
|
27469
|
+
}
|
|
27470
|
+
buf += ch;
|
|
27471
|
+
if (started && depth === 0) {
|
|
27472
|
+
endLine = j;
|
|
27473
|
+
break;
|
|
27474
|
+
}
|
|
27475
|
+
}
|
|
27476
|
+
if (started && depth === 0)
|
|
27477
|
+
break;
|
|
27478
|
+
buf += `
|
|
27479
|
+
`;
|
|
27480
|
+
}
|
|
27481
|
+
const argsStart = buf.indexOf("(");
|
|
27482
|
+
if (argsStart === -1)
|
|
27483
|
+
continue;
|
|
27484
|
+
const args2 = buf.substring(argsStart + 1, buf.length - 1);
|
|
27485
|
+
const parts2 = [];
|
|
27486
|
+
{
|
|
27487
|
+
let d = 0;
|
|
27488
|
+
let b = "";
|
|
27489
|
+
let inStr = null;
|
|
27490
|
+
for (let k = 0;k < args2.length; k++) {
|
|
27491
|
+
const ch = args2[k];
|
|
27492
|
+
if (inStr) {
|
|
27493
|
+
if (ch === "\\") {
|
|
27494
|
+
b += ch + (args2[k + 1] ?? "");
|
|
27495
|
+
k++;
|
|
27496
|
+
continue;
|
|
27497
|
+
}
|
|
27498
|
+
if (ch === inStr)
|
|
27499
|
+
inStr = null;
|
|
27500
|
+
b += ch;
|
|
27501
|
+
continue;
|
|
27502
|
+
}
|
|
27503
|
+
if (ch === '"' || ch === "`") {
|
|
27504
|
+
inStr = ch;
|
|
27505
|
+
b += ch;
|
|
27506
|
+
continue;
|
|
27507
|
+
}
|
|
27508
|
+
if (ch === "(" || ch === "[" || ch === "{")
|
|
27509
|
+
d++;
|
|
27510
|
+
else if (ch === ")" || ch === "]" || ch === "}")
|
|
27511
|
+
d--;
|
|
27512
|
+
if (ch === "," && d === 0) {
|
|
27513
|
+
parts2.push(b);
|
|
27514
|
+
b = "";
|
|
27515
|
+
continue;
|
|
27516
|
+
}
|
|
27517
|
+
b += ch;
|
|
27518
|
+
}
|
|
27519
|
+
if (b.trim().length > 0)
|
|
27520
|
+
parts2.push(b);
|
|
27521
|
+
}
|
|
27522
|
+
if (parts2.length < 7)
|
|
27523
|
+
continue;
|
|
27524
|
+
const filterExpr = parts2[6].trim();
|
|
27525
|
+
let tainted = false;
|
|
27526
|
+
for (const v of taintedVars) {
|
|
27527
|
+
if (new RegExp(`\\b${v}\\b`).test(filterExpr)) {
|
|
27528
|
+
tainted = true;
|
|
27529
|
+
break;
|
|
27530
|
+
}
|
|
27531
|
+
}
|
|
27532
|
+
if (!tainted)
|
|
27533
|
+
continue;
|
|
27534
|
+
if (seen.has(i2 + 1))
|
|
27535
|
+
continue;
|
|
27536
|
+
seen.add(i2 + 1);
|
|
27537
|
+
findings.push({
|
|
27538
|
+
id: `ldap_injection-${file}-${i2 + 1}-go-newsearchrequest`,
|
|
27539
|
+
pass: "language-sources",
|
|
27540
|
+
category: "security",
|
|
27541
|
+
rule_id: "ldap_injection",
|
|
27542
|
+
cwe: "CWE-90",
|
|
27543
|
+
severity: "critical",
|
|
27544
|
+
level: "error",
|
|
27545
|
+
message: "LDAP injection: go-ldap `ldap.NewSearchRequest(..., <tainted " + "filter>, ...)` lets the attacker break out of the filter " + "expression and enumerate the directory. Escape the user input " + "with `ldap.EscapeFilter(...)` or use a structured filter " + "builder.",
|
|
27546
|
+
file,
|
|
27547
|
+
line: i2 + 1,
|
|
27548
|
+
snippet: (lines[i2] + (endLine > i2 ? " …" : "")).trim()
|
|
27549
|
+
});
|
|
27550
|
+
}
|
|
27551
|
+
return findings;
|
|
27552
|
+
}
|
|
27553
|
+
function findRustLdapInjectionFindings(code, file) {
|
|
27554
|
+
const findings = [];
|
|
27555
|
+
if (typeof code !== "string" || code.length === 0)
|
|
27556
|
+
return findings;
|
|
27557
|
+
if (!/\.\s*search\s*\(/.test(code))
|
|
27558
|
+
return findings;
|
|
27559
|
+
if (!/\b(?:ldap3|LdapConn|Ldap)\b/.test(code))
|
|
27560
|
+
return findings;
|
|
27561
|
+
const lines = code.split(`
|
|
27562
|
+
`);
|
|
27563
|
+
const extractorTypeRe = /:\s*(?:String|Bytes|bytes::Bytes|axum::body::Bytes|web::Query\b|web::Path\b|web::Form\b|web::Json\b|HttpRequest\b|actix_web::HttpRequest\b)/;
|
|
27564
|
+
const fns = [];
|
|
27565
|
+
let cur = null;
|
|
27566
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
27567
|
+
const t = lines[i2];
|
|
27568
|
+
if (/^\s*(?:pub\s+)?(?:async\s+)?fn\s+\w+\s*\(/.test(t)) {
|
|
27569
|
+
if (cur) {
|
|
27570
|
+
cur.end = i2 - 1;
|
|
27571
|
+
fns.push(cur);
|
|
27572
|
+
}
|
|
27573
|
+
cur = { start: i2, end: lines.length - 1, tainted: new Set };
|
|
27574
|
+
let headerJoined = "";
|
|
27575
|
+
for (let j = i2;j < Math.min(i2 + 12, lines.length); j++) {
|
|
27576
|
+
headerJoined += lines[j];
|
|
27577
|
+
if (/\{\s*$/.test(lines[j]))
|
|
27578
|
+
break;
|
|
27579
|
+
}
|
|
27580
|
+
const open = headerJoined.indexOf("(");
|
|
27581
|
+
const close = headerJoined.lastIndexOf(")");
|
|
27582
|
+
if (open !== -1 && close > open) {
|
|
27583
|
+
const params = headerJoined.substring(open + 1, close);
|
|
27584
|
+
let depth = 0;
|
|
27585
|
+
let buf = "";
|
|
27586
|
+
const parts2 = [];
|
|
27587
|
+
for (const ch of params) {
|
|
27588
|
+
if (ch === "<" || ch === "(")
|
|
27589
|
+
depth++;
|
|
27590
|
+
else if (ch === ">" || ch === ")")
|
|
27591
|
+
depth--;
|
|
27592
|
+
if (ch === "," && depth === 0) {
|
|
27593
|
+
parts2.push(buf);
|
|
27594
|
+
buf = "";
|
|
27595
|
+
continue;
|
|
27596
|
+
}
|
|
27597
|
+
buf += ch;
|
|
27598
|
+
}
|
|
27599
|
+
if (buf.trim().length > 0)
|
|
27600
|
+
parts2.push(buf);
|
|
27601
|
+
for (const p of parts2) {
|
|
27602
|
+
const pm = p.match(/(?:mut\s+)?(\w+)\s*:/);
|
|
27603
|
+
if (!pm)
|
|
27604
|
+
continue;
|
|
27605
|
+
if (extractorTypeRe.test(p))
|
|
27606
|
+
cur.tainted.add(pm[1]);
|
|
27607
|
+
}
|
|
27608
|
+
}
|
|
27609
|
+
}
|
|
27610
|
+
}
|
|
27611
|
+
if (cur)
|
|
27612
|
+
fns.push(cur);
|
|
27613
|
+
for (const fn of fns) {
|
|
27614
|
+
for (let pass = 0;pass < 3; pass++) {
|
|
27615
|
+
const before = fn.tainted.size;
|
|
27616
|
+
for (let i2 = fn.start;i2 <= fn.end; i2++) {
|
|
27617
|
+
const t = lines[i2].trim();
|
|
27618
|
+
const m = t.match(/^let\s+(?:mut\s+)?(\w+)\s*(?::\s*[^=]+)?=\s*(.+?);?$/);
|
|
27619
|
+
if (!m)
|
|
27620
|
+
continue;
|
|
27621
|
+
const lhs = m[1];
|
|
27622
|
+
const rhs = m[2];
|
|
27623
|
+
if (fn.tainted.has(lhs))
|
|
27624
|
+
continue;
|
|
27625
|
+
for (const v of fn.tainted) {
|
|
27626
|
+
if (new RegExp(`\\b${v}\\b`).test(rhs)) {
|
|
27627
|
+
fn.tainted.add(lhs);
|
|
27628
|
+
break;
|
|
27629
|
+
}
|
|
27630
|
+
}
|
|
27631
|
+
}
|
|
27632
|
+
if (fn.tainted.size === before)
|
|
27633
|
+
break;
|
|
27634
|
+
}
|
|
27635
|
+
}
|
|
27636
|
+
const seen = new Set;
|
|
27637
|
+
for (const fn of fns) {
|
|
27638
|
+
if (fn.tainted.size === 0)
|
|
27639
|
+
continue;
|
|
27640
|
+
for (let i2 = fn.start;i2 <= fn.end; i2++) {
|
|
27641
|
+
const searchIdx = lines[i2].indexOf(".search(");
|
|
27642
|
+
if (searchIdx === -1)
|
|
27643
|
+
continue;
|
|
27644
|
+
let depth = 0;
|
|
27645
|
+
let buf = "";
|
|
27646
|
+
let started = false;
|
|
27647
|
+
for (let j = i2;j < Math.min(i2 + 15, lines.length); j++) {
|
|
27648
|
+
const startCol = j === i2 ? searchIdx : 0;
|
|
27649
|
+
for (let k = startCol;k < lines[j].length; k++) {
|
|
27650
|
+
const ch = lines[j][k];
|
|
27651
|
+
if (ch === "(") {
|
|
27652
|
+
depth++;
|
|
27653
|
+
started = true;
|
|
27654
|
+
} else if (ch === ")") {
|
|
27655
|
+
depth--;
|
|
27656
|
+
}
|
|
27657
|
+
buf += ch;
|
|
27658
|
+
if (started && depth === 0)
|
|
27659
|
+
break;
|
|
27660
|
+
}
|
|
27661
|
+
if (started && depth === 0)
|
|
27662
|
+
break;
|
|
27663
|
+
buf += `
|
|
27664
|
+
`;
|
|
27665
|
+
}
|
|
27666
|
+
const argsStart = buf.indexOf("(");
|
|
27667
|
+
if (argsStart === -1)
|
|
27668
|
+
continue;
|
|
27669
|
+
const args2 = buf.substring(argsStart + 1, buf.length - 1);
|
|
27670
|
+
const parts2 = [];
|
|
27671
|
+
{
|
|
27672
|
+
let d = 0;
|
|
27673
|
+
let b = "";
|
|
27674
|
+
let inStr = null;
|
|
27675
|
+
for (let k = 0;k < args2.length; k++) {
|
|
27676
|
+
const ch = args2[k];
|
|
27677
|
+
if (inStr) {
|
|
27678
|
+
if (ch === "\\") {
|
|
27679
|
+
b += ch + (args2[k + 1] ?? "");
|
|
27680
|
+
k++;
|
|
27681
|
+
continue;
|
|
27682
|
+
}
|
|
27683
|
+
if (ch === inStr)
|
|
27684
|
+
inStr = null;
|
|
27685
|
+
b += ch;
|
|
27686
|
+
continue;
|
|
27687
|
+
}
|
|
27688
|
+
if (ch === '"') {
|
|
27689
|
+
inStr = ch;
|
|
27690
|
+
b += ch;
|
|
27691
|
+
continue;
|
|
27692
|
+
}
|
|
27693
|
+
if (ch === "(" || ch === "[" || ch === "{")
|
|
27694
|
+
d++;
|
|
27695
|
+
else if (ch === ")" || ch === "]" || ch === "}")
|
|
27696
|
+
d--;
|
|
27697
|
+
if (ch === "," && d === 0) {
|
|
27698
|
+
parts2.push(b);
|
|
27699
|
+
b = "";
|
|
27700
|
+
continue;
|
|
27701
|
+
}
|
|
27702
|
+
b += ch;
|
|
27703
|
+
}
|
|
27704
|
+
if (b.trim().length > 0)
|
|
27705
|
+
parts2.push(b);
|
|
27706
|
+
}
|
|
27707
|
+
if (parts2.length < 3)
|
|
27708
|
+
continue;
|
|
27709
|
+
const filterExpr = parts2[2].trim();
|
|
27710
|
+
let tainted = false;
|
|
27711
|
+
for (const v of fn.tainted) {
|
|
27712
|
+
if (new RegExp(`\\b${v}\\b`).test(filterExpr)) {
|
|
27713
|
+
tainted = true;
|
|
27714
|
+
break;
|
|
27715
|
+
}
|
|
27716
|
+
}
|
|
27717
|
+
if (!tainted)
|
|
27718
|
+
continue;
|
|
27719
|
+
if (seen.has(i2 + 1))
|
|
27720
|
+
continue;
|
|
27721
|
+
seen.add(i2 + 1);
|
|
27722
|
+
findings.push({
|
|
27723
|
+
id: `ldap_injection-${file}-${i2 + 1}-rust-ldap3-search`,
|
|
27724
|
+
pass: "language-sources",
|
|
27725
|
+
category: "security",
|
|
27726
|
+
rule_id: "ldap_injection",
|
|
27727
|
+
cwe: "CWE-90",
|
|
27728
|
+
severity: "critical",
|
|
27729
|
+
level: "error",
|
|
27730
|
+
message: "LDAP injection: ldap3 `LdapConn::search(..., &<tainted " + "filter>, ...)` lets the attacker break out of the filter " + "expression and enumerate the directory. Escape the user input " + "or use a structured filter builder.",
|
|
27731
|
+
file,
|
|
27732
|
+
line: i2 + 1,
|
|
27733
|
+
snippet: lines[i2].trim()
|
|
27734
|
+
});
|
|
27735
|
+
}
|
|
27736
|
+
}
|
|
27737
|
+
return findings;
|
|
27738
|
+
}
|
|
27739
|
+
function findRustLogInjectionFindings(code, file) {
|
|
27740
|
+
const findings = [];
|
|
27741
|
+
if (typeof code !== "string" || code.length === 0)
|
|
27742
|
+
return findings;
|
|
27743
|
+
if (!/\b(?:info|warn|error|debug|trace)\s*!\s*\(/.test(code))
|
|
27744
|
+
return findings;
|
|
27745
|
+
if (!/\b(?:log|tracing)\b/.test(code))
|
|
27746
|
+
return findings;
|
|
27747
|
+
const lines = code.split(`
|
|
27748
|
+
`);
|
|
27749
|
+
const extractorTypeRe = /:\s*(?:String|Bytes|bytes::Bytes|axum::body::Bytes|web::Query\b|web::Path\b|web::Form\b|web::Json\b|HttpRequest\b|actix_web::HttpRequest\b)/;
|
|
27750
|
+
const fns = [];
|
|
27751
|
+
let cur = null;
|
|
27752
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
27753
|
+
const t = lines[i2];
|
|
27754
|
+
if (/^\s*(?:pub\s+)?(?:async\s+)?fn\s+\w+\s*\(/.test(t)) {
|
|
27755
|
+
if (cur) {
|
|
27756
|
+
cur.end = i2 - 1;
|
|
27757
|
+
fns.push(cur);
|
|
27758
|
+
}
|
|
27759
|
+
cur = { start: i2, end: lines.length - 1, tainted: new Set };
|
|
27760
|
+
let headerJoined = "";
|
|
27761
|
+
for (let j = i2;j < Math.min(i2 + 12, lines.length); j++) {
|
|
27762
|
+
headerJoined += lines[j];
|
|
27763
|
+
if (/\{\s*$/.test(lines[j]))
|
|
27764
|
+
break;
|
|
27765
|
+
}
|
|
27766
|
+
const open = headerJoined.indexOf("(");
|
|
27767
|
+
const close = headerJoined.lastIndexOf(")");
|
|
27768
|
+
if (open !== -1 && close > open) {
|
|
27769
|
+
const params = headerJoined.substring(open + 1, close);
|
|
27770
|
+
let depth = 0;
|
|
27771
|
+
let buf = "";
|
|
27772
|
+
const parts2 = [];
|
|
27773
|
+
for (const ch of params) {
|
|
27774
|
+
if (ch === "<" || ch === "(")
|
|
27775
|
+
depth++;
|
|
27776
|
+
else if (ch === ">" || ch === ")")
|
|
27777
|
+
depth--;
|
|
27778
|
+
if (ch === "," && depth === 0) {
|
|
27779
|
+
parts2.push(buf);
|
|
27780
|
+
buf = "";
|
|
27781
|
+
continue;
|
|
27782
|
+
}
|
|
27783
|
+
buf += ch;
|
|
27784
|
+
}
|
|
27785
|
+
if (buf.trim().length > 0)
|
|
27786
|
+
parts2.push(buf);
|
|
27787
|
+
for (const p of parts2) {
|
|
27788
|
+
const pm = p.match(/(?:mut\s+)?(\w+)\s*:/);
|
|
27789
|
+
if (!pm)
|
|
27790
|
+
continue;
|
|
27791
|
+
if (extractorTypeRe.test(p))
|
|
27792
|
+
cur.tainted.add(pm[1]);
|
|
27793
|
+
}
|
|
27794
|
+
}
|
|
27795
|
+
}
|
|
27796
|
+
}
|
|
27797
|
+
if (cur)
|
|
27798
|
+
fns.push(cur);
|
|
27799
|
+
for (const fn of fns) {
|
|
27800
|
+
for (let pass = 0;pass < 3; pass++) {
|
|
27801
|
+
const before = fn.tainted.size;
|
|
27802
|
+
for (let i2 = fn.start;i2 <= fn.end; i2++) {
|
|
27803
|
+
const t = lines[i2].trim();
|
|
27804
|
+
const m = t.match(/^let\s+(?:mut\s+)?(\w+)\s*(?::\s*[^=]+)?=\s*(.+?);?$/);
|
|
27805
|
+
if (!m)
|
|
27806
|
+
continue;
|
|
27807
|
+
const lhs = m[1];
|
|
27808
|
+
const rhs = m[2];
|
|
27809
|
+
if (fn.tainted.has(lhs))
|
|
27810
|
+
continue;
|
|
27811
|
+
for (const v of fn.tainted) {
|
|
27812
|
+
if (new RegExp(`\\b${v}\\b`).test(rhs)) {
|
|
27813
|
+
fn.tainted.add(lhs);
|
|
27814
|
+
break;
|
|
27815
|
+
}
|
|
27816
|
+
}
|
|
27817
|
+
}
|
|
27818
|
+
if (fn.tainted.size === before)
|
|
27819
|
+
break;
|
|
27820
|
+
}
|
|
27821
|
+
}
|
|
27822
|
+
const macroRe = /\b(info|warn|error|debug|trace)\s*!\s*\(\s*([^;]+?)\)\s*;?\s*$/;
|
|
27823
|
+
const seen = new Set;
|
|
27824
|
+
for (const fn of fns) {
|
|
27825
|
+
if (fn.tainted.size === 0)
|
|
27826
|
+
continue;
|
|
27827
|
+
for (let i2 = fn.start;i2 <= fn.end; i2++) {
|
|
27828
|
+
const line = lines[i2];
|
|
27829
|
+
const m = line.match(macroRe);
|
|
27830
|
+
if (!m)
|
|
27831
|
+
continue;
|
|
27832
|
+
const macroName = m[1];
|
|
27833
|
+
const argSpan = m[2];
|
|
27834
|
+
const parts2 = [];
|
|
27835
|
+
{
|
|
27836
|
+
let d = 0;
|
|
27837
|
+
let b = "";
|
|
27838
|
+
let inStr = null;
|
|
27839
|
+
for (let k = 0;k < argSpan.length; k++) {
|
|
27840
|
+
const ch = argSpan[k];
|
|
27841
|
+
if (inStr) {
|
|
27842
|
+
if (ch === "\\") {
|
|
27843
|
+
b += ch + (argSpan[k + 1] ?? "");
|
|
27844
|
+
k++;
|
|
27845
|
+
continue;
|
|
27846
|
+
}
|
|
27847
|
+
if (ch === inStr)
|
|
27848
|
+
inStr = null;
|
|
27849
|
+
b += ch;
|
|
27850
|
+
continue;
|
|
27851
|
+
}
|
|
27852
|
+
if (ch === '"') {
|
|
27853
|
+
inStr = ch;
|
|
27854
|
+
b += ch;
|
|
27855
|
+
continue;
|
|
27856
|
+
}
|
|
27857
|
+
if (ch === "(" || ch === "[" || ch === "{")
|
|
27858
|
+
d++;
|
|
27859
|
+
else if (ch === ")" || ch === "]" || ch === "}")
|
|
27860
|
+
d--;
|
|
27861
|
+
if (ch === "," && d === 0) {
|
|
27862
|
+
parts2.push(b);
|
|
27863
|
+
b = "";
|
|
27864
|
+
continue;
|
|
27865
|
+
}
|
|
27866
|
+
b += ch;
|
|
27867
|
+
}
|
|
27868
|
+
if (b.trim().length > 0)
|
|
27869
|
+
parts2.push(b);
|
|
27870
|
+
}
|
|
27871
|
+
if (parts2.length < 2)
|
|
27872
|
+
continue;
|
|
27873
|
+
let tainted = false;
|
|
27874
|
+
for (let p = 1;p < parts2.length; p++) {
|
|
27875
|
+
for (const v of fn.tainted) {
|
|
27876
|
+
if (new RegExp(`\\b${v}\\b`).test(parts2[p])) {
|
|
27877
|
+
tainted = true;
|
|
27878
|
+
break;
|
|
27879
|
+
}
|
|
27880
|
+
}
|
|
27881
|
+
if (tainted)
|
|
27882
|
+
break;
|
|
27883
|
+
}
|
|
27884
|
+
if (!tainted)
|
|
27885
|
+
continue;
|
|
27886
|
+
const key = `${i2 + 1}:${macroName}`;
|
|
27887
|
+
if (seen.has(key))
|
|
27888
|
+
continue;
|
|
27889
|
+
seen.add(key);
|
|
27890
|
+
findings.push({
|
|
27891
|
+
id: `log_injection-${file}-${i2 + 1}-rust-${macroName}`,
|
|
27892
|
+
pass: "language-sources",
|
|
27893
|
+
category: "security",
|
|
27894
|
+
rule_id: "log_injection",
|
|
27895
|
+
cwe: "CWE-117",
|
|
27896
|
+
severity: "medium",
|
|
27897
|
+
level: "warning",
|
|
27898
|
+
message: `Log injection: Rust \`${macroName}!(...)\` interpolates a ` + "tainted value into the log line. Unsanitized CRLF lets an " + "attacker forge log entries or split records. Strip control " + "characters or use a structured logging API.",
|
|
27899
|
+
file,
|
|
27900
|
+
line: i2 + 1,
|
|
27901
|
+
snippet: line.trim()
|
|
27902
|
+
});
|
|
27903
|
+
}
|
|
27904
|
+
}
|
|
27905
|
+
return findings;
|
|
27906
|
+
}
|
|
27907
|
+
function findGoGobDeserializationFindings(code, file) {
|
|
27908
|
+
const findings = [];
|
|
27909
|
+
if (typeof code !== "string" || code.length === 0)
|
|
27910
|
+
return findings;
|
|
27911
|
+
if (!/\bgob\s*\.\s*NewDecoder\s*\(/.test(code))
|
|
27912
|
+
return findings;
|
|
27913
|
+
if (!/\*http\.Request\b/.test(code) && !/\bhttp\.HandlerFunc\b/.test(code)) {
|
|
27914
|
+
return findings;
|
|
27915
|
+
}
|
|
27916
|
+
const lines = code.split(`
|
|
27917
|
+
`);
|
|
27918
|
+
const decoderFromBody = new Set;
|
|
27919
|
+
const newDecoderAssignRe = /^(\w+)\s*(?::=|=)\s*gob\s*\.\s*NewDecoder\s*\(\s*([^)]+)\)/;
|
|
27920
|
+
for (const raw of lines) {
|
|
27921
|
+
const t = raw.trim();
|
|
27922
|
+
if (t.startsWith("//"))
|
|
27923
|
+
continue;
|
|
27924
|
+
const m = t.match(newDecoderAssignRe);
|
|
27925
|
+
if (!m)
|
|
27926
|
+
continue;
|
|
27927
|
+
const lhs = m[1];
|
|
27928
|
+
const arg = m[2];
|
|
27929
|
+
if (/\b\w+\s*\.\s*Body\b/.test(arg)) {
|
|
27930
|
+
decoderFromBody.add(lhs);
|
|
27931
|
+
}
|
|
27932
|
+
}
|
|
27933
|
+
const seen = new Set;
|
|
27934
|
+
if (decoderFromBody.size > 0) {
|
|
27935
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
27936
|
+
const line = lines[i2];
|
|
27937
|
+
if (line.trim().startsWith("//"))
|
|
27938
|
+
continue;
|
|
27939
|
+
const m = line.match(/\b(\w+)\s*\.\s*Decode\s*\(/);
|
|
27940
|
+
if (!m)
|
|
27941
|
+
continue;
|
|
27942
|
+
if (!decoderFromBody.has(m[1]))
|
|
27943
|
+
continue;
|
|
27944
|
+
if (seen.has(i2 + 1))
|
|
27945
|
+
continue;
|
|
27946
|
+
seen.add(i2 + 1);
|
|
27947
|
+
findings.push({
|
|
27948
|
+
id: `insecure_deserialization-${file}-${i2 + 1}-go-gob`,
|
|
27949
|
+
pass: "language-sources",
|
|
27950
|
+
category: "security",
|
|
27951
|
+
rule_id: "insecure_deserialization",
|
|
27952
|
+
cwe: "CWE-502",
|
|
27953
|
+
severity: "critical",
|
|
27954
|
+
level: "error",
|
|
27955
|
+
message: "Insecure deserialization: `gob.NewDecoder(req.Body).Decode(...)` " + "reconstructs arbitrary registered Go types from attacker-controlled " + "bytes. Use an authenticated framing (signed/MAC payloads), avoid " + "decoding interface{} values, or switch to a schema-bound format " + "(JSON with explicit types, Protocol Buffers).",
|
|
27956
|
+
file,
|
|
27957
|
+
line: i2 + 1,
|
|
27958
|
+
snippet: line.trim()
|
|
27959
|
+
});
|
|
27960
|
+
}
|
|
27961
|
+
}
|
|
27962
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
27963
|
+
const line = lines[i2];
|
|
27964
|
+
if (line.trim().startsWith("//"))
|
|
27965
|
+
continue;
|
|
27966
|
+
if (!/\bgob\s*\.\s*NewDecoder\s*\(\s*\w+\s*\.\s*Body\s*\)\s*\.\s*Decode\s*\(/.test(line)) {
|
|
27967
|
+
continue;
|
|
27968
|
+
}
|
|
27969
|
+
if (seen.has(i2 + 1))
|
|
27970
|
+
continue;
|
|
27971
|
+
seen.add(i2 + 1);
|
|
27972
|
+
findings.push({
|
|
27973
|
+
id: `insecure_deserialization-${file}-${i2 + 1}-go-gob-inline`,
|
|
27974
|
+
pass: "language-sources",
|
|
27975
|
+
category: "security",
|
|
27976
|
+
rule_id: "insecure_deserialization",
|
|
27977
|
+
cwe: "CWE-502",
|
|
27978
|
+
severity: "critical",
|
|
27979
|
+
level: "error",
|
|
27980
|
+
message: "Insecure deserialization: `gob.NewDecoder(req.Body).Decode(...)` " + "reconstructs arbitrary registered Go types from attacker-controlled " + "bytes. Use an authenticated framing (signed/MAC payloads), avoid " + "decoding interface{} values, or switch to a schema-bound format " + "(JSON with explicit types, Protocol Buffers).",
|
|
27981
|
+
file,
|
|
27982
|
+
line: i2 + 1,
|
|
27983
|
+
snippet: line.trim()
|
|
27984
|
+
});
|
|
27985
|
+
}
|
|
27986
|
+
return findings;
|
|
27987
|
+
}
|
|
27988
|
+
function findJsJsonParseBodyFindings(code, file) {
|
|
27989
|
+
const findings = [];
|
|
27990
|
+
if (typeof code !== "string" || code.length === 0)
|
|
27991
|
+
return findings;
|
|
27992
|
+
if (!/\bJSON\s*\.\s*parse\s*\(/.test(code))
|
|
27993
|
+
return findings;
|
|
27994
|
+
if (!/\breq\s*\.\s*body\b/.test(code))
|
|
27995
|
+
return findings;
|
|
27996
|
+
const lines = code.split(`
|
|
27997
|
+
`);
|
|
27998
|
+
const reqBodyRe = /\breq\s*\.\s*body\b/;
|
|
27999
|
+
const taintedVars = new Set;
|
|
28000
|
+
for (let pass = 0;pass < 3; pass++) {
|
|
28001
|
+
const before = taintedVars.size;
|
|
28002
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
28003
|
+
const t = lines[i2].trim();
|
|
28004
|
+
if (t.startsWith("//"))
|
|
28005
|
+
continue;
|
|
28006
|
+
const m = t.match(/^(?:const|let|var)\s+(\w+)(?:\s*:\s*[^=]+)?\s*=\s*(.+?);?$/);
|
|
28007
|
+
if (!m)
|
|
28008
|
+
continue;
|
|
28009
|
+
const lhs = m[1];
|
|
28010
|
+
const rhs = m[2];
|
|
28011
|
+
if (taintedVars.has(lhs))
|
|
28012
|
+
continue;
|
|
28013
|
+
if (reqBodyRe.test(rhs)) {
|
|
28014
|
+
taintedVars.add(lhs);
|
|
28015
|
+
continue;
|
|
28016
|
+
}
|
|
28017
|
+
for (const v of taintedVars) {
|
|
28018
|
+
if (new RegExp(`\\b${v}\\b`).test(rhs)) {
|
|
28019
|
+
taintedVars.add(lhs);
|
|
28020
|
+
break;
|
|
28021
|
+
}
|
|
28022
|
+
}
|
|
28023
|
+
}
|
|
28024
|
+
if (taintedVars.size === before)
|
|
28025
|
+
break;
|
|
28026
|
+
}
|
|
28027
|
+
const callRe = /\bJSON\s*\.\s*parse\s*\(\s*([^)]+?)\s*\)/g;
|
|
28028
|
+
const seen = new Set;
|
|
28029
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
28030
|
+
const line = lines[i2];
|
|
28031
|
+
if (line.trim().startsWith("//"))
|
|
28032
|
+
continue;
|
|
28033
|
+
callRe.lastIndex = 0;
|
|
28034
|
+
let m;
|
|
28035
|
+
while ((m = callRe.exec(line)) !== null) {
|
|
28036
|
+
const arg = m[1];
|
|
28037
|
+
let tainted = reqBodyRe.test(arg);
|
|
28038
|
+
if (!tainted) {
|
|
28039
|
+
for (const v of taintedVars) {
|
|
28040
|
+
if (new RegExp(`\\b${v}\\b`).test(arg)) {
|
|
28041
|
+
tainted = true;
|
|
28042
|
+
break;
|
|
28043
|
+
}
|
|
28044
|
+
}
|
|
28045
|
+
}
|
|
28046
|
+
if (!tainted)
|
|
28047
|
+
continue;
|
|
28048
|
+
if (seen.has(i2 + 1))
|
|
28049
|
+
continue;
|
|
28050
|
+
seen.add(i2 + 1);
|
|
28051
|
+
findings.push({
|
|
28052
|
+
id: `insecure_deserialization-${file}-${i2 + 1}-js-jsonparse-body`,
|
|
28053
|
+
pass: "language-sources",
|
|
28054
|
+
category: "security",
|
|
28055
|
+
rule_id: "insecure_deserialization",
|
|
28056
|
+
cwe: "CWE-502",
|
|
28057
|
+
severity: "high",
|
|
28058
|
+
level: "warning",
|
|
28059
|
+
message: "`JSON.parse(req.body)` deserializes attacker-controlled bytes " + "directly. With `express.text()` / `bodyParser.raw()` middleware " + "`req.body` is an unvetted string; the parsed object can carry a " + "`__proto__` payload that pollutes downstream property lookups. " + "Validate against a schema (zod/ajv) before consuming the value.",
|
|
28060
|
+
file,
|
|
28061
|
+
line: i2 + 1,
|
|
28062
|
+
snippet: line.trim()
|
|
28063
|
+
});
|
|
28064
|
+
}
|
|
28065
|
+
}
|
|
28066
|
+
return findings;
|
|
28067
|
+
}
|
|
28068
|
+
function findJsDomXpathInjectionFindings(code, file) {
|
|
28069
|
+
const findings = [];
|
|
28070
|
+
if (typeof code !== "string" || code.length === 0)
|
|
28071
|
+
return findings;
|
|
28072
|
+
if (!/\.\s*evaluate\s*\(/.test(code))
|
|
28073
|
+
return findings;
|
|
28074
|
+
if (!/\bXPathResult\b/.test(code))
|
|
28075
|
+
return findings;
|
|
28076
|
+
const browserSourceRe = /\b(?:location\s*\.\s*(?:search|hash|href)|URLSearchParams|window\s*\.\s*name|document\s*\.\s*cookie)\b/;
|
|
28077
|
+
if (!browserSourceRe.test(code))
|
|
28078
|
+
return findings;
|
|
28079
|
+
const lines = code.split(`
|
|
28080
|
+
`);
|
|
28081
|
+
const taintedVars = new Set;
|
|
28082
|
+
for (let pass = 0;pass < 3; pass++) {
|
|
28083
|
+
const before = taintedVars.size;
|
|
28084
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
28085
|
+
const t = lines[i2].trim();
|
|
28086
|
+
if (t.startsWith("//"))
|
|
28087
|
+
continue;
|
|
28088
|
+
const m = t.match(/^(?:const|let|var)\s+(\w+)(?:\s*:\s*[^=]+)?\s*=\s*(.+?);?$/);
|
|
28089
|
+
if (!m)
|
|
28090
|
+
continue;
|
|
28091
|
+
const lhs = m[1];
|
|
28092
|
+
const rhs = m[2];
|
|
28093
|
+
if (taintedVars.has(lhs))
|
|
28094
|
+
continue;
|
|
28095
|
+
if (browserSourceRe.test(rhs)) {
|
|
28096
|
+
taintedVars.add(lhs);
|
|
28097
|
+
continue;
|
|
28098
|
+
}
|
|
28099
|
+
for (const v of taintedVars) {
|
|
28100
|
+
if (new RegExp(`\\b${v}\\b`).test(rhs)) {
|
|
28101
|
+
taintedVars.add(lhs);
|
|
28102
|
+
break;
|
|
28103
|
+
}
|
|
28104
|
+
}
|
|
28105
|
+
}
|
|
28106
|
+
if (taintedVars.size === before)
|
|
28107
|
+
break;
|
|
28108
|
+
}
|
|
28109
|
+
if (taintedVars.size === 0)
|
|
28110
|
+
return findings;
|
|
28111
|
+
const evalRe = /\.\s*evaluate\s*\(\s*([^,)]+)/;
|
|
28112
|
+
const seen = new Set;
|
|
28113
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
28114
|
+
const line = lines[i2];
|
|
28115
|
+
if (line.trim().startsWith("//"))
|
|
28116
|
+
continue;
|
|
28117
|
+
const m = line.match(evalRe);
|
|
28118
|
+
if (!m)
|
|
28119
|
+
continue;
|
|
28120
|
+
const arg = m[1].trim();
|
|
28121
|
+
let tainted = false;
|
|
28122
|
+
for (const v of taintedVars) {
|
|
28123
|
+
if (new RegExp(`\\b${v}\\b`).test(arg)) {
|
|
28124
|
+
tainted = true;
|
|
28125
|
+
break;
|
|
28126
|
+
}
|
|
28127
|
+
}
|
|
28128
|
+
if (!tainted)
|
|
28129
|
+
continue;
|
|
28130
|
+
if (seen.has(i2 + 1))
|
|
28131
|
+
continue;
|
|
28132
|
+
seen.add(i2 + 1);
|
|
28133
|
+
findings.push({
|
|
28134
|
+
id: `xpath_injection-${file}-${i2 + 1}-js-dom-evaluate`,
|
|
28135
|
+
pass: "language-sources",
|
|
28136
|
+
category: "security",
|
|
28137
|
+
rule_id: "xpath_injection",
|
|
28138
|
+
cwe: "CWE-643",
|
|
28139
|
+
severity: "high",
|
|
28140
|
+
level: "warning",
|
|
28141
|
+
message: "XPath injection: `document.evaluate(<tainted>, ...)` lets the " + "attacker break out of the XPath expression and read sibling " + "nodes. Bind user input through XPath variables / parameterized " + "expressions, or escape with an allowlist before concatenation.",
|
|
28142
|
+
file,
|
|
28143
|
+
line: i2 + 1,
|
|
28144
|
+
snippet: line.trim()
|
|
28145
|
+
});
|
|
28146
|
+
}
|
|
28147
|
+
return findings;
|
|
28148
|
+
}
|
|
28149
|
+
function findGoXmlDecoderXxeFindings(code, file) {
|
|
28150
|
+
const findings = [];
|
|
28151
|
+
if (typeof code !== "string" || code.length === 0)
|
|
28152
|
+
return findings;
|
|
28153
|
+
if (!/\bxml\s*\.\s*NewDecoder\s*\(/.test(code))
|
|
28154
|
+
return findings;
|
|
28155
|
+
if (!/\*http\.Request\b/.test(code) && !/\bhttp\.HandlerFunc\b/.test(code)) {
|
|
28156
|
+
return findings;
|
|
28157
|
+
}
|
|
28158
|
+
const lines = code.split(`
|
|
28159
|
+
`);
|
|
28160
|
+
const decoderFromBody = new Map;
|
|
28161
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
28162
|
+
const t = lines[i2].trim();
|
|
28163
|
+
if (t.startsWith("//"))
|
|
28164
|
+
continue;
|
|
28165
|
+
const m = t.match(/^(\w+)\s*(?::=|=)\s*xml\s*\.\s*NewDecoder\s*\(\s*([^)]+)\)/);
|
|
28166
|
+
if (!m)
|
|
28167
|
+
continue;
|
|
28168
|
+
if (/\b\w+\s*\.\s*Body\b/.test(m[2]))
|
|
28169
|
+
decoderFromBody.set(m[1], i2);
|
|
28170
|
+
}
|
|
28171
|
+
if (decoderFromBody.size === 0)
|
|
28172
|
+
return findings;
|
|
28173
|
+
const seen = new Set;
|
|
28174
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
28175
|
+
const line = lines[i2];
|
|
28176
|
+
if (line.trim().startsWith("//"))
|
|
28177
|
+
continue;
|
|
28178
|
+
const m = line.match(/\b(\w+)\s*\.\s*(?:Strict\s*=\s*false|Entity\s*=)\b/);
|
|
28179
|
+
if (!m)
|
|
28180
|
+
continue;
|
|
28181
|
+
if (!decoderFromBody.has(m[1]))
|
|
28182
|
+
continue;
|
|
28183
|
+
if (seen.has(i2 + 1))
|
|
28184
|
+
continue;
|
|
28185
|
+
seen.add(i2 + 1);
|
|
28186
|
+
findings.push({
|
|
28187
|
+
id: `xml_entity_expansion-${file}-${i2 + 1}-go-xml-decoder`,
|
|
28188
|
+
pass: "language-sources",
|
|
28189
|
+
category: "security",
|
|
28190
|
+
rule_id: "xml_entity_expansion",
|
|
28191
|
+
cwe: "CWE-611",
|
|
28192
|
+
severity: "high",
|
|
28193
|
+
level: "warning",
|
|
28194
|
+
message: "XXE: Go `xml.NewDecoder(req.Body)` with `Strict = false` (or a " + "custom `Entity` map) allows entity references that the standard " + "library normally rejects. Keep `Strict = true` and avoid setting " + "`Entity` from attacker-controlled inputs.",
|
|
28195
|
+
file,
|
|
28196
|
+
line: i2 + 1,
|
|
28197
|
+
snippet: line.trim()
|
|
28198
|
+
});
|
|
28199
|
+
}
|
|
28200
|
+
return findings;
|
|
28201
|
+
}
|
|
28202
|
+
function findPythonJinjaTemplateSstiFindings(code, file) {
|
|
28203
|
+
const findings = [];
|
|
28204
|
+
if (typeof code !== "string" || code.length === 0)
|
|
28205
|
+
return findings;
|
|
28206
|
+
if (!/\bfrom\s+jinja2\s+import\s+[^#\n]*\bTemplate\b/.test(code) && !/\bjinja2\s*\.\s*Template\b/.test(code)) {
|
|
28207
|
+
return findings;
|
|
28208
|
+
}
|
|
28209
|
+
const reqExtractRe = /\brequest\s*\.\s*(?:args|form|values|json|data|cookies|headers)\b/;
|
|
28210
|
+
if (!reqExtractRe.test(code))
|
|
28211
|
+
return findings;
|
|
28212
|
+
const lines = code.split(`
|
|
28213
|
+
`);
|
|
28214
|
+
const taintedVars = new Set;
|
|
28215
|
+
for (let pass = 0;pass < 3; pass++) {
|
|
28216
|
+
const before = taintedVars.size;
|
|
28217
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
28218
|
+
const t = lines[i2].trim();
|
|
28219
|
+
if (t.startsWith("#"))
|
|
28220
|
+
continue;
|
|
28221
|
+
const m = t.match(/^(\w+)\s*=\s*(.+?)$/);
|
|
28222
|
+
if (!m)
|
|
28223
|
+
continue;
|
|
28224
|
+
const lhs = m[1];
|
|
28225
|
+
const rhs = m[2];
|
|
28226
|
+
if (taintedVars.has(lhs))
|
|
28227
|
+
continue;
|
|
28228
|
+
if (reqExtractRe.test(rhs)) {
|
|
28229
|
+
taintedVars.add(lhs);
|
|
28230
|
+
continue;
|
|
28231
|
+
}
|
|
28232
|
+
for (const v of taintedVars) {
|
|
28233
|
+
if (new RegExp(`\\b${v}\\b`).test(rhs)) {
|
|
28234
|
+
taintedVars.add(lhs);
|
|
28235
|
+
break;
|
|
28236
|
+
}
|
|
28237
|
+
}
|
|
28238
|
+
}
|
|
28239
|
+
if (taintedVars.size === before)
|
|
28240
|
+
break;
|
|
28241
|
+
}
|
|
28242
|
+
if (taintedVars.size === 0)
|
|
28243
|
+
return findings;
|
|
28244
|
+
const ctorRe = /\b(?:jinja2\s*\.\s*)?Template\s*\(\s*([^)]+?)\s*\)/;
|
|
28245
|
+
const seen = new Set;
|
|
28246
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
28247
|
+
const line = lines[i2];
|
|
28248
|
+
if (line.trim().startsWith("#"))
|
|
28249
|
+
continue;
|
|
28250
|
+
const m = line.match(ctorRe);
|
|
28251
|
+
if (!m)
|
|
28252
|
+
continue;
|
|
28253
|
+
const arg = m[1].trim();
|
|
28254
|
+
let tainted = reqExtractRe.test(arg);
|
|
28255
|
+
if (!tainted) {
|
|
28256
|
+
for (const v of taintedVars) {
|
|
28257
|
+
if (new RegExp(`\\b${v}\\b`).test(arg)) {
|
|
28258
|
+
tainted = true;
|
|
28259
|
+
break;
|
|
28260
|
+
}
|
|
28261
|
+
}
|
|
28262
|
+
}
|
|
28263
|
+
if (!tainted)
|
|
28264
|
+
continue;
|
|
28265
|
+
if (seen.has(i2 + 1))
|
|
28266
|
+
continue;
|
|
28267
|
+
seen.add(i2 + 1);
|
|
28268
|
+
findings.push({
|
|
28269
|
+
id: `template_injection-${file}-${i2 + 1}-py-jinja-template`,
|
|
28270
|
+
pass: "language-sources",
|
|
28271
|
+
category: "security",
|
|
28272
|
+
rule_id: "template_injection",
|
|
28273
|
+
cwe: "CWE-1336",
|
|
28274
|
+
severity: "critical",
|
|
28275
|
+
level: "error",
|
|
28276
|
+
message: "Server-side template injection: `jinja2.Template(<tainted>).render()` " + "compiles attacker-controlled source. Sandbox-escape gadgets such as " + "`{{ ().__class__.__bases__[0].__subclasses__() }}` lead to RCE. " + "Render fixed templates with user data passed as context variables.",
|
|
28277
|
+
file,
|
|
28278
|
+
line: i2 + 1,
|
|
28279
|
+
snippet: line.trim()
|
|
28280
|
+
});
|
|
28281
|
+
}
|
|
28282
|
+
return findings;
|
|
28283
|
+
}
|
|
28284
|
+
function findJsTemplateInjectionSstiFindings(code, file) {
|
|
28285
|
+
const findings = [];
|
|
28286
|
+
if (typeof code !== "string" || code.length === 0)
|
|
28287
|
+
return findings;
|
|
28288
|
+
if (!/\bHandlebars\s*\.\s*compile\s*\(/.test(code) && !/\bejs\s*\.\s*(?:render|compile)\s*\(/.test(code)) {
|
|
28289
|
+
return findings;
|
|
28290
|
+
}
|
|
28291
|
+
if (!/\breq\s*\.\s*(?:query|body|params|headers|cookies)\b/.test(code)) {
|
|
28292
|
+
return findings;
|
|
28293
|
+
}
|
|
28294
|
+
const lines = code.split(`
|
|
28295
|
+
`);
|
|
28296
|
+
const reqExtractRe = /\breq\s*\.\s*(?:query|body|params|headers|cookies)\b/;
|
|
28297
|
+
const taintedVars = new Set;
|
|
28298
|
+
for (let pass = 0;pass < 3; pass++) {
|
|
28299
|
+
const before = taintedVars.size;
|
|
28300
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
28301
|
+
const t = lines[i2].trim();
|
|
28302
|
+
if (t.startsWith("//"))
|
|
28303
|
+
continue;
|
|
28304
|
+
const m = t.match(/^(?:const|let|var)\s+(\w+)(?:\s*:\s*[^=]+)?\s*=\s*(.+?);?$/);
|
|
28305
|
+
if (!m)
|
|
28306
|
+
continue;
|
|
28307
|
+
const lhs = m[1];
|
|
28308
|
+
const rhs = m[2];
|
|
28309
|
+
if (taintedVars.has(lhs))
|
|
28310
|
+
continue;
|
|
28311
|
+
if (reqExtractRe.test(rhs)) {
|
|
28312
|
+
taintedVars.add(lhs);
|
|
28313
|
+
continue;
|
|
28314
|
+
}
|
|
28315
|
+
for (const v of taintedVars) {
|
|
28316
|
+
if (new RegExp(`\\b${v}\\b`).test(rhs)) {
|
|
28317
|
+
taintedVars.add(lhs);
|
|
28318
|
+
break;
|
|
28319
|
+
}
|
|
28320
|
+
}
|
|
28321
|
+
}
|
|
28322
|
+
if (taintedVars.size === before)
|
|
28323
|
+
break;
|
|
28324
|
+
}
|
|
28325
|
+
if (taintedVars.size === 0)
|
|
28326
|
+
return findings;
|
|
28327
|
+
const callRe = /\b(Handlebars\s*\.\s*compile|ejs\s*\.\s*(?:render|compile))\s*\(\s*([^,)]+)/;
|
|
28328
|
+
const seen = new Set;
|
|
28329
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
28330
|
+
const line = lines[i2];
|
|
28331
|
+
if (line.trim().startsWith("//"))
|
|
28332
|
+
continue;
|
|
28333
|
+
const m = line.match(callRe);
|
|
28334
|
+
if (!m)
|
|
28335
|
+
continue;
|
|
28336
|
+
const arg = m[2].trim();
|
|
28337
|
+
let tainted = reqExtractRe.test(arg);
|
|
28338
|
+
if (!tainted) {
|
|
28339
|
+
for (const v of taintedVars) {
|
|
28340
|
+
if (new RegExp(`\\b${v}\\b`).test(arg)) {
|
|
28341
|
+
tainted = true;
|
|
28342
|
+
break;
|
|
28343
|
+
}
|
|
28344
|
+
}
|
|
28345
|
+
}
|
|
28346
|
+
if (!tainted)
|
|
28347
|
+
continue;
|
|
28348
|
+
if (seen.has(i2 + 1))
|
|
28349
|
+
continue;
|
|
28350
|
+
seen.add(i2 + 1);
|
|
28351
|
+
findings.push({
|
|
28352
|
+
id: `template_injection-${file}-${i2 + 1}-js-${m[1].replace(/[\s.]/g, "").toLowerCase()}`,
|
|
28353
|
+
pass: "language-sources",
|
|
28354
|
+
category: "security",
|
|
28355
|
+
rule_id: "template_injection",
|
|
28356
|
+
cwe: "CWE-1336",
|
|
28357
|
+
severity: "critical",
|
|
28358
|
+
level: "error",
|
|
28359
|
+
message: "Server-side template injection: compiling/rendering a template " + "whose source is attacker-controlled (`Handlebars.compile(...)` / " + "`ejs.render(...)`) opens helper-shadowing and prototype-gadget RCE " + "paths. Use a fixed template and pass user data as context.",
|
|
28360
|
+
file,
|
|
28361
|
+
line: i2 + 1,
|
|
28362
|
+
snippet: line.trim()
|
|
28363
|
+
});
|
|
28364
|
+
}
|
|
28365
|
+
return findings;
|
|
28366
|
+
}
|
|
27294
28367
|
|
|
27295
28368
|
// ../circle-ir/dist/analysis/passes/sink-filter-pass.js
|
|
27296
28369
|
var JS_XSS_SANITIZERS = [
|
|
@@ -40418,7 +41491,7 @@ var colors = {
|
|
|
40418
41491
|
};
|
|
40419
41492
|
|
|
40420
41493
|
// src/version.ts
|
|
40421
|
-
var version = "3.
|
|
41494
|
+
var version = "3.138.0";
|
|
40422
41495
|
|
|
40423
41496
|
// src/formatters.ts
|
|
40424
41497
|
var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.138.0",
|
|
4
4
|
"description": "Static Application Security Testing CLI for detecting security vulnerabilities via taint tracking",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@cognium/project-profile-detect": "^1.1.0",
|
|
69
|
-
"circle-ir": "^3.
|
|
69
|
+
"circle-ir": "^3.138.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/node": "^25.5.0",
|