Hidden persistence checklist¶
A structured inventory of mechanisms to check during a persistence hunt or post-incident review. Organised by control plane so that different teams can work in parallel.
Endpoint: Windows¶
Registry autorun locations¶
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce
HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon (Userinit, Shell values)
HKLM\SYSTEM\CurrentControlSet\Services (service entries)
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options
HKCU\Environment\UserInitMprLogonScript
Scheduled tasks¶
Enumerate all non-disabled tasks:
Get-ScheduledTask | Where-Object { $_.State -ne 'Disabled' }Look for tasks with encoded PowerShell or unusual binary paths in their actions
Check task paths: tasks outside
\Microsoft\*or with names that do not match the naming conventions in their path are suspiciousCompare against a known-good baseline from a clean build
WMI subscriptions¶
Get-WMIObject -Namespace root\subscription -Class __EventFilter
Get-WMIObject -Namespace root\subscription -Class CommandLineEventConsumer
Get-WMIObject -Namespace root\subscription -Class ActiveScriptEventConsumer
Get-WMIObject -Namespace root\subscription -Class __FilterToConsumerBinding
Legitimate WMI subscriptions are rare; any subscription not from a known endpoint agent should be investigated.
Services¶
Get-Service | Where-Object { $_.StartType -eq 'Automatic' }Check binary paths for non-standard locations (
C:\Windows\System32is expected;C:\Users\*,C:\ProgramData\*, temp directories are not)Compare display names and binary paths against manufacturer expectations
Startup folders¶
C:\Users\*\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\
C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\
DLL hijacking / COM hijacking¶
Check
HKCU\SOFTWARE\Classes\CLSID\for COM registrations that override system entriesCheck
%APPDATA%\Microsoft\Windows\Start Menufor unusual shortcutsReview DLL search path for known-vulnerable executables
Endpoint: Linux¶
Cron¶
crontab -l # current user
cat /etc/crontab
ls -la /etc/cron.d/
ls -la /etc/cron.daily/ /etc/cron.hourly/ /etc/cron.weekly/ /etc/cron.monthly/
Systemd¶
systemctl list-units --type=service --state=enabled
systemctl list-timers --all
ls -la /etc/systemd/system/ # system services
ls -la ~/.config/systemd/user/ # user services
Services with names mimicking systemd-* but installed after the system
baseline date should be investigated.
Shell initialisation files¶
~/.bashrc, ~/.bash_profile, ~/.profile
/etc/bash.bashrc, /etc/profile, /etc/profile.d/*
~/.zshrc, ~/.zprofile (if zsh is in use)
Check for curl | bash, wget | sh, or background invocations (nohup ... &).
SUID/SGID binaries added post-install¶
find / -perm -4000 -newer /etc/passwd -ls 2>/dev/null
SSH authorised keys¶
~/.ssh/authorized_keys # each user
/root/.ssh/authorized_keys
/etc/ssh/authorized_keys # non-standard but possible
Any key not matching known administrative keys should be removed and investigated.
Identity: Azure AD¶
App registrations created recently or with unusual display names
Service principals with Contributor, Owner, or Global Administrator assignments
Service principals added to privileged groups (Global Admins, Exchange Admins)
Federated identity credentials and pass-through authentication configurations
Conditional access exclusions that exempt specific service principals from MFA
Long-lived client secrets (check
endDateTimevalues)
# list all app registrations and their credentials
Get-AzADApplication | ForEach-Object {
$app = $_
Get-AzADAppCredential -ObjectId $app.Id |
Select-Object @{N='App';E={$app.DisplayName}}, EndDateTime, KeyId
}
Identity: AWS¶
IAM users created after the account baseline:
aws iam list-usersAccess keys for service accounts not recently rotated:
aws iam list-access-keysRoles with trust policies allowing assumption from external accounts
Inline policies on existing roles (harder to spot than attached managed policies)
Users added to high-privilege groups unexpectedly
CloudTrail is enabled and has not been disabled or had its logging bucket modified
# check for cross-account role trust policies
aws iam list-roles --query 'Roles[*].{Name:RoleName,Trust:AssumeRolePolicyDocument}' |
python3 -c "import json,sys; [print(r['Name'], r['Trust']) for r in json.load(sys.stdin)
if 'sts:AssumeRole' in str(r['Trust']) and 'arn:aws:iam' in str(r['Trust'])]"
Application layer¶
Web-accessible directories¶
File integrity monitoring alerts on web root and upload directories
PHP/ASPX files in upload directories (these should never be executable)
Files with recent modification timestamps that do not correspond to a deployment event
Unusually small files (minimal web shells can be under 100 bytes)
Database¶
-- SQL Server: check for unusual logon triggers
SELECT name, type_desc FROM sys.server_triggers WHERE type_desc = 'SERVER'
-- SQL Server: check for stored procedures owned by unexpected principals
SELECT name, SUSER_SNAME(principal_id) AS owner FROM sys.procedures
WHERE SUSER_SNAME(principal_id) NOT IN ('dbo', 'sys')
-- PostgreSQL: check for SECURITY DEFINER functions
SELECT proname, prosecdef, proowner::regrole FROM pg_proc WHERE prosecdef = true
CI/CD pipelines¶
Review recent changes to pipeline definition files (
.github/workflows/,.gitlab-ci.yml,Jenkinsfile)Look for new steps with
curl | bash,wget | sh, or environment variable exfiltrationCheck secret access logs in GitHub/GitLab for unexpected accesses
Review third-party actions/orbs/plugins pinned to mutable tags (
:latest, branch names) rather than commit SHAs
Cloud configuration¶
Lambda functions or Azure Functions created recently or with unusual triggers
S3 bucket policies or Azure Storage account policies allowing public access or unexpected cross-account access
CloudWatch log subscriptions forwarding to unexpected destinations
SSM Parameter Store values modified outside of normal deployment processes
Secrets Manager secrets accessed by unexpected IAM entities (check CloudTrail)