Essential PHP Security -PHP安全基础 (连载三)
时间:2006-03-20
来源:互联网
上一章
下一章
第三章 数据库及SQL
PHP的作用常常是沟通各种数据源及用户的桥梁。事实上,有些人认为PHP更像是一个平台而不是一个编程语言。基于这些原因,PHP频繁用于与数据库的交流。
PHP可以很好的胜任这个任务,其原因特别是由于它能与很多种数据库连接。下面列举了PHP支持的小部分数据库:
DB2
ODBC
SQLite
InterBase
Oracle
Sybase
MySQL
PostgreSQL
DBM
与任何的远程数据存储方式相同,数据库本身也存在着一些风险。尽管数据库安全不是本书讨论的问题,但数据库安全是需要时刻注意的,特别是关于如何对待从数据库读取作为输入的数据的问题。
正如第一章所讨论的,所有输入必需要进行过滤,同时所有的输出必须要转义。当处理数据库时,意味着所有来自数据库的数据要过滤,所有写入数据库的数据要进行转义。
小提示
常犯的错误是忘记了SELECT语句本身是向数据库传送的数据。尽管该语句的目的是取得数据,但语句本身则是输出。
很多PHP开发人员不会去过滤来自数据库的数据,他们认为数据库内保存的是已过滤的数据。虽然这种做法的安全风险是很小的,但是这不是最好的做法,同时我也不推荐这样做。这种做法是基于对数据库安全的绝对信任,但同时违反了深度防范的原则。如果恶意数据由于某些原因被注入了数据库,如果你有过滤机制的话,就能发现并抓住它。请记住,冗余的安全措施是有价值的,这就是一个很好的例子。
本章包括了其它几个需要关心的主题,包括访问权限暴露及SQL注入。SQL注入是需要特别关注的,这是因为在流行的PHP应用中频繁发现了SQL注入漏洞。
3.1. 访问权限暴露
数据库使用中需要关注的主要问题之一是访问权限即用户名及密码的暴露。在编程中为了方便,一般都会用一个db.inc文件保存,如:
代码:
<?php$db_user = 'myuser';
$db_pass = 'mypass';
$db_host = '127.0.0.1';
$db = mysql_connect($db_host, $db_user, $db_pass);
?>
如果你读过http.conf(Apache的配置文件)的默认版本的话,你会发现默认的文件类型是text/plain(普通文本)。这样,如果db.inc这样的文件被保存在网站根目录下时,就引发了风险。所有位于网站根目录下的资源都有相应的URL,由于Apache没有定义对.inc后缀的文件的处理方式类型,在对这一类文件进行访问时,会以普通文本的类型进行返回(默认类型),这样访问权限就被暴露在客户的浏览器上了。
为了进一步说明这个风险,考虑一下一个以/www为网站根目录的服务器,如果db.inc被保存在/www/inc,它有了一个自已的URLhttp://example.org/inc/db.inc(假设example.org是主机域名)。通过访问该URL就可以看到db.inc以文本方式显示的源文件。无论你把该文件保存在/www哪个子目录下,都无法避免访问权限暴露的风险。
对这个问题最好的解决方案是把它保存在网站根目录以外的包含目录中。你无需为了达到包含它们的目的而把它们放至在文件系统中的特定位置,所有只要做的只是保证Web服务器对其有读取权限。因此,把它们放在网站根目录下是没有必要的风险,只要包含文件还位于网站根目录下,任何减少风险的努力都是徒劳的。事实上,你只要把必须要通过URL访问的资源放置在网站根目录下即可。毕竟这是一个公共的目录。
前面的话题对于SQLite数据库也有用。把数据库保存在当前目录下是非常方便的,因为你只要调用文件名而无需指定路径。但是,把数据库保存在网站根目录下就代表着不必要的风险。如果你没有采用安全措施防止直接访问的话,你的数据库就危险了。
如果由于外部因素导致无法做到把所有包含文件放在网站根目录之外,你可以在Apache配置成拒绝对.inc资源的请求。
代码:
<Files ~ "\.inc$">Order allow,deny
Deny from all
</Files>
在第8章中你还可以看到另外一种防止数据库访问权限暴露的方法,该方法对于共享服务器环境(在该环境下尽管文件位于网站根目录之外,但依然存在暴露的风险)非常有效。
[ 本帖最后由 alex 于 2006-3-21 10:49 编辑 ]
作者: alex 发布时间: 2006-03-20
SQL 注入是PHP应用中最常见的漏洞之一。事实上令人惊奇的是,开发者要同时犯两个错误才会引发一个SQL注入漏洞,一个是没有对输入的数据进行过滤(过滤输入),还有一个是没有对发送到数据库的数据进行转义(转义输出)。这两个重要的步骤缺一不可,需要同时加以特别关注以减少程序错误。
对于攻击者来说,进行SQL注入攻击需要思考和试验,对数据库方案进行有根有据的推理非常有必要(当然假设攻击者看不到你的源程序和数据库方案),考虑以下简单的登录表单:
代码:
<form action="/login.php" method="POST"><p>Username: <input type="text" name="username" /></p>
<p>Password: <input type="password" name="password" /></p>
<p><input type="submit" value="Log In" /></p>
</form>
作为一个攻击者,他会从推测验证用户名和密码的查询语句开始。通过查看源文件,他就能开始猜测你的习惯。
图 3-1. 登录表单在浏览器中的显示
命名习惯。通常会假设你表单中的字段名为与数据表中的字段名相同。当然,确保它们不同未必是一个可靠的安全措施。
第一次猜测,一般会使用下面例子中的查询:
代码:
<?php$password_hash = md5($_POST['password']);
$sql = "SELECT count(*)
FROM users
WHERE username = '{$_POST['username']}'
AND password = '$password_hash'";
?>
译注:原文如此,山东大学教授王小云的研究表明可以很快的找到MD5的“碰撞”,就是可以产生相同的MD5值的不同两个文件和字串。MD5是信息摘要算法,而不是加密算法,反向破解也就无从谈起了。不过根据这个成果,在上面的特例中,直接使用md5是危险的。
最好的保护方法是在密码上附加一个你自己定义的字符串,例如:
代码:
<?php$salt = 'SHIFLETT';
$password_hash = md5($salt . md5($_POST['password'] . $salt));
?>
代码:
<?phpmysql_query($sql) or exit(mysql_error());
?>
代码:
<?php$sql = "SELECT *
FROM users
WHERE username = '''
AND password = 'a029d0df84eb5549c641e04a9ef389e5'";
?>
You have an error in your SQL syntax. Check the manual that corresponds to your
MySQL server version for the right syntax to use near 'WHERE username = ''' AND
password = 'a029d0df84eb55
不费吹灰之力,攻击者已经知道了两个字段名(username和password)以及他们出现在查询中的顺序。除此以外,攻击者还知道了数据没有正确进行过滤(程序没有提示非法用户名)和转义(出现了数据库错误),同时整个WHERE条件的格式也暴露了,这样,攻击者就可以尝试操纵符合查询的记录了。
在这一点上,攻击者有很多选择。一是尝试填入一个特殊的用户名,以使查询无论用户名密码是否符合,都能得到匹配:
myuser' or 'foo' = 'foo' --
假定将mypass作为密码,整个查询就会变成:
代码:
<?php$sql = "SELECT *
FROM users
WHERE username = 'myuser' or 'foo' = 'foo' --
AND password = 'a029d0df84eb5549c641e04a9ef389e5'";
?>
如果知道合法的用户名,攻击者就可以该用户(如chris)身份登录:
chris' --
只要chris是合法的用户名,攻击者就可以控制该帐号。原因是查询变成了下面的样子:
代码:
<?php$sql = "SELECT *
FROM users
WHERE username = 'chris' --
AND password = 'a029d0df84eb5549c641e04a9ef389e5'";
?>
虽然两个步骤都不能省略,但只要实现其中的一个就能消除大多数的SQL注入风险。如果你只是过滤输入而没有转义输出,你很可能会遇到数据库错误(合法的数据也可能影响SQL查询的正确格式),但这也不可靠,合法的数据还可能改变SQL语句的行为。另一方面,如果你转义了输出,而没有过滤输入,就能保证数据不会影响SQL语句的格式,同时也防止了多种常见SQL注入攻击的方法。
当然,还是要坚持同时使用这两个步骤。过滤输入的方式完全取决于输入数据的类型(见第一章的示例),但转义用于向数据库发送的输出数据只要使用同一个函数即可。对于MySQL用户,可以使用函数mysql_real_escape_string( ):
代码:
<?php$clean = array();
$mysql = array();
$clean['last_name'] = "O'Reilly";
$mysql['last_name'] = mysql_real_escape_string($clean['last_name']);
$sql = "INSERT
INTO user (last_name)
VALUES ('{$mysql['last_name']}')";
?>
当所有用于建立一个SQL语句的数据被正确过滤和转义时,实际上也就避免了SQL注入的风险。
代码:
如果你正在使用支持参数化查询语句和占位符的数据库操作类(如PEAR::DB, PDO等),你就会多得到一层保护。见下面的使用PEAR::DB的例子:代码:
<?php$sql = 'INSERT
INTO user (last_name)
VALUES (?)';
$dbh->query($sql, array($clean['last_name']));
?>
代码:
由于在上例中数据不能直接影响查询语句的格式,SQL注入的风险就降低了。PEAR::DB会自动根据你的数据库的要求进行转义,所以你只需要过滤输出即可。译注:关于SQL注入,不得不说的是现在大多虚拟主机都会把magic_quotes_gpc选项打开,在这种情况下所有的客户端GET和POST的数据都会自动进行addslashes处理,所以此时对字符串值的SQL注入是不可行的,但要防止对数字值的SQL注入,如用intval()等函数进行处理。但如果你编写的是通用软件,则需要读取服务器的magic_quotes_gpc后进行相应处理。
3.3. 数据的暴露
关于数据库,另外需要关心的一点是敏感数据的暴露。不管你是否保存了信用卡号,社会保险号,或其它数据,你还是希望确认数据库是安全的。
虽然数据库安全已经超出了本书所讨论的范围(也不是PHP开发者要负责的),但是你可以加密最敏感的数据,这样只要密钥不泄露,数据库的安全问题就不会造成灾难性的后果。(关于加密的详细介绍参见本书附录C)
[ 本帖最后由 alex 于 2006-3-20 13:14 编辑 ]
作者: alex 发布时间: 2006-03-20
方便了我们这些英文不好的人。
作者: xlmo 发布时间: 2006-03-20
作者: liumingl 发布时间: 2006-03-20
作者: 帅的不能再输啦 发布时间: 2006-03-20
作者: MySQL 发布时间: 2006-03-20
作者: seraph 发布时间: 2006-03-20
引用:
原帖由 帅的不能再输啦 于 2006-3-20 21:11 发表顺便说一句.楼主的衣服已经发出啦...
作者: alex 发布时间: 2006-03-20
引用:
原帖由 MySQL 于 2006-3-20 21:23 发表相当好的文件,有图就完美了.
要看图的话,请至技术文档区下载原版chm
http://www.phpchina.cn/bbs/viewt ... &extra=page%3D1
作者: alex 发布时间: 2006-03-20
作者: MySQL 发布时间: 2006-03-22
作者: pennybp 发布时间: 2006-08-19
热门阅读
-
office 2019专业增强版最新2021版激活秘钥/序列号/激活码推荐 附激活工具
阅读:74
-
如何安装mysql8.0
阅读:31
-
Word快速设置标题样式步骤详解
阅读:28
-
20+道必知必会的Vue面试题(附答案解析)
阅读:37
-
HTML如何制作表单
阅读:22
-
百词斩可以改天数吗?当然可以,4个步骤轻松修改天数!
阅读:31
-
ET文件格式和XLS格式文件之间如何转化?
阅读:24
-
react和vue的区别及优缺点是什么
阅读:121
-
支付宝人脸识别如何关闭?
阅读:21
-
腾讯微云怎么修改照片或视频备份路径?
阅读:28