Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ntlmrelayx.py SOCKS option TypeError with socks5 on relaying #1575

Open
yesiamdollar opened this issue Jun 21, 2023 · 7 comments · May be fixed by #1843
Open

ntlmrelayx.py SOCKS option TypeError with socks5 on relaying #1575

yesiamdollar opened this issue Jun 21, 2023 · 7 comments · May be fixed by #1843
Assignees
Labels
medium Medium priority item

Comments

@yesiamdollar
Copy link

Configuration

impacket version: Impacket v0.10.0
Python version: 3.11
Target OS: Kali Linux

Debug Output With Command String

ps : I've installed the latest release from https://github.com/fortra/impacket/releases/download/impacket_0_10_0/impacket-0.10.0.tar.gz
When using socks option from ntlmrelayx.py, there is an error when trying to use crackmapexec or any other tool with proxychains.

pc crackmapexec smb -u 'A.FOOR' -p '' -d DOMAIN 10.13.15.2

I got this error and I can't relay

----------------------------------------
Exception occurred during processing of request from ('127.0.0.1', 55444)
Traceback (most recent call last):
  File "/usr/lib/python3.11/socketserver.py", line 691, in process_request_thread
    self.finish_request(request, client_address)
  File "/usr/lib/python3.11/socketserver.py", line 361, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "/usr/lib/python3/dist-packages/impacket/examples/ntlmrelayx/servers/socksserver.py", line 286, in __init__
    socketserver.BaseRequestHandler.__init__(self, request, client_address, server)
  File "/usr/lib/python3.11/socketserver.py", line 755, in __init__
    self.handle()
  File "/usr/lib/python3/dist-packages/impacket/examples/ntlmrelayx/servers/socksserver.py", line 322, in handle
    hostLength = unpack('!B',request['PAYLOAD'][0])[0]
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: a bytes-like object is required, not 'int'
----------------------------------------
----------------------------------------
Exception occurred during processing of request from ('127.0.0.1', 55460)
Traceback (most recent call last):
  File "/usr/lib/python3.11/socketserver.py", line 691, in process_request_thread
    self.finish_request(request, client_address)
  File "/usr/lib/python3.11/socketserver.py", line 361, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "/usr/lib/python3/dist-packages/impacket/examples/ntlmrelayx/servers/socksserver.py", line 286, in __init__
    socketserver.BaseRequestHandler.__init__(self, request, client_address, server)
  File "/usr/lib/python3.11/socketserver.py", line 755, in __init__
    self.handle()
  File "/usr/lib/python3/dist-packages/impacket/examples/ntlmrelayx/servers/socksserver.py", line 322, in handle
    hostLength = unpack('!B',request['PAYLOAD'][0])[0]
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: a bytes-like object is required, not 'int'
----------------------------------------
@anadrianmanrique
Copy link
Contributor

anadrianmanrique commented Jul 1, 2023

I tried with the master branch version and I couldn't reproduce it. Can you provide more context about your configuration? Can you try the master branch version also ? thanks

@anadrianmanrique anadrianmanrique added the waiting for response Further information is needed from people who opened the issue or pull request label Jul 6, 2023
@gjhami
Copy link
Contributor

gjhami commented Mar 26, 2024

I have the same issue, here are the details

NTLMRelayx Host

  • Hostname: ATTACKER.test.local
  • IP: 192.168.86.59
  • OS: Kali 6.6.9-1kali1 (2024-01-08)
  • Python version: 3.11.7
  • Impacket version: v0.11.0 installed using pipx
  • Relay Command: ntlmrelayx.py -t ws01.test.local -socks -smb2support -debug

Authentication Sender (WS02.test.local)

  • OS: Windows 10 Pro 22H2
  • Build: 19045.3930
  • Coercion Method: Enter \\192.168.86.59\test into file explorer

Authentication Target (WS01.test.local)

  • OS: Windows 10 Pro 22H2
  • Build: 19045.4170
  1. Relay setup
    image
  2. Socks connection established
    image
  3. Validate proxychains config
    image
  4. Attempt smbclient connection using the socks proxy
    image
    image
  5. Attempt secretsdump using the socks proxy
    image
    image
  6. Validate a successful relay is possible by specifying the attack directly with ntlmrelayx (no socks)
    image

