npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
When to Use
- When securing access to SaaS applications (Microsoft 365, Google Workspace, Salesforce, Slack)
- When implementing conditional access policies requiring MFA and device compliance for SaaS
- When deploying CASB for shadow IT discovery and unsanctioned app blocking
- When enforcing session-level controls (DLP, download restrictions) for sensitive SaaS data
- When governing OAuth application permissions and detecting excessive consent grants
Do not use as a replacement for SaaS-native security controls (configure those first), for applications with no SAML/OIDC support, or when SaaS vendor does not support API integration for CASB/SSPM.
Prerequisites
- Identity provider with conditional access: Microsoft Entra ID P1/P2, Okta
- CASB solution: Microsoft Defender for Cloud Apps, Netskope, or Zscaler CASB
- SaaS applications configured with SSO via SAML 2.0 or OIDC
- MDM enrollment for device compliance signals (Intune, Jamf)
- DLP policies defined for sensitive data categories
Workflow
Step 1: Federate SaaS Authentication Through Identity Provider
Centralize authentication for all SaaS applications through a single IdP.
# Configure SAML SSO for Salesforce via Entra ID
Connect-MgGraph -Scopes "Application.ReadWrite.All"
# Create enterprise application for Salesforce
$app = New-MgServicePrincipal -AppId "SALESFORCE_APP_ID" -DisplayName "Salesforce"
# Configure SAML SSO settings
$samlSettings = @{
preferredSingleSignOnMode = "saml"
samlSingleSignOnSettings = @{
relayState = ""
}
}
Update-MgServicePrincipal -ServicePrincipalId $app.Id -BodyParameter $samlSettings
# Assign user groups to the application
New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $app.Id -BodyParameter @{
principalId = "SALES_GROUP_ID"
resourceId = $app.Id
appRoleId = "DEFAULT_ROLE_ID"
}Step 2: Create Conditional Access Policies for SaaS Applications
Enforce identity and device requirements before granting SaaS access.
# Block access from non-compliant devices to sensitive SaaS apps
$policy = @{
displayName = "ZT - Require Compliant Device for SaaS"
state = "enabled"
conditions = @{
applications = @{
includeApplications = @("SALESFORCE_APP_ID", "M365_APP_ID", "SLACK_APP_ID")
}
users = @{
includeUsers = @("All")
excludeGroups = @("BREAK_GLASS_GROUP")
}
clientAppTypes = @("browser", "mobileAppsAndDesktopClients")
}
grantControls = @{
operator = "AND"
builtInControls = @("mfa", "compliantDevice")
}
sessionControls = @{
cloudAppSecurity = @{
isEnabled = $true
cloudAppSecurityType = "mcasConfigured"
}
signInFrequency = @{
value = 8
type = "hours"
isEnabled = $true
}
}
}
New-MgIdentityConditionalAccessPolicy -BodyParameter $policy
# Block downloads on unmanaged devices
$downloadPolicy = @{
displayName = "ZT - Block Downloads on Unmanaged Devices"
state = "enabled"
conditions = @{
applications = @{ includeApplications = @("SHAREPOINT_APP_ID") }
users = @{ includeUsers = @("All") }
devices = @{
deviceFilter = @{
mode = "include"
rule = "device.isCompliant -ne True -or device.trustType -ne 'ServerAD'"
}
}
}
sessionControls = @{
cloudAppSecurity = @{
isEnabled = $true
cloudAppSecurityType = "mcasConfigured"
}
}
}
New-MgIdentityConditionalAccessPolicy -BodyParameter $downloadPolicyStep 3: Deploy CASB for Shadow IT Discovery and App Governance
Configure Microsoft Defender for Cloud Apps to discover and control SaaS usage.
# Query discovered cloud apps via Defender for Cloud Apps API
curl -X GET "https://api.cloudappsecurity.com/api/v1/discovery/" \
-H "Authorization: Token ${MDCA_API_TOKEN}" \
-H "Content-Type: application/json"
# Get list of unsanctioned apps
curl -X GET "https://api.cloudappsecurity.com/api/v1/discovery/discovered_apps/" \
-H "Authorization: Token ${MDCA_API_TOKEN}" \
-d '{
"filters": {
"appTag": {"eq": "unsanctioned"},
"traffic": {"gte": 1000}
},
"sortField": "traffic",
"sortDirection": "desc"
}'
# Create session policy for DLP enforcement
curl -X POST "https://api.cloudappsecurity.com/api/v1/policies/" \
-H "Authorization: Token ${MDCA_API_TOKEN}" \
-d '{
"name": "Block PII Upload to SaaS",
"policyType": "SESSION",
"severity": "HIGH",
"enabled": true,
"sessionPolicyType": "CONTROL_UPLOAD",
"filters": {
"fileType": {"eq": ["DOCUMENT", "SPREADSHEET"]},
"contentInspection": {
"dataType": ["CREDIT_CARD", "SSN", "PASSPORT"]
}
},
"actions": {
"block": true,
"notify": {
"emailRecipients": ["security-team@company.com"]
}
}
}'Step 4: Configure OAuth App Governance
Review and restrict OAuth application permissions to prevent excessive consent.
# Query OAuth apps with high-privilege permissions
$oauthApps = Invoke-MgGraphRequest -Method GET `
"https://graph.microsoft.com/v1.0/servicePrincipals?\$filter=tags/any(t:t eq 'WindowsAzureActiveDirectoryIntegratedApp')&\$select=displayName,appId,oauth2PermissionScopes"
# Review consent grants
$grants = Get-MgOauth2PermissionGrant -All
$highRisk = $grants | Where-Object {
$_.Scope -match "Mail.ReadWrite|Files.ReadWrite.All|Directory.ReadWrite.All"
}
Write-Host "High-risk OAuth grants: $($highRisk.Count)"
$highRisk | ForEach-Object {
$sp = Get-MgServicePrincipal -ServicePrincipalId $_.ClientId
Write-Host " App: $($sp.DisplayName) | Scope: $($_.Scope) | Type: $($_.ConsentType)"
}
# Configure app consent policy to require admin approval
$consentPolicy = @{
displayName = "Require Admin Approval for High-Risk Permissions"
conditions = @{
clientApplications = @{ includeAllClientApplications = $true }
permissions = @{
permissionClassification = "high"
permissions = @(
@{ permissionValue = "Mail.ReadWrite"; permissionType = "delegated" }
@{ permissionValue = "Files.ReadWrite.All"; permissionType = "delegated" }
)
}
}
}Step 5: Implement SaaS Security Posture Management (SSPM)
Audit and remediate SaaS security configuration drift.
# Query SaaS security posture via CASB API
curl -X GET "https://api.cloudappsecurity.com/api/v1/security_config/" \
-H "Authorization: Token ${MDCA_API_TOKEN}" \
-d '{"app": "Microsoft 365"}'
# Common SSPM checks:
# - MFA enforcement for all admin accounts
# - External sharing restrictions in SharePoint/OneDrive
# - Email forwarding rules to external domains blocked
# - Idle session timeout configured (< 8 hours)
# - Legacy authentication protocols disabled
# - Admin consent workflow enabled
# - Conditional access policies active
# - Audit logging enabled for all servicesKey Concepts
| Term | Definition |
|---|---|
| CASB | Cloud Access Security Broker - intermediary enforcing security policies between users and SaaS applications |
| SSPM | SaaS Security Posture Management - continuous monitoring of SaaS application security configurations |
| OAuth Governance | Review and control of third-party application permissions granted through OAuth consent flows |
| Session Controls | Real-time access restrictions (block downloads, DLP inspection, watermarking) applied during active SaaS sessions |
| Shadow IT | Unauthorized SaaS applications used by employees without IT approval or security review |
| Conditional Access | Policy engine evaluating identity, device, location, and risk signals before granting SaaS access |
Tools & Systems
- Microsoft Defender for Cloud Apps: CASB providing shadow IT discovery, session controls, DLP, and SSPM
- Microsoft Entra ID Conditional Access: Policy engine for identity-based access control to SaaS applications
- Netskope CASB: Cloud-native CASB with inline and API-based SaaS security controls
- Okta Identity Governance: OAuth app governance and access certification for SaaS applications
- SSPM Tools: AppOmni, Adaptive Shield, Valence Security for SaaS configuration monitoring
Common Scenarios
Scenario: Securing Microsoft 365 and Salesforce for 1,000-User Organization
Context: A professional services firm with 1,000 users uses Microsoft 365, Salesforce, Slack, and 20+ other SaaS apps. Several data breaches in the industry drive a zero trust initiative for all SaaS access.
Approach:
- Federate all SaaS authentication through Entra ID with SAML SSO
- Create conditional access policies requiring MFA + compliant device for all SaaS apps
- Deploy Defender for Cloud Apps for shadow IT discovery (identify 150+ unauthorized apps)
- Mark unauthorized apps as unsanctioned and block via SWG/proxy
- Configure session controls: block downloads on unmanaged devices, DLP for file uploads
- Review OAuth app permissions: revoke 45 high-risk consent grants, enable admin approval workflow
- Enable SSPM monitoring for Microsoft 365 and Salesforce configurations
- Set up weekly automated posture reports for security leadership
Pitfalls: Conditional access policies need break-glass exclusions. Some legacy SaaS apps may not support modern authentication. Session controls require proxy-based CASB which can impact performance. OAuth app revocation may break integrations; coordinate with app owners first.
Output Format
Zero Trust SaaS Security Report
==================================================
Organization: ProServices Corp
Report Date: 2026-02-23
SAAS INVENTORY:
Sanctioned Apps: 25
Unsanctioned (blocked): 127
Shadow IT Users: 342 (discovered in last 30 days)
CONDITIONAL ACCESS:
Policies active: 8
Sign-ins evaluated: 456,789
Blocked by policy: 2,345 (0.5%)
MFA enforced: 100% of sign-ins
DEVICE COMPLIANCE:
Compliant device required: All 25 sanctioned apps
Sign-ins from compliant: 448,123 (98.1%)
Sign-ins blocked (non-compliant): 8,666
CASB / DLP:
DLP violations detected: 89
Files blocked from upload: 34
Downloads blocked (unmanaged): 1,234
OAUTH GOVERNANCE:
Total OAuth apps: 312
High-risk permissions: 12 (reviewed)
Revoked consents: 45
Pending admin approval: 8
SSPM FINDINGS:
Critical misconfigurations: 3
High: 7
Medium: 15
Remediated this month: 18References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 1
api-reference.md1.7 KB
API Reference: Zero Trust for SaaS Applications
Microsoft Graph API v1.0
Authentication
POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
Body: grant_type=client_credentials&client_id=X&client_secret=Y&scope=https://graph.microsoft.com/.defaultConditional Access
| Method | Endpoint | Description |
|---|---|---|
| GET | /identity/conditionalAccess/policies |
List CA policies |
| GET | /identity/conditionalAccess/policies/{id} |
Get policy details |
Enterprise Applications
| Method | Endpoint | Description |
|---|---|---|
| GET | /servicePrincipals |
List service principals |
| GET | /oauth2PermissionGrants |
List OAuth consent grants |
| GET | /appRoleAssignments |
List app role assignments |
Identity Protection
| Method | Endpoint | Description |
|---|---|---|
| GET | /identityProtection/riskyUsers |
List at-risk users |
| GET | /identityProtection/riskDetections |
Risk detection events |
CA Policy Grant Controls
| Control | Description |
|---|---|
mfa |
Require multi-factor authentication |
compliantDevice |
Require Intune-compliant device |
domainJoinedDevice |
Require hybrid Azure AD join |
passwordChange |
Force password change |
Risky OAuth Scopes
| Scope | Risk |
|---|---|
Mail.ReadWrite |
Full mailbox access |
Files.ReadWrite.All |
All OneDrive/SharePoint files |
Directory.ReadWrite.All |
Full directory modification |
References
- Graph API: https://learn.microsoft.com/en-us/graph/api/overview
- Conditional Access: https://learn.microsoft.com/en-us/entra/identity/conditional-access/
Scripts 1
agent.py5.8 KB
#!/usr/bin/env python3
"""Agent for auditing zero trust controls on SaaS applications via Microsoft Graph API."""
import requests
import json
import argparse
from datetime import datetime, timezone
GRAPH_API = "https://graph.microsoft.com/v1.0"
def get_token(tenant_id, client_id, client_secret):
"""Acquire OAuth2 token for Microsoft Graph API."""
url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
data = {"grant_type": "client_credentials", "client_id": client_id,
"client_secret": client_secret, "scope": "https://graph.microsoft.com/.default"}
resp = requests.post(url, data=data, timeout=30)
resp.raise_for_status()
token = resp.json()["access_token"]
print("[*] Authenticated to Microsoft Graph API")
return {"Authorization": f"Bearer {token}"}
def list_conditional_access_policies(headers):
"""List Entra ID conditional access policies for SaaS apps."""
url = f"{GRAPH_API}/identity/conditionalAccess/policies"
resp = requests.get(url, headers=headers, timeout=30)
resp.raise_for_status()
policies = resp.json().get("value", [])
findings = []
print(f"\n[*] Conditional Access Policies: {len(policies)}")
for p in policies:
state = p.get("state", "disabled")
conditions = p.get("conditions", {})
grant_controls = p.get("grantControls", {})
mfa_required = "mfa" in str(grant_controls.get("builtInControls", [])).lower()
print(f" [{'+' if state == 'enabled' else '-'}] {p['displayName']} "
f"(state={state}, MFA={'Yes' if mfa_required else 'No'})")
if state == "enabled" and not mfa_required:
findings.append({"policy": p["displayName"], "issue": "No MFA required",
"severity": "HIGH"})
return policies, findings
def list_enterprise_apps(headers):
"""List enterprise applications (service principals) for shadow IT discovery."""
url = f"{GRAPH_API}/servicePrincipals"
params = {"$top": 100, "$select": "displayName,appId,accountEnabled,signInAudience"}
resp = requests.get(url, headers=headers, params=params, timeout=30)
resp.raise_for_status()
apps = resp.json().get("value", [])
print(f"\n[*] Enterprise Applications: {len(apps)}")
third_party = [a for a in apps if a.get("signInAudience") != "AzureADMyOrg"]
print(f" Third-party apps: {len(third_party)}")
for a in third_party[:10]:
print(f" {a['displayName']} (enabled={a.get('accountEnabled', '?')})")
return apps
def check_oauth_app_consents(headers):
"""Audit OAuth2 permission grants for overprivileged consents."""
url = f"{GRAPH_API}/oauth2PermissionGrants"
resp = requests.get(url, headers=headers, timeout=30)
resp.raise_for_status()
grants = resp.json().get("value", [])
findings = []
for g in grants:
scope = g.get("scope", "")
if any(perm in scope for perm in ["Mail.ReadWrite", "Files.ReadWrite.All",
"Directory.ReadWrite.All", "User.ReadWrite.All"]):
findings.append({
"clientId": g.get("clientId"), "scope": scope,
"consentType": g.get("consentType"), "severity": "HIGH",
})
print(f"\n[*] OAuth grants: {len(grants)} total, {len(findings)} overprivileged")
for f in findings[:5]:
print(f" [!] {f['clientId']}: {f['scope'][:80]}")
return findings
def check_sign_in_risk(headers, days=7):
"""Check risky sign-ins to SaaS applications."""
url = f"{GRAPH_API}/identityProtection/riskyUsers"
params = {"$filter": "riskState eq 'atRisk'", "$top": 50}
resp = requests.get(url, headers=headers, params=params, timeout=30)
if resp.status_code == 200:
users = resp.json().get("value", [])
print(f"\n[*] Risky users: {len(users)}")
for u in users[:10]:
print(f" [!] {u.get('userDisplayName', 'N/A')} - risk: {u.get('riskLevel')}")
return users
return []
def generate_report(policies, ca_findings, oauth_findings, risky_users, output_path):
"""Generate SaaS zero trust audit report."""
report = {
"audit_date": datetime.now(timezone.utc).isoformat(),
"summary": {
"conditional_access_policies": len(policies),
"ca_findings": len(ca_findings),
"overprivileged_oauth": len(oauth_findings),
"risky_users": len(risky_users),
},
"ca_findings": ca_findings,
"oauth_findings": oauth_findings[:20],
"risky_users": risky_users[:20],
}
with open(output_path, "w") as f:
json.dump(report, f, indent=2, default=str)
print(f"\n[*] Report saved to {output_path}")
def main():
parser = argparse.ArgumentParser(description="SaaS Zero Trust Audit Agent")
parser.add_argument("action", choices=["ca-policies", "apps", "oauth", "risk", "full-audit"])
parser.add_argument("--tenant-id", required=True)
parser.add_argument("--client-id", required=True)
parser.add_argument("--client-secret", required=True)
parser.add_argument("-o", "--output", default="saas_zt_audit.json")
args = parser.parse_args()
headers = get_token(args.tenant_id, args.client_id, args.client_secret)
if args.action == "ca-policies":
list_conditional_access_policies(headers)
elif args.action == "apps":
list_enterprise_apps(headers)
elif args.action == "oauth":
check_oauth_app_consents(headers)
elif args.action == "risk":
check_sign_in_risk(headers)
elif args.action == "full-audit":
policies, ca_f = list_conditional_access_policies(headers)
list_enterprise_apps(headers)
oauth_f = check_oauth_app_consents(headers)
risky = check_sign_in_risk(headers)
generate_report(policies, ca_f, oauth_f, risky, args.output)
if __name__ == "__main__":
main()