在日常开发过程中,程序员只关心 SQL 是否能实现预期的功能,而对于 SQL 的安全问题一般都不要太重视。实际上,如果 SQL 语句写作不当,将会给应用系统造成很大的安全问题。最重要的隐患就是 SQL 注入。
SQL 注入简介
结构化查询语言是一种用于和数据库交互的脚本语言。SQL 注入的本质就是将用户输入的内容当作代码执行。这里有两个关键条件:
- 用户能够控制输入
- 第二个是原本程序要执行的代码拼接了用户输入的数据
从而导致非法SQL语句的执行。
SQL注入有很大的危害性,攻击者可以直接利用它读取、修改、删除数据库内的数据,获取数据库中的用户名和密码等敏感信息,甚至可以获取到数据库管理员的权限,而且 SQL 注入很难防范。管理员无法通过系统安装补丁或进行简单的安全配置进行自我保护,一般防火墙也无法拦截SQL注入攻击。
比如用户登录时,验证程序的SQL注入(PHP为例):
<?php
$config['host'] = 'localhost';
$config['username'] = 'root';
$config['password'] = 'root';
$config['dbname'] = 'typecho';
$connect = mysqli_connect($config['host'],$config['username'],$config['password'],$config['dbname']) or dir('MySQL 连接失败');
$username = $_GET['username'];
$sql = "SELECT * FROM typecho_users WHERE `name` = '$username'";
$result = mysqli_query($connect,$sql);
$row = mysqli_fetch_row($result);
echo empty($row) ? '登录失败' : '登录成功';
然后提交URL:http://localhost/login.php?username=admin' or '1=1
结果发现,即使在用户名不存在的情况下依然可以登陆成功。SQL并没有按照我们预期去执行,这就是一个SQL注入的例子。
SQL注入的应对措施
PrepareStatement (预处理)
从PHP5开始,MySQLi 扩展中支持了 PrepareStatement
,使用预编译的方式执行代码时,admin' or '1=1
会被当作是一个完整的条件来执行,而不会执行 WHERE name = 'admin' OR 1=1
这样的条件。
<?php
$config['host'] = 'localhost';
$config['username'] = 'root';
$config['password'] = 'root';
$config['dbname'] = 'typecho';
$connect = new mysqli($config['host'],$config['username'],$config['password'],$config['dbname']) or dir('MySQL 连接失败');
$username = $_GET['u'];
$stmt = $connect->prepare("SELECT `uid`,`name` FROM typecho_users WHERE name = ?");
$stmt->bind_param('s',$username);
$stmt->execute();
$stmt->bind_result($uid,$name);
$stmt->fetch();
if(empty($uid)){
echo "用户 <code>{$username}</code> 不存在";
}else{
echo "用户 {$name} 存在,ID是 {$uid}";
}
使用应用程序提供的转换函数
很多应用程序都提供了对特殊字符转换的函数。合适的应用这些函数,可以防止应用程序用户输入使应用程序生成不期望的语句。
- MySQL C API:使用
mysql_real_escape_string()
API 调用 - MySQL++:使用
escape
和quote
修饰符。 - PHP:使用
mysql_real_escape_string()
函数(适用于PHP 4.3.0 版本)。从PHP5 开始,可以使用扩展的MySQLI
,该插件支持PrepareStatement
。 - Perl DBI: 使用
placeholders
或quote()
方法。 - Ruby DBI:使用
placeholders
或quote()
方法。
自己定义函数进行校验
如果现有的函数不能满足要求,则需要自己编写函数进行输入校验,输入验证是一个非常复杂的问题。输入验证的途径可以分为以下几种:
- 整理数据使之输入变得有效
- 拒绝已知的非法输入
- 只接受已知的合法输入
如果想要获得最好的安全状态,目前最好的解决方式是对用户提交或者可能改变的数据进行简单的分类,分别应用正则表达式来对用户提供的数据进行严格的检测和验证。
本笔记内容来自《深入浅出MySQL:数据库开发,优化与管理维护(第三版)》
本篇只是简单的讲解了一下SQL注入的概念和防范技巧,如果想深入了解注入攻击,本人推荐去看:《白帽子讲Web安全》,非常适合web安全入门的一本书