WordPress 4.7和4.7.1的提权漏洞

今天VempX联络我说Blog被黑了。结果我一看我俩的Blog、爱乳发布页都被黑了。症状是最新一条Blog文章遭到修改,标题和内容变成了“Hacked By BALA SNIPER”。

我的WordPress用的是强密码且不存在密码重复使用的问题,平日里后台也做了不少加强安全性的工作。因此可以初步排除爆破、撞库、社工渗透的可能性。

网上查了一下,同样症状的应该不少,日本WordPress社区有人提问相同的问题。原因貌似是WordPress 4.7和4.7.1的权限验证存在逻辑漏洞,造成恶意用户可以跳过用户登录直接修改某条特定ID的文章。

于是赶紧确认了本地保存的数据库备份,2月6日的数据库备份内容正常,而2月7日的数据库备份显示数据已经遭到恶意修改。于是赶紧回滚数据库并升级了WordPress版本。

因为比较好奇这次攻击的原理,又找了一些文章。

根据文章中的线索对服务器的Access Log进行了排查,找到了关联性较大的日志条目。

54.91.*.* – – [06/Feb/2017:19:05:27 +0800] “GET /wp-json/wp/v2/posts/ HTTP/1.1” 200 127421 “-” “Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.63 Safari/537.31” –
54.91.*.* – – [06/Feb/2017:19:05:29 +0800] “POST /wp-json/wp/v2/posts/210 HTTP/1.1” 200 3431 “-” “Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.63 Safari/537.31” –

相关入侵发生在2月6日,是针对wp-json接口进行的。

问题的所在

当用户试图编辑文章时,wp会调用下面两个方法。

  1. update_item_permissions_check (确认用户权限)
  2. update_item(保存更改)

先看update_item_permissions_check方法。

从方法名称可以推测,该方法用于修改文章时确认用户权限。如果用户有权修改,则返回true,否则返回WP_Error。我们来参照该方法代码(为更佳清晰地表现该方法的逻辑问题,对部分代码进行了省略):

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 ) ) {
……
}
if ( ! empty( $request[‘author’] ) && get_current_user_id() !== $request[‘author’] && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
……
}
if ( ! empty( $request[‘sticky’] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
……
}
if ( ! $this->check_assign_terms_permission( $request ) ) {
……
}
return true;
}

该方法的思路是:获取用户提交的request中的id参数,通过查询数据库获得文章信息,通过文章信息判断用户权限(四个if语句),如果存在不满足的条件就返回WP_Error,否则返回true。

按照这一逻辑,我们列出下面几种情况:

  1. 用户提供了不存在的id($post为空):方法返回true;
  2. 用户提供了实际存在的id,且用户有权编辑文章:方法返回true;
  3. 用户提供了实际存在的id,但用户无权编辑文章:方法返回WP_Error;

这段代码原本的意图,应该是第二种或第三种情况。但由于逻辑不够严谨,造成用户提交了不存在的文章时,代码会跳过所有判断直接返回true(第2行->第16行)。

再来看update_item方法。

public function update_item( $request ) {
$id = (int) $request[‘id’];
$post = get_post( $id );
if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
return new WP_Error( ‘rest_post_invalid_id’, __( ‘Invalid post ID.’ ), array( ‘status’ => 404 ) );
}
$post = $this->prepare_item_for_database( $request );
if ( is_wp_error( $post ) ) {
return $post;
}
……
}

按照这段代码,如果文章本身不存在,攻击者是无法进行任何操作的(会被第4行的if挡掉)。但是,如果构造如下请求,又会造成什么情况呢:

  • 正常请求:/wp-json/wp/v2/posts/1234
  • 恶意请求:/wp-json/wp/v2/posts/1234?id=1234helloworld

正常情况下,id会被设定为1234。如果id是1234的文章不存在,权限验证会返回true,但update_item会返回Error。如果该文章存在,系统会正常判断权限并编辑该文章。

但如果向代码提交上述恶意请求,update_item_permissions_check的id会被设定为1234helloworld。在权限验证函数中,由于没有对用户输入进行过滤,wp会试图去查询id为1234helloworld的文章,结果当然是不存在的,因此权限验证会返回true。接下来到update_item这里,由于该方法对用户输入进行了过滤,1234helloworld会被(int)整形,结果为“1234”。此时id会被设定为1234。如果id为1234的文章存在,则用户无需登录,就获得了编辑id为1234的文章的权限。

总结教训

  • 对权限的判断上逻辑不够严谨,没有照顾到所有可能出现的情况。特别是针对能够对内容进行修改的权限的判断时,应该默认返回false,只针对特定的、合法的情形才返回true。
  • 对用户输入过滤不够严格,导致程序接受了恶意用户构造的不存在的id。
  • 不同方法各自使用各自的参数,分别进行过滤,造成验证机制缺乏统一性、各自为政。造成验证出现疏漏。

在最新版4.7.2版中,只是添加了文章是否存在的判断逻辑,参数各自为政的问题依旧没有得到根本解决,为今后新的漏洞埋下了隐患。

结论

WordPress 4.7及4.7.1版本包含严重安全漏洞,使得非授权用户也可以利用漏洞编辑WordPress中的文章。目前已经有大量受到影响的Blog出现。任何正在使用受影响版本的用户都要立即升级到WordPress 4.7.2版。

《WordPress 4.7和4.7.1的提权漏洞》上有2条评论

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注