WordPress FS注册密码漏洞 | CVE-2025-15001 复现&研究

0x0 背景介绍

WordPressFS Registration Password插件在1.0.1及之前的所有版本中存在权限提升漏洞,可能导致账户被接管。该漏洞源于插件在更新用户密码前未正确验证用户身份,使得未经认证的攻击者能够修改任意用户(包括管理员)的密码,并利用此漏洞获取账户访问权限。

0x1 环境搭建

1、Ubuntu24+Docker搭建配置

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

if ! command -v unzip &> /dev/null; then
    apt update && apt install -y unzip wget curl
fi

echo "[*] 阶段1/5:创建漏洞复现目录..."
mkdir -p wp-fs-pw-vuln && cd wp-fs-pw-vuln || { 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: fs-pw-db
    environment:
      MYSQL_ROOT_PASSWORD: vuln-root-pass
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wpuser
      MYSQL_PASSWORD: vuln-user-pass
    volumes:
      - db_data:/var/lib/mysql
    command: --default-authentication-plugin=mysql_native_password

  wordpress:
    image: wordpress:6.5-php8.2-apache
    container_name: fs-pw-wp
    ports:
      - "8087:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wpuser
      WORDPRESS_DB_PASSWORD: vuln-user-pass
      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 {1..12}; do
    echo -n "."
    sleep 5
done
echo -e "\n[+] 服务启动完成"

echo "[*] 等待 WordPress 安装页面就绪..."
until [ "$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8087/wp-admin/install.php)" = "200" ]; do
    sleep 2
done
echo "[+] WordPress 已就绪"

echo "[*] 阶段4/5:下载并部署 FS Registration Password v1.0.1..."

PLUGIN_URL="https://downloads.wordpress.org/plugin/registration-password.1.0.1.zip"
wget -q "$PLUGIN_URL" -O registration-password.zip || {
    echo "[-] 插件下载失败"
    exit 1
}

unzip -q registration-password.zip

# 直接复制解压出的 registration-password 目录到容器
if [ ! -d "registration-password" ]; then
    echo "[-] 解压后未找到 registration-password 目录"
    exit 1
fi

# 复制到容器(使用明确的 container_name)
docker cp registration-password fs-pw-wp:/var/www/html/wp-content/plugins/

# 清理
rm -rf registration-password registration-password.zip

# 验证
if docker exec fs-pw-wp test -f /var/www/html/wp-content/plugins/registration-password/fs-registration-password.php; then
    echo "[+] 插件部署成功!"
else
    echo "[-] 插件部署失败:主文件 fs-registration-password.php 未找到"
    exit 1
fi

echo "=============================================="
echo " CVE-2025-15001 漏洞环境部署完成!"
echo "  - 访问站点: http://localhost:8087"
echo "  - 站点标题: CVE-2025-15001"
echo "  - 用户名: admin"
echo "  - 密码: 默认强密码"
echo "  - 电子邮箱: cve-2025-15001@local.test"
echo "  - 登录后台 → 插件 → 启用 'Registration Password'"
echo "=============================================="

0x2 漏洞复现

1、Python检查

bash 复制代码
https://github.com/Kai-One001/cve-/blob/main/WordPress_FS_%20CVE-2025-15001.py
  • 依照管理,没有直接修改密码

2、手动复现步骤

  • 找回密码接口,发送自定义KEY

  • 请求重置链接

  • 利用返回的Set-Cookie,成功请求到重置密码接口

  • 其实发送自定义KEY后,浏览器访问链接也可以,自动跳转(KEY就是设置的密码)

html 复制代码
http://192.168.119.131:8087/wp-login.php?action=rp&key=22222&login=admin
  • 验证成功修改密码,并正常登录

3、复现流量特征 (PCAP)

  • 自定义KEY找回密码

  • 请求重置密码链接

  • 成功访问重置密码页面

0x3 漏洞原理分析

1、入口文件分析

  • 插件代码简单,可以直接从入口分析
php 复制代码
#fs-registration-password.php
<?php
/**
 * Plugin Name: Registration Password
 * Plugin URI: https://github.com/fsylum/fs-registration-password
 * Description: Allow users to set their own password during site registration
 * Version: 1.0.1
 * Author: Firdaus Zahari
 * Author URI: https://fsylum.net
 * Requires at least: 5.6
 * Requires PHP:      7.3
 */

require __DIR__ . '/vendor/autoload.php';

define('FSRP_PLUGIN_URL', untrailingslashit(plugin_dir_url(__FILE__)));
define('FSRP_PLUGIN_PATH', untrailingslashit(plugin_dir_path(__FILE__)));
define('FSRP_PLUGIN_BASENAME', plugin_basename(__FILE__));
define('FSRP_PLUGIN_VERSION', '1.0.1');

$app = new Fsylum\RegistrationPassword\App;

$app->addService(new Fsylum\RegistrationPassword\WP\Auth);

// Finally run it
$app->run();
  • 加载自动加载器;
  • 初始化App容器;
  • 注册一个服务:Fsylum\RegistrationPassword\WP\Auth
  • 调用$app->run()启动所有服务,关注Auth服务注册了哪些WordPress钩子

2、服务启动逻辑:App::run()

php 复制代码
// src/App.php
<?php

namespace Fsylum\RegistrationPassword;

use Fsylum\RegistrationPassword\Contracts\Runnable;

class App
{
    protected $services = [];

    public function addService(Runnable $service)
    {
        $this->services[] = $service;
    }

    public function run()
    {
        foreach ($this->services as $service) {
            $service->run();
        }
    }
}
  • 遍历所有已注册服务,调用其run() 方法
  • 当前只有一个服务:Auth,那么直接进入Auth::run()

3、钩子注册分析

php 复制代码
// src/WP/Auth.php
public function run()
{
    add_action('login_enqueue_scripts', [$this, 'loadUserProfileJs']);
    add_action('register_form', [$this, 'addPasswordFields']);
    add_filter('registration_errors', [$this, 'validatePassword']);
    add_filter('random_password', [$this, 'setUserPassword']); // 关键钩子
    add_filter('wp_new_user_notification_email', [$this, 'modifyEmailNotification'], 10, 2);
}
  • random_password全局过滤器,任何调用wp_generate_password()的地方都会触发
  • 不仅用于新用户注册,还用于:
  • 密码重置密钥生成(retrieve_password())
  • 用户导入、CLI 脚本、插件兼容等场景

4、核心逻辑函数

  • 继续查看setUserPassword()方法。
php 复制代码
// src/WP/Auth.php
    public function setUserPassword($password)
    {
        if (!isset($_POST['fs_is_password_for_registration']) ) {
            return $password;
        }

        if (sanitize_text_field($_POST['fs_is_password_for_registration']) !== 'yes') {
            return $password;
        }

        return $_POST['pass1'];
    }
  • 检查是否存在fs_is_password_for_registration字段, 若不存在,返回原密码(安全路径)。
  • 检查该字段值是否为 yes, 使用 sanitize_text_field() 处理,但该函数仅移除无效字符,不影响yes 判断。
  • 若满足条件,直接返回 $_POST['pass1'], 未做任何类型校验、长度限制、转义或上下文判断。

思考、疑问:

这个函数是否只应在"注册"时生效?

bash 复制代码
从函数命名和上下文看,设计意图确实是仅用于注册流程;
但代码中没有任何机制确保这一点:
未检查当前请求是否为 action=register;
未验证 nonce;
未判断用户状态(如是否已登录);
未限制 HTTP 方法或路径。

只要任意请求携带这两个 POST 参数,即可触发覆盖行为。

假设下插件作者原本思想:

  • 新用户注册(预期场景)
bash 复制代码
路径:/wp-login.php?action=register
流程:
用户填写表单(含 pass1, pass2, fs_is_password_for_registration=yes);
WordPress 调用 wp_create_user() → 内部调用 wp_generate_password();
apply_filters('random_password', ...) → 触发 setUserPassword();
使用用户输入的密码创建账户。
  • 盗刷账户(意外场景)
bash 复制代码
路径:/wp-login.php?action=lostpassword
流程:
用户提交用户名;
WordPress 调用 retrieve_password();
内部调用 wp_generate_password(20, false) 生成 重置密钥(reset key);
apply_filters('random_password', $key) → 同样触发 setUserPassword();
若请求中携带 fs_is_password_for_registration=yes 和 pass1=xxx,则:
原本的随机密钥被替换为 xxx;
该值被哈希后存入数据库;
邮件中包含明文 key=xxx。

5、漏洞根因总结

bash 复制代码
入口无害:插件初始化逻辑正常;
问题在于钩子注册过宽:将仅适用于注册场景的逻辑,挂载到全局过滤器 random_password;
场景判断缺失:setUserPassword() 仅依赖一个可伪造的 POST 字段判断上下文,未绑定到特定 action 或权限状态;
数据流失控:用户输入 $_POST['pass1'] 被直接返回,无任何安全边界;
核心危害路径:在"忘记密码"流程中,重置密钥被攻击者控制,从而接管账户。

0x4 修复建议

修复方案

  1. 升级到最新版本:建议受影响的用户升级至2.0.1以上版本:插件地址
  2. 临时防护措施:
    关注异常请求日志 :关注异常的密码修改行为,尤其是来自非预期行为
    启用 WAF/IPS 规则 :拦截异常请求/wp-login.php?action=lostpassword地址,拦截异常的密码修改请求。
    加强输入验证:对所有涉及用户身份变更的操作,强制实施双重验证机制

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

相关推荐
北岛寒沫19 小时前
北京大学国家发展研究院 经济学原理课程笔记(第二十一课 金融学基础)
经验分享·笔记·学习
扫地的小何尚20 小时前
NVIDIA RTX PC开源AI工具升级:加速LLM和扩散模型的性能革命
人工智能·python·算法·开源·nvidia·1024程序员节
浩瀚地学1 天前
【Java】常用API(二)
java·开发语言·经验分享·笔记·学习
Kratzdisteln1 天前
【MVCD】PPT提纲汇总
经验分享·python
恩创软件开发1 天前
创业日常2026-1-8
java·经验分享·微信小程序·小程序
WordPress学习笔记1 天前
给wordpress网站的图片加alt标签
wordpress
拾荒的小海螺1 天前
开源项目:Three.js 构建 3D 世界的工具库
javascript·3d·开源
源代码•宸1 天前
Leetcode—39. 组合总和【中等】
经验分享·算法·leetcode·golang·sort·slices
三流架构师1 天前
高中英语资源合集(第二辑)
经验分享