安全研究

威胁情报
WordPress REST API 内容注入漏洞分析

WordPress是一种使用PHP语言开发的博客平台,用户可以在支持PHP和MySQL数据库的服务器上架设属于自己的网站。也可以把 WordPress当作一个内容管理系统来使用。


WordPress 在4.7.0版本之后将REST API插件集成到默认功能之中。REST
API为WordPress的使用者提供了一个方便快捷的管理接口。在WordPress
4.7.0-4.7.1版本中存在着一个越权漏洞,成功的利用这个漏洞,可以绕过管理员权限对文章进行增删改查操作。


影响版本


WordPress 4.7.0-4.7.1


漏洞分析


在正式的漏洞分析开始前,先来简单介绍下REST API的使用。官网给出的介绍如下



具体使用详情请参照REST API Handbook


https://developer.wordpress.org/rest-api/


在使用api对文章进行操作之前,需要对操作进行授权,授权方式有三种:cookie、oauth和简单认证。如果不进行授权直接通过api对文章进行修改操作,会返回一个401,如下图所示



如果想成功利用漏洞,必须绕过权限管理。我们跟踪下后台update处权限管理代码



public function update_item_permissions_check( $request ) {

   $post = get_post( $request['id'] );
   $post_type = get_post_type_object( $this->post_type );

   if ( $post && ! $this->check_update_permission( $post ) ) {
      return new WP_Error( 'rest_cannot_edit' __( 'Sorry you are not allowed to edit this post.' ) array( 'status' => rest_authorization_required_code() ) );
   }

   if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
      return new WP_Error( 'rest_cannot_edit_others' __( 'Sorry you are not allowed to update posts as this user.' ) array( 'status' => rest_authorization_required_code() ) );
   }

   if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
      return new WP_Error( 'rest_cannot_assign_sticky' __( 'Sorry you are not allowed to make posts sticky.' ) array( 'status' => rest_authorization_required_code() ) );
   }

   if ( ! $this->check_assign_terms_permission( $request ) ) {
      return new WP_Error( 'rest_cannot_assign_term' __( 'Sorry you are not allowed to assign the provided terms.' ) array( 'status' => rest_authorization_required_code() ) );
   }

   return true;
}




如上图所示,在这里if语句中,前半部分$post为真,后半部分check_update_permission()函数对$post的权限进行判断,结果为假,导致返回Sorry
you are not allowed to edit this post信息。


这个漏洞的挑战就在于,如何成功的绕过update_item_permissions_check()模块,使其最终return true。


我们回头看update_item_permissions_check()函数,



注意到$post = get_post( $request[‘id’] );这一行,这一行的作用是判断提交的文章id是否存在。



function get_post( $post = null $output = OBJECT $filter = 'raw' ) {
   if ( empty( $post ) && isset( $GLOBALS['post'] ) )
      $post = $GLOBALS['post'];

   if ( $post instanceof WP_Post ) {
      $_post = $post;
   } elseif ( is_object( $post ) ) {
      if ( empty( $post->filter ) ) {
         $_post = sanitize_post( $post 'raw' );
         $_post = new WP_Post( $_post );
      } elseif ( 'raw' == $post->filter ) {
         $_post = new WP_Post( $post );
      } else {
         $_post = WP_Post::get_instance( $post->ID );
      }
   } else {
      $_post = WP_Post::get_instance( $post );
   }

   if ( ! $_post )
      return null;

   $_post = $_post->filter( $filter );

   if ( $output == ARRAY_A )
      return $_post->to_array();
   elseif ( $output == ARRAY_N )
      return array_values( $_post->to_array() );

   return $_post;
}



可见,如果id对应的文章不存在,则返回null。


如果我们输入的url是这种形式


http://192.168.3.112/wordpress/index.php/wp-json/wp/v2/posts/1/?id=1grq


get_post()函数返回值一定为null,这样会使得$post值为null,回头来看update_item_permissions_check()函数,这时update_item_permissions_check()函数的返回值竟然为true了!


既然update_item_permissions_check()函数的返回值为true,说明我们绕过了update_item_permissions_check()权限验证,但是id为‘1grq’,根本不存在这样数字加字母组合的文章id,那怎么才能对指定文章进行越权操作呢?


有趣的事情发生了,让我们来看update_item()函数,这个函数是用来update通过权限验证的数据,我们看看它是怎么定义的



注意第二行,这里将$request[‘id’]进行了int类型的数值转换,在php中,$request[‘id’]
=”1grq”会被转换为数值类型1,如下图演示



所以这里的$id又由‘1grq’变回1了,get_post()函数也会找到对应id为1的文章了。


漏洞利用


目前已经有研究员在GitHub上给出相应的poc,链接如下

https://gist.github.com/leonjza/2244eb15510a0687ed93160c623762ab


官方修补方案分析



在4.7.2版本中,如果get_post()判定$post结果为 false,则直接返回$post,避免$post进入下层if判定绕过权限检查。


修补防御


升级wordpress至最新版本(4.7.2)。


浏览次数:

关 闭