WordPress Bricks(CVE-2024-25600)分析

8 分钟

前言

在玩手机的时候看到了一公众号的推文,标题是WordPress爆炸0day 直接RCE! ,标题有点夸张,我以为是Wordpress本身的漏洞,点了进去看到是一个名为Bricks Builder插件的未授权RCE漏洞,但是只给出了payload,看到了payload于是在网上找分析文章,并没有找到,于是找了源码自己来简单分析下,由于并没有用过这个插件,也没有仔细看大部分源码,因此以下分析不一定十分准确。

关于Bricks Builder

Bricks Builder 是一个 WordPress 页面构建插件,它的主要功能是让用户可以通过直观的界面和拖放操作来创建自定义的网页布局。使用 Bricks Builder,用户可以轻松地设计和定制其网站的页面,而无需编写任何代码。

以下是 Bricks Builder 插件的一些主要功能和特点:

  1. 拖放构建:Bricks Builder 提供直观的拖放构建界面,让用户可以通过拖放各种元素来建立网页布局,如文本、图像、按钮、分割线等。
  2. 预设模块:插件提供了各种预设的模块和布局,用户可以直接使用这些模块来快速构建页面,节省时间。
  3. 响应式设计:Bricks Builder 支持响应式设计,用户可以针对不同设备(如电脑、平板、手机)定制页面布局和样式,确保页面在各种设备上都能正常显示。
  4. 动画效果:插件提供了一些动画效果和过渡效果,用户可以为页面元素添加动感并提升用户体验。
  5. 定制样式:用户可以自定义页面的样式,如颜色、字体、间距等,以实现更个性化的设计。
  6. 快速预览:Bricks Builder 允许用户实时预览页面的变化,以便及时调整和优化页面布局。

漏洞分析

漏洞的最终触发代码是在includes/query.phpprepare_query_vars_from_settings函数中,这个函数的功能从settings变量中获取http请求的参数,换一句话就是settings变量存储了一些http请求的参数,漏洞的触发点是在$user_result = eval( $php_query_raw );语句中,而这个变量是用户可控的。当settings数组中useQueryEditorObjectType属于post,term,user其中之一,并且queryEditor不为空的时候,会通过bricks_render_dynamic_data执行数据渲染操作,其中

$query_vars['queryEditor'] 是一个存储动态数据配置的数组,该数组包含了查询参数和条件。

$post_id 是当前页面或发布的 ID,用于指定要渲染动态数据的特定页面或发布。

image-20240224202728538

1708777690477

bricks_render_dynamic_data函数实际上似乎只是根据queryEditor是否出现某些内容,做了一些过滤操作,也就是说$php_query_raw完全可被控制传入到eval中。image-20240224203737280

往上寻找,在query.phpQuery类构造函数能够直接触发prepare_query_vars_from_settings,需要的条件是进入else循环中,也就是element数组中的id的值为空即可。

image-20240224204054621

继续向上找,找在哪里会实例化Query类,在ajax.php#render_element中存在Query的实例化,需要的条件就是$loop_elementfalse,这里本身赋值为false,只需要POST请求中保持没有loopElement即可保持为false

image-20240224204654051

当然这里实现会经过verify_request的验证,除了验证nonce的值是否正确歪,通过传入postId来判断当前用户是否有权限使用页面构建器(builder),如果用户没有权限。

image-20240224204825588

verify_nonce方法是用于验证nonce是否正确,其实也是后面render_element_permissions_check的检查,因为这里的nonce 是被返回到了前端可见的,因此这个漏洞也称为了未授权RCE

image-20240224205003690

再看这个函数的下面,它会从elements中获取name,并且通过name 获取一个类,判断这个类是否存在,如果不存在会抛出doesn't exist内容,从而导致RCE失败。

image-20240224205234385

elements.php中,初始化就定义了很多name的名称,既然放在了初始化,那么必定就是存在可用的,这也是为什么payload中要赋值name:container,其实这里赋值其它比如section也是可以的。

image-20240224205404477

继续查找render_element的调用方法,在api.php中存在命名相同的方法调用了Ajax#render_element,这里的api.php实际上是一个处理注册的 REST API 端点之一的文件,

image-20240224205619542

在它的自定义初始化端点函数中可以看到它定义了很多能够注册的REST路由,API_NAMESPACE的值就是/bricks/v1/,通过后面拼接某个字符串的方式触发对应的callback回调函数以及权限检查,这里的render_element_permissions_check就是前文所说的nonce随机数,是能够被看到的,仅仅只检查了随机数的值,而没有检查用户的权限。

image-20240224205805767

因此,整个payload为:

{
"postId":"1",
"nonce":"3d6020fb9a",
"element":{
    "name":"container",
    "settings":{
        "hasLoop":"true",
        "query":{
            "useQueryEditor":true,
            "queryEditor":"system('whoami');throw new Exception();", //throw new Exception()是为了抛错回显
            "objectType":"post"
        }
    }
}
}

还有一个疑问是hasLoop的作用是什么,在去掉它的时候发现是不可以的,在builder.php中找到了hasLoop的说明,是用于表示当前的模块是否启动循环功能的,也可以理解为是否通过循环展示动态数据,可以看作是动态数据渲染的一个开关,因此要设置它为Trueimage-20240224210315939

漏洞验证

fofa语法:body="/wp-content/themes/bricks/"

获取nonce的值:

image-20240224210715543

直接进行RCE

image-20240224210524257

修复

官方在render_element_permissions_check中增加了对用户权限的检查,而不只检查nonce

image-20240224211023066

修复后的效果:

image-20240224211826862

总结

整个漏洞的产生原因是因为在定义API端点接口的时候,只对可见的随机数nonce进行了验证而并没有进行权限检查,导致能够传入数据从而控制动态渲染时$php_query_raw,最终传入到eval中,导致了代码的执行。

~  ~  The   End  ~  ~


 赏 
承蒙厚爱,倍感珍贵,我会继续努力哒!
logo图像
tips
(*) 6 + 8 =
快来做第一个评论的人吧~