While the industry obsesses over phishing campaigns and password hygiene, attackers have quietly pivoted to a far more permissive attack surface: the machine identities your organization has already forgotten about. In 2024, compromised service accounts and orphaned API keys were the root cause of 68% of cloud breaches — not human error, not social engineering. The uncomfortable truth is that for every person on your payroll, your environment is silently hosting 40 to 50 non-human identities (NHIs) — service accounts, OAuth grants, AI agent tokens, CI/CD pipeline credentials — and most of them have no owner, no expiry, and no one watching them. 🛡️
What Are Non-Human Identities — and Why “Ghost” Is the Right Word
A non-human identity is any credential or token that authenticates a process, service, or automated workload rather than a human user. Think: the AWS IAM role your Lambda function assumes, the GitHub Actions bot token with repo-write permissions, the OAuth grant your SaaS integration silently renewed three years ago, or the service account a contractor created for a proof-of-concept that shipped to production and then… stayed.
The “ghost” metaphor is deliberate. These identities were once alive — created intentionally, used actively. Then the project ended, the employee left, the vendor contract expired. But the credential didn’t die. It lingers in your IAM console, your secrets manager, your CI/CD pipeline, carrying the same permissions it was born with. Nobody deprovisioned it because nobody owned it. Nobody owned it because nobody thought to assign an owner. This is the lifecycle gap that attackers exploit, and it is embarrassingly common.
In enterprise deployments I’ve worked with, it’s routine to find service accounts with domain-admin-equivalent permissions that were last used 18 months ago. They’re not anomalies — they’re the norm.
How Ghost Identities Become Attack Vectors
The attack chain for NHI exploitation is deceptively simple, which is precisely what makes it so dangerous. ⚠️
Step 1 — Discovery: An attacker gains a foothold — a misconfigured S3 bucket, a leaked .env file in a public repo, a compromised developer laptop. Inside, they find a long-lived API key or service account credential. Automated scanners on GitHub, Shodan, and Pastebin harvest these daily.
Step 2 — Validation: They test the credential silently. No MFA challenge. No anomaly alert. Why? Because service accounts don’t have MFA, and if the credential hasn’t been used in months, there’s no behavioral baseline to trigger on.
Step 3 — Lateral movement & persistence: Using the valid credential, they enumerate IAM permissions, escalate privileges, pivot to adjacent services, and establish persistence — all while appearing as a “legitimate” automated process. Your SIEM sees API calls. It has no context that those calls are malicious because the identity is technically authorized.
This maps directly to MITRE ATT&CK T1078.004 — Valid Accounts: Cloud Accounts. The adversary isn’t breaking in; they’re logging in. Detection is orders of magnitude harder when the authentication itself is valid.
Secondary techniques in play: T1552.001 (Credentials in Files), T1098 (Account Manipulation), and T1136 (Create Account) when attackers mint new service principals to maintain persistence after the original ghost identity is eventually rotated.
Who’s at Risk — Spoiler: Everyone with a Cloud Footprint
If your organization uses any of the following, you have NHI sprawl right now:
- Multi-cloud environments (AWS, Azure, GCP) with IAM roles and service principals created across teams without centralized governance
- CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins) where bot tokens and deploy keys accumulate across projects
- SaaS integrations with OAuth grants that persist long after the integration is deprecated
- AI agent frameworks (LangChain, AutoGen, custom GPT wrappers) that create their own API connections and tool-use tokens
- Microservices architectures where every service authenticates to every other service via long-lived secrets
- Offboarded employees who created personal service accounts that were never tied to their HR lifecycle
The risk is amplified in organizations that moved fast during the 2020–2022 cloud migration wave. Velocity was rewarded; governance was deferred. That technical debt is now sitting in your IAM console with full production permissions.
🔧 How to Detect and Audit Ghost Identities with Wazuh
Detection starts with visibility. You cannot govern what you cannot see, and most organizations have no automated inventory of their NHIs. Here’s a practical approach using Wazuh’s AWS CloudTrail integration and custom rules to surface dormant and anomalous machine identities.
First, use this bash script to enumerate stale IAM users (service accounts) that haven’t authenticated in 90+ days:
#!/bin/bash
# Ghost Identity Audit: Find IAM users with no activity in 90+ days
# Requires: AWS CLI configured with IAM read permissions
THRESHOLD_DAYS=90
TODAY=$(date -u +%Y-%m-%dT%H:%M:%SZ)
echo "[*] Auditing IAM users for ghost identity candidates..."
echo "------------------------------------------------------------"
aws iam generate-credential-report > /dev/null 2>&1
sleep 5 # Wait for report generation
aws iam get-credential-report --query 'Content' --output text \
| base64 --decode \
| awk -F',' 'NR>1 {
user=$1; pw_last=$5; key1_last=$10; key2_last=$15;
print user, pw_last, key1_last, key2_last
}' \
| while read user pw_last key1_last key2_last; do
# Flag users where ALL auth methods are stale or never used
stale=true
for ts in "$pw_last" "$key1_last" "$key2_last"; do
if [[ "$ts" != "N/A" && "$ts" != "no_information" ]]; then
age=$(( ( $(date -u +%s) - $(date -d "$ts" +%s) ) / 86400 ))
if (( age < THRESHOLD_DAYS )); then
stale=false
break
fi
fi
done
if $stale; then
echo "[GHOST CANDIDATE] User: $user | Last PW: $pw_last | Key1: $key1_last | Key2: $key2_last"
fi
done
echo "------------------------------------------------------------"
echo "[*] Audit complete. Review ghost candidates for deprovisioning."
Next, add a Wazuh custom rule to alert on CloudTrail events where a service account (identified by naming convention) is used outside business hours or from an anomalous source IP:
<!-- Wazuh rules: Detect ghost/service account anomalous activity via CloudTrail -->
<!-- Place in: /var/ossec/etc/rules/nhi_detection.xml -->
<group name="aws,cloudtrail,nhi,identity">
<!-- Rule 1: Service account used after 90-day dormancy (requires enrichment via CDB list) -->
<rule id="100400" level="12">
<if_sid>80202</if_sid>
<field name="aws.userIdentity.type">IAMUser</field>
<list field="aws.userIdentity.userName" lookup="match_key">etc/lists/dormant_service_accounts</list>
<description>NHI Alert: Dormant service account $(aws.userIdentity.userName) just authenticated — possible ghost identity abuse</description>
<mitre>
<id>T1078.004</id>
</mitre>
<group>nhi,ghost_identity,cloud_breach</group>
</rule>
<!-- Rule 2: Service account assuming escalated role (privilege escalation pattern) -->
<rule id="100401" level="14">
<if_sid>80202</if_sid>
<field name="aws.eventName">AssumeRole</field>
<field name="aws.userIdentity.type">IAMUser</field>
<field name="aws.userIdentity.userName">\.svc\.|svc-|service-|bot-|ci-|deploy-</field>
<field name="aws.requestParameters.roleArn">AdministratorAccess|PowerUserAccess</field>
<description>NHI Privilege Escalation: Service account $(aws.userIdentity.userName) assuming high-privilege role $(aws.requestParameters.roleArn)</description>
<mitre>
<id>T1078.004</id>
<id>T1098</id>
</mitre>
<group>nhi,privilege_escalation,high_severity</group>
</rule>
<!-- Rule 3: New service principal created outside change-window hours -->
<rule id="100402" level="10">
<if_sid>80202</if_sid>
<field name="aws.eventName">CreateUser|CreateServiceAccount|CreateAccessKey</field>
<time>18:00 - 08:00</time>
<description>NHI Persistence: New machine identity $(aws.requestParameters.userName) created outside change window — review immediately</description>
<mitre>
<id>T1136</id>
</mitre>
<group>nhi,persistence,after_hours</group>
</rule>
</group>
Pair these rules with a Wazuh CDB list (/var/ossec/etc/lists/dormant_service_accounts) populated by your periodic ghost identity audit script. Automate the list refresh weekly via cron, and you have a closed-loop detection pipeline that doesn't require manual correlation.
What to Do Right Now: 6 Actionable Steps
- 🔍 Run a full NHI inventory immediately. Pull your AWS IAM credential report, Azure Service Principal list, and GitHub org bot tokens. You will find credentials you didn't know existed. Start there — you can't remediate what you haven't mapped.
- ⏳ Enforce maximum credential TTLs. No API key should live longer than 90 days without an automated rotation event. Use AWS Secrets Manager, HashiCorp Vault, or Azure Key Vault to enforce this at the platform level, not just policy.
- 👤 Assign human owners to every machine identity. Every service account, OAuth grant, and API token must be tied to an owning team in your CMDB or identity platform. When that team is disbanded or the project ends, the credential gets deprovisioned automatically — not manually, not "eventually."
- 🚫 Apply least-privilege and just-in-time access. Service accounts should not hold standing permissions to production resources 24/7. Use IAM permission boundaries, Attribute-Based Access Control (ABAC), and JIT provisioning to reduce the blast radius when a ghost identity is inevitably compromised.
- 📊 Build behavioral baselines for your NHIs. If your Lambda function has called S3 and DynamoDB every day for six months and suddenly starts calling IAM, KMS, and EC2 — that's your alarm. Wazuh's CloudTrail integration, combined with the rules above, gives you the detection layer to catch this pattern.
- 🔁 Integrate NHI lifecycle into your offboarding checklist. Every employee departure should trigger an automated query: "Did this person create any service accounts, OAuth apps, or API keys?" If yes, rotate or revoke before the exit date, not after.
Original source: https://thehackernews.com/2026/04/webinar-find-and-eliminate-orphaned-non.html
Bir Cevap Yazın