VNX-913: Request data chooses the file of code the process loads or runs (improper control of dynamically-managed code resources)
Detects HTTP request data naming a script, plugin or native library file that the process loads or executes, in Java, Python, Node.js and Go.
| Field | Value |
|---|---|
| Rule ID | VNX-913 |
| Severity | High |
| SARIF level | error |
| Kind | sast |
| Languages | Go, Java, JavaScript / TypeScript, Python |
| CWE | CWE-913 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:H/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:H/DI:H/EX:H/EC:N/P:H |
| Tags | dynamic-code code-loading path-control cwe-913 |
| Source | internal/sast/rules/vnx-913.rego |
Overview
CWE-913 is improper control of dynamically-managed code resources. This rule reports one slice of it: HTTP request data that chooses the file of code the process loads or runs. A requester who controls the path can point the server at an uploaded script, a world-writable directory or a traversal like ../../tmp/x, and the file runs with the server’s privileges.
The rule follows the request value inline, through string building, or through a variable assigned or destructured from it (and one further assignment) in the 25 lines above the call.
Reported Sinks
| Language | Sinks |
|---|---|
| Java | ScriptEngine.eval(new FileReader(path)) (also InputStreamReader, FileInputStream, Files.newBufferedReader), GroovyShell evaluate/parse/run with a File, GroovyClassLoader.parseClass, URLClassLoader.newInstance, JNA Native.load/loadLibrary and NativeLibrary.getInstance |
| Python | runpy.run_path/run_module, SourceFileLoader/SourcelessFileLoader/ExtensionFileLoader, imp.load_compiled, execfile, zipimporter, cffi ffi.dlopen |
| Node.js | new Worker(path) from worker_threads, Module._load, new vm.SourceTextModule, vm2 runFile, koffi.load, ffi.Library |
| Go | syscall/windows LoadDLL, NewLazyDLL, LoadLibrary, purego.Dlopen, gopher-lua DoFile, yaegi EvalPath |
script = request.args.get("script")
result = runpy.run_path(script) # reported
String template = request.getParameter("template");
engine.eval(new FileReader("/srv/reports/" + template)); // reported
Not Reported Here
Constant paths, values from configuration or the environment, a path reduced with basename/secure_filename/filepath.Base/getFileName, a value checked against an allowlist or a numeric conversion, a path confined by a prefix check (startsWith, startswith, HasPrefix, commonpath, is_relative_to), and anything in comments or string literals.
Neighbouring CWE-913 shapes belong to other rules, so a line is reported by only one of them:
- Code strings and file contents passed to
eval,exec,Functionorvm, includingexec(open(path).read()): VNX-94. System.load,Runtime.load,URLClassLoaderconstructors,plugin.Open,ctypes.CDLL,import_module,require()/import(),process.dlopen, Rubyrequire/load: VNX-114 and VNX-1027.- Reflection that picks a class, module or method: VNX-470.
- PHP
include/require: VNX-98. Mass assignment: VNX-915.
Remediation
Never build a loadable path from request data. Map the request value to a fixed set of files:
SCRIPTS = {"nightly": "/srv/jobs/nightly.py", "weekly": "/srv/jobs/weekly.py"}
path = SCRIPTS.get(request.args.get("job"))
if path is None:
abort(400)
runpy.run_path(path)
If a file name must come from the client, reduce it to its last element, join it to a fixed directory, resolve the result and check that it is still inside that directory before loading it.
References
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-913
// nosec vnx-913
Or record an ignore rule: vulnetix ignore add --rule vnx-913 --reason "...". See Suppressing findings.