@chiranthmoger/fortifyjs 1.1.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/LICENSE +9 -0
- package/README.md +186 -0
- package/bin/fortifyjs.js +135 -0
- package/examples/fastify.js +18 -0
- package/examples/hono.js +13 -0
- package/examples/kitchen-sink.js +50 -0
- package/examples/koa.js +14 -0
- package/examples/minimal-express.js +13 -0
- package/examples/production-express.js +20 -0
- package/index.d.ts +101 -0
- package/package.json +70 -0
- package/src/adapters/express.js +1027 -0
- package/src/adapters/fastify.js +56 -0
- package/src/adapters/generic.js +15 -0
- package/src/adapters/hono.js +77 -0
- package/src/adapters/koa.js +58 -0
- package/src/adapters/nestjs.js +15 -0
- package/src/adapters/nextjs.js +84 -0
- package/src/analyzers/adaptive.js +92 -0
- package/src/analyzers/behavioral.js +264 -0
- package/src/core/confidence.js +23 -0
- package/src/core/engine.js +162 -0
- package/src/core/normalizer.js +149 -0
- package/src/core/whitelist.js +40 -0
- package/src/dashboard/handler.js +307 -0
- package/src/detectors/cmdi.js +82 -0
- package/src/detectors/crlf.js +33 -0
- package/src/detectors/graphql.js +56 -0
- package/src/detectors/hpp.js +38 -0
- package/src/detectors/ldap.js +50 -0
- package/src/detectors/nosqli.js +134 -0
- package/src/detectors/open-redirect.js +42 -0
- package/src/detectors/path-traversal.js +55 -0
- package/src/detectors/prototype-pollution.js +65 -0
- package/src/detectors/sqli.js +447 -0
- package/src/detectors/sqli.js.bak +446 -0
- package/src/detectors/ssrf.js +64 -0
- package/src/detectors/template-injection.js +34 -0
- package/src/detectors/xss.js +191 -0
- package/src/detectors/xxe.js +45 -0
- package/src/forensics/reporter.js +74 -0
- package/src/index.js +132 -0
- package/src/logger.js +110 -0
- package/src/presets.js +183 -0
- package/src/shields/bot-detector.js +88 -0
- package/src/shields/cors.js +95 -0
- package/src/shields/csrf.js +120 -0
- package/src/shields/file-upload.js +160 -0
- package/src/shields/headers.js +99 -0
- package/src/shields/rate-limiter.js +70 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const DANGEROUS_COMMANDS = 'rm|cat|wget|curl|nc|bash|sh|cmd|powershell|python|perl|ruby|node|php';
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
name: 'cmdi',
|
|
7
|
+
label: 'cmdi',
|
|
8
|
+
getSignals() {
|
|
9
|
+
return [
|
|
10
|
+
{
|
|
11
|
+
id: 'shell-command-chain',
|
|
12
|
+
confidence: 0.80,
|
|
13
|
+
pattern: new RegExp(`(?:;|&&|\\|\\|)\\s*(?:${DANGEROUS_COMMANDS})\\b`, 'i')
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 'backtick-execution',
|
|
17
|
+
confidence: 0.85,
|
|
18
|
+
pattern: /`[^`]+`/
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'subshell-execution',
|
|
22
|
+
confidence: 0.85,
|
|
23
|
+
pattern: /\$\([^\)]+\)/
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'pipe-to-shell',
|
|
27
|
+
confidence: 0.75,
|
|
28
|
+
pattern: /\|\s*(?:sh|bash|cmd)\b/i
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'shell-binary-path',
|
|
32
|
+
confidence: 0.80,
|
|
33
|
+
pattern: /(?:\/bin\/sh|\/bin\/bash|\/usr\/bin\/env|cmd\.exe|powershell\.exe)\b/i
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'reverse-shell-pattern',
|
|
37
|
+
confidence: 0.90,
|
|
38
|
+
pattern: /(?:bash\s+-i\s+>&|nc\s+-e\s+\/bin\/sh|python\s+-c\s+['"]import\s+socket)/i
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'curl-wget-pipe',
|
|
42
|
+
confidence: 0.70,
|
|
43
|
+
pattern: /(?:curl|wget).*\|\s*(?:bash|sh)\b/i
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'windows-cmd-injection',
|
|
47
|
+
confidence: 0.80,
|
|
48
|
+
pattern: /(?:cmd(?:\.exe)?\s+\/c|powershell(?:\.exe)?\s+-(?:enc|e))\b/i
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'powershell-iex',
|
|
52
|
+
confidence: 0.85,
|
|
53
|
+
pattern: /(?:invoke-expression|iex)\b/i
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'windows-builtins',
|
|
57
|
+
confidence: 0.70,
|
|
58
|
+
pattern: /(?:^|[;&|])\s*(?:type|dir)(?:\s+[^\s]|$)/i
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'newline-injection',
|
|
62
|
+
confidence: 0.80,
|
|
63
|
+
pattern: /(?:%0[ad]|\r|\n)\s*(?:rm|cat|wget|curl|nc|bash|sh|cmd|powershell|python|perl|ruby|node|php)\b/i
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'env-variable-exfil',
|
|
67
|
+
confidence: 0.60,
|
|
68
|
+
pattern: /(?:\$(?:PATH|HOME|USER|ENV\{)|%(?:SYSTEMROOT|APPDATA|USERPROFILE|USERNAME)%)/i
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'process-substitution',
|
|
72
|
+
confidence: 0.75,
|
|
73
|
+
pattern: /[<>]\([^\)]+\)/
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'pipe-operator-command',
|
|
77
|
+
confidence: 0.80,
|
|
78
|
+
pattern: /\|\s*(?:ls|cat|whoami|id|pwd|dir|type)\b/i
|
|
79
|
+
}
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const HEADER_PATTERN = '(?:Set-Cookie|Location|Bcc|Cc|Content-Type|Content-Length|X-[A-Za-z0-9-]+|Host|Authorization)\\s*:';
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
name: 'crlf',
|
|
7
|
+
label: 'crlf',
|
|
8
|
+
|
|
9
|
+
getSignals() {
|
|
10
|
+
return [
|
|
11
|
+
{
|
|
12
|
+
id: 'crlf-header-injection',
|
|
13
|
+
confidence: 0.85,
|
|
14
|
+
pattern: new RegExp(`(?:\\r\\n|\\r|\\n|%0d%0a|%0a)[ \\t]*${HEADER_PATTERN}`, 'i')
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 'crlf-encoded-header-injection',
|
|
18
|
+
confidence: 0.80,
|
|
19
|
+
pattern: new RegExp(`(?:\\u560a\\u560d|%E5%98%8A%E5%98%8D|\\\\u000d\\\\u000a)[ \\t]*${HEADER_PATTERN}`, 'i')
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'crlf-log-injection',
|
|
23
|
+
confidence: 0.75,
|
|
24
|
+
pattern: /(?:\r\n|\r|\n|%0d%0a|%0a)[ \t]*(?:\[?(?:info|error|warn|debug|trace)\]?|\[?\d{4}-\d{2}-\d{2}|\[?\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/i
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'crlf-newline-alone',
|
|
28
|
+
confidence: 0.40,
|
|
29
|
+
pattern: /(?:\r\n|%0d%0a)/i
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function hasDeepNesting(value) {
|
|
4
|
+
let depth = 0;
|
|
5
|
+
let maxDepth = 0;
|
|
6
|
+
for (let i = 0; i < value.length; i++) {
|
|
7
|
+
if (value[i] === '{') {
|
|
8
|
+
depth++;
|
|
9
|
+
if (depth > maxDepth) maxDepth = depth;
|
|
10
|
+
} else if (value[i] === '}') {
|
|
11
|
+
depth--;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return maxDepth > 10;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function hasAliasBatching(value) {
|
|
18
|
+
const aliasPattern = /[a-zA-Z_][a-zA-Z0-9_]*\s*:\s*[a-zA-Z_][a-zA-Z0-9_]*/g;
|
|
19
|
+
const matches = value.match(aliasPattern);
|
|
20
|
+
return matches !== null && matches.length >= 100;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function hasFragmentSpreadAbuse(value) {
|
|
24
|
+
const spreadPattern = /\.\.\.[a-zA-Z0-9_]+/g;
|
|
25
|
+
const matches = value.match(spreadPattern);
|
|
26
|
+
return matches !== null && matches.length >= 10;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
name: 'graphql',
|
|
31
|
+
label: 'graphql',
|
|
32
|
+
getSignals() {
|
|
33
|
+
return [
|
|
34
|
+
{
|
|
35
|
+
id: 'graphql-introspection',
|
|
36
|
+
confidence: 0.9,
|
|
37
|
+
pattern: /\b(__schema|__type)\b/i
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'graphql-deep-nesting',
|
|
41
|
+
confidence: 0.8,
|
|
42
|
+
test: hasDeepNesting
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'graphql-alias-batching',
|
|
46
|
+
confidence: 0.85,
|
|
47
|
+
test: hasAliasBatching
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'graphql-fragment-spread-abuse',
|
|
51
|
+
confidence: 0.7,
|
|
52
|
+
test: hasFragmentSpreadAbuse
|
|
53
|
+
}
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
name: 'hpp',
|
|
5
|
+
label: 'hpp',
|
|
6
|
+
|
|
7
|
+
detectQuery(query, allowedArrayParams = []) {
|
|
8
|
+
const signals = [];
|
|
9
|
+
if (!query || typeof query !== 'object') return signals;
|
|
10
|
+
|
|
11
|
+
for (const [key, value] of Object.entries(query)) {
|
|
12
|
+
if (Array.isArray(value) && !allowedArrayParams.includes(key)) {
|
|
13
|
+
signals.push({
|
|
14
|
+
id: 'array-param-unexpected',
|
|
15
|
+
confidence: 0.55,
|
|
16
|
+
label: 'hpp'
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
signals.push({
|
|
20
|
+
id: 'duplicate-query-param',
|
|
21
|
+
confidence: 0.60,
|
|
22
|
+
label: 'hpp'
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return signals;
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
getSignals() {
|
|
30
|
+
return [
|
|
31
|
+
{
|
|
32
|
+
id: 'duplicate-query-param-string',
|
|
33
|
+
confidence: 0.60,
|
|
34
|
+
pattern: /(?:\?|&)([^=&]+)=.*&\1=/
|
|
35
|
+
}
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function hasUnbalancedParenthesesWithLdapKeywords(value) {
|
|
4
|
+
const ldapKeywords = /\b(cn|uid|objectclass|memberof)\b/i;
|
|
5
|
+
if (!ldapKeywords.test(value)) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let depth = 0;
|
|
10
|
+
for (let i = 0; i < value.length; i++) {
|
|
11
|
+
if (value[i] === '(') depth++;
|
|
12
|
+
if (value[i] === ')') depth--;
|
|
13
|
+
if (depth < 0) return true;
|
|
14
|
+
}
|
|
15
|
+
return depth !== 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
name: 'ldap',
|
|
20
|
+
label: 'ldap',
|
|
21
|
+
getSignals() {
|
|
22
|
+
return [
|
|
23
|
+
{
|
|
24
|
+
id: 'ldap-filter-concatenation',
|
|
25
|
+
confidence: 0.8,
|
|
26
|
+
pattern: /\)\s*\(/
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'ldap-wildcard-bypass',
|
|
30
|
+
confidence: 0.9,
|
|
31
|
+
pattern: /\*\)\s*\(\s*objectClass\s*=\s*\*/i
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'ldap-or-injection',
|
|
35
|
+
confidence: 0.85,
|
|
36
|
+
pattern: /\)\s*\(\s*\|/
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'ldap-null-byte',
|
|
40
|
+
confidence: 0.9,
|
|
41
|
+
pattern: /(?:\x00|%00|\\00)/i
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'ldap-unbalanced-parentheses',
|
|
45
|
+
confidence: 0.7,
|
|
46
|
+
test: hasUnbalancedParenthesesWithLdapKeywords
|
|
47
|
+
}
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const MONGO_OPERATORS = [
|
|
4
|
+
'\\$gt', '\\$gte', '\\$lt', '\\$lte', '\\$ne', '\\$in', '\\$nin',
|
|
5
|
+
'\\$or', '\\$and', '\\$not', '\\$nor', '\\$exists', '\\$type',
|
|
6
|
+
'\\$expr', '\\$regex', '\\$options', '\\$text', '\\$search',
|
|
7
|
+
'\\$where', '\\$all', '\\$elemMatch', '\\$size', '\\$mod',
|
|
8
|
+
'\\$jsonSchema', '\\$accumulator', '\\$function'
|
|
9
|
+
].join('|');
|
|
10
|
+
|
|
11
|
+
const ES_OPERATORS = [
|
|
12
|
+
'script', 'inline', 'painless', 'mvel', 'ctx\\._source'
|
|
13
|
+
].join('|');
|
|
14
|
+
|
|
15
|
+
const COUCH_OPERATORS = [
|
|
16
|
+
'_design', '_view', '_find', 'map', 'reduce'
|
|
17
|
+
].join('|');
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
name: 'nosqli',
|
|
21
|
+
label: 'nosqli',
|
|
22
|
+
|
|
23
|
+
detectObject(obj, maxDepth = 20) {
|
|
24
|
+
const signals = [];
|
|
25
|
+
if (!obj || typeof obj !== 'object') return signals;
|
|
26
|
+
|
|
27
|
+
function walk(node, depth) {
|
|
28
|
+
if (depth > maxDepth) return;
|
|
29
|
+
if (!node || typeof node !== 'object') return;
|
|
30
|
+
|
|
31
|
+
if (Array.isArray(node)) {
|
|
32
|
+
for (const item of node) {
|
|
33
|
+
walk(item, depth + 1);
|
|
34
|
+
}
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const key of Object.keys(node)) {
|
|
39
|
+
if (key.startsWith('$')) {
|
|
40
|
+
const validOperators = [
|
|
41
|
+
'$where', '$ne', '$gt', '$lt', '$gte', '$lte', '$in', '$nin',
|
|
42
|
+
'$regex', '$expr', '$or', '$and', '$jsonSchema', '$function',
|
|
43
|
+
'$accumulator', '$not', '$nor', '$exists', '$type', '$all',
|
|
44
|
+
'$elemMatch', '$size', '$mod', '$options', '$text', '$search'
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
if (validOperators.includes(key)) {
|
|
48
|
+
signals.push({
|
|
49
|
+
id: 'mongo-operator-key',
|
|
50
|
+
confidence: 0.80,
|
|
51
|
+
label: 'nosqli'
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (key.startsWith('_design/') || key === '_view' || key === 'map' || key === 'reduce') {
|
|
57
|
+
signals.push({
|
|
58
|
+
id: 'couchdb-injection',
|
|
59
|
+
confidence: 0.70,
|
|
60
|
+
label: 'nosqli'
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (key === 'script' && node[key] && typeof node[key] === 'object' && (node[key].inline || node[key].source)) {
|
|
65
|
+
signals.push({
|
|
66
|
+
id: 'es-script-injection',
|
|
67
|
+
confidence: 0.80,
|
|
68
|
+
label: 'nosqli'
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
walk(node[key], depth + 1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
walk(obj, 0);
|
|
77
|
+
return signals;
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
getSignals() {
|
|
81
|
+
return [
|
|
82
|
+
{
|
|
83
|
+
id: 'mongo-operator-value',
|
|
84
|
+
confidence: 0.85,
|
|
85
|
+
pattern: new RegExp(`[{,]\\s*["']?\\$(?:where|ne|gt|lt|gte|lte|in|nin|regex|expr|or|and|jsonSchema|function|accumulator)["']?\\s*:\\s*{\\s*["']?(?:${MONGO_OPERATORS})["']?\\s*:`, 'i')
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'mongo-operator-in-string',
|
|
89
|
+
confidence: 0.70,
|
|
90
|
+
pattern: new RegExp(`[{,]\\s*["'](?:${MONGO_OPERATORS})["']\\s*:`, 'i')
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'mongo-where-injection',
|
|
94
|
+
confidence: 0.80,
|
|
95
|
+
pattern: /(?:\$where["']?\s*:\s*["']|where\s*\(\s*["']|\$?where["']?\s*:\s*(?:function|=>))/i
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: 'mongo-regex-redos',
|
|
99
|
+
confidence: 0.75,
|
|
100
|
+
pattern: /\$regex["']?\s*:\s*["'].*(?:\(\.\*\)|\(\.\+\)|\(\.\*|\.\+\)|\(\?.*\)|\.\*.*(?:\.\*|\.\+)|\.\+.*(?:\.\*|\.\+)|\([a-zA-Z0-9]+\+\)\+).*["']/i
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'mongo-regex-general',
|
|
104
|
+
confidence: 0.65,
|
|
105
|
+
pattern: /\$regex["']?\s*:\s*["'].*[\+\*\{].*["']/i
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'mongo-mapreduce',
|
|
109
|
+
confidence: 0.75,
|
|
110
|
+
pattern: /(?:mapReduce|\$function|\$accumulator)["']?\s*:\s*(?:["']?function\s*\(|{)/i
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: 'mongo-aggregation-abuse',
|
|
114
|
+
confidence: 0.70,
|
|
115
|
+
pattern: /(?:\$lookup|\$graphLookup|\$out|\$merge|\$addFields)["']?\s*:/i
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: 'mongo-jsonschema',
|
|
119
|
+
confidence: 0.75,
|
|
120
|
+
pattern: /(?:\$jsonSchema)["']?\s*:\s*{/i
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: 'couchdb-injection',
|
|
124
|
+
confidence: 0.75,
|
|
125
|
+
pattern: /["']_design\/[^"']*["']\s*:\s*{|["']_view["']\s*:\s*["']/i
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: 'es-script-injection',
|
|
129
|
+
confidence: 0.75,
|
|
130
|
+
pattern: /["']script["']\s*:\s*(?:{|["'](?:painless|mvel|inline|ctx\._source))/i
|
|
131
|
+
}
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const REDIRECT_PARAMS = [
|
|
4
|
+
'redirect', 'url', 'next', 'return', 'returnUrl', 'continue', 'dest',
|
|
5
|
+
'destination', 'goto', 'target', 'link', 'out', 'view', 'rurl',
|
|
6
|
+
'return_url', 'callback', 'forward'
|
|
7
|
+
].join('|');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
name: 'open-redirect',
|
|
11
|
+
label: 'open-redirect',
|
|
12
|
+
|
|
13
|
+
getSignals() {
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
id: 'redirect-param-external-url',
|
|
17
|
+
confidence: 0.75,
|
|
18
|
+
pattern: new RegExp(`(?:\\?|&)(?:${REDIRECT_PARAMS})=(?:https?:)?\\/\\/[^&]+`, 'i')
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'protocol-relative-redirect',
|
|
22
|
+
confidence: 0.70,
|
|
23
|
+
pattern: new RegExp(`(?:\\?|&)(?:${REDIRECT_PARAMS})=\\/\\/[a-zA-Z0-9]`, 'i')
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'backslash-redirect',
|
|
27
|
+
confidence: 0.75,
|
|
28
|
+
pattern: new RegExp(`(?:\\?|&)(?:${REDIRECT_PARAMS})=(?:\\\\\\/|\\/\\\\)[a-zA-Z0-9]`, 'i')
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'data-uri-redirect',
|
|
32
|
+
confidence: 0.80,
|
|
33
|
+
pattern: new RegExp(`(?:\\?|&)(?:${REDIRECT_PARAMS})=data:`, 'i')
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'javascript-uri-redirect',
|
|
37
|
+
confidence: 0.85,
|
|
38
|
+
pattern: new RegExp(`(?:\\?|&)(?:${REDIRECT_PARAMS})=javascript:`, 'i')
|
|
39
|
+
}
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
name: 'path-traversal',
|
|
5
|
+
label: 'path-traversal',
|
|
6
|
+
getSignals() {
|
|
7
|
+
return [
|
|
8
|
+
{
|
|
9
|
+
id: 'dot-dot-slash',
|
|
10
|
+
confidence: 0.75,
|
|
11
|
+
pattern: /(?:\.\.[\\\/]){2,}/
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: 'windows-backslash-traversal',
|
|
15
|
+
confidence: 0.75,
|
|
16
|
+
pattern: /(?:\.\.\\){2,}/
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: 'url-encoded-traversal',
|
|
20
|
+
confidence: 0.80,
|
|
21
|
+
pattern: /(?:%2e%2e%2f|%252e%252e%252f|%2e%2e%5c|%252e%252e%255c){2,}/i
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'overlong-utf8-traversal',
|
|
25
|
+
confidence: 0.85,
|
|
26
|
+
pattern: /(?:%c0%ae%c0%ae%c0%af|%e0%40%ae%e0%40%ae%e0%40%af){2,}/i
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'double-dot-slash',
|
|
30
|
+
confidence: 0.75,
|
|
31
|
+
pattern: /(?:\.\.\.\.\/\/)+/
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'null-byte-injection',
|
|
35
|
+
confidence: 0.85,
|
|
36
|
+
pattern: /\x00/
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'sensitive-unix-file',
|
|
40
|
+
confidence: 0.80,
|
|
41
|
+
pattern: /(?:\/etc\/passwd|\/etc\/shadow|\/proc\/self\/environ|\/proc\/self\/fd\/|\/var\/log\/)/i
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'sensitive-windows-file',
|
|
45
|
+
confidence: 0.80,
|
|
46
|
+
pattern: /(?:\\SAM|\\boot\.ini|\\win\.ini|\\system32\\config)/i
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'dotfile-access',
|
|
50
|
+
confidence: 0.65,
|
|
51
|
+
pattern: /(?:\.env|\.git\/|\.htaccess|\.DS_Store|\.ssh\/|\.aws\/credentials)/i
|
|
52
|
+
}
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
name: 'prototype-pollution',
|
|
5
|
+
label: 'prototype-pollution',
|
|
6
|
+
|
|
7
|
+
detectObject(obj, maxDepth = 20) {
|
|
8
|
+
const signals = [];
|
|
9
|
+
if (!obj || typeof obj !== 'object') return signals;
|
|
10
|
+
|
|
11
|
+
function walk(node, depth) {
|
|
12
|
+
if (depth > maxDepth) return;
|
|
13
|
+
if (!node || typeof node !== 'object') return;
|
|
14
|
+
|
|
15
|
+
if (Array.isArray(node)) {
|
|
16
|
+
for (const item of node) {
|
|
17
|
+
walk(item, depth + 1);
|
|
18
|
+
}
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (const key of Object.keys(node)) {
|
|
23
|
+
if (key === '__proto__') {
|
|
24
|
+
signals.push({ id: 'proto-key', confidence: 0.90, label: 'prototype-pollution' });
|
|
25
|
+
} else if (key === 'constructor') {
|
|
26
|
+
signals.push({ id: 'constructor-key', confidence: 0.70, label: 'prototype-pollution' });
|
|
27
|
+
}
|
|
28
|
+
walk(node[key], depth + 1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
walk(obj, 0);
|
|
33
|
+
return signals;
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
getSignals() {
|
|
37
|
+
return [
|
|
38
|
+
{
|
|
39
|
+
id: 'proto-key',
|
|
40
|
+
confidence: 0.90,
|
|
41
|
+
pattern: /"__proto__"\s*:/
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'constructor-prototype',
|
|
45
|
+
confidence: 0.85,
|
|
46
|
+
pattern: /constructor\.prototype/
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'constructor-key',
|
|
50
|
+
confidence: 0.70,
|
|
51
|
+
pattern: /"constructor"\s*:/
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 'proto-in-url',
|
|
55
|
+
confidence: 0.80,
|
|
56
|
+
pattern: /(?:\?|&)[^=]*__proto__/
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 'prototype-in-brackets',
|
|
60
|
+
confidence: 0.80,
|
|
61
|
+
pattern: /\[(?:'|")?__proto__(?:'|")?\]|\[(?:'|")?constructor(?:'|")?\]\s*\[(?:'|")?prototype(?:'|")?\]/
|
|
62
|
+
}
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
};
|