第 7 章 注入攻击
注入攻击是 Web 安全领域中一种最为常见的攻击方式。
安全设计原则----“数据与代码分离”原则,它可以说是专门为了解决注入攻击而生的。
注入攻击的本质,是把用户输入的数据当作代码执行。这里有两个关键条件:
- 用户能够控制输入
- 原本程序要执行的代码,拼接了用户输入的数据。
7.1 SQL注入
在 SQL 注入的过程中,如果网站的 Web 服务器开启了错误回显,则会为攻击者提供极大的便利,比如攻击者在参数中输入一个单引号“ '
”,引起执行查询语句的语法错误,服务器直接返回了错误信息。
7.1.1 盲注( Blind Injection )
Web 服务器关闭了错误回显,这时就没有办法成功实施 SQL 注入攻击了吗?攻击者为了应对这种情况,研究出了“盲注”( Blind Injection )的技巧。
“盲注”就是在服务器没有错误回显时完成的注入攻击。
攻击者必须找到一个方法来验证注入的 SQL 语句是否得到执行。
最常见的盲注验证方法时,构造简单的条件语句,根据返回页面是否发生变化,来判断 SQL 语句是否得到执行。
7.1.2 Timing Attack
在 MySQL 数据库中,有一个 BENCHMARK() 函数,它是用来测试函数性能的。
利用 BENCHMARK() 函数,可以让同一个函数执行若干次,使得结果返回的时间比平时要长;通过时间长短的变化,可以判断出注入语句是否执行成功。 这是一种边信道攻击,这个技巧在盲注中被称为 Timing Attack 。
7.2 数据库攻击技巧
找到 SQL 注入漏洞,仅仅是一个开始。要实施一次完整的攻击,还有许多事情需要做。
SQL 注入是基于数据库的一种攻击。不同的数据库有着不同的功能、不同的语法和函数,因此针对不同的数据库,SQL 注入的技巧也有所不同。
7.2.1 常见的攻击技巧
SQL 注入可以拆解出数据库的对应版本
在注入攻击的过程中,常常会用到一些读写文件的技巧。比如在 MySQL 中,就可以通过 LOAD_FILE() 读取系统文件,并通过 INTO DUMPFILE 写入本地文件。当然这要求当前数据库用户有读写系统相应文件或目录的权限。
... union select 1,1 LOAD_FILE( '/etc/passwd' ),1,1;
如果要将文件读出后,再返回结果给攻击者,则可以使用下面这个技巧:
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_INFO() 将系统文件读出,再通过 INTO DUMPFILE 将该文件写入系统中,然后通过 LOAD DATA INFILE 将文件写入系统中,然后通过 LOAD DATA INFILE 将文件导入创建的表中,最后就可以通过一般的注入技巧直接操作表数据了。
写入文件的技巧,经常被用于导出一个 Webshell,为攻击者的进一步攻击做铺垫。因此在设计数据库安全方案时,可以禁止普通数据库用户具备操作文件的权限。
7.2.2 命令执行
在 MySQL 中,除了可以通过导出 webshell 间接地执行命令外,还可以 利用“用户自定义函数”的技巧,即 UDF(User-Defined Functions)来执行命令。
在建立数据库账户时应该遵循“最小权限原则”,尽量避免给 Web 应用使用数据库的管理员权限。
7.2.3 攻击存储过程
利用存储过程直接攻击外,存储过程本身也可能会存在注入漏洞。
7.2.4 编码问题
“基于字符集”的注入攻击技巧。
统一数据库、操作系统、Web 应用所使用的字符集,以避免各层对字符的理解存在差异。
基于字符集的攻击并不局限于 SQL 注入,凡是会解析数据的地方都可能存在此问题。比如再 XSS 攻击时,由于浏览器与服务器返回的字符编码不同,也可能会存在字符集攻击。
如果因为种种原因无法统一字符编码,则需要单独实现一个用于过滤或转义的安全函数,在其中需要考虑字符的可能范围。
根据不同字符集来限制用户输入数据的字符允许范围,以实现安全过滤。
7.2.5 SQL Column Truncation
2008 年 8 月,Stefan Esser 提出了一种名为“ SQL Column Truncation ”的攻击方式,在某些情况下,将会导致发生一些安全问题。
7.3 正确地防御 SQL 注入
- 找到所有的 SQL 注入漏洞;
- 修补这些漏洞。
7.3.1 使用预编译语句
防御 SQL 注入的最佳方式,就是使用预编译语句,绑定变量。
7.3.2 使用存储过程
使用安全的存储过程对抗 SQL 注入。
存储过程中也可能会存在注入问题,因此应该尽量避免在存储过程内使用动态的 SQL 语句。如果无法避免,则应该严格的输入过滤或者是编码函数来处理用户的输入数据。
7.3.3 检查数据类型
7.3.4 使用安全函数
从数据库自身的角度来说,应该使用 最小权限原则 ,避免 Web 应用直接使用 root 、dbowner 等高权限账户直接连接数据库。如果有多个不同的应用在使用同一个数据库,则也应该为每个应用分配不同的账户。Web 应用使用的数据库账户,不应该有创建自定义函数、操作本地文件的权限。
7.4 其他注入攻击
除了 SQl 注入外,在 Web 安全领域还有其他的注入攻击,这些注入攻击都有相同的特点,就是应用违背了“数据与代码分离”原则。
XML 攻击
代码注入
代码注入往往是由于不安全的编程习惯所造成的,危险函数应该尽量避免在开发中使用,可以在开发规范中明确指出哪些函数是禁止使用的。这些危险函数一般在开发语言的官方文档中可以找到一些建议。
7.4.3 CRLF 注入
CRLF 实际上是两个字符:CR 是 Carriage Return( ASCII 13, \r ) ,LF 是 Line Feed ( ASCII 10, \n ) 。 \r\n
这两个字符是用于表示换行的,其十六进制编码分别为 0x0d 、0x0a。
CRLF 常被用做不同语义之间的分隔符。因此通过“注入 CRLF 字符”,就有可能改变原有的语义。
在 HTTP 协议中,HTTP 头是通过 “ \r\n
”来分割的。因此如果服务器端没有过滤 “ \r\n
”,而又把用户输入的数据放在 HTTP 头中,则有可能导致安全隐患。这种在 HTTP 头中的 CRLF 注入,又可以称为 “ Http Response Splitting ”。
对抗 CRLF 的方法非常简单,只需要处理好 “ \r ”、" \n " 这两个保留字符即可,尤其是那些使用“换行符”作为分隔符的应用。
7.5 小结
注入攻击时应用违背了“数据与代码分离原则”导致的结果。它有两个条件:
- 用户能够控制数据的输入;
- 代码拼凑了用户输入的数据,把数据当作代码执行了。
在对抗注入攻击时,只需要牢记“数据与代码分离原则”,在“拼凑”发生的地方进行安全检查,就能避免此类问题。