Sanitize NTLM hostname to prevent path traversal and injection#1243
Sanitize NTLM hostname to prevent path traversal and injection#1243TristanInSec wants to merge 2 commits into
Conversation
Strip non-alphanumeric characters (except hyphens and dots) from
server-provided NTLM hostname before use in file paths or content.
Prevents:
- Path traversal via ../ in hostname (file creation outside ~/.nxc/logs/)
- DoS via null byte (ValueError crash in open())
- DoS via { characters (KeyError crash in str.format())
- Newline injection in --generate-hosts-file output
- Affects: SMB, RDP, VNC, WinRM, MSSQL credential dump and screenshot paths
|
It looks like the PR template may not have been filled out. The following sections appear to be missing:
Please edit your PR description to include them. The template helps reviewers understand and test your changes. Thanks! |
|
Thanks for the bug report! We'll look into it soon. Regarding the DoS: there are likely plenty of ways a server could respond with broken hostnames or other non-compliant structures that would lead to a crash in impacket. Since it doesn't crash nxc entirely, but just the scan of the host this is imo pretty much the same as just denying an SMB connection (just very noisy). Also I am not sure if I would like to be so restrictive, since I have no idea what weird hostnames could be out there in the world. Wouldn't bet against the chance some server puts weird chars in their hostname line the dollar symbol or similar. Maybe we should just strip strings ".." or similar, that are pretty much always intentionally bad |
Thanks for the review @NeffIsBack On the DoS: the null byte crash actually kills the entire nxc process, not just the per-host worker. The ValueError: embedded null byte from open() at smb.py:2099 propagates through proto_flow() and terminates nxc with a Rich
On the allowlist being too restrictive: valid NTLM hostnames follow NetBIOS naming rules (max 15 chars, restricted character set). Anything outside [a-zA-Z0-9_.-] in a hostname is already non-compliant. That said, I'm happy to widen it to cover edge cases like machine accounts (DC01$): re.sub(r'[^a-zA-Z0-9_.$@ -]', '', self.hostname) A denylist approach (stripping only ../, , \x00, \n) is riskier because it becomes a game of enumerating bad inputs -- encoding tricks, Windows reserved names (CON, NUL), alternate data streams, etc. Happy to update the PR with the wider allowlist if that works for you. |
Although I don't have the time at the moment to test and disprove this thesis, I highly doubt that this is the case. The entire proto flow is wrapped in a try&except block, catching every error that occurs while interacting with the target. The only way I see that you could escape that try&except block is to trigger an exception in pythons exception handling. Feel free to prove me otherwise by scanning a range of hosts with the rogue target killing the entire process.
Since we have just eliminated the 15 chars limit by using the DNS hostname instead of the NetBIOS name in #1195, I am not sure if this restriction still applies. I wouldn't bet that there are SMB servers out there replying with a non-compliant DNS name containing weird characters. |
|
Hi @NeffIsBack, Fair point on the DoS -- it shouldn't crash with multiple targets since (logger.exception() at line 187 prints the full traceback, which is what the screenshot shows and was misleading) The path traversal is the main issue though, and that one doesn't raise any exception. A rogue NTLM hostname like The sanitization here strips traversal sequences, null bytes, newlines, and non-printables from the hostname, which is basically what you suggested. |
Yes, you can place log files such as the loot output outside of the intended folder and that bug should be fixed. According to microsoft there are actually very restrictive rules for hostnames, so it would probably be fine to filter them prior to displaying them. However, we should alert the user if there is a difference between the received and the filtered output to alert the user for potential false positives (e.g. custom implementations that do not comply to the standard). |
Log a warning showing the original and sanitized hostname so users are alerted to potential non-compliant implementations or rogue servers.
|
Good idea. Added a warning log when the hostname gets sanitized, showing both the original and filtered value: This way operators notice if a server is sending non-compliant hostnames without silently changing what they see. |

Description
Sanitize server-provided NTLM hostname before use in file paths or content. A rogue SMB/RDP/VNC server can set its NTLM
AV_NbComputerNameto malicious values (path traversal sequences, null bytes, newlines, format string characters), causing file creation outside~/.nxc/logs/or hosts file injection.Single-line fix: strip all characters except
[a-zA-Z0-9_\-.]fromself.hostnameimmediately afterenum_host_info()inconnection.py.Fixes GHSA-pv83-jh5v-7q4j and GHSA-qqmq-pwrw-2j2w.
Affected Sinks (all fixed by this single change)
--generate-hosts-file--generate-krb5-fileType of change
Setup guide for the review
Bug trigger (path traversal):
poc-rogue-smb.py(rogue SMB server with NTLM hostname../../../tmp/evil)nxc smb 127.0.0.1 --port 4470 -u guest -p '' --dpapi~/evil_127.0.0.1_<timestamp>instead of~/.nxc/logs/dpapi/Bug trigger (hosts file newline injection):
\n10.13.37.99 dc01.corp.localnxc smb 127.0.0.1 --port 4472 -u guest -p '' --generate-hosts-file /tmp/hosts/tmp/hostsTested on: Kali Linux, Python 3.13, NXC 1.5.0, Impacket 0.14.0.
Checklist:
poetry run ruff check ., use--fixto automatically fix what it can)tests/e2e_commands.txtfile if necessary (new modules or features are required to be added to the e2e tests)