接上文 浅谈新形势下在线Web投票系统的攻防(1) ,本文主要讨论一种验证码绕过的情况和利用Http请求头字段X-Forwarded-For的IP绕过。
一种验证码绕过的情况上文用两种很不优雅的方式实现了案例二的刷票。当LZ同同伴准备把验证码识别和POST操作整合的时候,伙伴发现验证码原来可以不输入的,坑爹了,92%识别率的验证码破解工具派不上用场了。在仔细分析前端代码后我发现这个投票系统的验证码是js加载的:
刷票过程中模拟http post操作时直接把response的响应流读取出来进行处理,没有对html进行解析,这句 <body onload=]getcode();]> 中的js函数压根就没有被执行,而这个getcode()的作用是加载并显示验证码文件 /getcode.php 。这样验证码就没有被读取,服务器那头用来校验验证码也是没有被更新过的( 还是最近一次的值或者是初始化的值 ),看到这里如果你不懂怎么回事儿,建议仔细思考下验证码机制的工作原理。这也不能算是个bug,因为要是有意这样的效果也是不能避免的。在浏览器要产生这样的效果,有一种方法是不让浏览器加载/getcode.php这个页面,方法可以是Firefox的Autoproxy中对这个验证码图片页面url设置一个根本不存在的代理,这样这个图片就加载不了了。
但是!
但是这个漏洞是件 郎有情,妾有意 的事情,服务器为什么没有检测到验证码被禁用了没有被加载呢?后来拿到了那个系统的代码后我终于知道为什么了: 在处理投票post请求的代码中发现了这么个有bug的判断
{
$_SESSION [ 'code' ] = '' ;
echo '<script language= "javascript" >
alert( "验证码错误,请重新输入!" );
location = "javascript:history.go(-1)" ;
</script>';
exit ();
} 上述$code是post过来的,是一个空的字符串,就是说 $code==]" ;而$_SESSION['code']是不存在,空值,为 $_SESSION['code']==null ;而在php中 null==]" ,两者是等价的!八阿哥就这么来了!
受该怎么办: $_SESSION['code']初始化为一个非法的值,判断的时候再判断一下$_SESSION['code']的值是否合法。
利用X-Forwarded-For的IP绕过终于讲到本文的正经内容了,写这篇文章初衷其实就是讨论下IP绕过,但是又觉得讲IP绕过不讲刷票有点欠缺了,纠结了很久。
IP绕过的核心是HTTP协议头X-Forwarded-For的伪造,前几天我闲着蛋疼,就把Wiki上的X-Forwarded-For词条英文版照抄全部翻译了一遍, 在此 ,我想尽量表达完整英文版的意思,所以语言非常的生硬。关于这个协议字段头的官方文档 在此 。
从HTTP层上简单地讲:如果你的IP是10.23.231.23,通过一台透明的代理服务器上网,比如代理地址是10.23.231.1:8080,你请求一个web页面,这个HTTP请求被发到代理服务器后,服务器对该请求报头至少做了如下处理:
HTTP请求头上添加了 X-Forwarded-For:10.23.231.23 这么一行 如果特殊必要,HTTP请求头上添加了 CLIENT_IP :10.23.231.1 这么一行对于服务端,限制IP用的IP获取代码大多类似:
//传说这段代码是Discuz!中的,非常完整地表述了正常情况下访客的真实IP
function getip() {
if ( getenv ( 'HTTP_CLIENT_IP' ) && strcasecmp ( getenv ( 'HTTP_CLIENT_IP' ), 'unknown' )) {
$onlineip = getenv ( 'HTTP_CLIENT_IP' );
} elseif ( getenv ( 'HTTP_X_FORWARDED_FOR' ) && strcasecmp ( getenv ( 'HTTP_X_FORWARDED_FOR' ), 'unknown' )) {
$onlineip = getenv ( 'HTTP_X_FORWARDED_FOR' );
} elseif ( getenv ( 'REMOTE_ADDR' ) && strcasecmp ( getenv ( 'REMOTE_ADDR' ), 'unknown' )) {
$onlineip = getenv ( 'REMOTE_ADDR' );
} elseif (isset( $_SERVER [ 'REMOTE_ADDR' ]) && $_SERVER [ 'REMOTE_ADDR' ] && strcasecmp ( $_SERVER [ 'REMOTE_ADDR' ], 'unknown' )) {
$onlineip = $_SERVER [ 'REMOTE_ADDR' ];
}
return $onlineip ;
}
上面总共有三个字段 HTTP_CLIENT_IP、 HTTP_X_FORWARDED_FOR、REMOTE_ADDR 我在 这里 找到了解释并验证通过!验证过程如下:感谢同学借美国主机给我做测试:
测试代码:
<?php
echo '<p>getevn(\'HTTP_CLIENT_IP\')=' . getenv ( 'HTTP_CLIENT_IP' ). ';' ;
echo '<p>getevn(\'HTTP_X_FORWARDED_FOR\')=' . getenv ( 'HTTP_X_FORWARDED_FOR' ). ';' ;
echo '<p>getevn(\'REMOTE_ADDR\')=' . getenv ( 'REMOTE_ADDR' ). ';' ;
echo '<p>$_SERVER[\'REMOTE_ADDR\']=' . $_SERVER [ 'REMOTE_ADDR' ]. ';' ;
?> 通过一台普通非文艺非二逼的透明代理服务器访问页面,结果(三四行的结果一样,我隐藏了):
getevn(‘HTTP_CLIENT_IP’)=; getevn(‘HTTP_X_FORWARDED_FOR’)=172.23.60.145; getevn(‘REMOTE_ADDR’)=###.##.171.119; $_SERVER['REMOTE_ADDR']=###.##.171.119;
这种情况下getip()的值就是172.23.60.145,合法并且正确。
ADSL联网后直接访问页面:
getevn(‘HTTP_CLIENT_IP’)=; getevn(‘HTTP_X_FORWARDED_FOR’)=; getevn(‘REMOTE_ADDR’)=###.200.51.#9; $_SERVER['REMOTE_ADDR']=##.200.51.#9;
这种情况下getip()的值就是###.200.51.#9,合法并且正确。
ADSL联网后直接联网的状态下伪造http头(使用Firefox的Poster Addon,请求头Headers中加入一行X_FORWARDED_FOR=foo,如下图):
结果:
getevn(‘HTTP_CLIENT_IP’)=; getevn(‘HTTP_X_FORWARDED_FOR’)=foo; getevn(‘REMOTE_ADDR’)=###.200.51.#9; $_SERVER['REMOTE_ADDR']=###.200.51.#9;
伪造得非常好,这里我没有用一个合法的IPv4地址而是一个字符串,但是数据报还是顺利到达Web服务器并被识别。之后的测试中,我尝试在headers中加了CLIENT_IP,效果和XFF一样。随便网上找了一个在线代理,其中getevn(‘HTTP_X_FORWARDED_FOR’)显示空的,这应该就是传说中的匿名代理了吧!
受该怎么办:
XFF的中文wiki中提到:
完整起见,Web服务器应该记录请求来源的IP地址以及X-Forwarded-For 字段信息。
因此,请求来源的IP地址(也就是REMOTE_ADDR)和 X-Forwarded-For 都应该被记录下来。通常的刷票行为往往都是短时间内伪造大量投票请求的。虽然X-Forwarded-For是不一样的,但是REMOTE_ADDR往往是不变的。短时间内检测到含X-Forwarded-For信息的投票请求的REMOTE_ADDR的请求地址相同,就应该检查该代理服务器是否是合法的。就投票来说,另外一点要值得注意的是,X-Forwarded-For信息如果有的话,应该是一个 内网地址 ,如果是一个外网地址,那这家伙也是企图刷票吧,虽然手段不文艺不普通。
全文完毕 作者 McKelvin's Blog
查看更多关于浅谈新形势下在线Web投票系统的攻防(2) - 网站安的详细内容...