mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-14 15:04:56 +03:00
1322 lines
53 KiB
Python
1322 lines
53 KiB
Python
#!/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 = @"
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<Groups clsid="{{3125E937-EB16-4b4c-9934-544FC6D24D26}}">
|
|
<User clsid="{{DF5F1855-51E5-4d24-8B1A-D9BDE98BA1D1}}"
|
|
name="{decoy_username}"
|
|
image="2"
|
|
changed="2011-07-15 08:30:22"
|
|
uid="{{{str(uuid.uuid4()).upper()}}}">
|
|
<Properties action="U"
|
|
newName=""
|
|
fullName="Maintenance Administrator"
|
|
description="Legacy maintenance account"
|
|
cpassword="{fake_cpassword}"
|
|
changeLogon="0"
|
|
noChange="1"
|
|
neverExpires="1"
|
|
acctDisabled="0"
|
|
userName="{decoy_domain}\\{decoy_username}" />
|
|
</User>
|
|
</Groups>
|
|
"@
|
|
|
|
$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()
|