@yesiamdollar
Copy link
Author

yesiamdollar commented Mar 26, 2024

hello @gjhami , did you try socks4 on proxychains ?

@gjhami
Copy link
Contributor

gjhami commented Apr 4, 2024

@yesiamdollar sadly that didn't work either, but I think it is the expected behavior since ntlmrelayx states it will create a socks5 listener.

Setup NTLMRelayx Listener
image

Receive DA connection
image

Attempt to secretsdump using SOCKS proxy connection configured for SOCKS4
image

Output in NTLMRelayx Window
image

Note: I also tried specifying the target for secretsdump by IP and just hostname. I also tried specifying the full domain before the username instead of the NETBIOS name. I could never get NTLMRelayx to associate the incoming connection to the SOCKS proxy with the existing relay.

@gjhami
Copy link
Contributor

gjhami commented Apr 4, 2024

I tried downgrading to python 3.10, since I realized 3.11 isn't actually supported, via pyenv, but that did not resolve the error. The following changes did. I'm still not sure why others are not experiencing the same error or the cause of the underylying type mismatches.

  1. Selecting an element of the byte string in request['PAYLOAD'] unexpectedly resulted in an int instead of a buffer of 1 byte on line 326 of impacket/exampless/ntlmrelayx/servers/socksserver.py. Skipping the unpacking step since the data was already an int as follows allowed NTLMRelayx to successfully perform this part of parsing the intended target and it no longer threw an exception.
hostLength = request['PAYLOAD'][0]
  1. NTLMRelayx still wasn't working because hostnames of targets passed via proxychains were interpreted as bytestrings instead of strings and therefore didn't match hosts for any existing socks connections, since those were stored as strings. Decoding the hostname parsed from the socks proxy connection on line 327 of impacket/exampless/ntlmrelayx/servers/socksserver.py into a string as follows resolved this issue.
self.targetHost = request['PAYLOAD'][1:hostLength+1].decode()

Successful screenshots:

  • An exception is still generated because 'no such domain exists' that warrants further troubleshooting, but this occurs during the NTDS dump portion of secretsdump which doesn't apply since I was targeting a workstation. The following screenshots show success with python3.10, but the same changes also allowed me to successfully perform secretsdump using a SOCKS connection for authentication with python3.11.

image
image
image


Failure with python3.10 without the changes described above:
image
image
image

@gjhami
Copy link
Contributor

gjhami commented Jul 13, 2024

Update: I just had the same issue in a client environment. If anyone has any advice or is getting the socks proxy feature to work correctly and could share OS/python/impacket versions that would be much appreciated! Thanks in advance!

@anadrianmanrique anadrianmanrique added medium Medium priority item and removed waiting for response Further information is needed from people who opened the issue or pull request labels Jul 18, 2024
@gjhami
Copy link
Contributor

gjhami commented Oct 31, 2024

Update: I've attached screenshots and pcaps.zip for the loopback and ethernet interfaces that validate the issue persists in Impacktv0.12.0 using Python 3.12.0.

WORKAROUND: While the problem persists I did finally figure out how to make this work until the comprehensive solution described below is merged. If you use the hostname or FQDN of the target instead of the IP then it will result in the error posted here even though the attacker machine successfully resolves the FQDN to an IP as shown in eth0-capture-target-fqdn.pcap due to parsing issues with extracting the target from the command being proxied. However, if you just specify the IP of the target instead of the FQDN in both ntlmrelayx and the proxied command, then everything works as-is! This is still a bug and should be fixed, but at least there's a workaround for now and a solution below with a PR coming :)

Environment Details:

NTLMRelayx Host (ATTACKER.test.local)

  • IP: 192.168.86.59
  • OS: Kali 6.11.2-kali1 (2024-10-15)
  • Python version: 3.12.0
  • Impacket version: v0.12.0 installed using pyenv
  • Relay Commands:
    • FQDN Based: ntlmrelayx.py -t ws01.test.local -socks -smb2support -debug
    • IP Based: ntlmrelayx.py -t 192.168.86.58 -socks -smb2support -debug
  • Secretsdump Commands:

