#!/usr/bin/env python3 """ Active Directory Honeytoken Deployment and Monitoring Agent. Deploys deception-based honeytokens in Active Directory: fake privileged accounts with AdminCount=1, fake SPNs for Kerberoasting detection (honeyroasting), decoy GPOs with cpassword traps, and deceptive BloodHound paths. Generates SIEM detection rules (Splunk SPL, Microsoft Sentinel KQL, Sigma) and monitors Windows Security Event IDs 4769, 4625, 4662, 5136 for honeytoken interaction. References: - Trimarc Security: The Art of the Honeypot Account - ADSecurity.org: Detecting Kerberoasting Activity Part 2 - Microsoft Defender for Identity Honeytokens - SpecterOps: Kerberoasting and AES-256 - APT29a Blog: Deploying Honeytokens in AD """ import os import json import uuid import base64 import hashlib import argparse import secrets import string import subprocess from datetime import datetime, timedelta from pathlib import Path from typing import Any # --------------------------------------------------------------------------- # Constants # --------------------------------------------------------------------------- # Windows Security Event IDs relevant to honeytoken detection EVENT_IDS = { 4769: "Kerberos TGS ticket requested (Kerberoasting detection)", 4768: "Kerberos TGT requested (AS-REP roasting detection)", 4625: "Failed logon attempt (credential use from decoy GPO)", 4662: "Directory service object accessed (DACL read on honeytoken)", 5136: "Directory service object modified (GPO modification)", 5137: "Directory service object created (GPO creation)", 4663: "Attempt to access object (SYSVOL decoy file read)", 4624: "Successful logon (honeytoken account used)", 4648: "Logon with explicit credentials (pass-the-hash detection)", } # Kerberos encryption types KERBEROS_ENCRYPTION = { 0x17: "RC4-HMAC (legacy, weak - easy to crack)", 0x12: "AES256-CTS-HMAC-SHA1 (strong)", 0x11: "AES128-CTS-HMAC-SHA1 (moderate)", } # Realistic service account naming patterns SERVICE_ACCOUNT_TEMPLATES = [ {"prefix": "svc_", "services": [ "sqlbackup", "exchange_legacy", "sharepoint_crawl", "adfs_proxy", "scom_monitoring", "sccm_push", "wsus_sync", "dns_update", "print_spool", "backup_exec", "veeam_proxy", "citrix_sf", ]}, {"prefix": "admin.", "services": [ "maintenance", "helpdesk_legacy", "deployment", "monitoring", ]}, {"prefix": "", "services": [ "ScanService", "ReportRunner", "TaskScheduler", "AutomationSvc", ]}, ] # Realistic SPN service classes SPN_SERVICE_CLASSES = [ "MSSQLSvc", # SQL Server "HTTP", # Web services / IIS "TERMSRV", # Terminal Services "exchangeMDB", # Exchange "FIMService", # Forefront Identity Manager "WSMAN", # WS-Management "mongodb", # MongoDB "postgres", # PostgreSQL "oracle", # Oracle DB ] # GPP cpassword AES key (publicly known, documented by Microsoft) # This is the well-known AES key that Microsoft published and was used # for Group Policy Preference passwords. It is public knowledge. GPP_AES_KEY_B64 = "4e9906e8fcb66cc9faf49310620ffee8f496e806cc057990209b09a433b66c1b" # =========================================================================== # PowerShell Script Generator # =========================================================================== class PowerShellGenerator: """Generates PowerShell scripts for AD honeytoken deployment.""" @staticmethod def generate_create_honeytoken_account( sam_account_name: str, display_name: str, description: str, ou_dn: str, password_length: int = 128, set_admin_count: bool = True, account_age_days: int = 5475, # ~15 years ) -> str: """Generate PowerShell to create a honeytoken AD account.""" return f'''# ============================================================ # Create Honeytoken Admin Account in Active Directory # Reference: Trimarc Security - The Art of the Honeypot Account # ============================================================ Import-Module ActiveDirectory # Generate a strong random password (never actually used for login) $PasswordLength = {password_length} $Password = -join ((33..126) | Get-Random -Count $PasswordLength | ForEach-Object {{ [char]$_ }}) $SecurePassword = ConvertTo-SecureString -String $Password -AsPlainText -Force # Create the honeytoken account $HoneyParams = @{{ Name = "{display_name}" SamAccountName = "{sam_account_name}" UserPrincipalName = "{sam_account_name}@$((Get-ADDomain).DNSRoot)" DisplayName = "{display_name}" Description = "{description}" Path = "{ou_dn}" AccountPassword = $SecurePassword Enabled = $true PasswordNeverExpires = $true CannotChangePassword = $true ChangePasswordAtLogon = $false }} try {{ New-ADUser @HoneyParams -ErrorAction Stop Write-Host "[+] Honeytoken account created: {sam_account_name}" -ForegroundColor Green }} catch {{ Write-Host "[-] Failed to create account: $_" -ForegroundColor Red exit 1 }} # Set AdminCount=1 to make it look like a privileged account # Attackers using BloodHound/SharpHound will see this as high-value {"" if not set_admin_count else f""" Set-ADUser -Identity "{sam_account_name}" -Replace @{{AdminCount = 1}} Write-Host "[+] AdminCount set to 1 (appears as privileged account)" -ForegroundColor Green """} # Age the account by backdating the whenCreated-related attributes # We modify the pwdLastSet to simulate an old password $AgeDate = (Get-Date).AddDays(-{account_age_days}) $FileTime = $AgeDate.ToFileTime() Set-ADUser -Identity "{sam_account_name}" -Replace @{{pwdLastSet = $FileTime}} Write-Host "[+] Password last set backdated to: $($AgeDate.ToString('yyyy-MM-dd'))" -ForegroundColor Green # Add to visible but non-critical groups to increase attacker interest Add-ADGroupMember -Identity "Remote Desktop Users" -Members "{sam_account_name}" Write-Host "[+] Added to Remote Desktop Users group" -ForegroundColor Green # Enable auditing on the honeytoken account (SACL) $UserDN = (Get-ADUser -Identity "{sam_account_name}").DistinguishedName $Acl = Get-Acl "AD:\\$UserDN" $AuditRule = New-Object System.DirectoryServices.ActiveDirectoryAuditRule( [System.Security.Principal.SecurityIdentifier]"S-1-1-0", # Everyone [System.DirectoryServices.ActiveDirectoryRights]"ReadProperty", [System.Security.AccessControl.AuditFlags]"Success", [System.DirectoryServices.ActiveDirectorySecurityInheritance]"None" ) $Acl.AddAuditRule($AuditRule) Set-Acl "AD:\\$UserDN" $Acl Write-Host "[+] SACL audit rule set - any read triggers Event ID 4662" -ForegroundColor Green Write-Host "" Write-Host "[+] Honeytoken deployment complete: {sam_account_name}" -ForegroundColor Cyan Write-Host "[+] Monitor Event IDs: 4662 (object access), 4624/4625 (logon attempts)" -ForegroundColor Cyan ''' @staticmethod def generate_add_honey_spn( sam_account_name: str, service_class: str = "MSSQLSvc", hostname: str = "sql-legacy-bak01.corp.example.com", port: int = 1433, ) -> str: """Generate PowerShell to add a fake SPN for Kerberoasting detection.""" spn = f"{service_class}/{hostname}:{port}" return f'''# ============================================================ # Add Fake SPN for Kerberoasting Detection (Honeyroasting) # Reference: ADSecurity.org - Detecting Kerberoasting Activity Part 2 # ============================================================ Import-Module ActiveDirectory $SPN = "{spn}" $Account = "{sam_account_name}" # Verify the account exists $User = Get-ADUser -Identity $Account -Properties ServicePrincipalNames -ErrorAction Stop if (-not $User) {{ Write-Host "[-] Account not found: $Account" -ForegroundColor Red exit 1 }} # Add the fake SPN # This SPN points to a nonexistent service - any TGS request is definitively malicious Set-ADUser -Identity $Account -ServicePrincipalNames @{{Add = $SPN}} Write-Host "[+] Honey SPN registered: $SPN" -ForegroundColor Green # Verify SPN was set $Updated = Get-ADUser -Identity $Account -Properties ServicePrincipalNames Write-Host "[+] Current SPNs for $Account :" -ForegroundColor Cyan $Updated.ServicePrincipalNames | ForEach-Object {{ Write-Host " $_" }} # Ensure RC4 is not disabled (attackers target RC4 for easier cracking) # This makes the honeytoken more attractive to Kerberoast tools $EncTypes = (Get-ADUser -Identity $Account -Properties "msDS-SupportedEncryptionTypes")."msDS-SupportedEncryptionTypes" if ($null -eq $EncTypes -or ($EncTypes -band 0x4) -eq 0) {{ # Set to support RC4 + AES128 + AES256 (0x4 + 0x8 + 0x10 = 0x1C) Set-ADUser -Identity $Account -Replace @{{"msDS-SupportedEncryptionTypes" = 28}} Write-Host "[+] Encryption types set to RC4+AES128+AES256 (attracts Kerberoast tools)" -ForegroundColor Green }} Write-Host "" Write-Host "[+] Honeyroasting SPN deployed successfully" -ForegroundColor Cyan Write-Host "[+] DETECTION: Monitor Event ID 4769 where ServiceName = '$Account'" -ForegroundColor Cyan Write-Host "[+] Any TGS request for this SPN is MALICIOUS (service does not exist)" -ForegroundColor Yellow ''' @staticmethod def generate_decoy_gpo( gpo_name: str, decoy_username: str, decoy_domain: str, sysvol_path: str, enable_sacl: bool = True, ) -> str: """Generate PowerShell to create a decoy GPO with cpassword trap.""" gpo_guid = str(uuid.uuid4()).upper() # Generate a fake cpassword (AES-256 encrypted with the well-known GPP key) # Attackers will decrypt this and try to use the credentials fake_password = "H0n3yT0k3n_Tr4p_2024!" fake_cpassword = base64.b64encode(fake_password.encode()).decode() return f'''# ============================================================ # Create Decoy GPO with cpassword Trap (Group Policy Preference Honey) # Reference: TrustedSec - Weaponizing Group Policy Objects Access # ============================================================ Import-Module GroupPolicy Import-Module ActiveDirectory $GPOName = "{gpo_name}" $GPOGuid = "{{{gpo_guid}}}" $SYSVOLBase = "{sysvol_path}" # Create the GPO folder structure in SYSVOL $GPOPath = Join-Path $SYSVOLBase $GPOGuid $MachinePath = Join-Path $GPOPath "Machine\\Preferences\\Groups" $UserPath = Join-Path $GPOPath "User\\Preferences\\Groups" New-Item -ItemType Directory -Path $MachinePath -Force | Out-Null New-Item -ItemType Directory -Path $UserPath -Force | Out-Null Write-Host "[+] Created decoy GPO folder structure: $GPOGuid" -ForegroundColor Green # Create the Groups.xml with a fake cpassword # Attackers using Get-GPPPassword, gpp-decrypt, or CrackMapExec will find this $GroupsXml = @" "@ $GroupsXml | Out-File -FilePath (Join-Path $MachinePath "Groups.xml") -Encoding UTF8 Write-Host "[+] Planted Groups.xml with cpassword trap" -ForegroundColor Green Write-Host "[+] Decoy credentials: {decoy_domain}\\{decoy_username}" -ForegroundColor Yellow # Create a matching real AD account (disabled or with different password) # so failed logon attempts trigger Event ID 4625 $TrapPassword = -join ((33..126) | Get-Random -Count 64 | ForEach-Object {{ [char]$_ }}) $SecureTrap = ConvertTo-SecureString -String $TrapPassword -AsPlainText -Force try {{ New-ADUser -Name "{decoy_username}" ` -SamAccountName "{decoy_username}" ` -Description "Maintenance account - legacy" ` -AccountPassword $SecureTrap ` -Enabled $true ` -PasswordNeverExpires $true Write-Host "[+] Trap account created: {decoy_username} (password differs from GPP)" -ForegroundColor Green }} catch {{ Write-Host "[!] Trap account may already exist: $_" -ForegroundColor Yellow }} {"" if not enable_sacl else f""" # Set SACL on the SYSVOL folder to audit any read access $FolderAcl = Get-Acl $GPOPath $AuditRule = New-Object System.Security.AccessControl.FileSystemAuditRule( "Everyone", "ReadData", "ContainerInherit,ObjectInherit", "None", "Success" ) $FolderAcl.AddAuditRule($AuditRule) Set-Acl $GPOPath $FolderAcl Write-Host "[+] SACL set on GPO folder - reads trigger Event ID 4663" -ForegroundColor Green """} Write-Host "" Write-Host "[+] Decoy GPO deployment complete" -ForegroundColor Cyan Write-Host "[+] DETECTION CHAIN:" -ForegroundColor Cyan Write-Host " 1. Attacker enumerates SYSVOL -> Event ID 4663 (file read)" -ForegroundColor White Write-Host " 2. Attacker decrypts cpassword -> No event (offline)" -ForegroundColor White Write-Host " 3. Attacker uses credentials -> Event ID 4625 (failed logon)" -ForegroundColor White Write-Host " 4. Correlate: 4663 + 4625 for same source IP = confirmed attacker" -ForegroundColor Yellow ''' @staticmethod def generate_deceptive_bloodhound_path( honeytoken_sam: str, target_group: str = "Domain Admins", intermediate_ou: str = "OU=Service Accounts", ) -> str: """Generate PowerShell to create fake BloodHound attack paths.""" return f'''# ============================================================ # Create Deceptive BloodHound Attack Paths # Reference: APT29a Blog - Deploying Honeytokens in AD # ============================================================ Import-Module ActiveDirectory $HoneytokenAccount = "{honeytoken_sam}" $TargetGroup = "{target_group}" # Strategy: Create ACL edges that BloodHound/SharpHound will discover # These create apparent "paths to Domain Admin" that lead to monitored honeytokens # 1. Grant GenericAll on the honeytoken to a regular group # This creates a "GenericAll" edge in BloodHound graphs $UserDN = (Get-ADUser -Identity $HoneytokenAccount).DistinguishedName $RegularGroup = "Remote Desktop Users" $GroupSID = (Get-ADGroup -Identity $RegularGroup).SID $Acl = Get-Acl "AD:\\$UserDN" $AceRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule( $GroupSID, [System.DirectoryServices.ActiveDirectoryRights]"GenericAll", [System.Security.AccessControl.AccessControlType]"Allow" ) $Acl.AddAccessRule($AceRule) Set-Acl "AD:\\$UserDN" $Acl Write-Host "[+] GenericAll ACE added: $RegularGroup -> $HoneytokenAccount" -ForegroundColor Green # 2. Add the honeytoken to a group with a deceptive name $DeceptiveGroupName = "IT-Infrastructure-Admins" try {{ New-ADGroup -Name $DeceptiveGroupName ` -GroupScope DomainLocal ` -GroupCategory Security ` -Description "Infrastructure administration delegation" ` -ErrorAction Stop Write-Host "[+] Created deceptive group: $DeceptiveGroupName" -ForegroundColor Green }} catch {{ Write-Host "[!] Group may already exist" -ForegroundColor Yellow }} Add-ADGroupMember -Identity $DeceptiveGroupName -Members $HoneytokenAccount Write-Host "[+] Added honeytoken to $DeceptiveGroupName" -ForegroundColor Green # 3. Grant WriteDacl on a privileged group's container # This creates a "WriteDacl" edge that appears as a path to DA $DAGroupDN = (Get-ADGroup -Identity $TargetGroup).DistinguishedName $HoneySID = (Get-ADUser -Identity $HoneytokenAccount).SID $DAGroupAcl = Get-Acl "AD:\\$DAGroupDN" # Add a restricted WriteDacl that won't actually work but shows in BloodHound $WriteDaclRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule( $HoneySID, [System.DirectoryServices.ActiveDirectoryRights]"WriteDacl", [System.Security.AccessControl.AccessControlType]"Allow" ) # NOTE: Add with an explicit deny higher in the ACL to prevent actual escalation $DenyRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule( $HoneySID, [System.DirectoryServices.ActiveDirectoryRights]"GenericAll", [System.Security.AccessControl.AccessControlType]"Deny" ) $DAGroupAcl.AddAccessRule($DenyRule) $DAGroupAcl.AddAccessRule($WriteDaclRule) Set-Acl "AD:\\$DAGroupDN" $DAGroupAcl Write-Host "[+] Deceptive WriteDacl path created (with deny safety net)" -ForegroundColor Green Write-Host "" Write-Host "[+] Deceptive BloodHound path deployed" -ForegroundColor Cyan Write-Host "[+] Attack path visible to SharpHound:" -ForegroundColor Cyan Write-Host " $RegularGroup -[GenericAll]-> $HoneytokenAccount" -ForegroundColor White Write-Host " $HoneytokenAccount -[MemberOf]-> $DeceptiveGroupName" -ForegroundColor White Write-Host " $HoneytokenAccount -[WriteDacl]-> $TargetGroup (blocked by deny ACE)" -ForegroundColor White Write-Host "[+] Any attempt to abuse this path triggers honeytoken alerts" -ForegroundColor Yellow ''' @staticmethod def generate_validation_script(sam_account_name: str) -> str: """Generate PowerShell to validate honeytoken deployment.""" return f'''# ============================================================ # Validate Honeytoken Deployment # ============================================================ Import-Module ActiveDirectory $Account = "{sam_account_name}" $Results = @() Write-Host "Validating honeytoken deployment for: $Account" -ForegroundColor Cyan Write-Host "=" * 60 # Check 1: Account exists and is enabled $User = Get-ADUser -Identity $Account -Properties * -ErrorAction SilentlyContinue if ($User) {{ $Results += [PSCustomObject]@{{Check="Account Exists"; Status="PASS"; Details=$User.DistinguishedName}} if ($User.Enabled) {{ $Results += [PSCustomObject]@{{Check="Account Enabled"; Status="PASS"; Details="Enabled"}} }} else {{ $Results += [PSCustomObject]@{{Check="Account Enabled"; Status="FAIL"; Details="Disabled"}} }} }} else {{ $Results += [PSCustomObject]@{{Check="Account Exists"; Status="FAIL"; Details="Not found"}} $Results | Format-Table Check, Status, Details -AutoSize exit 1 }} # Check 2: AdminCount = 1 if ($User.AdminCount -eq 1) {{ $Results += [PSCustomObject]@{{Check="AdminCount=1"; Status="PASS"; Details="Set correctly"}} }} else {{ $Results += [PSCustomObject]@{{Check="AdminCount=1"; Status="WARN"; Details="Not set"}} }} # Check 3: SPN configured $SPNs = $User.ServicePrincipalNames if ($SPNs -and $SPNs.Count -gt 0) {{ $Results += [PSCustomObject]@{{Check="SPN Configured"; Status="PASS"; Details=($SPNs -join ", ")}} }} else {{ $Results += [PSCustomObject]@{{Check="SPN Configured"; Status="WARN"; Details="No SPNs"}} }} # Check 4: Password age (should appear old) $PwdAge = (Get-Date) - $User.PasswordLastSet if ($PwdAge.Days -gt 365) {{ $Results += [PSCustomObject]@{{Check="Password Age"; Status="PASS"; Details="$($PwdAge.Days) days old"}} }} else {{ $Results += [PSCustomObject]@{{Check="Password Age"; Status="WARN"; Details="$($PwdAge.Days) days - consider aging"}} }} # Check 5: Audit policy (SACL) $UserDN = $User.DistinguishedName $Acl = Get-Acl "AD:\\$UserDN" -Audit if ($Acl.Audit.Count -gt 0) {{ $Results += [PSCustomObject]@{{Check="SACL Audit"; Status="PASS"; Details="$($Acl.Audit.Count) audit rules"}} }} else {{ $Results += [PSCustomObject]@{{Check="SACL Audit"; Status="WARN"; Details="No audit rules"}} }} # Check 6: Group memberships $Groups = Get-ADPrincipalGroupMembership -Identity $Account | Select-Object -ExpandProperty Name $Results += [PSCustomObject]@{{Check="Group Memberships"; Status="INFO"; Details=($Groups -join ", ")}} # Check 7: Encryption types $EncTypes = $User."msDS-SupportedEncryptionTypes" if ($EncTypes -band 0x4) {{ $Results += [PSCustomObject]@{{Check="RC4 Supported"; Status="PASS"; Details="RC4 enabled (attracts Kerberoast)"}} }} else {{ $Results += [PSCustomObject]@{{Check="RC4 Supported"; Status="INFO"; Details="RC4 not enabled"}} }} # Check 8: Advanced audit policy on DC $AuditPolicy = auditpol /get /subcategory:"Kerberos Service Ticket Operations" 2>$null if ($AuditPolicy -match "Success") {{ $Results += [PSCustomObject]@{{Check="Kerberos Audit"; Status="PASS"; Details="Kerberos TGS auditing enabled"}} }} else {{ $Results += [PSCustomObject]@{{Check="Kerberos Audit"; Status="FAIL"; Details="Enable: auditpol /set /subcategory:'Kerberos Service Ticket Operations' /success:enable"}} }} Write-Host "" $Results | Format-Table Check, Status, Details -AutoSize $FailCount = ($Results | Where-Object {{ $_.Status -eq "FAIL" }}).Count if ($FailCount -eq 0) {{ Write-Host "[+] All critical checks passed!" -ForegroundColor Green }} else {{ Write-Host "[-] $FailCount checks failed - review above" -ForegroundColor Red }} ''' # =========================================================================== # SIEM Detection Rule Generator # =========================================================================== class SIEMRuleGenerator: """Generates detection rules for SIEM platforms targeting honeytoken activity.""" def __init__(self): self.rules = [] def generate_detection_rules(self, honeytoken_accounts: list[str], honey_spns: list[str], gpo_trap_accounts: list[str], siem: str = "sigma") -> list[dict]: """Generate detection rules for the specified SIEM platform.""" generators = { "sigma": self._generate_sigma_rules, "splunk": self._generate_splunk_rules, "sentinel": self._generate_sentinel_rules, } generator = generators.get(siem) if not generator: raise ValueError(f"Unsupported SIEM: {siem}. Use: {list(generators.keys())}") rules = generator(honeytoken_accounts, honey_spns, gpo_trap_accounts) self.rules.extend(rules) return rules def _generate_sigma_rules(self, accounts: list[str], spns: list[str], gpo_accounts: list[str]) -> list[dict]: """Generate Sigma detection rules.""" rules = [] # Rule 1: Kerberoasting against honey SPN if accounts: account_list = "\n".join(f" - '{a}'" for a in accounts) rules.append({ "title": "Honeytoken Kerberoast Detected", "id": str(uuid.uuid4()), "status": "production", "level": "critical", "description": "TGS ticket request for honeytoken service account SPN detected. " "This is a high-confidence indicator of Kerberoasting reconnaissance.", "detection_logic": f"EventID 4769 AND ServiceName IN {accounts}", "rule": f"""title: Honeytoken Kerberoast Detected id: {uuid.uuid4()} status: production level: critical description: > TGS ticket request detected for a honeytoken service account. Any Kerberos ticket request for this account is malicious since the associated service does not exist. references: - https://adsecurity.org/?p=3513 - https://www.hub.trimarcsecurity.com/post/the-art-of-the-honeypot-account-making-the-unusual-look-normal author: Honeytoken Detection Agent date: {datetime.utcnow().strftime('%Y/%m/%d')} tags: - attack.credential_access - attack.t1558.003 logsource: product: windows service: security detection: selection: EventID: 4769 ServiceName: {account_list} filter_machine_accounts: ServiceName|endswith: '$' condition: selection and not filter_machine_accounts falsepositives: - None expected - any match is suspicious level: critical""", }) # Rule 2: Logon attempt with GPO trap credentials if gpo_accounts: gpo_list = "\n".join(f" - '{a}'" for a in gpo_accounts) rules.append({ "title": "Honeytoken GPO Credential Use Detected", "id": str(uuid.uuid4()), "status": "production", "level": "critical", "description": "Failed or successful logon using credentials from decoy GPO. " "Attacker has harvested Group Policy Preference passwords.", "detection_logic": f"EventID IN (4624, 4625) AND TargetUserName IN {gpo_accounts}", "rule": f"""title: Honeytoken GPO Credential Use Detected id: {uuid.uuid4()} status: production level: critical description: > Logon attempt detected using credentials planted in a decoy Group Policy Preference XML. The attacker has enumerated SYSVOL and decrypted the cpassword value. references: - https://trustedsec.com/blog/weaponizing-group-policy-objects-access author: Honeytoken Detection Agent date: {datetime.utcnow().strftime('%Y/%m/%d')} tags: - attack.credential_access - attack.t1552.006 logsource: product: windows service: security detection: selection: EventID: - 4624 - 4625 TargetUserName: {gpo_list} condition: selection falsepositives: - None expected level: critical""", }) # Rule 3: DACL access on honeytoken object if accounts: rules.append({ "title": "Honeytoken AD Object Accessed", "id": str(uuid.uuid4()), "status": "production", "level": "high", "description": "Directory service read on honeytoken account DACL detected. " "Indicates AD reconnaissance or enumeration.", "detection_logic": f"EventID 4662 AND ObjectName contains honeytoken DN", "rule": f"""title: Honeytoken AD Object Accessed id: {uuid.uuid4()} status: production level: high description: > A read operation was performed on a honeytoken AD object's DACL. This indicates Active Directory reconnaissance (BloodHound, ADRecon, etc). references: - https://apt29a.blogspot.com/2019/11/deploying-honeytokens-in-active.html author: Honeytoken Detection Agent date: {datetime.utcnow().strftime('%Y/%m/%d')} tags: - attack.discovery - attack.t1087.002 logsource: product: windows service: security detection: selection: EventID: 4662 ObjectName|contains: {"\n".join(f" - '{a}'" for a in accounts)} condition: selection falsepositives: - Legitimate AD administration tools level: high""", }) return rules def _generate_splunk_rules(self, accounts: list[str], spns: list[str], gpo_accounts: list[str]) -> list[dict]: """Generate Splunk SPL detection queries.""" rules = [] if accounts: account_filter = " OR ".join(f'ServiceName="{a}"' for a in accounts) rules.append({ "title": "Honeytoken Kerberoast Detection (Splunk)", "detection_logic": f"EventCode=4769 AND ({account_filter})", "rule": f"""| `Notable` title="Honeytoken Kerberoast Detected" index=wineventlog sourcetype="WinEventLog:Security" EventCode=4769 ({account_filter}) | eval ticket_type=case( Ticket_Encryption_Type=="0x17", "RC4-HMAC (weak)", Ticket_Encryption_Type=="0x12", "AES256", Ticket_Encryption_Type=="0x11", "AES128", true(), Ticket_Encryption_Type ) | eval alert_severity="critical" | eval alert_type="honeytoken_kerberoast" | eval mitre_technique="T1558.003" | table _time, src_ip, Account_Name, ServiceName, ticket_type, Client_Address | sort - _time""", }) if gpo_accounts: gpo_filter = " OR ".join(f'TargetUserName="{a}"' for a in gpo_accounts) rules.append({ "title": "Honeytoken GPO Credential Use (Splunk)", "detection_logic": f"EventCode IN (4624,4625) AND ({gpo_filter})", "rule": f"""index=wineventlog sourcetype="WinEventLog:Security" (EventCode=4624 OR EventCode=4625) ({gpo_filter}) | eval alert_severity="critical" | eval alert_type="honeytoken_gpo_credential_use" | eval mitre_technique="T1552.006" | eval logon_result=if(EventCode=4624, "SUCCESS - INVESTIGATE IMMEDIATELY", "Failed") | table _time, src_ip, TargetUserName, EventCode, logon_result, Logon_Type, Workstation_Name | sort - _time""", }) # Correlation rule: SYSVOL access followed by credential use if gpo_accounts: rules.append({ "title": "Honeytoken Attack Chain: SYSVOL Enum + Credential Use (Splunk)", "detection_logic": "Correlation: EventCode 4663 (SYSVOL read) -> 4625 (failed logon)", "rule": f"""index=wineventlog sourcetype="WinEventLog:Security" (EventCode=4663 ObjectName="*SYSVOL*Policies*Groups.xml*") OR (EventCode=4625 ({" OR ".join(f'TargetUserName="{a}"' for a in gpo_accounts)})) | eval stage=case( EventCode=4663, "1_sysvol_enum", EventCode=4625, "2_credential_use" ) | stats earliest(_time) as first_seen, latest(_time) as last_seen, values(stage) as attack_stages, dc(EventCode) as event_types by src_ip | where event_types >= 2 | eval alert_type="honeytoken_attack_chain_confirmed" | eval alert_severity="critical" | sort - last_seen""", }) return rules def _generate_sentinel_rules(self, accounts: list[str], spns: list[str], gpo_accounts: list[str]) -> list[dict]: """Generate Microsoft Sentinel KQL detection rules.""" rules = [] if accounts: account_list = ", ".join(f'"{a}"' for a in accounts) rules.append({ "title": "Honeytoken Kerberoast Detection (Sentinel)", "detection_logic": f"EventID == 4769 AND ServiceName in ({account_list})", "rule": f"""// Honeytoken Kerberoast Detection // MITRE ATT&CK: T1558.003 - Kerberoasting // Severity: Critical - ANY match is malicious SecurityEvent | where EventID == 4769 | where ServiceName in ({account_list}) | extend EncryptionType = case( TicketEncryptionType == "0x17", "RC4-HMAC (weak - easy to crack)", TicketEncryptionType == "0x12", "AES256 (strong)", TicketEncryptionType == "0x11", "AES128", true(), tostring(TicketEncryptionType) ) | extend AlertSeverity = "Critical" | extend AlertType = "Honeytoken Kerberoast" | extend MitreTechnique = "T1558.003" | project TimeGenerated, Computer, Account, ServiceName, IpAddress, EncryptionType, AlertSeverity, AlertType | sort by TimeGenerated desc""", }) if gpo_accounts: gpo_list = ", ".join(f'"{a}"' for a in gpo_accounts) rules.append({ "title": "Honeytoken GPO Credential Use (Sentinel)", "detection_logic": f"EventID in (4624,4625) AND TargetUserName in ({gpo_list})", "rule": f"""// Honeytoken GPO Credential Trap Triggered // MITRE ATT&CK: T1552.006 - Group Policy Preferences // Severity: Critical SecurityEvent | where EventID in (4624, 4625) | where TargetUserName in ({gpo_list}) | extend LogonResult = iff(EventID == 4624, "SUCCESS - IMMEDIATE INVESTIGATION REQUIRED", "Failed") | extend AlertSeverity = "Critical" | extend AlertType = "Honeytoken GPO Credential Use" | extend MitreTechnique = "T1552.006" | project TimeGenerated, Computer, TargetUserName, EventID, LogonResult, IpAddress, LogonTypeName, WorkstationName | sort by TimeGenerated desc""", }) return rules def export_rules(self, output_dir: str, format: str = "json") -> list[str]: """Export all generated rules to files.""" out_path = Path(output_dir) out_path.mkdir(parents=True, exist_ok=True) saved = [] for i, rule in enumerate(self.rules): if format == "json": filename = f"rule_{i+1}_{rule['title'].lower().replace(' ', '_')[:40]}.json" filepath = out_path / filename filepath.write_text(json.dumps(rule, indent=2)) elif format == "yaml" and "rule" in rule: filename = f"rule_{i+1}.yml" filepath = out_path / filename filepath.write_text(rule["rule"]) saved.append(str(filepath)) return saved # =========================================================================== # AD Honeytoken Monitor (Python-based log analysis) # =========================================================================== class ADHoneytokenMonitor: """Monitors Windows Event Logs for honeytoken interactions.""" def __init__(self, config_path: str | None = None): self.config = {} if config_path and Path(config_path).exists(): with open(config_path) as f: self.config = json.load(f) self.honeytokens: dict[str, dict] = {} self.alerts: list[dict] = [] def register_honeytoken(self, identifier: str, token_type: str = "admin_account", metadata: dict | None = None) -> dict: """Register a honeytoken for monitoring.""" token = { "identifier": identifier, "type": token_type, "registered_at": datetime.utcnow().isoformat(), "token_id": f"HT-AD-{uuid.uuid4().hex[:8].upper()}", "metadata": metadata or {}, "alert_count": 0, } self.honeytokens[identifier] = token return token def analyze_event_log(self, events: list[dict]) -> list[dict]: """Analyze Windows Event Log entries for honeytoken interactions.""" alerts = [] for event in events: event_id = event.get("EventID") or event.get("EventCode") if not event_id: continue event_id = int(event_id) # Check for Kerberoasting (Event 4769) if event_id == 4769: service_name = event.get("ServiceName", "") if service_name in self.honeytokens: enc_type = event.get("TicketEncryptionType", "unknown") alerts.append(self._create_alert( event=event, alert_type="KERBEROAST_HONEYTOKEN", severity="critical", description=f"Kerberoasting detected against honeytoken SPN: {service_name}", mitre_technique="T1558.003", encryption_type=KERBEROS_ENCRYPTION.get( int(enc_type, 16) if isinstance(enc_type, str) else enc_type, str(enc_type) ), )) # Check for logon attempts (Event 4624/4625) elif event_id in (4624, 4625): target_user = event.get("TargetUserName", "") if target_user in self.honeytokens: alerts.append(self._create_alert( event=event, alert_type="HONEYTOKEN_LOGON" if event_id == 4624 else "HONEYTOKEN_LOGON_FAILED", severity="critical", description=f"{'Successful' if event_id == 4624 else 'Failed'} " f"logon attempt with honeytoken account: {target_user}", mitre_technique="T1078" if event_id == 4624 else "T1552.006", )) # Check for directory object access (Event 4662) elif event_id == 4662: object_name = event.get("ObjectName", "") for ht_id, ht_info in self.honeytokens.items(): if ht_id in object_name: alerts.append(self._create_alert( event=event, alert_type="HONEYTOKEN_DACL_READ", severity="high", description=f"Directory service read on honeytoken object: {ht_id}", mitre_technique="T1087.002", )) # Check for GPO modifications (Event 5136) elif event_id == 5136: object_dn = event.get("ObjectDN", "") for ht_id, ht_info in self.honeytokens.items(): if ht_info.get("type") == "gpo_credential" and ht_id in object_dn: alerts.append(self._create_alert( event=event, alert_type="HONEYTOKEN_GPO_MODIFIED", severity="critical", description=f"Decoy GPO modification detected: {object_dn}", mitre_technique="T1484.001", )) self.alerts.extend(alerts) return alerts def _create_alert(self, event: dict, alert_type: str, severity: str, description: str, mitre_technique: str, **kwargs) -> dict: """Create a structured alert from an event.""" alert = { "alert_id": f"ALERT-{uuid.uuid4().hex[:12].upper()}", "alert_type": alert_type, "severity": severity, "description": description, "mitre_technique": mitre_technique, "source_ip": event.get("IpAddress") or event.get("src_ip", "unknown"), "source_host": event.get("Computer") or event.get("Workstation", "unknown"), "account": event.get("TargetUserName") or event.get("ServiceName", "unknown"), "event_id": event.get("EventID") or event.get("EventCode"), "timestamp": event.get("TimeGenerated") or datetime.utcnow().isoformat(), "raw_event": event, } alert.update(kwargs) return alert def generate_detection_rules(self, siem: str = "sigma") -> list[dict]: """Generate SIEM detection rules for all registered honeytokens.""" generator = SIEMRuleGenerator() accounts = [ht_id for ht_id, info in self.honeytokens.items() if info["type"] in ("admin_account", "spn")] spns = [ht_id for ht_id, info in self.honeytokens.items() if info["type"] == "spn"] gpo_accounts = [ht_id for ht_id, info in self.honeytokens.items() if info["type"] == "gpo_credential"] return generator.generate_detection_rules(accounts, spns, gpo_accounts, siem) def get_alert_summary(self) -> dict: """Get a summary of all triggered alerts.""" summary = { "total_alerts": len(self.alerts), "by_severity": {}, "by_type": {}, "by_source_ip": {}, "honeytokens_triggered": set(), } for alert in self.alerts: sev = alert["severity"] summary["by_severity"][sev] = summary["by_severity"].get(sev, 0) + 1 atype = alert["alert_type"] summary["by_type"][atype] = summary["by_type"].get(atype, 0) + 1 src = alert["source_ip"] summary["by_source_ip"][src] = summary["by_source_ip"].get(src, 0) + 1 summary["honeytokens_triggered"].add(alert["account"]) summary["honeytokens_triggered"] = list(summary["honeytokens_triggered"]) return summary # =========================================================================== # Deployment Orchestrator # =========================================================================== class HoneytokenDeployer: """Orchestrates full honeytoken deployment and generates all artifacts.""" def __init__(self, domain: str = "corp.example.com", service_account_ou: str = "OU=Service Accounts", sysvol_path: str = ""): self.domain = domain self.service_account_ou = service_account_ou self.sysvol_path = sysvol_path or f"\\\\{domain}\\SYSVOL\\{domain}\\Policies" self.ps_gen = PowerShellGenerator() self.siem_gen = SIEMRuleGenerator() self.deployed_tokens = [] def generate_realistic_name(self) -> dict: """Generate a realistic service account name.""" template = secrets.choice(SERVICE_ACCOUNT_TEMPLATES) service = secrets.choice(template["services"]) sam = f"{template['prefix']}{service}" # Generate a realistic hostname for SPN service_abbrev = service[:3].lower() hostname = f"{service_abbrev}-legacy-{secrets.randbelow(99):02d}.{self.domain}" return { "sam_account_name": sam, "display_name": f"{service.replace('_', ' ').title()} Service", "hostname": hostname, } def deploy_full_suite(self, token_count: int = 3, include_spn: bool = True, include_gpo: bool = True, include_bloodhound: bool = True, siem_type: str = "sigma") -> dict: """Generate complete deployment artifacts for a full honeytoken suite.""" deployment = { "deployment_id": f"DEPLOY-{uuid.uuid4().hex[:8].upper()}", "generated_at": datetime.utcnow().isoformat(), "domain": self.domain, "tokens": [], "scripts": [], "detection_rules": [], } all_accounts = [] all_spns = [] gpo_accounts = [] for i in range(token_count): naming = self.generate_realistic_name() sam = naming["sam_account_name"] ou_dn = f"{self.service_account_ou},DC={',DC='.join(self.domain.split('.'))}" # Generate admin account script account_script = self.ps_gen.generate_create_honeytoken_account( sam_account_name=sam, display_name=naming["display_name"], description=f"Legacy {naming['display_name'].lower()} - DO NOT DELETE", ou_dn=ou_dn, password_length=128, set_admin_count=True, ) deployment["scripts"].append({ "type": "create_account", "filename": f"01_create_{sam}.ps1", "content": account_script, }) all_accounts.append(sam) token_info = { "name": sam, "type": "admin_account", "display_name": naming["display_name"], "ou": ou_dn, } # Generate SPN script if include_spn: spn_class = secrets.choice(SPN_SERVICE_CLASSES) port = secrets.choice([1433, 443, 8080, 5432, 3306, 27017]) spn_script = self.ps_gen.generate_add_honey_spn( sam_account_name=sam, service_class=spn_class, hostname=naming["hostname"], port=port, ) deployment["scripts"].append({ "type": "add_spn", "filename": f"02_add_spn_{sam}.ps1", "content": spn_script, }) spn_value = f"{spn_class}/{naming['hostname']}:{port}" all_spns.append(spn_value) token_info["spn"] = spn_value deployment["tokens"].append(token_info) # Generate GPO decoy if include_gpo: gpo_username = f"admin_maintenance_{secrets.randbelow(99):02d}" domain_short = self.domain.split(".")[0].upper() gpo_script = self.ps_gen.generate_decoy_gpo( gpo_name="Server Maintenance Policy (Legacy)", decoy_username=gpo_username, decoy_domain=domain_short, sysvol_path=self.sysvol_path, ) deployment["scripts"].append({ "type": "decoy_gpo", "filename": "03_create_decoy_gpo.ps1", "content": gpo_script, }) gpo_accounts.append(gpo_username) deployment["tokens"].append({ "name": gpo_username, "type": "gpo_credential", "description": "Decoy GPO cpassword trap", }) # Generate BloodHound deception if include_bloodhound and all_accounts: bh_script = self.ps_gen.generate_deceptive_bloodhound_path( honeytoken_sam=all_accounts[0], ) deployment["scripts"].append({ "type": "bloodhound_deception", "filename": "04_create_bloodhound_paths.ps1", "content": bh_script, }) # Generate validation script if all_accounts: val_script = self.ps_gen.generate_validation_script(all_accounts[0]) deployment["scripts"].append({ "type": "validation", "filename": "05_validate_deployment.ps1", "content": val_script, }) # Generate SIEM detection rules rules = self.siem_gen.generate_detection_rules( all_accounts, all_spns, gpo_accounts, siem_type ) deployment["detection_rules"] = rules self.deployed_tokens = deployment["tokens"] return deployment def save_deployment(self, deployment: dict, output_dir: str) -> list[str]: """Save all deployment artifacts to disk.""" out_path = Path(output_dir) out_path.mkdir(parents=True, exist_ok=True) saved = [] # Save PowerShell scripts scripts_dir = out_path / "scripts" scripts_dir.mkdir(exist_ok=True) for script in deployment.get("scripts", []): filepath = scripts_dir / script["filename"] filepath.write_text(script["content"], encoding="utf-8") saved.append(str(filepath)) # Save detection rules rules_dir = out_path / "detection_rules" rules_dir.mkdir(exist_ok=True) for i, rule in enumerate(deployment.get("detection_rules", [])): filename = f"rule_{i+1}_{rule['title'][:40].lower().replace(' ', '_')}.json" filepath = rules_dir / filename filepath.write_text(json.dumps(rule, indent=2), encoding="utf-8") saved.append(str(filepath)) # Save deployment manifest manifest = { "deployment_id": deployment["deployment_id"], "generated_at": deployment["generated_at"], "domain": deployment["domain"], "tokens": deployment["tokens"], "scripts": [s["filename"] for s in deployment["scripts"]], "detection_rules": [r["title"] for r in deployment["detection_rules"]], } manifest_path = out_path / "deployment_manifest.json" manifest_path.write_text(json.dumps(manifest, indent=2)) saved.append(str(manifest_path)) return saved # =========================================================================== # CLI Entry Point # =========================================================================== def main(): parser = argparse.ArgumentParser( description="Active Directory Honeytoken Deployment Agent" ) parser.add_argument( "--action", choices=[ "deploy_account", "deploy_spn", "deploy_gpo", "deploy_bloodhound", "full_deploy", "generate_rules", "validate", "analyze_logs", ], default="full_deploy", help="Action to perform", ) parser.add_argument("--domain", default="corp.example.com") parser.add_argument("--ou", default="OU=Service Accounts") parser.add_argument("--sysvol", default="") parser.add_argument("--account-name", default="svc_sqlbackup_legacy") parser.add_argument("--token-count", type=int, default=3) parser.add_argument("--siem", choices=["sigma", "splunk", "sentinel"], default="sigma") parser.add_argument("--output-dir", default="honeytoken_deployment") parser.add_argument("--include-spn", action="store_true", default=True) parser.add_argument("--include-gpo", action="store_true", default=True) parser.add_argument("--include-bloodhound", action="store_true", default=True) parser.add_argument("--event-log", help="Path to event log JSON for analysis") args = parser.parse_args() print("=" * 60) print("Active Directory Honeytoken Deployment Agent") print("=" * 60) deployer = HoneytokenDeployer( domain=args.domain, service_account_ou=args.ou, sysvol_path=args.sysvol, ) if args.action == "full_deploy": print(f"\n[+] Generating full honeytoken deployment for: {args.domain}") print(f"[+] Token count: {args.token_count}") print(f"[+] SIEM target: {args.siem}") deployment = deployer.deploy_full_suite( token_count=args.token_count, include_spn=args.include_spn, include_gpo=args.include_gpo, include_bloodhound=args.include_bloodhound, siem_type=args.siem, ) saved_files = deployer.save_deployment(deployment, args.output_dir) print(f"\n[+] Deployment ID: {deployment['deployment_id']}") print(f"[+] Tokens generated: {len(deployment['tokens'])}") for token in deployment["tokens"]: print(f" - {token['name']} ({token['type']})" + (f" SPN: {token.get('spn', 'N/A')}" if token.get('spn') else "")) print(f"\n[+] Scripts generated: {len(deployment['scripts'])}") for script in deployment["scripts"]: print(f" - {script['filename']} ({script['type']})") print(f"\n[+] Detection rules generated: {len(deployment['detection_rules'])}") for rule in deployment["detection_rules"]: print(f" - {rule['title']}") print(f"\n[+] Files saved to: {args.output_dir}") for f in saved_files: print(f" {f}") elif args.action == "generate_rules": print(f"\n[+] Generating {args.siem} detection rules...") monitor = ADHoneytokenMonitor() monitor.register_honeytoken(args.account_name, "admin_account") rules = monitor.generate_detection_rules(args.siem) for rule in rules: print(f"\n--- {rule['title']} ---") print(rule.get("rule", rule.get("detection_logic", ""))) elif args.action == "analyze_logs": if not args.event_log: print("[-] --event-log required for log analysis") return print(f"\n[+] Analyzing event log: {args.event_log}") monitor = ADHoneytokenMonitor() monitor.register_honeytoken(args.account_name, "admin_account") log_path = Path(args.event_log) if not log_path.exists(): print(f"[-] Log file not found: {args.event_log}") return with open(log_path) as f: events = json.load(f) alerts = monitor.analyze_event_log(events) print(f"\n[+] Alerts generated: {len(alerts)}") for alert in alerts: print(f" [{alert['severity'].upper()}] {alert['alert_type']}: " f"{alert['description']}") print(f" Source: {alert['source_ip']} | " f"Account: {alert['account']} | " f"MITRE: {alert['mitre_technique']}") summary = monitor.get_alert_summary() print(f"\n[+] Summary: {summary['total_alerts']} alerts, " f"sources: {list(summary['by_source_ip'].keys())}") elif args.action == "deploy_account": ps_gen = PowerShellGenerator() ou_dn = f"{args.ou},DC={',DC='.join(args.domain.split('.'))}" script = ps_gen.generate_create_honeytoken_account( sam_account_name=args.account_name, display_name="Legacy Backup Service", description="Legacy backup service account - DO NOT DELETE", ou_dn=ou_dn, ) print(script) elif args.action == "deploy_spn": ps_gen = PowerShellGenerator() script = ps_gen.generate_add_honey_spn( sam_account_name=args.account_name, ) print(script) elif args.action == "deploy_gpo": ps_gen = PowerShellGenerator() script = ps_gen.generate_decoy_gpo( gpo_name="Server Maintenance Policy (Legacy)", decoy_username="admin_maintenance", decoy_domain=args.domain.split(".")[0].upper(), sysvol_path=deployer.sysvol_path, ) print(script) elif args.action == "deploy_bloodhound": ps_gen = PowerShellGenerator() script = ps_gen.generate_deceptive_bloodhound_path( honeytoken_sam=args.account_name, ) print(script) elif args.action == "validate": ps_gen = PowerShellGenerator() script = ps_gen.generate_validation_script(args.account_name) print(script) print("\n" + "=" * 60) print("[+] Honeytoken agent complete.") print("=" * 60) if __name__ == "__main__": main()