8.1 钻GPC等转义的空子
GPC会自动把我们提交上去的单引号等敏感字符给转义掉,这样我们的攻击代码就没法执行了,GPC是PHP天生自带的功能,所以是我们最大的天敌。不过不要担心,GPC并不是把所有变量都进行了过滤,反而人们容易忽视而又用得多的$_SERVER变量没有被GPC过滤,包括编码转换的过程中,部分情况下我们也是可以干掉GPC的转义符号,是不是有点小激动?下面我们来仔细了解下。
8.1.1 不受GPC保护的$_SERVER变量
GPC上面我们已经介绍过,是用来过滤request中提交的数据,将特殊字符进行转义来防止攻击,在PHP5之后用$_SERVER取到的header字段不受GPC影响,所以当GPC开启的时候,它里面的特殊字符如单引号也不会被转义掉,另外一点是普通程序员很少会考虑到这些字段被修改。而在header注入里面最常见的是user-agent、referer以及client-ip/x-forward-for,因为大多的Web应用都会记录访问者的IP以及referer等信息。同样的$_FILES变量也一样不受GPC保护。
测试代码如下:
< ? php
echo 'GPC'.get_magic_quotes_gpc ();
echo '<br /> client-ip = '.$_SERVER["HTTP_CLIENT_IP"] ;
echo '<br />$_GET[a] = '.$_GET['a'] ;
测试截图见图8-1。
图 8-1
8.1.2 编码转换问题
本书前面第4章介绍过宽字节注入,这就是一种非常典型的编码转换问题导致绕过GPC的方式。我们之前的举例说明,给一个查询页面ID参数请求/1.php?id=-1%df’and 1=1%23时,这时MySQL运行的SQL语句为:
select * from user where id= ’ 1 運’ and 1=1# ’
这是由于单引号被自动转义成\’,前面的%df和转义字符\反斜杠(%5c)组合成了%df%5c,也就是“運”字,这时候单引号依然还在,于是成功闭合了前面的单引号。
这个例子讲的是PHP与MySQL交互过程中发生编码转换导致的问题,而其实只要发生编码转换就有可能出现这种问题,也就是说在PHP自带的编码转换函数上面也会存在这个问题,比如mb_convert_encoding()函数。
我们来证实一下,代码如下:
<meta http-equiv="Content-Type" content="text/html ; charset=utf-8"/>
< ? php
$sql="where id='".urldecode ( "-1%df%5c' -- " ) ."'" ;
print_r ( mb_convert_encoding ( $sql , "UTF-8" , "GBK" ));? >
这里要注意的是,把网页和文件编码都设置成UTF-8,不然浏览器会自动转码,这段代码是把UTF-8编码转换成GBK,运行这段代码,输出如下:
where id='-1 運 ' -- '
可以看到也成功闭合了前面的单引号。
这种方式造成的SQL注入也有不少先例,比如ecshop就出过多次这个问题,我们来看看出现这个问题的核心代码,代码位置在includes/cls_iconv.php文件的chinese类中的Convert()函数:
function Convert ( $source_lang , $target_lang , $source_string = '' )
{
/****** 省略 ****/
if (( $this->iconv_enabled || $this->mbstring_enabled ) && !( $this-> config['source_lang'] == 'GBK' && $this->config['target_lang'] == 'BIG-5' ))
{
if ( $this->config['target_lang'] ! = 'UNICODE' )
{
$string = $this->_convert_iconv_mbstring ( $this-> SourceText , $this->config['target_lang'] , $this-> config['source_lang'] );
/* 如果正确转换 */
if ( $string )
{
return $string ;
}
}
else
{
$string = '' ;
$text = $SourceText ;
while ( $text )
{
if ( ord ( substr ( $text , 0 , 1 )) > 127 )
{
if ( $this->config['source_lang'] ! = 'UTF-8' )
{
$char = $this->_convert_iconv_mbstring ( substr ( $text , 0 , 2 ), 'UTF-8' , $this->config ['source_lang'] );
}
else
这个函数的作用是将UTF-8的编码转换成GBK,本函数调用到$this->_convert_iconv_mbstring()函数,我们跟进去看看,代码如下:
function _convert_iconv_mbstring ( $string , $target_lang , $source_lang )
{
if ( $this->iconv_enabled )
{
$return_string = @iconv ( $source_lang , $target_lang , $string );
if ( $return_string ! == false )
{
return $return_string ;
}
}
if ( $this->mbstring_enabled )
{
if ( $source_lang == 'GBK' )
{
$source_lang = 'CP936' ;
}
if ( $target_lang == 'GBK' )
{
$target_lang = 'CP936' ;
}
$return_string = @mb_convert_encoding ( $string , $target_lang , $source_lang );
if ( $return_string ! == false )
可以看到最终调用iconv()函数或者mb_convert_encoding()函数来进行转码,如果调用这个函数之后没有再次过滤,则会存在注入问题。