> 技术文档 > FortiWeb预授权RCE(CVE-2025-25257)PoC与EXP原理分析_fortiweb cve 2025 25757

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. 在 [1] 处,Authorization从 HTTP 请求中提取标头并存储在v3变量中
  2. 在 [2] 处,__isoc23_sscanflibc 函数用于解析标头
    1. 标头的值以 Bearer(注意空格)开头,后跟最多 128 个字符
    2. 这些字符会被提取到v4中
  3. 在 [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

通过在核心模块目录中创建文件能覆盖第三方模块,所以我们可以:

  1. 创建/var/log/lib/python3.10/pylab.py(或者其他第三方模块)
    1. 写入自定义的内容,比如反弹shell
  2. ml-draw.py文件发送 HTTP 请求
  3. ml-draw.py文件导入pylab
  4. 触发我们的代码

如果想了解EXP的细节,比如如何处理128个字符的限制等问题,或者更直接的想要EXP本身,一定要读watchtowr的文章,EXP的具体内容或链接审核无法通过

防御措施

升级到最新版本

  • PSIRT | FortiGuard Labs