数字身份与访问管理(Digital Identity and Access Management, DIAM)是现代企业安全架构的核心支柱。从云端 IAM 策略到 OAuth/OIDC 单点登录,从多因素认证到特权访问管理,身份系统构成了信任链的根基。一旦身份系统被攻破,攻击者即可获得合法凭据的掩护,大幅降低被检测的概率。
近年来,针对身份系统的攻击呈爆发式增长。2020 年 SolarWinds 事件中攻击者伪造 SAML 令牌实现持久化,2022 年 Lapsus$ 组织通过社会工程绕过 Okta MFA 入侵多家企业,Azure AD Golden Ticket 攻击利用 Kerberos 协议弱点实现无密码域控。这些攻击的共同特点是:攻击者将身份系统本身作为攻击目标,利用身份基础设施的信任传递机制扩大战果。
对于取证分析人员而言,身份安全事件的分析面临独特挑战:身份日志分散在多个系统中(IdP、RADIUS、云平台、应用层),需要跨域关联;令牌和断言是瞬态数据,取证窗口有限;合法凭据与攻击者的操作难以区分。本文系统性地梳理 DIAM 安全事件的取证分析方法论,提供可落地的检测规则和分析工具。
0x01 技术基础与 DIAM 架构概述 IAM 核心架构模型 现代身份与访问管理系统由多个层次组成,每一层都可能成为攻击者的入侵点。理解 DIAM 架构是进行身份安全取证的前提。
架构层次 核心组件 功能定位 代表产品/标准 身份存储层 LDAP、Active Directory、Directory-as-a-Service 存储用户身份属性和凭据 AD、Azure AD、Okta Universal Directory 认证层 认证服务器、MFA 服务、FIDO2 验证用户身份真实性 Keycloak、Azure AD MFA、Duo Security 授权层 策略引擎、RBAC/ABAC 管理器 决定已认证用户的访问权限 AWS IAM、Azure RBAC、OPA 联邦层 IdP、SP、SAML/OIDC 中继 跨域身份传递和信任建立 ADFS、PingFederate、Auth0 会话管理层 Token Service、Session Store 管理认证状态的生命周期 OAuth 2.0 Authorization Server、Redis Session 审计层 日志收集、SIEM、UEBA 记录和分析身份相关事件 Splunk、Microsoft Sentinel、Google Chronicle
身份生命周期与攻击面映射 用户的数字身份从创建到销毁经历完整生命周期,每个阶段都存在对应的安全风险。
生命周期阶段 安全事件类型 MITRE ATT&CK 典型攻击手法 供应(Provisioning) 身份伪造、权限过度分配 T1136.003 创建后门账户、配置过度宽松的 RBAC 策略 认证(Authentication) 凭据窃取、MFA 绕过 T1556、T1621 凭据填充、SIM Swap、Session Fixation 授权(Authorization) 权限提升、横向移动 T1078、T1098 云 IAM 策略篡改、角色假设滥用 会话管理(Session) 令牌劫持、会话固定 T1539、T1550.004 OAuth 授权码注入、Cookie 伪造 联邦信任(Federation) 断言伪造、信任链劫持 T1550.003 SAML 断言篡改、Kerberoasting 审计/注销(De-provisioning) 隐蔽持久化、幽灵账户 T1136.001 禁用而非删除账户、服务账户复活
攻击面分类体系 基于攻击者对身份系统的利用方式,可以将 DIAM 攻击面划分为以下类别:
攻击面类别 攻击目标 危害等级 取证难度 云 IAM 配置滥用 IAM 策略、角色、信任关系 🔴 高 中等 OAuth/OIDC 协议攻击 授权流程、令牌签发 🔴 高 较高 MFA 绕过与会话劫持 认证因子、会话令牌 🔴 高 较高 PAM 系统攻击 特权凭据、密码保险箱 🔴 高 中等 身份联邦攻击 SAML 断言、Kerberos 票据 🔴 高 高 凭据存储攻击 密码哈希、密钥库 🟡 中 中等 目录服务攻击 LDAP 绑定、组策略 🟡 中 中等
取证工具链 身份安全取证需要一套专门化的工具链,覆盖日志分析、令牌解析、策略审计和行为基线建模等多个环节。
工具名称 功能定位 适用场景 获取方式 CloudTrail + Athena AWS 身份事件查询与分析 云 IAM 操作审计 AWS Console / CLI Azure AD Audit Logs Azure AD 身份事件日志 云身份认证/授权审计 Azure Portal / Graph API ScoutSuite 多云 IAM 安全审计 云 IAM 配置基线检查 pip install scoutsuite Prowler AWS 安全最佳实践检查 AWS IAM 策略合规审计 CLI 二进制 BloodHound / SharpHound AD 攻击路径分析 Active Directory 攻击面映射 GitHub Release Impacket Windows 协议攻击工具集 Kerberos 攻击、NTLM 中继 pip install impacket jwt_tool JWT/OAuth 令牌测试 JWT 伪造、算法混淆攻击 pip install jwt_tool SAML Raider SAML 断言篡改 SAML 安全测试 Burp Suite 插件 Sigma 通用检测规则引擎 身份攻击日志检测规则 GitHub 仓库 Plaso/log2timeline 时间线分析 多源身份日志时间线整合 pip install plaso Elastic Security SIEM 与行为分析 身份异常行为检测 Elastic Stack CyberArk Conjur 特权凭据管理审计 PAM 系统安全检查 商业产品
0x02 云 IAM 配置滥用与权限提升取证 AWS IAM 配置审计 AWS IAM 是最广泛使用的云身份管理系统之一。攻击者通常通过枚举过度宽松的 IAM 策略来寻找提权路径。
AWS IAM 权限边界检查:
aws iam list-policies --scope Local --query 'Policies[].{Name:PolicyName,Arn:Arn}' --output table
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/EC2-Role \
--action-names iam:CreateUser iam:AttachUserPolicy iam:CreateAccessKey \
--output json | jq '.EvaluationResults[] | {Action:EvalActionName, Effect:EvalPolicyEffect}' IAM 角色信任关系审计:
aws iam list-roles --query 'Roles[].{RoleName:RoleName,AssumeRolePolicyDocument:AssumeRolePolicyDocument}' --output json | \
python3 -c "
import json, sys
for role in json.load(sys.stdin):
doc = role['AssumeRolePolicyDocument']
for stmt in doc.get('Statement', []):
if stmt.get('Effect') == 'Allow':
principal = stmt.get('Principal', {})
if '*' in str(principal):
print(f'[CRITICAL] Wildcard trust: {role[\"RoleName\"]}')
elif 'AWS' in principal:
aws_prin = principal['AWS']
if isinstance(aws_prin, str):
aws_prin = [aws_prin]
for p in aws_prin:
if 'root' in p or '*' in p:
print(f'[HIGH] Overly broad trust: {role[\"RoleName\"]} -> {p}')
" IAM 提权路径检测:
攻击手法 MITRE ATT&CK 检测要点 取证日志源 委托角色(AssumeRole) T1078.004 检查异常的 AssumeRole 调用 CloudTrail AssumeRole 事件 策略版本修改 T1098.003 监控 CreatePolicyVersion 和 SetDefaultPolicyVersion CloudTrail IAM 事件 用户权限边界绕过 T1078.004 检查 PutUserPermissionsBoundary 调用 CloudTrail IAM 事件 服务账户令牌创建 T1078.004 监控 CreateAccessKey 对服务账户的操作 CloudTrail IAM 事件 资源策略修改 T1098.003 监控 S3 存储桶策略、SQS 策略变更 CloudTrail 配置事件
Azure AD 配置审计 Azure AD(现 Entra ID)的权限模型包含目录角色、应用程序权限和委托权限三个层次。
目录角色过度分配检测:
az rest --method GET \
--url "https://graph.microsoft.com/v1.0/directoryRoleAssignments" \
--query "value[?contains(roleId, 'e8611ab8-c189-46e8-94e1-60193e7e4f99') || contains(roleId, 'f28a1ee5-3d28-4e93-88f5-53b03e48f73e')].{Principal:principalId,Role:roleId}" \
--output table Azure AD 权限提升检测要点:
目录角色 权限等级 风险描述 检测日志 Global Administrator 🔴 最高 完全控制 Azure AD 所有设置 AuditLogs Privileged Role Administrator 🔴 高 可分配任何目录角色 AuditLogs Application Administrator 🟡 中 可创建和管理应用程序 AuditLogs User Administrator 🟡 中 可重置用户密码 AuditLogs Security Administrator 🟡 中 可管理安全策略和警报 AuditLogs
Azure AD 检测命令:
az rest --method GET \
--url "https://graph.microsoft.com/v1.0/auditLogs/directories" \
--query "value[?contains(category, 'RoleManagement')].{Time:activityDateTime,Operation:operationName,Actor:initiatedBy.user.userPrincipalName,Target:targetResources[0].displayName}" \
--output table GCP IAM 审计 GCP IAM 采用资源层次结构,权限从 Organization 向下继承。
GCP IAM 异常权限检测:
gcloud asset search-all-iam-policies \
--scope= organizations/123456789 \
--query= "policy:roles/owner OR policy:roles/editor" \
--format= "table(policy.bindings[].members, policy.bindings[].role, resource)" \
--flatten= "policy.bindings[]" Cloud IAM 权限边界对比:
云平台 最小权限实现 权限边界机制 跨账户风险 AWS IAM Policy + SCP Permission Boundary + SCP 跨账户角色信任 Azure RBAC 角色分配 条件访问策略 租户间应用隔离 GCP IAM Policy Binding Organization Policy 服务账户密钥泄露 GCP Resource Hierarchy VPC Service Controls 跨项目权限继承
0x03 OAuth/OIDC 协议攻击与令牌劫持取证 OAuth 2.0 授权码注入攻击 授权码注入(Authorization Code Injection, MITRE ATT&CK T1550.001)是 OAuth 协议最常见的攻击方式之一。攻击者通过操纵授权码重定向将受害者的认证会话绑定到攻击者控制的账户。
攻击流程:
攻击者向受害者发送恶意链接,其中包含攻击者的 state 参数 受害者点击链接,完成身份认证 授权服务器将授权码重定向到攻击者控制的回调地址 攻击者使用受害者的授权码获取令牌 检测授权码注入的 Python 脚本:
import json
import re
import sys
from collections import defaultdict
from datetime import datetime, timedelta
def analyze_oauth_redirect_logs (log_file):
redirects = defaultdict(list)
anomalies = []
with open(log_file, 'r' ) as f:
for line in f:
try :
entry = json. loads(line)
except json. JSONDecodeError:
continue
if entry. get('event_type' ) not in ('oauth_redirect' , 'authorization_code_grant' ):
continue
client_id = entry. get('client_id' , 'unknown' )
redirect_uri = entry. get('redirect_uri' , 'unknown' )
user_agent = entry. get('user_agent' , 'unknown' )
source_ip = entry. get('source_ip' , 'unknown' )
timestamp = entry. get('timestamp' , '' )
state_param = entry. get('state' , '' )
session_id = entry. get('session_id' , '' )
key = f " { client_id} : { session_id} "
redirects[key]. append({
'timestamp' : timestamp,
'redirect_uri' : redirect_uri,
'source_ip' : source_ip,
'user_agent' : user_agent,
'state' : state_param
})
for session_key, events in redirects. items():
if len(events) < 2 :
continue
uris = set(e['redirect_uri' ] for e in events)
ips = set(e['source_ip' ] for e in events)
agents = set(e['user_agent' ] for e in events)
states = set(e['state' ] for e in events)
if len(uris) > 1 :
anomalies. append({
'type' : 'REDIRECT_URI_VARIANCE' ,
'severity' : 'HIGH' ,
'session' : session_key,
'detail' : f 'Multiple redirect URIs: { uris} ' ,
'events' : events
})
if len(states) > 1 and len(ips) > 1 :
anomalies. append({
'type' : 'STATE_PARAM_REUSE' ,
'severity' : 'CRITICAL' ,
'session' : session_key,
'detail' : f 'State reuse across IPs: { ips} ' ,
'events' : events
})
if len(agents) > 1 :
anomalies. append({
'type' : 'USER_AGENT_VARIANCE' ,
'severity' : 'MEDIUM' ,
'session' : session_key,
'detail' : f 'UA change within session: { agents} ' ,
'events' : events
})
timestamps = []
for e in events:
try :
ts = datetime. fromisoformat(e['timestamp' ]. replace('Z' , '+00:00' ))
timestamps. append(ts)
except (ValueError , AttributeError ):
pass
if len(timestamps) >= 2 :
timestamps. sort()
time_span = (timestamps[- 1 ] - timestamps[0 ]). total_seconds()
if time_span < 10 and len(ips) > 1 :
anomalies. append({
'type' : 'RAPID_IP_CHANGE' ,
'severity' : 'CRITICAL' ,
'session' : session_key,
'detail' : f 'IP changed within { time_span: .1f } s: { ips} ' ,
'events' : events
})
return anomalies
def print_report (anomalies):
severity_order = {'CRITICAL' : 0 , 'HIGH' : 1 , 'MEDIUM' : 2 , 'LOW' : 3 }
anomalies. sort(key= lambda x: severity_order. get(x['severity' ], 99 ))
print(f 'OAuth Anomaly Report - { datetime. now(). isoformat()} ' )
print(f 'Total anomalies found: { len(anomalies)} ' )
print('=' * 70 )
for i, anomaly in enumerate(anomalies, 1 ):
print(f ' \n [ { anomaly["severity" ]} ] # { i} - { anomaly["type" ]} ' )
print(f 'Session: { anomaly["session" ]} ' )
print(f 'Detail: { anomaly["detail" ]} ' )
print(f 'Related events: { len(anomaly["events" ])} ' )
print('-' * 50 )
if __name__ == '__main__' :
if len(sys. argv) < 2 :
print(f 'Usage: { sys. argv[0 ]} <oauth_log_file>' )
sys. exit(1 )
results = analyze_oauth_redirect_logs(sys. argv[1 ])
print_report(results) Token 窃取与滥用检测 OAuth 令牌是身份认证的核心凭据,一旦被窃取,攻击者可以完全冒充受害者。
令牌滥用检测 Sigma 规则:
title : OAuth Token Replay from Unusual Location
id : 5a2d3e4f-6b7c-8d9e-0f1a-2b3c4d5e6f7a
status : experimental
description : Detects potential OAuth token replay attacks based on geographic impossibility
references :
- https://attack.mitre.org/techniques/T1550/001/
author : BlueTeam Forensics
date : 2026 /07/18
tags :
- attack.credential_access
- attack.t1550.001
- attack.t1078
logsource :
category : authentication
service : oauth
detection :
selection_event :
event_id :
- '53000'
- '53001'
- '53002'
selection_token :
token_type :
- 'access_token'
- 'refresh_token'
condition : selection_event and selection_token
timeframe : 1h
fields :
- source_ip
- user_agent
- token_issuer
- client_id
- geographic_location
filters :
geo_impossible :
field : geographic_distance_km
operator : '>'
value : 800
time_impossible :
field : time_between_requests_sec
operator : '<'
value : 300
falsepositives :
- VPN reconnection
- CDN edge node routing
level : high Redirect URI 操纵与 PKCE 绕过 常见 Redirect URI 操纵手法对比:
操纵手法 描述 危害等级 检测方法 子域名接管 注册目标域名的已过期子域名 🔴 高 DNS 记录审计 + 证书透明度日志 路径遍历 利用 URL 解析差异注入回调路径 🔴 高 严格字符串匹配审计 开放重定向利用 通过应用层重定向绕过白名单 🟡 中 重定向链分析 Fragment 操纵 利用 fragment 不发送到服务器的特性 🟡 中 客户端日志审计 HTTP 参数污染 利用框架解析差异注入额外参数 🟡 中 请求参数规范化检查
PKCE 绕过检测:
jwt_tool() {
local token= " $1"
echo " $token" | cut -d'.' -f2 | base64 -d 2>/dev/null | python3 -c "
import json, sys
try:
payload = json.load(sys.stdin)
print(f'Header: {json.dumps(header, indent=2)}')
print(f'Payload: {json.dumps(payload, indent=2)}')
alg = payload.get('header', {}).get('alg', payload.get('alg', 'unknown'))
if alg in ('none', 'HS256'):
print('[WARNING] Weak algorithm detected')
if 'pkce' not in str(payload).lower():
print('[INFO] No PKCE claim in token')
except Exception as e:
print(f'Parse error: {e}')
"
} 0x04 MFA 绕过与会话劫持取证 SIM Swap 与 SS7 攻击 SIM Swap 攻击是绕过 SMS-based MFA 的最常见方式。攻击者通过社会工程或内部人员协助,将受害者的手机号转移到攻击者控制的 SIM 卡上。
SIM Swap 检测特征:
检测维度 正常基线 异常指标 置信度 SIM 变更频率 < 1次/年 短时间内多次变更 🔴 高 变更渠道 运营商门店 客服电话/在线 🟡 中 变更后登录 与 SIM 变更时间一致 变更后立即出现新设备登录 🔴 高 MFA 重试模式 偶尔失败 短时间内大量失败后成功 🔴 高
SS7 攻击检测日志分析:
grep -E "(sms_receive|sms_forward|ss7_route|location_update)" /var/log/sms_gateway.log | \
python3 -c "
import sys
from collections import Counter
numbers = []
for line in sys.stdin:
parts = line.strip().split('|')
if len(parts) >= 4:
action = parts[2]
number = parts[3]
numbers.append((action, number))
action_counts = Counter(a for a, n in numbers)
for action, count in action_counts.most_common(20):
print(f'{action}: {count}')
" MFA 疲劳攻击(MFA Fatigue) MFA 疲劳攻击(MITRE ATT&CK T1621)通过反复发送 MFA 推送通知直到用户误批准。Okta、Microsoft Authenticator 等平台都曾遭受此类攻击。
MFA 疲劳攻击检测 Sigma 规则:
title : MFA Fatigue Attack Detection
id : 7c8d9e0f-1a2b-3c4d-5e6f-7a8b9c0d1e2f
status : stable
description : Detects potential MFA fatigue attacks through repeated push notification approvals
references :
- https://attack.mitre.org/techniques/T1621/
author : BlueTeam Forensics
date : 2026 /07/18
tags :
- attack.initial_access
- attack.t1621
- attack.credential_access
logsource :
category : authentication
service : mfa
detection :
selection_push :
auth_method :
- 'push_notification'
- 'mfa_push'
- 'totp'
selection_failed :
auth_result :
- 'denied'
- 'timeout'
selection_success :
auth_result :
- 'approved'
- 'success'
condition : selection_push and (selection_failed R10 selection_success)
timeframe : 10m
count_threshold : 5
fields :
- user_id
- source_ip
- device_id
- user_agent
- notification_timestamp
- approval_timestamp
extra :
time_diff :
field : approval_timestamp
minus : notification_timestamp
threshold_seconds : 60
falsepositives :
- User legitimately declined and re-approved
- Network latency causing delayed notifications
level : critical Python MFA 异常行为检测脚本:
import json
import sys
from collections import defaultdict
from datetime import datetime, timedelta
class MFADetector :
def __init__ (self):
self. events = defaultdict(list)
self. alerts = []
def load_events (self, log_file):
with open(log_file, 'r' ) as f:
for line in f:
try :
entry = json. loads(line)
except json. JSONDecodeError:
continue
if entry. get('category' ) == 'mfa' :
user = entry. get('user_id' , 'unknown' )
self. events[user]. append({
'timestamp' : entry. get('timestamp' ),
'method' : entry. get('auth_method' ),
'result' : entry. get('auth_result' ),
'source_ip' : entry. get('source_ip' ),
'device_id' : entry. get('device_id' ),
'user_agent' : entry. get('user_agent' , '' )
})
def detect_fatigue_attack (self, window_minutes= 10 , threshold= 5 ):
for user, events in self. events. items():
events. sort(key= lambda x: x. get('timestamp' , '' ))
for i, event in enumerate(events):
if event. get('result' ) not in ('approved' , 'success' ):
continue
window_start = datetime. fromisoformat(
event['timestamp' ]. replace('Z' , '+00:00' )
) - timedelta(minutes= window_minutes)
recent_denials = []
for j in range(max(0 , i - 20 ), i):
prev = events[j]
try :
prev_ts = datetime. fromisoformat(
prev['timestamp' ]. replace('Z' , '+00:00' )
)
except (ValueError , KeyError ):
continue
if prev_ts >= window_start and prev. get('result' ) in ('denied' , 'timeout' ):
recent_denials. append(prev)
if len(recent_denials) >= threshold:
self. alerts. append({
'type' : 'MFA_FATIGUE' ,
'severity' : 'CRITICAL' ,
'user' : user,
'denial_count' : len(recent_denials),
'approval_event' : event,
'window_start' : window_start. isoformat(),
'mitre' : 'T1621'
})
def detect_impossible_travel (self, max_speed_kmh= 900 ):
for user, events in self. events. items():
for i in range(1 , len(events)):
curr = events[i]
prev = events[i - 1 ]
try :
curr_ts = datetime. fromisoformat(
curr['timestamp' ]. replace('Z' , '+00:00' )
)
prev_ts = datetime. fromisoformat(
prev['timestamp' ]. replace('Z' , '+00:00' )
)
except (ValueError , KeyError ):
continue
time_diff = (curr_ts - prev_ts). total_seconds()
if time_diff <= 0 or time_diff > 86400 :
continue
if curr. get('source_ip' ) != prev. get('source_ip' ):
self. alerts. append({
'type' : 'MFA_GEO_ANOMALY' ,
'severity' : 'HIGH' ,
'user' : user,
'prev_ip' : prev. get('source_ip' ),
'curr_ip' : curr. get('source_ip' ),
'time_diff_seconds' : time_diff,
'mitre' : 'T1078'
})
def detect_device_anomaly (self):
user_devices = defaultdict(lambda : defaultdict(int))
user_agents = defaultdict(lambda : defaultdict(int))
for user, events in self. events. items():
for event in events:
if event. get('result' ) in ('approved' , 'success' ):
device = event. get('device_id' , 'unknown' )
ua = event. get('user_agent' , 'unknown' )
user_devices[user][device] += 1
user_agents[user][ua] += 1
for user, devices in user_devices. items():
if len(devices) > 3 :
self. alerts. append({
'type' : 'MULTI_DEVICE_MFA' ,
'severity' : 'MEDIUM' ,
'user' : user,
'device_count' : len(devices),
'devices' : dict(devices),
'mitre' : 'T1078'
})
def generate_report (self):
severity_order = {'CRITICAL' : 0 , 'HIGH' : 1 , 'MEDIUM' : 2 , 'LOW' : 3 }
self. alerts. sort(key= lambda x: severity_order. get(x['severity' ], 99 ))
print(f 'MFA Security Analysis Report' )
print(f 'Generated: { datetime. now(). isoformat()} ' )
print(f 'Total users analyzed: { len(self. events)} ' )
print(f 'Total alerts: { len(self. alerts)} ' )
print('=' * 70 )
for i, alert in enumerate(self. alerts, 1 ):
print(f ' \n [ { alert["severity" ]} ] Alert # { i} : { alert["type" ]} ' )
print(f ' User: { alert["user" ]} ' )
print(f ' MITRE ATT&CK: { alert["mitre" ]} ' )
for k, v in alert. items():
if k not in ('type' , 'severity' , 'user' , 'mitre' ):
print(f ' { k} : { v} ' )
print('-' * 50 )
if __name__ == '__main__' :
if len(sys. argv) < 2 :
print(f 'Usage: { sys. argv[0 ]} <mfa_log_file>' )
sys. exit(1 )
detector = MFADetector()
detector. load_events(sys. argv[1 ])
detector. detect_fatigue_attack()
detector. detect_impossible_travel()
detector. detect_device_anomaly()
detector. generate_report() Session Fixation 与 Cookie 伪造 会话劫持攻击对比:
攻击手法 MITRE ATT&CK 攻击原理 检测方法 Session Fixation T1539 强制使用已知 Session ID Session ID 变更审计 Cookie 伪造 T1550.004 直接构造有效 Session Cookie Cookie 签名验证日志 会话令牌窃取 T1539 从内存/日志/网络中提取令牌 异常 Token 使用模式 会话重放 T1550.004 重放已截获的会话令牌 令牌一次性使用检查
0x05 特权访问管理(PAM)攻防取证 密码保险箱攻击 特权访问管理(Privileged Access Management, PAM)系统存储了企业最敏感的凭据。攻击者一旦攻破 PAM 系统,即可获取全域特权访问权限。
密码保险箱攻击向量:
攻击向量 MITRE ATT&CK 攻击描述 取证重点 API 令牌窃取 T1552.001 窃取 PAM 系统的 API 访问令牌 API 调用日志、令牌生命周期 管理员后门 T1556 在 PAM 系统中植入后门账户 管理员账户变更日志 凭据导出 T1552.001 批量导出所有存储的凭据 批量读取操作审计 复制粘贴窃取 T1552.001 通过剪贴板获取检索的凭据 终端 DLP 日志 JIT 提权滥用 T1078.004 滥用 Just-In-Time 提权机制 JIT 审批和使用日志
PAM 日志分析脚本:
#!/bin/bash
PAM_LOG= " ${ 1:- /var/log/pam-audit.log} "
REPORT_FILE= "/tmp/pam_audit_report_ $( date +%Y%m%d_%H%M%S) .txt"
echo "PAM Security Audit Report" > " $REPORT_FILE"
echo "Generated: $( date) " >> " $REPORT_FILE"
echo "Log Source: $PAM_LOG" >> " $REPORT_FILE"
echo "========================================" >> " $REPORT_FILE"
echo "" >> " $REPORT_FILE"
echo "[1] Failed Authentication Attempts" >> " $REPORT_FILE"
grep -c "authentication_failure\|auth_fail\|login_failed" " $PAM_LOG" >> " $REPORT_FILE"
echo "" >> " $REPORT_FILE"
grep "authentication_failure\|auth_fail\|login_failed" " $PAM_LOG" | \
awk '{print $1, $2, $3, $5}' | sort | uniq -c | sort -rn | head -20 >> " $REPORT_FILE"
echo "" >> " $REPORT_FILE"
echo "[2] Privilege Escalation Events" >> " $REPORT_FILE"
grep -i "sudo\|su_exec\|runas\|elevate" " $PAM_LOG" | \
awk '{print $1, $2, $3, $5}' | sort | uniq -c | sort -rn | head -20 >> " $REPORT_FILE"
echo "" >> " $REPORT_FILE"
echo "[3] Credential Retrieval Operations" >> " $REPORT_FILE"
grep -i "retrieve\|checkout\|read_password\|get_credential" " $PAM_LOG" | \
awk '{print $1, $2, $3, $5}' | sort | uniq -c | sort -rn | head -20 >> " $REPORT_FILE"
echo "" >> " $REPORT_FILE"
echo "[4] After-Hours Access (18:00-08:00)" >> " $REPORT_FILE"
grep -E "2[0-3]:|0[0-7]:" " $PAM_LOG" | grep -i "login\|access\|auth" | \
awk '{print $1, $2, $3, $5}' | sort | uniq -c | sort -rn | head -20 >> " $REPORT_FILE"
echo "" >> " $REPORT_FILE"
echo "[5] Admin Account Usage" >> " $REPORT_FILE"
grep -iE "admin|root|superuser|privileged" " $PAM_LOG" | \
awk '{print $1, $2, $3, $5}' | sort | uniq -c | sort -rn | head -20 >> " $REPORT_FILE"
echo "" >> " $REPORT_FILE"
echo "[6] Concurrent Session Anomalies" >> " $REPORT_FILE"
grep -i "session_start\|session_open" " $PAM_LOG" | \
awk '{print $1, $2, $3, $5}' | sort | uniq -c | sort -rn | \
awk '$1 > 3 {print "[ALERT] Multiple concurrent sessions:", $0}' >> " $REPORT_FILE"
echo "" >> " $REPORT_FILE"
echo "[7] Bulk Credential Access" >> " $REPORT_FILE"
grep -i "retrieve\|checkout\|read_password" " $PAM_LOG" | \
awk '{print $5}' | sort | uniq -c | sort -rn | \
awk '$1 > 10 {print "[ALERT] Excessive credential access by:", $2, "- Count:", $1}' >> " $REPORT_FILE"
echo "" >> " $REPORT_FILE"
echo "Report saved to: $REPORT_FILE"
cat " $REPORT_FILE" JIT 提权滥用分析 Just-In-Time(JIT)提权是 PAM 系统的最佳实践之一,通过临时授予特权来减少永久特权暴露。然而,攻击者可以滥用 JIT 机制实现隐蔽的特权提升。
JIT 滥用检测 Sigma 规则:
title : JIT Privilege Escalation Abuse Detection
id : 3f4a5b6c-7d8e-9f0a-1b2c-3d4e5f6a7b8c
status : experimental
description : Detects potential abuse of Just-In-Time privilege elevation mechanisms
references :
- https://attack.mitre.org/techniques/T1078/004/
author : BlueTeam Forensics
date : 2026 /07/18
tags :
- attack.privilege_escalation
- attack.t1078.004
logsource :
category : pam
service : jit
detection :
selection_jit :
event_type :
- 'jit_access_request'
- 'jit_access_granted'
- 'jit_elevation'
selection_anomaly :
request_outside_business_hours : true
no_approval : true
duration_exceeds_normal : true
condition : selection_jit and selection_anomaly
fields :
- user_id
- requested_role
- approval_status
- duration_minutes
- request_timestamp
- activation_timestamp
- source_ip
- justification
timeframe : 24h
aggregation :
field : user_id
function : count
operator : '>'
value : 3
falsepositives :
- Emergency on-call access
- Planned maintenance window
level : high 特权会话监控分析 特权会话是 PAM 取证的关键证据来源。通过分析特权会话的录制和审计日志,可以还原攻击者的操作轨迹。
监控维度 正常基线 异常检测规则 证据价值 会话时长 < 30 分钟 超长会话(> 2小时) 🟡 中 操作频率 10-50 次/会话 异常高频操作(> 200次) 🟡 中 文件访问范围 预期工作目录 跨目录/跨系统访问 🔴 高 数据传输 正常工作流量 大量数据下载/外传 🔴 高 时间窗口 工作时间 非工作时间操作 🟡 中 操作一致性 操作与申请理由匹配 操作与申请理由不匹配 🔴 高
0x06 身份联邦与 SSO 安全取证 SAML 断言伪造 SAML(Security Assertion Markup Language)断言伪造是联邦身份系统最严重的攻击类型。攻击者通过伪造或篡改 SAML 断言,可以绕过认证机制获得未授权访问。
SAML 攻击类型对比:
攻击类型 MITRE ATT&CK 攻击原理 防御措施 断言签名绕过 T1550.003 利用 XML 签名包装攻击(XML Signature Wrapping) 严格签名验证 断言属性篡改 T1550.003 修改断言中的用户属性和角色声明 断言加密 + 完整性检查 重放攻击 T1550.003 重放已截获的有效断言 断言有效期检查 + OneTimeUse 密钥窃取 T1552.004 窃取 IdP 签名私钥 HSM 密钥管理 元数据操纵 T1550.003 篡改 SP 或 IdP 元数据 元数据签名验证
SAML 断言分析 Python 脚本:
import base64
import json
import sys
import xml.etree.ElementTree as ET
from datetime import datetime
from urllib.parse import unquote
def decode_saml_response (encoded_response):
decoded = base64. b64decode(unquote(encoded_response))
return decoded. decode('utf-8' , errors= 'replace' )
def parse_saml_assertion (xml_content):
findings = []
try :
root = ET. fromstring(xml_content)
except ET. ParseError as e:
findings. append({
'type' : 'PARSE_ERROR' ,
'severity' : 'HIGH' ,
'detail' : f 'SAML XML parse failure: { str(e)} '
})
return findings
ns = {
'saml' : 'urn:oasis:names:tc:SAML:2.0:assertion' ,
'samlp' : 'urn:oasis:names:tc:SAML:2.0:protocol' ,
'ds' : 'http://www.w3.org/2000/09/xmldsig#'
}
assertions = root. findall('.//saml:Assertion' , ns)
for assertion in assertions:
issue_instant = assertion. get('IssueInstant' , '' )
if issue_instant:
try :
issue_dt = datetime. fromisoformat(
issue_instant. replace('Z' , '+00:00' )
)
now = datetime. now(issue_dt. tzinfo)
age = (now - issue_dt). total_seconds()
if age > 300 :
findings. append({
'type' : 'STALE_ASSERTION' ,
'severity' : 'MEDIUM' ,
'detail' : f 'Assertion age: { age: .0f } s (threshold: 300s)' ,
'issue_instant' : issue_instant
})
except ValueError :
pass
subject = assertion. find('.//saml:Subject' , ns)
if subject is not None :
name_id = subject. find('.//saml:NameID' , ns)
if name_id is not None :
name_value = name_id. text or ''
if 'admin' in name_value. lower() or 'root' in name_value. lower():
findings. append({
'type' : 'ADMIN_ASSERTION' ,
'severity' : 'INFO' ,
'detail' : f 'Admin subject: { name_value} ' ,
'subject' : name_value
})
conditions = assertion. find('.//saml:Conditions' , ns)
if conditions is not None :
not_before = conditions. get('NotBefore' , '' )
not_on_or_after = conditions. get('NotOnOrAfter' , '' )
if not_before and not_on_or_after:
try :
nb = datetime. fromisoformat(not_before. replace('Z' , '+00:00' ))
noa = datetime. fromisoformat(not_on_or_after. replace('Z' , '+00:00' ))
validity = (noa - nb). total_seconds()
if validity > 3600 :
findings. append({
'type' : 'OVERLY_LONG_VALIDITY' ,
'severity' : 'MEDIUM' ,
'detail' : f 'Assertion validity window: { validity: .0f } s (recommended: < 300s)'
})
except ValueError :
pass
authn_statement = assertion. find('.//saml:AuthnStatement' , ns)
if authn_statement is not None :
authn_context = authn_statement. find('.//saml:AuthnContext' , ns)
if authn_context is not None :
authn_class = authn_context. find('.//saml:AuthnContextClassRef' , ns)
if authn_class is not None and authn_class. text:
ctx = authn_class. text
if 'unspecified' in ctx or 'none' in ctx. lower():
findings. append({
'type' : 'WEAK_AUTHN_CONTEXT' ,
'severity' : 'HIGH' ,
'detail' : f 'Weak authentication context: { ctx} '
})
signature = assertion. find('.//ds:Signature' , ns)
if signature is None :
findings. append({
'type' : 'UNSIGNED_ASSERTION' ,
'severity' : 'CRITICAL' ,
'detail' : 'Assertion is not signed'
})
attributes = assertion. find('.//saml:AttributeStatement' , ns)
if attributes is not None :
for attr in attributes. findall('saml:Attribute' , ns):
attr_name = attr. get('Name' , '' )
for val in attr. findall('saml:AttributeValue' , ns):
value = val. text or ''
if 'role' in attr_name. lower() and 'admin' in value. lower():
findings. append({
'type' : 'ADMIN_ROLE_ATTRIBUTE' ,
'severity' : 'HIGH' ,
'detail' : f 'Admin role in attribute: { attr_name} = { value} '
})
return findings
def analyze_saml_request (request_data):
findings = []
if 'SAMLRequest' in request_data:
encoded = request_data. split('SAMLRequest=' )[1 ]. split('&' )[0 ]
try :
decoded = base64. b64decode(unquote(encoded))
root = ET. fromstring(decoded)
assertion_consumer_url = root. get('AssertionConsumerServiceURL' , '' )
if assertion_consumer_url:
findings. append({
'type' : 'ACS_URL_INFO' ,
'severity' : 'INFO' ,
'detail' : f 'ACS URL: { assertion_consumer_url} '
})
except Exception as e:
findings. append({
'type' : 'REQUEST_PARSE_ERROR' ,
'severity' : 'MEDIUM' ,
'detail' : f 'SAML Request parse issue: { str(e)} '
})
if 'SAMLResponse' in request_data:
encoded = request_data. split('SAMLResponse=' )[1 ]. split('&' )[0 ]
xml_content = decode_saml_response(encoded)
findings. extend(parse_saml_assertion(xml_content))
return findings
def print_saml_report (findings):
severity_order = {'CRITICAL' : 0 , 'HIGH' : 1 , 'MEDIUM' : 2 , 'LOW' : 3 , 'INFO' : 4 }
findings. sort(key= lambda x: severity_order. get(x['severity' ], 99 ))
print('SAML Security Analysis Report' )
print('=' * 60 )
for i, f in enumerate(findings, 1 ):
print(f '[ { f["severity" ]} ] # { i} - { f["type" ]} ' )
print(f ' Detail: { f["detail" ]} ' )
print()
if __name__ == '__main__' :
if len(sys. argv) < 2 :
print(f 'Usage: { sys. argv[0 ]} <saml_post_data_file>' )
sys. exit(1 )
with open(sys. argv[1 ], 'r' ) as f:
data = f. read(). strip()
if data. startswith('{' ):
request_data = json. loads(data)
else :
request_data = {}
for pair in data. split('&' ):
if '=' in pair:
key, val = pair. split('=' , 1 )
request_data[key] = unquote(val)
results = analyze_saml_request(request_data)
print_saml_report(results) Kerberos 降级与身份传递攻击 攻击手法 MITRE ATT&CK 攻击原理 检测方法 Kerberoasting T1558.003 离线破解服务账户 TGS 票据 异常 TGS 请求量监控 AS-REP Roasting T1558.004 攻击无预认证用户 AS-REQ 日志异常检测 Golden Ticket T1558.001 伪造 TGT 票据 异常 TGT 签名验证 Silver Ticket T1558.002 伪造 TGS 票据 服务端票据验证日志 Pass-the-Hash T1550.002 使用 NTLM 哈希直接认证 NTLM 认证异常模式 DCSync T1003.006 模拟域控复制凭据 目录复制请求监控
Kerberos 攻击检测 Sigma 规则:
title : Kerberos Golden Ticket Detection
id : 9a0b1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d
status : stable
description : Detects potential Golden Ticket attacks through abnormal Kerberos TGT patterns
references :
- https://attack.mitre.org/techniques/T1558/001/
author : BlueTeam Forensics
date : 2026 /07/18
tags :
- attack.credential_access
- attack.t1558.001
logsource :
category : authentication
service : kerberos
detection :
selection_tgt :
event_id :
- '4768'
- '4769'
ticket_type :
- 'TGT'
- 'TGS'
selection_anomaly :
encryption_type :
- '0x17'
- '0x18'
pre_auth_result : '0x0'
service_name : 'krbtgt'
condition : selection_tgt and selection_anomaly
fields :
- account_name
- source_ip
- ticket_encryption_type
- ticket_options
- target_service
- logon_id
timeframe : 1h
aggregation :
field : account_name
function : count
operator : '>'
value : 100
falsepositives :
- Normal TGT renewal cycle
- Large user base concurrent authentication
level : critical 联邦信任链审计 SAML 信任关系映射:
az rest --method GET \
--url "https://login.microsoftonline.com/{tenant_id}/federationSettings/DomainDns" \
--query "value[?authenticationProtocol=='saml'].{Domain:name,Protocol:authenticationProtocol,SigningCert:signingCertificate}" \
--output table 身份联邦安全审计检查清单:
审计项目 检查内容 风险等级 检查方法 签名算法 使用 SHA-1 签名 🔴 高 解析 IdP 元数据中的签名算法 证书过期 IdP/SP 证书是否过期 🟡 中 检查 X.509 证书有效期 信任方向 双向信任配置 🔴 高 审查 SP 和 IdP 元数据 断言加密 断言是否加密传输 🟡 中 检查 SP 配置中的加密设置 URL 验证 ACS URL 和 SLO URL 白名单 🔴 高 审查 SP 元数据中的 URL 配置
0x07 证据强度分层与案例关联 证据分级标准 在身份安全事件取证中,不同来源的证据具有不同的可信度和证明力。建立标准化的证据分级框架对于准确还原攻击链至关重要。
证据等级 标记 证据类型 证明力 典型来源 一级证据 🔴 加密签名的审计日志 最高,难以伪造 CloudTrail 签名日志、HSM 审计日志 二级证据 🟡 系统级认证日志 较高,可被具有系统权限的攻击者篡改 Windows Security Event Log、Linux auth.log 三级证据 🟢 应用级访问日志 中等,需要交叉验证 Web 应用 Access Log、API Gateway Log 补充证据 ⚪ 网络流量、DNS 日志 辅助验证 PCAP、Zeek 日志、DNS 查询日志
证据关联方法 跨域日志关联分析:
python3 << 'EOF'
import json
import sys
from datetime import datetime, timedelta
def correlate_identity_events(cloudtrail_log, ad_log, vpn_log, oauth_log):
events = []
with open(cloudtrail_log, 'r') as f:
for line in f:
try:
entry = json.loads(line)
events.append({
'source': 'CloudTrail',
'timestamp': entry.get('eventTime', ''),
'user': entry.get('userIdentity', {}).get('arn', ''),
'action': entry.get('eventName', ''),
'source_ip': entry.get('sourceIPAddress', ''),
'event_id': entry.get('eventID', ''),
'detail': json.dumps(entry.get('requestParameters', {}))
})
except json.JSONDecodeError:
continue
with open(ad_log, 'r') as f:
for line in f:
try:
entry = json.loads(line)
events.append({
'source': 'AD',
'timestamp': entry.get('timestamp', ''),
'user': entry.get('subjectUserName', ''),
'action': entry.get('eventID', ''),
'source_ip': entry.get('ipAddress', ''),
'event_id': str(entry.get('eventRecordId', '')),
'detail': entry.get('description', '')
})
except json.JSONDecodeError:
continue
with open(vpn_log, 'r') as f:
for line in f:
try:
entry = json.loads(line)
events.append({
'source': 'VPN',
'timestamp': entry.get('timestamp', ''),
'user': entry.get('username', ''),
'action': entry.get('event_type', ''),
'source_ip': entry.get('public_ip', ''),
'event_id': entry.get('session_id', ''),
'detail': entry.get('message', '')
})
except json.JSONDecodeError:
continue
events.sort(key=lambda x: x.get('timestamp', ''))
correlations = []
for i, event in enumerate(events):
if not event.get('source_ip'):
continue
window_start = datetime.fromisoformat(
event['timestamp'].replace('Z', '+00:00')
) - timedelta(minutes=5)
window_end = datetime.fromisoformat(
event['timestamp'].replace('Z', '+00:00')
) + timedelta(minutes=5)
related = []
for j, other in enumerate(events):
if i == j:
continue
try:
other_ts = datetime.fromisoformat(
other['timestamp'].replace('Z', '+00:00')
)
if window_start <= other_ts <= window_end:
if (other['user'] == event['user'] or
other['source_ip'] == event['source_ip']):
related.append({
'source': other['source'],
'timestamp': other['timestamp'],
'action': other['action'],
'match_reason': 'user' if other['user'] == event['user'] else 'ip'
})
except (ValueError, KeyError):
continue
if len(related) >= 2:
sources = set(r['source'] for r in related) | {event['source']}
if len(sources) >= 2:
correlations.append({
'primary_event': event,
'correlated_events': related,
'correlation_strength': len(sources),
'cross_source': list(sources)
})
return correlations
def print_correlation_report(correlations):
print(f'Identity Event Correlation Report')
print(f'Total correlated chains: {len(correlations)}')
print('=' * 70)
for i, corr in enumerate(correlations, 1):
primary = corr['primary_event']
print(f'\nCorrelation Chain #{i} (Strength: {corr["correlation_strength"]})')
print(f'Sources: {", ".join(corr["cross_source"])}')
print(f'Primary: [{primary["source"]}] {primary["action"]} by {primary["user"]}')
print(f' Time: {primary["timestamp"]}')
print(f' IP: {primary["source_ip"]}')
print(f' Related events:')
for evt in corr['correlated_events']:
print(f' [{evt["source"]}] {evt["action"]} ({evt["match_reason"]} match)')
print(f' Time: {evt["timestamp"]}')
print('-' * 50)
if __name__ == '__main__':
if len(sys.argv) < 5:
print(f'Usage: {sys.argv[0]} <cloudtrail> <ad_log> <vpn_log> <oauth_log>')
sys.exit(1)
results = correlate_identity_events(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])
print_correlation_report(results)
EOF 攻击链重建方法论 身份安全事件的攻击链通常包含以下阶段,每个阶段都会在不同的日志系统中留下痕迹。
攻击阶段 日志源 关键 IOC 时间关联 初始访问 VPN 日志、SSO 登录日志 新 IP、新设备 事件开始 凭据获取 认证失败/成功日志 MFA 变更、密码重置 初始访问后 0-24h 权限提升 IAM 策略变更日志 新角色、新权限 凭据获取后 0-48h 横向移动 服务间认证日志 异常服务调用 权限提升后 0-72h 数据访问 应用访问日志、API 调用日志 批量数据读取 横向移动后 0-96h 数据外传 网络出口日志、DLP 告警 大量数据传输 数据访问后
0x08 自动化检测与狩猎 Sigma 检测规则 云 IAM 策略篡改检测规则:
title : Cloud IAM Policy Tampering Detection
id : b1c2d3e4-f5a6-b7c8-d9e0-f1a2b3c4d5e6
status : stable
description : Detects potential IAM policy tampering activities across cloud platforms
references :
- https://attack.mitre.org/techniques/T1098/003/
- https://attack.mitre.org/techniques/T1098/001/
author : BlueTeam Forensics
date : 2026 /07/18
tags :
- attack.persistence
- attack.t1098.003
- attack.t1098.001
- attack.privilege_escalation
logsource :
category : cloud_trail
service : iam
detection :
selection_aws :
event_source : 'iam.amazonaws.com'
event_name :
- 'CreatePolicyVersion'
- 'SetDefaultPolicyVersion'
- 'AttachUserPolicy'
- 'AttachRolePolicy'
- 'AttachGroupPolicy'
- 'PutUserPolicy'
- 'PutRolePolicy'
- 'PutGroupPolicy'
- 'CreateAccessKey'
- 'UpdateAssumeRolePolicy'
selection_azure :
category : 'RoleManagement'
operationName :
- 'Add member to role'
- 'Add eligible member to role'
- 'Update role assignment'
selection_gcp :
methodName :
- 'SetIamPolicy'
- 'TestIamPermissions'
- 'AddAllowedPolicyBinding'
condition : selection_aws or selection_azure or selection_gcp
fields :
- event_time
- user_identity_arn
- event_name
- request_parameters
- source_ip_address
- user_agent
timeframe : 1h
filters :
privileged_actions :
event_name :
- 'CreatePolicyVersion'
- 'AttachUserPolicy'
- 'AttachRolePolicy'
- 'UpdateAssumeRolePolicy'
privileged_roles :
role_name :
- 'AdministratorAccess'
- 'PowerUserAccess'
- 'OrganizationAccountAccessRole'
falsepositives :
- Legitimate IAM policy updates during maintenance
- Infrastructure-as-Code deployments
- Automated CI/CD pipeline role management
level : critical 异常身份认证模式检测规则:
title : Abnormal Authentication Pattern Detection
id : c2d3e4f5-a6b7-c8d9-e0f1-a2b3c4d5e6f7
status : experimental
description : Detects abnormal authentication patterns indicative of credential compromise
references :
- https://attack.mitre.org/techniques/T1078/
- https://attack.mitre.org/techniques/T1110/
author : BlueTeam Forensics
date : 2026 /07/18
tags :
- attack.credential_access
- attack.t1078
- attack.t1110
logsource :
category : authentication
service : all
detection :
selection_brute_force :
auth_result :
- 'failure'
- 'failed'
event_id :
- '4625'
- '4771'
- '4776'
selection_password_spray :
auth_result :
- 'failure'
- 'failed'
selection_impossible_travel :
auth_result :
- 'success'
- 'approved'
fields_required :
- source_ip
- geographic_location
- user_agent
condition : selection_brute_force | count by source_ip > 10 within 5m
or selection_password_spray | count(distinct account_name) by source_ip > 5 within 15m
timeframe : 15m
fields :
- account_name
- source_ip
- auth_method
- auth_result
- user_agent
- geographic_location
- failure_reason
falsepositives :
- Service account automation
- Credential rotation scripts
- Legitimate pentesting activities
level : high Bash 云 IAM 审计脚本 #!/bin/bash
echo "============================================"
echo " Multi-Cloud IAM Security Audit Script"
echo " Generated: $( date) "
echo "============================================"
check_aws_iam() {
echo ""
echo "[AWS IAM AUDIT]"
echo "-------------------------"
echo "[1] IAM Users without MFA:"
aws iam list-users --query 'Users[?MFADevices==`[]`].{UserName:UserName,CreateDate:CreateDate}' --output table 2>/dev/null
echo ""
echo "[2] IAM Users with Console Access:"
aws iam generate-credential-report 2>/dev/null
sleep 2
aws iam get-credential-report --query 'Content' --output text 2>/dev/null | \
base64 -d 2>/dev/null | \
awk -F',' 'NR==1 || ($4=="true" && $8=="true") {print $1, $4, $8, $9}'
echo ""
echo "[3] Overly Permissive Policies:"
for policy_arn in $( aws iam list-policies --scope Local --query 'Policies[].[Arn]' --output text 2>/dev/null) ; do
default_ver= $( aws iam get-policy --policy-arn " $policy_arn" --query 'Policy.DefaultVersionId' --output text 2>/dev/null)
has_wildcard= $( aws iam get-policy-version --policy-arn " $policy_arn" --version-id " $default_ver" --query 'PolicyVersion.Document.Statement[?Action==`*`]' --output text 2>/dev/null)
if [ -n " $has_wildcard" ] ; then
echo "[CRITICAL] Wildcard policy: $policy_arn"
fi
done
echo ""
echo "[4] IAM Roles with Cross-Account Trust:"
aws iam list-roles --query 'Roles[].{RoleName:RoleName,Arn:Arn}' --output json 2>/dev/null | \
python3 -c "
import json, sys
roles = json.load(sys.stdin)
for role in roles:
arn = role['Arn']
trust = aws iam get-role --role-name $( echo $arn | cut -d'/' -f2) 2>/dev/null
" 2>/dev/null || true
}
check_azure_ad() {
echo ""
echo "[Azure AD AUDIT]"
echo "-------------------------"
if command -v az &> /dev/null; then
echo "[1] Global Admins:"
az ad directory-role list --query "[?displayName=='Global Administrator'].assignments[].{User:principalDisplayName}" --output table 2>/dev/null
echo ""
echo "[2] Users with No MFA:"
az rest --method GET \
--url "https://graph.microsoft.com/v1.0/users?\$select=displayName,userPrincipalName,authenticationMethods" \
--query 'value[].{Name:displayName,UPN:userPrincipalName}' \
--output table 2>/dev/null
else
echo "[SKIP] Azure CLI not installed"
fi
}
check_gcp_iam() {
echo ""
echo "[GCP IAM AUDIT]"
echo "-------------------------"
if command -v gcloud &> /dev/null; then
echo "[1] Project IAM Bindings:"
gcloud asset search-all-iam-policies \
--scope= organizations/$( gcloud organizations list --format= "value(name)" 2>/dev/null | head -1 | cut -d'/' -f2) \
--query= "policy.bindings.role:" \
--flatten= "policy.bindings[]" \
--format= "table(policy.bindings.role, policy.bindings.members, resource)" 2>/dev/null | \
grep -i "roles/owner\|roles/editor\|roles/iam.securityAdmin"
echo ""
echo "[2] Service Accounts with Keys:"
gcloud iam service-accounts list --format= "table(email,disabled)" 2>/dev/null | \
while read -r sa_email _; do
key_count= $( gcloud iam service-accounts keys list --iam-account= " $sa_email" --format= "value(name)" 2>/dev/null | wc -l)
if [ " $key_count" -gt 0 ] ; then
echo "[WARNING] SA $sa_email has $key_count keys"
fi
done
else
echo "[SKIP] gcloud CLI not installed"
fi
}
check_aws_iam
check_azure_ad
check_gcp_iam
echo ""
echo "============================================"
echo " Audit Complete"
echo "============================================" Python 身份行为基线分析 import json
import sys
from collections import defaultdict
from datetime import datetime, timedelta
from statistics import mean, stdev
class IdentityBehaviorBaseline :
def __init__ (self):
self. user_events = defaultdict(list)
self. baselines = {}
self. anomalies = []
def load_cloudtrail (self, log_file):
with open(log_file, 'r' ) as f:
for line in f:
try :
event = json. loads(line)
except json. JSONDecodeError:
continue
user = event. get('userIdentity' , {}). get('arn' , 'unknown' )
ts = event. get('eventTime' , '' )
action = event. get('eventName' , '' )
source_ip = event. get('sourceIPAddress' , '' )
ua = event. get('userAgent' , '' )
try :
dt = datetime. fromisoformat(ts. replace('Z' , '+00:00' ))
except (ValueError , AttributeError ):
continue
self. user_events[user]. append({
'timestamp' : dt,
'action' : action,
'source_ip' : source_ip,
'user_agent' : ua,
'hour' : dt. hour,
'weekday' : dt. weekday()
})
def build_baselines (self):
for user, events in self. user_events. items():
if len(events) < 10 :
continue
hours = [e['hour' ] for e in events]
action_types = defaultdict(int)
ip_set = defaultdict(int)
ua_set = defaultdict(int)
for e in events:
action_types[e['action' ]] += 1
ip_set[e['source_ip' ]] += 1
ua_set[e['user_agent' ]] += 1
most_common_actions = sorted(action_types. items(), key= lambda x: x[1 ], reverse= True )[:10 ]
most_common_ips = sorted(ip_set. items(), key= lambda x: x[1 ], reverse= True )[:5 ]
self. baselines[user] = {
'total_events' : len(events),
'mean_hour' : mean(hours) if hours else 0 ,
'hour_std' : stdev(hours) if len(hours) > 1 else 0 ,
'common_actions' : set(a for a, _ in most_common_actions),
'common_ips' : set(ip for ip, _ in most_common_ips),
'unique_actions' : len(action_types),
'unique_ips' : len(ip_set),
'activity_days' : len(set(e['timestamp' ]. date() for e in events)),
'events_per_day' : len(events) / max(1 , len(set(e['timestamp' ]. date() for e in events)))
}
def detect_anomalies (self, current_events_file):
self. build_baselines()
with open(current_events_file, 'r' ) as f:
for line in f:
try :
event = json. loads(line)
except json. JSONDecodeError:
continue
user = event. get('userIdentity' , {}). get('arn' , 'unknown' )
action = event. get('eventName' , '' )
source_ip = event. get('sourceIPAddress' , '' )
ts = event. get('eventTime' , '' )
if user not in self. baselines:
continue
baseline = self. baselines[user]
try :
dt = datetime. fromisoformat(ts. replace('Z' , '+00:00' ))
hour = dt. hour
except (ValueError , AttributeError ):
continue
if action not in baseline['common_actions' ]:
self. anomalies. append({
'type' : 'NOVEL_ACTION' ,
'severity' : 'HIGH' ,
'user' : user,
'action' : action,
'source_ip' : source_ip,
'timestamp' : ts,
'mitre' : 'T1078'
})
if source_ip not in baseline['common_ips' ]:
self. anomalies. append({
'type' : 'NOVEL_IP' ,
'severity' : 'MEDIUM' ,
'user' : user,
'source_ip' : source_ip,
'timestamp' : ts,
'mitre' : 'T1078'
})
if abs(hour - baseline['mean_hour' ]) > 4 :
self. anomalies. append({
'type' : 'OFF_HOURS_ACTIVITY' ,
'severity' : 'MEDIUM' ,
'user' : user,
'action' : action,
'hour' : hour,
'mean_hour' : baseline['mean_hour' ],
'timestamp' : ts,
'mitre' : 'T1078'
})
def generate_hunting_report (self):
severity_order = {'CRITICAL' : 0 , 'HIGH' : 1 , 'MEDIUM' : 2 , 'LOW' : 3 }
self. anomalies. sort(key= lambda x: severity_order. get(x['severity' ], 99 ))
print(f 'Identity Behavior Anomaly Report' )
print(f 'Total anomalies: { len(self. anomalies)} ' )
print('=' * 70 )
for i, a in enumerate(self. anomalies, 1 ):
print(f '[ { a["severity" ]} ] # { i} - { a["type" ]} ' )
print(f ' User: { a["user" ]} ' )
print(f ' Time: { a. get("timestamp" , "N/A" )} ' )
print(f ' MITRE: { a. get("mitre" , "N/A" )} ' )
print('-' * 50 )
if __name__ == '__main__' :
if len(sys. argv) < 3 :
print(f 'Usage: { sys. argv[0 ]} <baseline_cloudtrail> <current_cloudtrail>' )
sys. exit(1 )
analyzer = IdentityBehaviorBaseline()
analyzer. load_cloudtrail(sys. argv[1 ])
analyzer. detect_anomalies(sys. argv[2 ])
analyzer. generate_hunting_report() CloudTrail 日志分析 SQL 模式 使用 Amazon Athena 分析 CloudTrail 日志是身份行为取证的核心手段。以下 SQL 模式可用于常见的身份攻击检测场景。
检测异常 AssumeRole 调用:
SELECT
useridentity.arn AS actor_arn,
eventname AS action,
sourceipaddress AS source_ip,
eventtime AS event_time,
requestparameters
FROM cloudtrail_logs
WHERE eventname = 'AssumeRole'
AND useridentity.type = 'AssumedRole'
AND datehour >= date_format(date_add('day' , - 7 , current_date ), '%Y%m%d%H' )
ORDER BY eventtime DESC
LIMIT 500 ;检测 IAM 策略变更:
SELECT
useridentity.arn AS actor,
eventname AS action,
eventtime AS time,
requestparameters,
sourceipaddress AS ip
FROM cloudtrail_logs
WHERE eventname IN (
'CreatePolicyVersion' , 'SetDefaultPolicyVersion' ,
'AttachUserPolicy' , 'AttachRolePolicy' ,
'PutUserPolicy' , 'PutRolePolicy' ,
'UpdateAssumeRolePolicy'
)
AND useridentity.type != 'AWSService'
ORDER BY eventtime DESC ;身份异常行为检测聚合查询:
SELECT
useridentity.arn AS user_arn,
COUNT (DISTINCT eventname) AS unique_actions,
COUNT (DISTINCT sourceipaddress) AS unique_ips,
MIN (eventtime) AS first_seen,
MAX (eventtime) AS last_seen,
COUNT (* ) AS total_events
FROM cloudtrail_logs
WHERE datehour >= date_format(date_add('day' , - 1 , current_date ), '%Y%m%d%H' )
GROUP BY useridentity.arn
HAVING COUNT (DISTINCT sourceipaddress) > 5
OR COUNT (DISTINCT eventname) > 50
ORDER BY unique_ips DESC , unique_actions DESC ;0x09 公开案例分析 案例一:SolarWinds SAML 令牌伪造攻击(2020) SolarWinds 事件是近年来最严重的供应链攻击之一,也是身份系统被滥用的典型案例。攻击者在 SolarWinds Orion 软件中植入 SUNBURST 后门,通过供应链分发至约 18,000 个组织,最终约 100 个高价值目标被实际入侵。
攻击链描述:
阶段 时间线 攻击操作 使用技术 初始投毒 2019年10月 入侵 SolarWinds 构建环境,植入 SUNBURST 代码 T1195.002 软件供应链 供应链分发 2020年2月-6月 通过软件更新通道分发恶意版本 T1195.002 软件供应链 横向侦察 2020年3月起 SUNBURST 回连 C2,枚举 Active Directory 环境 T1087.002 域账户枚举 凭据窃取 持续进行 使用 KEEMBARGE 提取内存中的令牌和凭据 T1003.001 LSASS 内存 SAML 伪造 持续进行 窃取 SAML 签名证书,伪造 SAML 令牌 T1550.003 SAML 令牌伪造 持久化 持续进行 使用伪造的 SAML 令牌维持对云服务的访问 T1078 有效账户
取证发现:
取证维度 发现内容 证据类型 IOC 恶意 DLL SolarWinds.Orion.Core.BusinessLayer.dll 被篡改 文件哈希 SHA256: c15abaf51e78ca560b1395f1f85645f69af655310b9b5cc73d25b38bd81eee42 C2 域名 avsvmcloud.com 及子域名(DNS 隧道) 网络 IOC *.avsvmcloud.com SAML 签名证书 攻击者导出 SAML signing certificate 私钥 身份 IOC 异常 SAML 签名模式 邮箱规则 创建隐藏邮箱规则转发所有邮件 邮件规则 IOC Inbox Rules: ForwardTo OAuth 应用 创建高权限 OAuth 应用维持访问 身份 IOC 异常应用注册 云持久化 在 Azure AD 中创建凭据和应用 云 IOC 异常服务主体
经验教训:
SAML 证书轮换是关键 :一旦 SAML 签名证书可能泄露,必须立即轮换。SolarWinds 攻击者正是利用被窃取的 SAML 证书伪造令牌供应链完整性验证不可或缺 :软件构建和分发管道必须实施严格的完整性验证机制云身份审计需要跨域关联 :攻击者在本地 AD 和 Azure AD 之间切换操作,单一视图无法发现完整攻击链异常令牌使用检测 :即使使用合法凭据,异常的地理位置、时间模式和行为基线偏差也能暴露攻击者案例二:Okta 供应链攻击与 Lapsus$ MFA 绕过(2022) 2022 年初,Lapsus$ 组织对 Okta 发起了大规模攻击,通过社会工程手段获取了 Okta 支持工程师的工作凭据,并利用远程管理工具对客户环境进行了未授权访问。
攻击链描述:
阶段 时间线 攻击操作 使用技术 初始入侵 2022年1月 入侵第三方提供商 Sykes Enterprises 的 Okta 管理员 T1133 外部远程服务 权限扩展 2022年1月 在 Okta 支持系统中获取 Super Admin 权限 T1078.004 云账户 MFA 绕过 2022年1月 利用会话令牌重放绕过 MFA 检查 T1621 MFA 疲劳攻击 客户访问 2022年1-3月 通过 Okta Admin Console 访问客户租户 T1078 有效账户 数据窃取 2022年3月 对客户环境执行截图和数据外传 T1005 数据从本地系统获取
取证发现:
取证维度 发现内容 证据类型 IOC 异常登录 支持工程师账户从异常 IP 登录 认证日志 异常 Source IP 操作时间 大量操作发生在非工作时间 时间线分析 UTC 凌晨时段操作 截图操作 执行大量屏幕截图操作 终端日志 特定进程创建 工具下载 下载远程桌面和渗透测试工具 文件日志 ScreenConnect、AnyDesk 客户范围 约 366 个客户租户被访问 Okta 审计日志 大量租户切换操作
经验教训:
MFA 并非万能 :社会工程和供应链攻击可以完全绕过 MFA 保护第三方访问管理至关重要 :供应商的特权访问必须实施最小权限和实时监控会话令牌保护 :Okta 管理控制台的会话令牌需要加强保护,防止重放攻击异常行为检测 :支持人员的大规模跨租户操作应该触发告警零信任网络 :即使通过 VPN 访问,也需要持续验证身份和设备状态案例三:Azure AD Golden Ticket 攻击 Azure AD Kerberos 攻击利用了混合身份环境中 Active Directory 与 Azure AD 之间的信任关系。
攻击链描述:
阶段 攻击操作 技术编号 凭据获取 通过 DCSync 获取 krbtgt NTLM 哈希 T1003.006 Golden Ticket 创建 使用 krbtgt 哈希伪造 TGT 票据 T1558.001 域控访问 使用伪造 TGT 访问域控制器 T1078 有效账户 混合身份利用 利用 Azure AD Connect 同步账户提权 T1078.004 云账户 云服务访问 通过同步账户访问 Azure AD T1550.003 SAML 令牌伪造
取证发现与检测方法:
检测维度 正常模式 攻击特征 检测方法 TGT 生命周期 正常 10h 有效期 异常超长有效期 事件 ID 4768/4769 分析 加密类型 AES-256 (0x11) RC4 (0x17) 加密类型筛选 事件频率 单账户每小时 <10 次 单账户高频请求 聚合分析 登录事件 标准 Windows 登录 Kerberos 无 NTLM 事件 ID 4624 类型分析
经验教训:
定期轮换 krbtgt 密码 :至少每 180 天轮换两次(间隔 12 小时以上)监控 DCSync 操作 :非 DC 服务器发起的目录复制请求必须告警混合身份安全审查 :Azure AD Connect 同步账户的权限需要最小化Kerberos 加密策略 :禁用 RC4 加密,强制使用 AES 加密0x10 参考资料 总结 数字身份与访问管理安全取证是现代应急响应中最复杂的领域之一。本文从云 IAM 配置滥用、OAuth/OIDC 协议攻击、MFA 绕过、PAM 攻防、身份联邦攻击五个维度,系统性地构建了 DIAM 安全取证的完整方法论。
核心要点回顾:
维度 关键检测手段 证据类型 取证优先级 云 IAM CloudTrail/Audit Log 策略变更监控 一级证据 🔴 立即 OAuth/OIDC 授权码注入和令牌重放检测 二级证据 🔴 立即 MFA 疲劳攻击和地理不可能检测 一级证据 🔴 立即 PAM JIT 提权和批量凭据访问监控 一级证据 🔴 立即 联邦信任 SAML 签名验证和 Golden Ticket 检测 一级证据 🔴 立即
身份安全事件的取证分析需要跨域关联多个日志源,建立统一的时间线,并结合行为基线模型识别偏离正常模式的异常活动。通过本文提供的 Sigma 规则、Bash 审计脚本和 Python 分析工具,取证人员可以快速构建起身份安全事件的检测和分析能力。