TL; DR: I fixed-up net-creds and MITMf to solve the CHALLENGE NOT FOUND bug.
A while back on an internal assessment, I was having a hard time getting a high-privileged user account.
This was the third assessment SensePost has done for the client, and they have implemented several of our recommendations. In particular, Responder wasn’t providing me with any hashes even though I was connected to the same network segment as several users, including some administrators. The client has a strict policy of only using the latest operating systems, i.e. Windows 10, and had disabled NBNS and LLMNR.
Hoping to intercept some of the administrators’ credentials, I started up Man-In-The-Middle Framework (MITMf) early one day, and conducted an ARP spoof attack. This was extremely successfull, with several of the administrators’ hashes flying by. This was likely due to being there early – in my experience, it’s better to get to a client early, and run a ARP/NBNS spoofing attack such that it’s active when most employees start up their laptops. This way their cache is clear, and the poisoned responses are the first they receive and thus subsequently use.
After closer inspection however, I noted that most of the hashes were missing their challenge portion of the NTLMSSP challenge response. The challenge portion was simply replaced with the text “CHALLENGE NOT FOUND”, while the response was duly present.
This made little sense, as the response is sent as a reply to the challenge. Without a valid challenge, a system would simply not issue a response.
As far as I could establish, there would only be two possible reasons that this could happen:
- The ARP spoofing was only intercepting certain packets, or only intercepting the connection in one way, i.e. half duplex.
- The challenge was intercepted, but was not properly being parsed by MITMf to create a valid hash.
After some coffee and reasoning about TCP/IP, I spun up Wireshark, and captured the network traffic during the attack. While the PCAP clearly contained the challenge, MITMf reported that it was simply not found.
This led me down a rabbit hole into MITMf, such as to find the bug causing this issue. During my R&PD time, I started looking and the MITMf code base and soon realised that it uses a different project, net-creds to parse and extract hashes and passwords from the intercepted network traffic.
The net-creds code base can parse several types of packets for credentials and other sensitive data. In this particular case, the NETNTLMv2 hash was extracted from the Proxy-Authenticate HTTP header.
This header in itself is quite interesting – its part of the HTTP 407 response, which specifies that one needs to authenticate before one is allowed to use the HTTP proxy. One can specify which authentication type should be used as well, such as NTLM. This can be quite useful in combination with WPAD and Responder – instead of requesting the client to NTLM authenticate when trying to download the PAC file, one can allow anyone to download the file without authentication. Once they use the proxy however, they are forced to authenticate via the Proxy-Authenticate and Proxy-Authorization headers. This allows one to capture hashes in Windows 10, which has been configured not to automatically authenticate when downloading a WPAD proxy’s PAC file. The approach has been suggested in this blogpost by Fox-IT and is now incorporated in ntlmrelayx.py in the impacket project.
Looking closely at the net-creds code, I saw that the headers_to_dict function would take in a set of HTTP headers, and split them into key value pairs.
For instance, the following header line “Content-type: text/html” would be split into [‘Content-type’, ‘text/html’]. All key value pairs are added to a dictionary, which is then sent back for further processing.
While investigating the issue, I saw that the resulting dictionary did not have a Proxy-Authenticate key or a corresponding value which should hold the NTLMSSP challenge.
I decided to trace the input and output of the function, and see what happened when a HTTP response with a Proxy-Authenticate header was processed. I passed the following response headers through the headers_to_dict function:
All the key and value pairs in the resulting dictionary were mismatched, with some of the header values acting as keys, and vice versa. Specifically, the Proxy-Authenticate value which contains the NTLMSSP challenge was incorrectly assigned as a key.
Looking again at the headers_to_dict function I saw that the code splits the header lines on the : character, however the first line of either HTTP requests or responses do not contain a valid : character to split on, e.g.
GET http://www.google.com/ HTTP/1.1
HTTP/1.1 407 Proxy Authentication Required.
Some provision was made in the code for the case of HTTP requests, however none seem to have been made for HTTP responses. This caused several of the HTTP response header keys and values to be incorrectly assigned in the resulting dictionary.
I rewrote the function so that it would parse the headers correctly, by making provision for the first line of HTTP responses. The rewritten function produced a far more understandable dictionary:
However this didn’t solve the problem completely, as I was still getting “CHALLENGE NOT FOUND” errors. After looking for quite a while, I found that one of the function calls were passing their arguments in the incorrect order.
Fixing this resolved the issue, and allowed the challenges to be parsed correctly and be placed inside the NETNTLMv2 hash.
There was some added complexity however – MITMf incorporates their own version of net-creds, which is not synced with the upstream version. I included the fixes to this version as well, and finally created pull requests for both net-creds and MITMf. One can thus now capture the full NETNTLMv2 hashes via MITMf:
Should you want to use the patched versions of net-creds or MITMf, just clone it from their respective github repositories, as both pull requests have been accepted.
Ciao – Reino