Merge pull request #1311 from Functionhx/fix/netexec-1273-export-keys

fix: add get_keys fallback to prevent export keys crash
This commit is contained in:
Alex authored and GitHub committed 2026-07-21 19:32:43 +02:00
commit cd599504ad
3 files changed
+85

No files matched your search

+4
View File
@@ -208,3 +208,7 @@ class BaseDB:
res = self.sess.execute(*args)
self.lock.release()
return res
def get_keys(self, key_id=None, cred_id=None):
"""Return an empty list by default. Protocols that support keys (e.g. SSH) override this."""
return []
+8
View File
@@ -310,10 +310,18 @@ class DatabaseNavigator(cmd.Cmd):
return
print("[+] DPAPI secrets exported")
elif command == "keys":
if len(line) < 3:
print("[-] invalid arguments, export keys <all|id> <filename>")
return
keys = self.db.get_keys() if line[1].lower() == "all" else self.db.get_keys(key_id=int(line[1]))
if not keys:
print("[-] No keys found in the database for the current protocol")
return
writable_keys = [key[2] for key in keys]
filename = line[2]
write_list(filename, writable_keys)
print("[+] Keys exported")
elif command == "wcc":
if len(line) < 3:
print("[-] invalid arguments, export wcc <simple|detailed> <filename>")
+73
View File
@@ -256,3 +256,76 @@ def test_get_loggedin_relations():
def test_remove_loggedin_relations():
pass
def test_get_keys_returns_empty_for_smb(db):
"""Protocols without key support (like SMB) must return an empty list.
This also verifies the BaseDB default doesn't raise AttributeError.
"""
keys = db.get_keys()
assert keys == []
assert isinstance(keys, list)
def test_get_keys_all_and_by_id_fallback(db):
"""Both call variants (all / by id) fall back to empty for unsupported protocols."""
assert db.get_keys() == []
assert db.get_keys(key_id=1) == []
def test_write_list_empty_entries(tmp_path):
"""write_list with empty entries truncates the file and writes nothing.
This is why the keys export must guard against empty keys — see the
check in DatabaseNavigator.do_export for the 'keys' command.
"""
from nxc.nxcdb import write_list
test_file = tmp_path / "test_export.txt"
# Pre-populate the file with data
test_file.write_text("existing content")
assert test_file.read_text() == "existing content"
# write_list with empty entries opens in "w" mode and writes nothing
write_list(str(test_file), [])
assert test_file.read_text() == ""
def test_do_export_keys_empty_guard(db, tmp_path):
"""R2 regression: do_export('keys all <file>') must NOT truncate the
target file when the protocol returns no keys (e.g., SMB). Removing
the empty-key guard in do_export would leave all other tests passing
while restoring false success and file truncation.
"""
import io
test_file = tmp_path / "keys_export.txt"
test_file.write_text("do not overwrite me")
assert test_file.read_text() == "do not overwrite me"
# Minimal mock — DatabaseNavigator only needs .config and .workspace
class MockMainMenu:
config = {}
workspace = "test"
from nxc.nxcdb import DatabaseNavigator
nav = DatabaseNavigator(MockMainMenu(), db, "smb")
buf = io.StringIO()
import sys
old_stdout = sys.stdout
sys.stdout = buf
try:
nav.do_export(f"keys all {test_file}")
finally:
sys.stdout = old_stdout
output = buf.getvalue()
# File must remain unchanged — the whole point of the guard
assert test_file.read_text() == "do not overwrite me"
# Failure message must appear
assert "[-] No keys found" in output
# Success message must NOT appear
assert "[+] Keys exported" not in output