Authentication Sender (WS02.test.local)

  • IP: 192.168.86.60
  • OS: Windows 10 Pro 22H2
  • Build: 19045.5011
  • Coercion Method: Enter ls \\192.168.86.59\test123 into PowerShell

Authentication Target (WS01.test.local)

  • IP: 192.168.86.58
  • OS: Windows 10 Pro 22H2
  • Build: 19045.5011

Domain Controller / DNS Server (DC01.test.local)

  • IP: 192.168.86.57
  • OS: Windows Server 2022 Standard 21H2
  • Build: 20348.2340

FQDN Based Screenshots

Relay
image
image
image

Secretsdump
image

IP Based Screenshots

Relay

  • Notice there was a timeout error but this did not affect the success of secretsdump and I think it was just network related. It does not appear when debug mode is disabled.

image
image
image

Secretsdump
image
image


Root Cause Analysis & Proposed Solution

The bug is triggered only when targeting by hostname or FQDN because of this codeblock from Lines 321-332 of socksserver.py

        # Let's process the request to extract the target to connect.
        # SOCKS5
        if self.__socksVersion == 5:
            if request['ATYP'] == ATYP.IPv4.value:
                self.targetHost = socket.inet_ntoa(request['PAYLOAD'][:4])
                self.targetPort = unpack('>H',request['PAYLOAD'][4:])[0]
            elif request['ATYP'] == ATYP.DOMAINNAME.value:
                hostLength = unpack('!B',request['PAYLOAD'][0])[0]
                self.targetHost = request['PAYLOAD'][1:hostLength+1]
                self.targetPort = unpack('>H',request['PAYLOAD'][hostLength+1:])[0]
            else:
                LOG.error('No support for IPv6 yet!')

Setting up some print statements for debugging showed the following:

request: 050100030f777330312e746573742e6c6f63616c01bd
request["ATYP"]: 3
request["PAYLOAD"]:  b'\x0fws01.test.local\x01\xbd'
request["PAYLOAD"][0]: 15

A little testing showed accessing the value at an index of a bytes object yielded an int, rather than another bytes object. Thanks for the surprise type conversion Python :).
image

Removing the type conversion allowed execution to progress, but it subsequently interpreted the target passed by the proxied command as a bytes object instead of a string:
image

Decoding the bytes object for target name resolved this issue as well, so my original solution was correct and I was just thrown off by the erroneous error shown in debug mode. I will make a PR for this. Checking git blame, it appears this feature has worked this way since the addition of the socks proxy functionality, so I'm not sure why more people aren't running into this.

A review of the git blame and PyPi history showed the code was introduced when python2.7 was still in use, and I confirmed the code would have been valid in Python2.7. It looks like it just never got updated to work with python3.
image

Fixed Code

You could make the fix look nicer by just grabbing the 0th byte of the payload and expecting an int, but I chose to preserve the unpack behavior for the hostname length since byte order remains explicit this way.

        # Let's process the request to extract the target to connect.
        # SOCKS5
        if self.__socksVersion == 5:
            if request['ATYP'] == ATYP.IPv4.value:
                self.targetHost = socket.inet_ntoa(request['PAYLOAD'][:4])
                self.targetPort = unpack('>H',request['PAYLOAD'][4:])[0]
            elif request['ATYP'] == ATYP.DOMAINNAME.value:
                hostLength = unpack('!B',request['PAYLOAD'][:1])[0]
                self.targetHost = request['PAYLOAD'][1:hostLength+1].decode(encoding='utf-8')
                self.targetPort = unpack('>H',request['PAYLOAD'][hostLength+1:])[0]
            else:
                LOG.error('No support for IPv6 yet!')

gjhami added a commit to gjhami/impacket that referenced this issue Oct 31, 2024
Fixes fortra#1575 by parsing the target hostname correctly from commands run through ntlmrelayx's socks5 proxy.
This was referenced Oct 31, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
medium Medium priority item
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants