Summary
scripts/lua/rest/v2/get/system/configurations/download_backup.lua and .../list_available_backups.lua expose full historical system-configuration backups to any authenticated ntopng user, with no administrator or capability check. Sibling endpoints performing equivalent operations (scripts/lua/rest/v2/export/all/config.lua, import/all/config.lua) correctly gate on isAdministratorOrPrintErr(); these two backup endpoints are the outliers.
Critically, these backups are not merely "configuration" in the informal sense — they are a raw dump of every Redis key under ntopng.prefs.* and ntopng.user.*, which includes every local user's password hash (ntopng.user.<name>.password), and, where configured, API tokens (ntopng.user.<name>.api_token), TOTP secrets (ntopng.user.<name>.totp_secret), and WebAuthn credential blobs (ntopng.user.<name>.webauthn_cred_*). Combined with the unsalted, unkeyed MD5 password hashing documented separately in this audit (12-unsalted-md5-password-hashing.md), a non-administrator user can download one backup, crack the disclosed admin password hash offline, and obtain full administrative control of the application — a direct, low-effort, guaranteed-not-speculative escalation chain, not merely a config-confidentiality issue.
Details
scripts/lua/rest/v2/get/system/configurations/download_backup.lua (entire file):
local download = _GET["download"]
local epoch = _GET["epoch"]
local success, response = backup_config.export_backup(epoch)
...
if download then
sendHTTPContentTypeHeader('application/json', 'attachment; filename="ntopng_backup_' .. epoch .. '.json"')
print(response)
else
rest_utils.answer(rest_utils.consts.success.ok, rsp)
end
scripts/lua/rest/v2/get/system/configurations/list_available_backups.lua (entire file):
local order = _GET["order"] or "desc"
local epoch_list = backup_config.list_backup(_SESSION["user"], order)
...
rest_utils.extended_answer(rc, epoch_list, extra_rsp_data)
Neither file contains isAdministrator() or auth.has_capability(...). Compare with scripts/lua/rest/v2/export/all/config.lua:24:
if not isAdministratorOrPrintErr() then
rest_utils.answer(rest_utils.consts.err.not_granted)
return
end
What the backup actually contains — traced end to end: both backup_config.exec_backup() (scripts/lua/modules/system_config/backup_config.lua, triggered daily by scripts/callbacks/daily-delayed/system/backup_configurations.lua, and reachable on-demand indirectly via the sibling admin-gated export endpoint using the identical mechanism) and the vulnerable download path ultimately call all_import_export:export() (scripts/lua/modules/import_export/all_import_export.lua), which calls prefs_dump_utils.build_prefs_dump_table() (scripts/lua/modules/prefs_dump_utils.lua:20,36-54):
local patterns = {"ntopng.prefs.*", "ntopng.user.*"}
...
function prefs_dump_utils.build_prefs_dump_table()
local out = {}
for _, pattern in pairs(patterns) do
local keys = ntop.getKeysCache(pattern)
for k in pairs(keys or {}) do
local dump = ntop.dumpCache(k) -- Redis DUMP-serialized value
if dump ~= empty_string_dump then
out[k] = dump
end
end
end
return out
end
The ntopng.user.* glob unconditionally matches every key defined under NTOPNG_USER_PREFIX in include/ntop_defines.h, including:
CONST_STR_USER_PASSWORD → ntopng.user.%s.password (unsalted MD5 hash, see 12-unsalted-md5-password-hashing.md)
CONST_STR_USER_API_TOKEN → ntopng.user.%s.api_token
CONST_STR_USER_TOTP_SECRET → ntopng.user.%s.totp_secret
CONST_STR_USER_WEBAUTHN_CRED → ntopng.user.%s.webauthn_cred_%d
Live confirmation (same session, captured while testing the properly-gated sibling endpoint as admin):
GET /lua/rest/v2/export/all/config.lua (as admin — this endpoint IS correctly gated; used here only to observe real export content)
Response included:
"ntopng.user.testadmin.password":"002035346638323235313431343464376262313464373063613063613165356661330A003F0F8F68F9CA7733", ...
confirming the export mechanism genuinely embeds the raw password-hash key. This value is a hex-encoded Redis DUMP blob (per ntop.dumpCache()), directly restorable via RESTORE into an attacker-controlled Redis instance to recover the plain MD5 hash — a trivial, standard-tooling step, not a novel technique. The vulnerable, unauthenticated-by-role download_backup.lua reaches the identical export mechanism with no admin gate in front of it.
PoC
As any authenticated, non-administrator user:
GET /lua/rest/v2/get/system/configurations/list_available_backups.lua
GET /lua/rest/v2/get/system/configurations/download_backup.lua?epoch=<epoch-from-list-above>&download=1
returns a full JSON backup containing, among other keys, ntopng.user.admin.password and equivalent entries for every other local account.
Impact
Disclosure of the complete local-user credential store (password hashes for every account, plus API tokens/TOTP secrets/WebAuthn credentials where configured) to any logged-in, non-privileged account. Because ntopng hashes passwords with unsalted, single-round MD5 (12-unsalted-md5-password-hashing.md), this is not merely a confidentiality issue confined to "configuration data" — it is a direct, low-effort path to full administrative account takeover: download one backup, crack the disclosed admin hash offline, log in as administrator. If an API token happens to be configured for any user, no cracking step is even required.
Summary
scripts/lua/rest/v2/get/system/configurations/download_backup.luaand.../list_available_backups.luaexpose full historical system-configuration backups to any authenticated ntopng user, with no administrator or capability check. Sibling endpoints performing equivalent operations (scripts/lua/rest/v2/export/all/config.lua,import/all/config.lua) correctly gate onisAdministratorOrPrintErr(); these two backup endpoints are the outliers.Critically, these backups are not merely "configuration" in the informal sense — they are a raw dump of every Redis key under
ntopng.prefs.*andntopng.user.*, which includes every local user's password hash (ntopng.user.<name>.password), and, where configured, API tokens (ntopng.user.<name>.api_token), TOTP secrets (ntopng.user.<name>.totp_secret), and WebAuthn credential blobs (ntopng.user.<name>.webauthn_cred_*). Combined with the unsalted, unkeyed MD5 password hashing documented separately in this audit (12-unsalted-md5-password-hashing.md), a non-administrator user can download one backup, crack the disclosedadminpassword hash offline, and obtain full administrative control of the application — a direct, low-effort, guaranteed-not-speculative escalation chain, not merely a config-confidentiality issue.Details
scripts/lua/rest/v2/get/system/configurations/download_backup.lua(entire file):scripts/lua/rest/v2/get/system/configurations/list_available_backups.lua(entire file):Neither file contains
isAdministrator()orauth.has_capability(...). Compare withscripts/lua/rest/v2/export/all/config.lua:24:What the backup actually contains — traced end to end: both
backup_config.exec_backup()(scripts/lua/modules/system_config/backup_config.lua, triggered daily byscripts/callbacks/daily-delayed/system/backup_configurations.lua, and reachable on-demand indirectly via the sibling admin-gated export endpoint using the identical mechanism) and the vulnerable download path ultimately callall_import_export:export()(scripts/lua/modules/import_export/all_import_export.lua), which callsprefs_dump_utils.build_prefs_dump_table()(scripts/lua/modules/prefs_dump_utils.lua:20,36-54):The
ntopng.user.*glob unconditionally matches every key defined underNTOPNG_USER_PREFIXininclude/ntop_defines.h, including:CONST_STR_USER_PASSWORD→ntopng.user.%s.password(unsalted MD5 hash, see12-unsalted-md5-password-hashing.md)CONST_STR_USER_API_TOKEN→ntopng.user.%s.api_tokenCONST_STR_USER_TOTP_SECRET→ntopng.user.%s.totp_secretCONST_STR_USER_WEBAUTHN_CRED→ntopng.user.%s.webauthn_cred_%dLive confirmation (same session, captured while testing the properly-gated sibling endpoint as admin):
Response included:
confirming the export mechanism genuinely embeds the raw password-hash key. This value is a hex-encoded Redis
DUMPblob (perntop.dumpCache()), directly restorable viaRESTOREinto an attacker-controlled Redis instance to recover the plain MD5 hash — a trivial, standard-tooling step, not a novel technique. The vulnerable, unauthenticated-by-roledownload_backup.luareaches the identical export mechanism with no admin gate in front of it.PoC
As any authenticated, non-administrator user:
returns a full JSON backup containing, among other keys,
ntopng.user.admin.passwordand equivalent entries for every other local account.Impact
Disclosure of the complete local-user credential store (password hashes for every account, plus API tokens/TOTP secrets/WebAuthn credentials where configured) to any logged-in, non-privileged account. Because ntopng hashes passwords with unsalted, single-round MD5 (
12-unsalted-md5-password-hashing.md), this is not merely a confidentiality issue confined to "configuration data" — it is a direct, low-effort path to full administrative account takeover: download one backup, crack the disclosedadminhash offline, log in as administrator. If an API token happens to be configured for any user, no cracking step is even required.