VNX-114: Process Control: request chooses the library or module loaded
HTTP request data names the native library, plugin or module loaded by plugin.Open, System.load, URLClassLoader, require/import(), dl(), FFI, importlib, ctypes, Ruby require/load or Fiddle.
| Field | Value |
|---|---|
| Rule ID | VNX-114 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | Go, Java, JavaScript / TypeScript, PHP, Python, Ruby |
| CWE | CWE-114 |
| CAPEC | CAPEC-97 |
| ATT&CK | T1557 |
| CWSS vector | CWSS:1.0/TI:M/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C |
| Tags | process-control library-loading dynamic-import cwe-114 |
| Source | internal/sast/rules/vnx-114.rego |
Overview
VNX-114 maps to CWE-114: Process Control. It reports a library- or module-loading call whose argument carries HTTP request data, inline, concatenated, interpolated, or through a variable assigned from it in the 25 lines above:
- Go
plugin.Open - Java
System.load/loadLibrary,Runtime.getRuntime().load/loadLibrary,new URLClassLoader - JavaScript/TypeScript
require(), dynamicimport(),process.dlopen - PHP
dl(),FFI::cdef,FFI::load - Python
importlib.import_module,__import__,imp.load_source,spec_from_file_location,ctypes.CDLL/WinDLL/cdll.LoadLibrary - Ruby
require,require_relative,load,Fiddle.dlopen,Fiddle::Handle.new
Not reported: constant names, values from configuration, environment or system properties, and request values checked against an allowlist or looked up in a fixed map before the load. PHP include/require is covered by VNX-98.
Why This Matters
Loading a library or module runs its initialisation code inside the process. When the requester picks the name or path, they can load any module on the import path (including ones that run commands on import), a library they uploaded earlier, or a path traversed out of the plugin directory.
What Gets Flagged
const plugin = require('../plugins/' + req.query.name); // FLAGGED
const mod = await import(`./addons/${addon}.js`); // FLAGGED when addon = req.body.addon
name = request.args.get("module")
mod = importlib.import_module("app.handlers." + name) # FLAGGED
lib = ctypes.CDLL(request.args["lib"]) # FLAGGED
name := r.URL.Query().Get("plugin")
p, err := plugin.Open("/opt/plugins/" + name + ".so") // FLAGGED
require "reports/#{params[:kind]}" # FLAGGED
Not flagged: require('./greetings').for(name), importlib.import_module(HANDLERS[kind]) after if kind not in HANDLERS, System.loadLibrary("acme-native"), System.load(System.getProperty("acme.lib.path")).
Remediation
- Map the request value to a fixed table of permitted modules or libraries and load only entries from that table.
- Never build a module name or library path from request data; if a path is unavoidable, resolve it and confirm it stays inside a fixed plugin directory and names an allowlisted file.
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-114
// nosec vnx-114
Or record an ignore rule: vulnetix ignore add --rule vnx-114 --reason "...". See Suppressing findings.