Akamai 在 2019 年研究表明 65.1% 的 Web 应用程序攻击来自 SQL 注入
SQL 注入的两种分类:
盲注:在服务器没有错误回显时完成的注入攻击。
Timing Attack:利用 MySQL 的
BENCHMARK()
函数。
数据库攻击技巧
常见攻击技巧
我们假设后台的服务存在这样一个没有校验的语句:
|
|
SQL 注入时基于数据库的一种攻击。不同的数据库有着不同的功能、不同的语法和函数:
SQL 注入可以猜解出数据库的对应版本,比如下面这段 Payload,如果 MySQL 的版本是 4,则会返回 TRUE:
1
http://victim.com/index.php?id=5 and substring(@@version, 1, 1)=4
测试表名
admin
是否存在,列名passwd
是否存在:1 2 3
http://victim.com/index.php?id=5 union all select 1,2,3 from admin http://victim.com/index.php?id=5 union all select 1,2,passwd from admin
想要进一步猜解除
username
和password
具体的值,可以通过判断字符的范围读出来:1 2
http://victim.com/index.php?id=5 and ascii(substring((select concat(username, 0x3a, passwd) from users limit 0,1),1,1))>64 ....
可见利用一个
sql
注入的过程非常繁琐,所以非常有必要使用一个自动化的工具来帮助完成整个过程,sqlmap
就是一个非常好的自动注入工具:1
python sqlmap.py -u "http://victim.com.index.php?id=5" --dump -T users
在 MySQL 中,可以通过
LOAD_FILE()
读取文件系统,并且通过INTO DUMPFILE
写入本地文件。另外,如果要将文件读出后,再把结果返还给攻击者,可以使用下面的技巧:1 2 3
create table potatoes(line BLOB); select 1,1,hex(LOAD_FILE('/etc/passwd')),1,1 into DUMPFILE '/tmp/potatoes'; LOAD DATA INFILE '/tmp/potatoes' into table potatos;
上面写入文件的技巧,经常被用于导出一个 webshell
,为攻击者的进一步攻击做铺垫。
因此再设计数据库安全方案时,可以禁止数据库用户具备操作文件的权限。
命令执行
除了可以通过导出 webshell
间接地执行命令外,还可以利用 “用户自定义函数 UDF” 的技巧来执行命令。大多数数据库一般都支持从本地文件系统中导入一个链接库文件作为自定义函数。
通过以下的语法就可以简历 UDF:
|
|
安全研究者们发现通过 lib_mysqludf_sys
中提供的几个函数(主要是 sys_eval
与 sys_exec()
)就可以执行系统命令。在攻击过程中,将 lib_mysqludf_sys.so
上传到数据库能访问的路径下,并且创建了 UDF 之后就可以执行系统命令了。这个链接库主要有以下四个函数:
sys_eval()
:执行任意命令,并且将输出返回;sys_exec()
:执行任意命令。并且将退出码返回;sys_get
、sys_set()
:获取、修改(创建)一个环境变量;
共享链接库可以通过开源信息获得:
|
|
sqlmap
中也集成了这个功能:
|
|
UDF 不仅仅是 MySQL 的特性,其他数据库也有着类似的功能。利用 UDF 的功能实施攻击的技巧也大同小异。比如:
- 在 MS SQL-Server 中,可以直接使用存储过程
xp_cmdshell
执行命令; - 在 Oracle 数据库中,如果服务器同时还有 Java 环境,那么也可能造成命令执行。
一般来说,在数据库中执行系统命令,要求具有较高的权限。
攻击存储过程
存储过程为数据库提供了强大的功能,它与 UDF 很像,但它必须使用 CALL
或者 EXECUTE
来执行。在注入攻击的过程中,存储过程将为攻击者提供很大的便利。
在微软 SQL-Server 中 xp_cmdshell
可谓是臭名昭著了,它在 2000 版本中是默认开启的,但在 2005 以及以后的版本中则被默认禁止了:
|
|
但是如果当前数据库用户拥有 sysadmin
权限,则可以使用 sp_configure
(2005 与 2008 版本)或 sp_addextendedproc
(2000 版本)重新开启它:
|
|
除了 xp_cmdshell
可以用于执行命令外。还有其他一些有用的函数。比如 xp_regread
可以操作注册表等等。
SQL Column Truncation
在 MySQL 的配置选项中,有一个 sql_mode
选项。以下命令开启 strict
模式:
|
|
当 MySQL 的 sql-mode
设置为 default
时(即没有开启 STRICT_ALL_TABLES
),MySQL 对于超长值只会提示 warning
而不是 error
,这可能导致一些截断问题。
如果用户在数据库中插入一个之前已经存在的数据,则可能造成一些越权访问。
防御 SQL 注入
一些绕过技巧
SQL 注入的防御并不是一件简单的事情,开发者往往会走入一些误区。比如只对用户输入做一些 escape
处理,这是不够的。比如:
|
|
当攻击者构造如下的注入代码时,仍然会注入成功:
|
|
这是因为 php_real_escape_string()
这个函数仅仅会转义:
'
、"
、\r
、\n
、NULL
、Ctrl-Z
那么是否增加一些比如空格之类的过滤字符,就可以了呢?基于黑名单的方法总是存在问题的。比如下面就是几个不需要使用空格的例子:
|
|
不需要引号,可以用十六进制编码字符串:
|
|
那么应该如何防御 SQL 注入呢?
使用预编译
防御 SQL 注入的最佳方式就是:使用预编译语句绑定变量。
比如在 JAVA 中使用预编译的 SQL 语句:
|
|
在 PHP 中绑定变量的示例:
|
|
使用存储过程
除使用预编译语句外,我们还可以使用安全的存储过程对抗 SQL 注入。这个方法与前者类似,区别就是存储过程需要将 SQL 语句定义在数据库中。但需要注意的是,存储过程中也可能会存在注入问题。因此应该尽量避免在存储过程内使用动态的 SQL 语句。
下面那是一个在 Java 中调用存储过程的例子:
|
|
检查数据类型
检查输入类型,在很大程度上可以对抗 SQL 注入。
比如下面这段代码,就限制了输入数据的类型只能为整数:
|
|
使用安全函数
一般来说,各种 WEB 语言都实现了一些编码函数,可以帮助对抗 SQL 注入。各种数据库厂商都对这些编码函数进行了一些 “指导”。