circle-ir 3.81.0 → 3.83.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/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +35 -10
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/passes/info-disclosure-stacktrace-pass.d.ts +48 -0
- package/dist/analysis/passes/info-disclosure-stacktrace-pass.d.ts.map +1 -0
- package/dist/analysis/passes/info-disclosure-stacktrace-pass.js +222 -0
- package/dist/analysis/passes/info-disclosure-stacktrace-pass.js.map +1 -0
- package/dist/analysis/passes/unrestricted-file-upload-pass.d.ts +46 -0
- package/dist/analysis/passes/unrestricted-file-upload-pass.d.ts.map +1 -0
- package/dist/analysis/passes/unrestricted-file-upload-pass.js +193 -0
- package/dist/analysis/passes/unrestricted-file-upload-pass.js.map +1 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +6 -0
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +363 -10
- package/dist/core/circle-ir-core.cjs +35 -10
- package/dist/core/circle-ir-core.js +35 -10
- package/package.json +1 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass: info-disclosure-stacktrace (CWE-209, category: security)
|
|
3
|
+
*
|
|
4
|
+
* Detects exception stack traces / messages being returned to remote clients
|
|
5
|
+
* via an HTTP response handler. This leaks framework internals, file paths,
|
|
6
|
+
* SQL fragments, and class names — useful reconnaissance for an attacker.
|
|
7
|
+
*
|
|
8
|
+
* Detection per language:
|
|
9
|
+
* Java:
|
|
10
|
+
* - `e.printStackTrace(response.getWriter())` / `.printStackTrace(out)`
|
|
11
|
+
* where `out` is a response writer.
|
|
12
|
+
* - `response.getWriter().write(e.toString())`
|
|
13
|
+
* - `response.getWriter().println(e.getMessage())`
|
|
14
|
+
* - `new ResponseEntity<>(e.getStackTrace(), …)`
|
|
15
|
+
*
|
|
16
|
+
* Python:
|
|
17
|
+
* - `return traceback.format_exc()` from a handler-like function
|
|
18
|
+
* - `flask.jsonify(error=traceback.format_exc())`
|
|
19
|
+
* - Bare `return str(e)` / `return {"error": str(e)}` in handler
|
|
20
|
+
*
|
|
21
|
+
* JS/TS:
|
|
22
|
+
* - `res.send(err.stack)` / `res.json({error: err.stack})`
|
|
23
|
+
* - `res.json(err)` (whole error object)
|
|
24
|
+
* - `res.status(N).send(err.message + err.stack)` / similar
|
|
25
|
+
*
|
|
26
|
+
* Go:
|
|
27
|
+
* - `http.Error(w, err.Error()+debug.Stack(), 500)` — narrow
|
|
28
|
+
* - `fmt.Fprintln(w, err)` in an HTTP handler
|
|
29
|
+
*
|
|
30
|
+
* Negative guard: if the consumer is a logger (`console.error`,
|
|
31
|
+
* `logger.error`, `log.Error`), do NOT fire — logging stack traces
|
|
32
|
+
* server-side is not a leak.
|
|
33
|
+
*/
|
|
34
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
35
|
+
export interface InfoDisclosureStacktraceResult {
|
|
36
|
+
findings: Array<{
|
|
37
|
+
line: number;
|
|
38
|
+
api: string;
|
|
39
|
+
language: string;
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
export declare class InfoDisclosureStacktracePass implements AnalysisPass<InfoDisclosureStacktraceResult> {
|
|
43
|
+
readonly name = "info-disclosure-stacktrace";
|
|
44
|
+
readonly category: "security";
|
|
45
|
+
run(ctx: PassContext): InfoDisclosureStacktraceResult;
|
|
46
|
+
private makeFinding;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=info-disclosure-stacktrace-pass.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"info-disclosure-stacktrace-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/info-disclosure-stacktrace-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAG9E,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClE;AAoHD,qBAAa,4BAA6B,YAAW,YAAY,CAAC,8BAA8B,CAAC;IAC/F,QAAQ,CAAC,IAAI,gCAAgC;IAC7C,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,8BAA8B;IAqDrD,OAAO,CAAC,WAAW;CAsBpB"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass: info-disclosure-stacktrace (CWE-209, category: security)
|
|
3
|
+
*
|
|
4
|
+
* Detects exception stack traces / messages being returned to remote clients
|
|
5
|
+
* via an HTTP response handler. This leaks framework internals, file paths,
|
|
6
|
+
* SQL fragments, and class names — useful reconnaissance for an attacker.
|
|
7
|
+
*
|
|
8
|
+
* Detection per language:
|
|
9
|
+
* Java:
|
|
10
|
+
* - `e.printStackTrace(response.getWriter())` / `.printStackTrace(out)`
|
|
11
|
+
* where `out` is a response writer.
|
|
12
|
+
* - `response.getWriter().write(e.toString())`
|
|
13
|
+
* - `response.getWriter().println(e.getMessage())`
|
|
14
|
+
* - `new ResponseEntity<>(e.getStackTrace(), …)`
|
|
15
|
+
*
|
|
16
|
+
* Python:
|
|
17
|
+
* - `return traceback.format_exc()` from a handler-like function
|
|
18
|
+
* - `flask.jsonify(error=traceback.format_exc())`
|
|
19
|
+
* - Bare `return str(e)` / `return {"error": str(e)}` in handler
|
|
20
|
+
*
|
|
21
|
+
* JS/TS:
|
|
22
|
+
* - `res.send(err.stack)` / `res.json({error: err.stack})`
|
|
23
|
+
* - `res.json(err)` (whole error object)
|
|
24
|
+
* - `res.status(N).send(err.message + err.stack)` / similar
|
|
25
|
+
*
|
|
26
|
+
* Go:
|
|
27
|
+
* - `http.Error(w, err.Error()+debug.Stack(), 500)` — narrow
|
|
28
|
+
* - `fmt.Fprintln(w, err)` in an HTTP handler
|
|
29
|
+
*
|
|
30
|
+
* Negative guard: if the consumer is a logger (`console.error`,
|
|
31
|
+
* `logger.error`, `log.Error`), do NOT fire — logging stack traces
|
|
32
|
+
* server-side is not a leak.
|
|
33
|
+
*/
|
|
34
|
+
/** Receiver names that almost always indicate an HTTP response handle. */
|
|
35
|
+
const RESPONSE_RECEIVER_RE = /^(res|response|w|writer|ctx|c)$/i;
|
|
36
|
+
/** Logger receivers (negative guard). */
|
|
37
|
+
const LOGGER_RECEIVER_RE = /^(log|logger|slog|console|pino|winston|sentry)$/i;
|
|
38
|
+
/** Method names on a response that send data to the client. */
|
|
39
|
+
const RESPONSE_SEND_METHODS = new Set([
|
|
40
|
+
'send', 'json', 'write', 'writeHead', 'end', 'sendFile',
|
|
41
|
+
'println', 'print', 'getWriter',
|
|
42
|
+
'Fprintln', 'Fprintf', 'Fprint',
|
|
43
|
+
]);
|
|
44
|
+
/** Expression heuristics for "this is an exception value". */
|
|
45
|
+
function isExceptionExpression(expr) {
|
|
46
|
+
if (!expr)
|
|
47
|
+
return false;
|
|
48
|
+
const e = expr.trim();
|
|
49
|
+
// err.stack | err.message | e.toString() | e.getMessage() | e.getStackTrace()
|
|
50
|
+
// exc.format_exc() | traceback.format_exc() | str(e)
|
|
51
|
+
return (/\b(err|error|exc|exception|e|t|throwable)\.(stack|message|toString\(|getMessage\(|getStackTrace\(|getLocalizedMessage\(|getCause\()/i.test(e) ||
|
|
52
|
+
/\btraceback\.(format_exc|format_exception|print_exc)\b/i.test(e) ||
|
|
53
|
+
/\bdebug\.Stack\(\)/.test(e) ||
|
|
54
|
+
/\bstr\(\s*(err|error|exc|exception|e)\s*\)/i.test(e) ||
|
|
55
|
+
/\bString\(\s*(err|error|exc|exception|e)\s*\)/i.test(e));
|
|
56
|
+
}
|
|
57
|
+
/** True if an argument carries an exception-like value. */
|
|
58
|
+
function argIsException(arg) {
|
|
59
|
+
if (!arg)
|
|
60
|
+
return false;
|
|
61
|
+
if (arg.variable && /^(err|error|exc|exception|e|t|throwable)$/i.test(arg.variable)) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return isExceptionExpression(arg.expression);
|
|
65
|
+
}
|
|
66
|
+
/** Detect Java: e.printStackTrace(out) where out is a response writer. */
|
|
67
|
+
function detectJavaPrintStackTrace(call) {
|
|
68
|
+
if (call.method_name !== 'printStackTrace')
|
|
69
|
+
return null;
|
|
70
|
+
// Receiver should look like an exception variable name.
|
|
71
|
+
const rec = call.receiver ?? '';
|
|
72
|
+
if (!/^(e|ex|exc|exception|err|error|t|throwable)$/i.test(rec))
|
|
73
|
+
return null;
|
|
74
|
+
// Arg 0 should be a response writer expression.
|
|
75
|
+
const arg0 = call.arguments.find((a) => a.position === 0);
|
|
76
|
+
if (!arg0)
|
|
77
|
+
return null;
|
|
78
|
+
const expr = (arg0.expression ?? arg0.variable ?? '').trim();
|
|
79
|
+
if (/\bresponse\.getWriter\(\)/.test(expr) ||
|
|
80
|
+
/\bresp\.getWriter\(\)/.test(expr) ||
|
|
81
|
+
/\bout\b/.test(expr) || // common name; conservative
|
|
82
|
+
/\bgetWriter\(\)/.test(expr)) {
|
|
83
|
+
return 'e.printStackTrace(response.getWriter())';
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
/** Detect calls of the shape `response.send(err.stack)` / `.json(err)`. */
|
|
88
|
+
function detectResponseLeakCall(call) {
|
|
89
|
+
const method = call.method_name ?? '';
|
|
90
|
+
const receiver = call.receiver ?? '';
|
|
91
|
+
if (!RESPONSE_SEND_METHODS.has(method))
|
|
92
|
+
return null;
|
|
93
|
+
if (LOGGER_RECEIVER_RE.test(receiver))
|
|
94
|
+
return null;
|
|
95
|
+
// Accept either a bare known receiver name, or one whose tail is a known name
|
|
96
|
+
// (e.g. `ctx.response`, `event.res`, `response.status(500)` chained returns).
|
|
97
|
+
const recTail = receiver.split('.').pop() ?? receiver;
|
|
98
|
+
const recHead = receiver.split('.')[0] ?? receiver;
|
|
99
|
+
if (!RESPONSE_RECEIVER_RE.test(recTail) && !RESPONSE_RECEIVER_RE.test(recHead)) {
|
|
100
|
+
// Allow chained: res.status(500).send(...) — receiver text often contains
|
|
101
|
+
// `.status(` substring.
|
|
102
|
+
if (!/(?:^|[.\s])(res|response)\.(?:status|set|header|cookie)\b/i.test(receiver)) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Any argument contains an exception expression?
|
|
107
|
+
for (const a of call.arguments) {
|
|
108
|
+
if (argIsException(a)) {
|
|
109
|
+
return `${receiver || ''}${receiver ? '.' : ''}${method}(${(a.expression ?? a.variable ?? '').trim()})`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
/** Detect Python: `return traceback.format_exc()` inside any function. */
|
|
115
|
+
function detectPythonTracebackReturn(ctx) {
|
|
116
|
+
const out = [];
|
|
117
|
+
const lines = ctx.code.split('\n');
|
|
118
|
+
for (let i = 0; i < lines.length; i++) {
|
|
119
|
+
const ln = lines[i] ?? '';
|
|
120
|
+
if (/\breturn\s+traceback\.format_exc\s*\(\s*\)/.test(ln) ||
|
|
121
|
+
/\breturn\s+\{[^}]*traceback\.format_exc\s*\(\s*\)[^}]*\}/.test(ln) ||
|
|
122
|
+
/\bjsonify\s*\([^)]*traceback\.format_exc\s*\(\s*\)/.test(ln)) {
|
|
123
|
+
out.push({ line: i + 1, api: 'return traceback.format_exc()' });
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
// `return str(e)` in a handler context — conservative: require the
|
|
127
|
+
// surrounding 5-line window to contain a Flask/FastAPI/Django marker.
|
|
128
|
+
if (/\breturn\s+(?:str|repr)\s*\(\s*(?:e|err|error|exc|exception)\s*\)/.test(ln)) {
|
|
129
|
+
const start = Math.max(0, i - 8);
|
|
130
|
+
const end = Math.min(lines.length, i + 2);
|
|
131
|
+
const window = lines.slice(start, end).join('\n');
|
|
132
|
+
if (/@(?:app|router|blueprint)\.(?:route|get|post|put|delete|patch)\b/.test(window)) {
|
|
133
|
+
out.push({ line: i + 1, api: 'return str(e) in handler' });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
export class InfoDisclosureStacktracePass {
|
|
140
|
+
name = 'info-disclosure-stacktrace';
|
|
141
|
+
category = 'security';
|
|
142
|
+
run(ctx) {
|
|
143
|
+
const { graph, language } = ctx;
|
|
144
|
+
const file = graph.ir.meta.file;
|
|
145
|
+
const findings = [];
|
|
146
|
+
if (language === 'python') {
|
|
147
|
+
for (const f of detectPythonTracebackReturn(ctx)) {
|
|
148
|
+
findings.push({ line: f.line, api: f.api, language });
|
|
149
|
+
ctx.addFinding(this.makeFinding(file, f.line, f.api));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (const call of graph.ir.calls) {
|
|
153
|
+
let api = null;
|
|
154
|
+
if (language === 'java') {
|
|
155
|
+
api = detectJavaPrintStackTrace(call);
|
|
156
|
+
if (!api)
|
|
157
|
+
api = detectResponseLeakCall(call);
|
|
158
|
+
}
|
|
159
|
+
else if (language === 'javascript' || language === 'typescript') {
|
|
160
|
+
api = detectResponseLeakCall(call);
|
|
161
|
+
}
|
|
162
|
+
else if (language === 'go') {
|
|
163
|
+
// http.Error(w, err.Error()+debug.Stack(), 500)
|
|
164
|
+
// fmt.Fprintln(w, err)
|
|
165
|
+
const method = call.method_name ?? '';
|
|
166
|
+
const rec = call.receiver ?? '';
|
|
167
|
+
if (rec === 'http' && method === 'Error') {
|
|
168
|
+
const arg1 = call.arguments.find((a) => a.position === 1);
|
|
169
|
+
if (argIsException(arg1))
|
|
170
|
+
api = 'http.Error(w, err.Error())';
|
|
171
|
+
}
|
|
172
|
+
else if (rec === 'fmt' && (method === 'Fprintln' || method === 'Fprintf' || method === 'Fprint')) {
|
|
173
|
+
const arg0 = call.arguments.find((a) => a.position === 0);
|
|
174
|
+
if (arg0 && /^(w|writer|resp|response)$/i.test((arg0.variable ?? arg0.expression ?? '').trim())) {
|
|
175
|
+
for (const a of call.arguments) {
|
|
176
|
+
if (a.position === 0)
|
|
177
|
+
continue;
|
|
178
|
+
if (argIsException(a)) {
|
|
179
|
+
api = `fmt.${method}(w, err)`;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
api = detectResponseLeakCall(call);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else if (language === 'python') {
|
|
190
|
+
// Handle response leak shape too: e.g. `return jsonify(stack=...)`
|
|
191
|
+
api = detectResponseLeakCall(call);
|
|
192
|
+
}
|
|
193
|
+
if (!api)
|
|
194
|
+
continue;
|
|
195
|
+
const line = call.location.line;
|
|
196
|
+
findings.push({ line, api, language });
|
|
197
|
+
ctx.addFinding(this.makeFinding(file, line, api));
|
|
198
|
+
}
|
|
199
|
+
return { findings };
|
|
200
|
+
}
|
|
201
|
+
makeFinding(file, line, api) {
|
|
202
|
+
return {
|
|
203
|
+
id: `${this.name}-${file}-${line}`,
|
|
204
|
+
pass: this.name,
|
|
205
|
+
category: this.category,
|
|
206
|
+
rule_id: this.name,
|
|
207
|
+
cwe: 'CWE-209',
|
|
208
|
+
severity: 'medium',
|
|
209
|
+
level: 'warning',
|
|
210
|
+
message: `Exception detail returned to client via \`${api}\`. ` +
|
|
211
|
+
'Leaking stack traces / exception messages reveals framework internals, ' +
|
|
212
|
+
'file paths, and class names — useful reconnaissance for an attacker.',
|
|
213
|
+
file,
|
|
214
|
+
line,
|
|
215
|
+
fix: 'Return a generic error response to the client (e.g. status 500 + a ' +
|
|
216
|
+
'request id) and log the full exception server-side via your logger ' +
|
|
217
|
+
'(e.g. `logger.error("…", e)` or `console.error(err)`).',
|
|
218
|
+
evidence: { api },
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=info-disclosure-stacktrace-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"info-disclosure-stacktrace-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/info-disclosure-stacktrace-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass: unrestricted-file-upload (CWE-434, category: security)
|
|
3
|
+
*
|
|
4
|
+
* Detects when an HTTP-uploaded file is saved to disk WITHOUT a filename
|
|
5
|
+
* allow-list (extension check) or `secure_filename` normalization.
|
|
6
|
+
*
|
|
7
|
+
* Detection (per language):
|
|
8
|
+
*
|
|
9
|
+
* Java (Spring MultipartFile / Servlet Part):
|
|
10
|
+
* - `file.transferTo(new File(dir, file.getOriginalFilename()))`
|
|
11
|
+
* - `Files.copy(part.getInputStream(), Path.of(dir, part.getSubmittedFileName()))`
|
|
12
|
+
* - Without preceding `ALLOWED_*.contains(ext)` or
|
|
13
|
+
* `FilenameUtils.getExtension(name)` + check.
|
|
14
|
+
*
|
|
15
|
+
* JS/TS (multer / express-fileupload):
|
|
16
|
+
* - `multer({ dest: '…' })` with NO `fileFilter` option.
|
|
17
|
+
* - `fs.writeFile(path, req.file.buffer)` / `req.files.x.mv(path)`
|
|
18
|
+
* without prior `path.extname` allow-list check.
|
|
19
|
+
*
|
|
20
|
+
* Python (Flask / Django / FastAPI):
|
|
21
|
+
* - `f.save(os.path.join(UPLOAD_DIR, f.filename))` without prior
|
|
22
|
+
* `secure_filename(f.filename)` wrapping.
|
|
23
|
+
*
|
|
24
|
+
* Go:
|
|
25
|
+
* - `io.Copy(dst, file)` where `dst = os.Create(fileHeader.Filename)`
|
|
26
|
+
* without an extension allow-list.
|
|
27
|
+
*
|
|
28
|
+
* The pass is intentionally conservative — it only fires when an upload-name
|
|
29
|
+
* expression flows directly into a save sink in the same function and no
|
|
30
|
+
* known allow-list / canonicalizer call appears earlier in the function.
|
|
31
|
+
*/
|
|
32
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
33
|
+
export interface UnrestrictedFileUploadResult {
|
|
34
|
+
findings: Array<{
|
|
35
|
+
line: number;
|
|
36
|
+
api: string;
|
|
37
|
+
language: string;
|
|
38
|
+
}>;
|
|
39
|
+
}
|
|
40
|
+
export declare class UnrestrictedFileUploadPass implements AnalysisPass<UnrestrictedFileUploadResult> {
|
|
41
|
+
readonly name = "unrestricted-file-upload";
|
|
42
|
+
readonly category: "security";
|
|
43
|
+
run(ctx: PassContext): UnrestrictedFileUploadResult;
|
|
44
|
+
private emit;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=unrestricted-file-upload-pass.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unrestricted-file-upload-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/unrestricted-file-upload-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAG9E,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClE;AA2BD,qBAAa,0BAA2B,YAAW,YAAY,CAAC,4BAA4B,CAAC;IAC3F,QAAQ,CAAC,IAAI,8BAA8B;IAC3C,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,4BAA4B;IAyHnD,OAAO,CAAC,IAAI;CAgCb"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass: unrestricted-file-upload (CWE-434, category: security)
|
|
3
|
+
*
|
|
4
|
+
* Detects when an HTTP-uploaded file is saved to disk WITHOUT a filename
|
|
5
|
+
* allow-list (extension check) or `secure_filename` normalization.
|
|
6
|
+
*
|
|
7
|
+
* Detection (per language):
|
|
8
|
+
*
|
|
9
|
+
* Java (Spring MultipartFile / Servlet Part):
|
|
10
|
+
* - `file.transferTo(new File(dir, file.getOriginalFilename()))`
|
|
11
|
+
* - `Files.copy(part.getInputStream(), Path.of(dir, part.getSubmittedFileName()))`
|
|
12
|
+
* - Without preceding `ALLOWED_*.contains(ext)` or
|
|
13
|
+
* `FilenameUtils.getExtension(name)` + check.
|
|
14
|
+
*
|
|
15
|
+
* JS/TS (multer / express-fileupload):
|
|
16
|
+
* - `multer({ dest: '…' })` with NO `fileFilter` option.
|
|
17
|
+
* - `fs.writeFile(path, req.file.buffer)` / `req.files.x.mv(path)`
|
|
18
|
+
* without prior `path.extname` allow-list check.
|
|
19
|
+
*
|
|
20
|
+
* Python (Flask / Django / FastAPI):
|
|
21
|
+
* - `f.save(os.path.join(UPLOAD_DIR, f.filename))` without prior
|
|
22
|
+
* `secure_filename(f.filename)` wrapping.
|
|
23
|
+
*
|
|
24
|
+
* Go:
|
|
25
|
+
* - `io.Copy(dst, file)` where `dst = os.Create(fileHeader.Filename)`
|
|
26
|
+
* without an extension allow-list.
|
|
27
|
+
*
|
|
28
|
+
* The pass is intentionally conservative — it only fires when an upload-name
|
|
29
|
+
* expression flows directly into a save sink in the same function and no
|
|
30
|
+
* known allow-list / canonicalizer call appears earlier in the function.
|
|
31
|
+
*/
|
|
32
|
+
/** Receivers / expressions that look like an uploaded file value. */
|
|
33
|
+
const UPLOAD_NAME_RE = /(?:getOriginalFilename|getSubmittedFileName|originalname|originalName|\.filename|\.Filename|FileHeader\.Filename|UploadFile)/;
|
|
34
|
+
/** Identifier-level allow-list / canonicalization calls that defang upload paths. */
|
|
35
|
+
const FILE_SAFE_CALL_RE = /(?:secure_filename|FilenameUtils\.getExtension|\.lastIndexOf\(['"]\.['"]\)|ALLOWED_EXT|ALLOWED_EXTENSIONS|allowedExtensions|\bfileFilter\b|filepath\.Ext|path\.extname)/;
|
|
36
|
+
function lineWindow(code, startLine, endLine) {
|
|
37
|
+
const lines = code.split('\n');
|
|
38
|
+
const s = Math.max(0, startLine - 1);
|
|
39
|
+
const e = Math.min(lines.length, endLine);
|
|
40
|
+
return lines.slice(s, e).join('\n');
|
|
41
|
+
}
|
|
42
|
+
function callHasUploadName(call) {
|
|
43
|
+
for (const a of call.arguments) {
|
|
44
|
+
const expr = (a.expression ?? a.variable ?? '').trim();
|
|
45
|
+
if (UPLOAD_NAME_RE.test(expr))
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
// Receiver too (e.g. multipart `file.transferTo(...)`).
|
|
49
|
+
if (UPLOAD_NAME_RE.test(call.receiver ?? ''))
|
|
50
|
+
return true;
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
export class UnrestrictedFileUploadPass {
|
|
54
|
+
name = 'unrestricted-file-upload';
|
|
55
|
+
category = 'security';
|
|
56
|
+
run(ctx) {
|
|
57
|
+
const { graph, language, code } = ctx;
|
|
58
|
+
const file = graph.ir.meta.file;
|
|
59
|
+
const findings = [];
|
|
60
|
+
// Build per-function safety windows: a function is "safe" if its body
|
|
61
|
+
// contains any FILE_SAFE_CALL_RE marker. Used to suppress findings inside
|
|
62
|
+
// that scope.
|
|
63
|
+
const safeFunctionRanges = [];
|
|
64
|
+
for (const t of graph.ir.types) {
|
|
65
|
+
for (const m of t.methods) {
|
|
66
|
+
const body = lineWindow(code, m.start_line, m.end_line);
|
|
67
|
+
if (FILE_SAFE_CALL_RE.test(body)) {
|
|
68
|
+
safeFunctionRanges.push({ start: m.start_line, end: m.end_line });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const inSafeRange = (line) => {
|
|
73
|
+
for (const r of safeFunctionRanges) {
|
|
74
|
+
if (line >= r.start && line <= r.end)
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
// No method information — fall back to a ±20-line window around the call.
|
|
78
|
+
const win = lineWindow(code, Math.max(1, line - 20), line + 5);
|
|
79
|
+
return FILE_SAFE_CALL_RE.test(win);
|
|
80
|
+
};
|
|
81
|
+
// --- Per-language detection -----------------------------------------------
|
|
82
|
+
if (language === 'java') {
|
|
83
|
+
for (const call of graph.ir.calls) {
|
|
84
|
+
const m = call.method_name ?? '';
|
|
85
|
+
// file.transferTo(...)
|
|
86
|
+
if (m === 'transferTo' && callHasUploadName(call)) {
|
|
87
|
+
if (inSafeRange(call.location.line))
|
|
88
|
+
continue;
|
|
89
|
+
this.emit(ctx, findings, file, call.location.line, language, 'MultipartFile.transferTo(<original filename>)');
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
// Files.copy(getInputStream, Path.of(dir, getOriginalFilename))
|
|
93
|
+
if (m === 'copy' && (call.receiver === 'Files' || (call.receiver ?? '').endsWith('.Files'))) {
|
|
94
|
+
if (callHasUploadName(call)) {
|
|
95
|
+
if (inSafeRange(call.location.line))
|
|
96
|
+
continue;
|
|
97
|
+
this.emit(ctx, findings, file, call.location.line, language, 'Files.copy(input, Path.of(dir, <original filename>))');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (language === 'javascript' || language === 'typescript') {
|
|
103
|
+
for (const call of graph.ir.calls) {
|
|
104
|
+
const m = call.method_name ?? '';
|
|
105
|
+
const rec = call.receiver ?? '';
|
|
106
|
+
// multer({ dest: '...' }) — flag if same call object lacks fileFilter.
|
|
107
|
+
if (m === 'multer' || (rec === '' && m === 'multer')) {
|
|
108
|
+
const arg0 = call.arguments.find((a) => a.position === 0);
|
|
109
|
+
const expr = (arg0?.expression ?? '').trim();
|
|
110
|
+
if (/\bdest\s*:/.test(expr) && !/\bfileFilter\s*:/.test(expr)) {
|
|
111
|
+
if (inSafeRange(call.location.line))
|
|
112
|
+
continue;
|
|
113
|
+
this.emit(ctx, findings, file, call.location.line, language, 'multer({ dest }) without fileFilter');
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// fs.writeFile(<path>, req.file.buffer) / req.files.x.mv(<path>)
|
|
118
|
+
if (rec === 'fs' && (m === 'writeFile' || m === 'writeFileSync' || m === 'appendFile')) {
|
|
119
|
+
if (callHasUploadName(call) ||
|
|
120
|
+
call.arguments.some((a) => /\breq\.file(?:s)?\b/.test((a.expression ?? a.variable ?? '')))) {
|
|
121
|
+
if (inSafeRange(call.location.line))
|
|
122
|
+
continue;
|
|
123
|
+
this.emit(ctx, findings, file, call.location.line, language, `fs.${m}(<path>, req.file.buffer)`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (language === 'python') {
|
|
129
|
+
for (const call of graph.ir.calls) {
|
|
130
|
+
const m = call.method_name ?? '';
|
|
131
|
+
// f.save(os.path.join(UPLOAD_DIR, f.filename))
|
|
132
|
+
if (m === 'save') {
|
|
133
|
+
const rec = call.receiver ?? '';
|
|
134
|
+
// Accept any receiver that looks file-ish or empty (`f.save`, `file.save`).
|
|
135
|
+
if (!/^(f|file|upload|attachment)$/i.test(rec) && rec !== '')
|
|
136
|
+
continue;
|
|
137
|
+
if (!callHasUploadName(call))
|
|
138
|
+
continue;
|
|
139
|
+
if (inSafeRange(call.location.line))
|
|
140
|
+
continue;
|
|
141
|
+
this.emit(ctx, findings, file, call.location.line, language, 'f.save(<dir>, f.filename) without secure_filename');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (language === 'go') {
|
|
146
|
+
for (const call of graph.ir.calls) {
|
|
147
|
+
const m = call.method_name ?? '';
|
|
148
|
+
const rec = call.receiver ?? '';
|
|
149
|
+
// os.Create(header.Filename)
|
|
150
|
+
if (rec === 'os' && (m === 'Create' || m === 'OpenFile')) {
|
|
151
|
+
if (callHasUploadName(call)) {
|
|
152
|
+
if (inSafeRange(call.location.line))
|
|
153
|
+
continue;
|
|
154
|
+
this.emit(ctx, findings, file, call.location.line, language, `os.${m}(<uploaded filename>)`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// ioutil.WriteFile(header.Filename, …)
|
|
158
|
+
if ((rec === 'os' || rec === 'ioutil') && m === 'WriteFile') {
|
|
159
|
+
if (callHasUploadName(call)) {
|
|
160
|
+
if (inSafeRange(call.location.line))
|
|
161
|
+
continue;
|
|
162
|
+
this.emit(ctx, findings, file, call.location.line, language, `${rec}.WriteFile(<uploaded filename>, …)`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return { findings };
|
|
168
|
+
}
|
|
169
|
+
emit(ctx, findings, file, line, language, api) {
|
|
170
|
+
findings.push({ line, api, language });
|
|
171
|
+
ctx.addFinding({
|
|
172
|
+
id: `${this.name}-${file}-${line}`,
|
|
173
|
+
pass: this.name,
|
|
174
|
+
category: this.category,
|
|
175
|
+
rule_id: this.name,
|
|
176
|
+
cwe: 'CWE-434',
|
|
177
|
+
severity: 'high',
|
|
178
|
+
level: 'error',
|
|
179
|
+
message: `File upload saved using untrusted name (${api}) — no extension allow-list or ` +
|
|
180
|
+
'filename canonicalization detected. An attacker can upload a `.jsp`/`.php`/`.html` ' +
|
|
181
|
+
'file and request it back, achieving RCE or stored XSS.',
|
|
182
|
+
file,
|
|
183
|
+
line,
|
|
184
|
+
fix: 'Validate the uploaded extension against an allow-list (e.g. ' +
|
|
185
|
+
'`Set.of("png","jpg")`), then save with a sanitized filename. In Python use ' +
|
|
186
|
+
'`werkzeug.utils.secure_filename`. In multer pass a `fileFilter`. Never ' +
|
|
187
|
+
'concatenate the upload\'s original filename into a save path without ' +
|
|
188
|
+
'validation.',
|
|
189
|
+
evidence: { api, language },
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=unrestricted-file-upload-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unrestricted-file-upload-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/unrestricted-file-upload-pass.ts"],"names":[],"mappings":"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"}
|
package/dist/analyzer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAA2B,eAAe,EAAe,MAAM,kBAAkB,CAAC;AAC1H,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAcL,KAAK,iBAAiB,EACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAKL,eAAe,EAChB,MAAM,qBAAqB,CAAC;AAgC7B,OAAO,EAAwB,KAAK,uBAAuB,EAAE,MAAM,8CAA8C,CAAC;AAKlH,OAAO,EAA2B,KAAK,0BAA0B,EAAE,MAAM,gDAAgD,CAAC;AAe1H,OAAO,EAAwB,KAAK,uBAAuB,EAAE,MAAM,6CAA6C,CAAC;AACjH,OAAO,EAAuB,KAAK,sBAAsB,EAA6B,MAAM,4CAA4C,CAAC;
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAA2B,eAAe,EAAe,MAAM,kBAAkB,CAAC;AAC1H,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAcL,KAAK,iBAAiB,EACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAKL,eAAe,EAChB,MAAM,qBAAqB,CAAC;AAgC7B,OAAO,EAAwB,KAAK,uBAAuB,EAAE,MAAM,8CAA8C,CAAC;AAKlH,OAAO,EAA2B,KAAK,0BAA0B,EAAE,MAAM,gDAAgD,CAAC;AAe1H,OAAO,EAAwB,KAAK,uBAAuB,EAAE,MAAM,6CAA6C,CAAC;AACjH,OAAO,EAAuB,KAAK,sBAAsB,EAA6B,MAAM,4CAA4C,CAAC;AAwCzI,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,UAAU,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC;IAEhC;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC,CAAC;IAE3D;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;IAEzE;;OAEG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAE1B;;OAEG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAE1B;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;IAC3C,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;IAC3C,iDAAiD;IACjD,mBAAmB,CAAC,EAAE,0BAA0B,CAAC;IACjD,6CAA6C;IAC7C,eAAe,CAAC,EAAE,sBAAsB,CAAC;CAC1C;AAID;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAc/E;AAkID;;GAEG;AACH,wBAAsB,OAAO,CAC3B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,QAAQ,CAAC,CA2LnB;AA4GD;;GAEG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,gBAAgB,CAAC,CAwG3B;AAkID;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAMD;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,iBAAiB,CAAA;CAAE,CAAC,EAC7E,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,eAAe,CAAC,CAmE1B;AAsBD,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
package/dist/analyzer.js
CHANGED
|
@@ -112,6 +112,8 @@ import { WeakCryptoPass } from './analysis/passes/weak-crypto-pass.js';
|
|
|
112
112
|
import { WeakRandomPass } from './analysis/passes/weak-random-pass.js';
|
|
113
113
|
import { WeakPasswordHashPass } from './analysis/passes/weak-password-hash-pass.js';
|
|
114
114
|
import { WeakPasswordEncodingPass } from './analysis/passes/weak-password-encoding-pass.js';
|
|
115
|
+
import { InfoDisclosureStacktracePass } from './analysis/passes/info-disclosure-stacktrace-pass.js';
|
|
116
|
+
import { UnrestrictedFileUploadPass } from './analysis/passes/unrestricted-file-upload-pass.js';
|
|
115
117
|
import { PlaintextPasswordStoragePass } from './analysis/passes/plaintext-password-storage-pass.js';
|
|
116
118
|
import { CleartextCredentialTransportPass } from './analysis/passes/cleartext-credential-transport-pass.js';
|
|
117
119
|
import { TlsVerifyDisabledPass } from './analysis/passes/tls-verify-disabled-pass.js';
|
|
@@ -443,6 +445,10 @@ export async function analyze(code, filePath, language, options = {}) {
|
|
|
443
445
|
pipeline.add(new XmlEntityExpansionPass());
|
|
444
446
|
if (!disabledPasses.has('mass-assignment'))
|
|
445
447
|
pipeline.add(new MassAssignmentPass());
|
|
448
|
+
if (!disabledPasses.has('info-disclosure-stacktrace'))
|
|
449
|
+
pipeline.add(new InfoDisclosureStacktracePass());
|
|
450
|
+
if (!disabledPasses.has('unrestricted-file-upload'))
|
|
451
|
+
pipeline.add(new UnrestrictedFileUploadPass());
|
|
446
452
|
// Run the pipeline
|
|
447
453
|
const { results, findings } = pipeline.run(graph, code, language, config);
|
|
448
454
|
const sinkFilter = results.get('sink-filter');
|