FortiWeb预授权RCE(CVE-2025-25257)PoC与EXP原理分析_fortiweb cve 2025 25757
免责声明
本文所述漏洞复现方法仅供安全研究及授权测试使用;
任何个人/组织须在合法合规前提下实施,严禁用于非法目的;
作者不对任何滥用行为及后果负责,如发现新漏洞请及时联系厂商并遵循漏洞披露规则。
漏洞简述
Fortinet 的 FortiWeb Fabric Connector 旨在成为 FortiWeb(其 Web 应用防火墙)与其他 Fortinet 生态系统产品之间的粘合剂,允许根据基础设施或威胁态势的实时变化进行动态、基于策略的安全更新
可以将其视为一个高级中间人——从 FortiGate 防火墙、FortiManager 甚至 AWS 等外部服务等来源提取元数据,并将其输入 FortiWeb,以便其自动调整防护措施
FortiWeb存在预授权 SQLi 漏洞,可以利用该漏洞在 Fortinet 的 FortiWeb 中实现 RCE,攻击者可在 Authorization: Bearer 头中注入恶意 SQL语句,实现远程代码执行获取服务器权限
影响版本
- FortiWeb 7.6,7.6.0 <= version <=7.6.3
- FortiWeb 7.4,7.4.0 <= version <=7.4.7
- FortiWeb 7.2,7.2.0 <= version <=7.2.10
- FortiWeb 7.0,7.0.0 <= version <=7.0.10
FOFA
app=\"FORTINET-FortiWeb\"body=\"FortiToken clock drift detected\"
PoC
GET /api/fabric/device/status HTTP/1.1Host: xxx.xxx.xxx.xxxAuthorization: Bearer AAAA\'/**/or/**/\'1\'=\'1
漏洞原理
SQLi
在httpd.conf中,有一个Native Apache 句柄:
SetHandler fabric_device_status-handler
查看其代码可知,它负责解析用户提供的标头,例如Authorization:
- 在 [1] 处,Authorization从 HTTP 请求中提取标头并存储在v3变量中
- 在 [2] 处,
__isoc23_sscanflibc
函数用于解析标头- 标头的值以
Bearer
(注意空格)开头,后跟最多 128 个字符 - 这些字符会被提取到v4中
- 标头的值以
- 在 [3] 处,
get_fabric_user_by_token
使用v4中存储的值进行调用,无需验证或清理
__int64 __fastcall fabric_access_check(__int64 a1){ __int64 v1; // rdi __int64 v2; // rax _OWORD v4[8]; // [rsp+0h] [rbp-A0h] BYREF char v5; // [rsp+80h] [rbp-20h] unsigned __int64 v6; // [rsp+88h] [rbp-18h] v1 = *(_QWORD *)(a1 + 248); v6 = __readfsqword(0x28u); v5 = 0; memset(v4, 0, sizeof(v4)); v3 = apr_table_get(v1, \"Authorization\"); // [1] if ( (unsigned int)__isoc23_sscanf(v2, \"Bearer %128s\", v4) != 1 ) // [2] return 0; v5 = 0; if ( (unsigned int)fabric_user_db_init() || (unsigned int)refresh_fabric_user() || (unsigned int)get_fabric_user_by_token((const char *)v4) ) // [3] { return 0; } else { return 2 * (unsigned int)((unsigned int)update_fabric_user_expire_time_by_token((const char *)v4) == 0); }}
在get_fabric_user_by_token()
中,token被拼接到 SQL 查询:
__int64 __fastcall get_fabric_user_by_token(const char *a1){ unsigned int v1; // ebx __int128 v3; // [rsp+0h] [rbp-4B0h] BYREF __int64 v4; // [rsp+10h] [rbp-4A0h] _BYTE v5[16]; // [rsp+20h] [rbp-490h] BYREF __int64 (__fastcall *v6)(_BYTE *); // [rsp+30h] [rbp-480h] __int64 (__fastcall *v7)(_BYTE *, char *); // [rsp+38h] [rbp-478h] void (__fastcall *v8)(_BYTE *); // [rsp+58h] [rbp-458h] __int64 (__fastcall *v9)(_BYTE *, __int128 *); // [rsp+60h] [rbp-450h] void (__fastcall *v10)(__int128 *); // [rsp+68h] [rbp-448h] char s[16]; // [rsp+80h] [rbp-430h] BYREF _BYTE v12[1008]; // [rsp+90h] [rbp-420h] BYREF unsigned __int64 v13; // [rsp+488h] [rbp-28h] v13 = __readfsqword(0x28u); *(_OWORD *)s = 0; memset(v12, 0, sizeof(v12)); if ( a1 && *a1 ) { init_ml_db_obj((__int64)v5); v1 = v6(v5); if ( !v1 ) { snprintf(s, 0x400u, \"select id from fabric_user.user_table where token=\'%s\'\", a1); ......
经典的SQL语句拼接
RCE
Mysql的INTO OUTFILE
语句允许将文件写入文件系统,前提是需要高权限 MySQL 用户使用,例如root
,而FortiWeb恰恰以root
权限执行着Mysql查询操作,到了这一步已经可以以root
权限通过 SQL 注入实现了任意文件写入
如何执行任意代码呢?有一个名为ml-draw.py
的文件,它是一个 CGI 脚本,无需授权即可访问
他导入了以下一些模块
import osimport sysimport cgiimport cgitb; cgitb.enable()import timefrom datetime import datetimeimport matplotlibimport pylabimport numpy as npfrom numpy import array
通过在核心模块目录中创建文件能覆盖第三方模块,所以我们可以:
- 创建
/var/log/lib/python3.10/pylab.py
(或者其他第三方模块)- 写入自定义的内容,比如反弹shell
- 向
ml-draw.py
文件发送 HTTP 请求 ml-draw.py
文件导入pylab
库- 触发我们的代码
如果想了解EXP的细节,比如如何处理128个字符的限制等问题,或者更直接的想要EXP本身,一定要读watchtowr的文章,EXP的具体内容或链接审核无法通过
防御措施
升级到最新版本
- PSIRT | FortiGuard Labs