PHP 如何 安全配置
配置选项
phpinfo( ) 函数可用于php.ini文件的定位
A.1. allow_url_fopen
选项允许你如同本地文件一样引用远程资源:
我推荐关闭allow_url_fopen选项,除非你的应用需要它。
A.2. disable_functions
disable_functions选项是非常有用的,它可以确保一些有潜在威胁的函数不能被使用。尽管可以建立规范去禁止使用这些函数,但在PHP配置中进行限制要比依赖于开发者对规范的遵循要可靠得多。
我建立对附录B列出的函数进行检查,看一下是否要对一些函数进行限制。
A.3. display_errors
PHP的错误报告可以帮助你发现你所写代码中的错误。当你开发应用时,把错误提示显示出来是取得即时反馈的有效方法,同时也可以加快开发速度。
在一个产品级的应用中,这一行为会成为一项安全风险。如果它显示错误信息,所有人就可以得知你的应用中的重要信息。
在产品中你需要关闭display_errors选项。
A.4. enable_dl
enable_dl选项用于控制dl()函数是否生效,该函数允许在运行时加载PHP扩展。
使用dl()函数可能导致攻击者绕过open_basedir限制,因此除非有必要,你必须在你的应用中禁止它。 (利用方法)
A.5. error_reporting
很多安全漏洞是由于使用了未初始化的变量或其它随意的编程方法引起的。通过把PHP的error_reporting选项置为E_ALL 或 E_ALL | E_STRICT,PHP就会对上述行为进行提示。这些设置都为报告Notice级别的错误。
我建议把error_reporting至少设定为E_ALL。(译注:在开发中)
A.6. file_uploads
file_uploads选项决定了是否允许上传文件。因此,如果你的应用不需要用户上传文件,那么关闭该选项就是最好的选择。
只是简单地在PHP代码中不对上传文件进行处理是不够的,因为在执行你的代码前,PHP就做了一些工作(如根据相关部据生成$_FILES数组)。
A.7. log_errors
当log_errors设为有效时,PHP会向error_log配置选项指定的文件中写入所有出错信息。
当display_errors设为无效时,将log_errors设为有效是很重要的;否则你将无法看到睛出错信息。
我建议将log_errors设为有效并在error_log设定日志文件所在位置。
A.8. magic_quotes_gpc
magic_quotes_gpc是一个常用的选项,它目的是防止SQL注入。但出于很多原因,包括它转义输入的方式,证明了它是不完善的。
它对$_GET, $_POST, 以及 $_COOKIE中的数据使用同样的规则即addslashes( )函数进行处理。从而,它并没有根据你的数据库选用对应的转义函数进行处理。
基于两个主要的原因,你需要把get_magic_quotes_gpc设为无效:
首先,它会加大你的输入过滤逻辑的复杂性,这是由于它在执行你的代码前首先对数据进行了编辑。例如,你需要对输入的姓名进行过滤,其逻辑是只允许字母、空格、连词符以及单引号,当magic_quotes_gpc生效时,你必须适应形如O\'Reilly的姓名或者使用stripslashes( )尝试将它恢复原形。这一不必要的复杂性(或者说不严谨的过滤规则)加大了发生错误的可能性,同时,你的输入过滤机制中的缺陷必然会导致安全漏洞。
其次,它并没有根据你的数据库选用对应的转义函数进行处理。这样,由于它可以抵挡一些低层次或偶发的攻击,掩盖了它是一个糟糕的过滤或转义机制这个事实,从而留下了一个安全漏洞,使你的应用无法抵挡如针对字符集的攻击等更复杂的攻击手段。
A.9. memory_limit
为防止写得糟糕的脚本占用所有的可用内存,可以使用memory_limit选项对最大内存使用量进行限制(以字节方式或缩写方式如8M指定)。
尽管最佳的取值是与运行的应用是相关的,我还是建议在大多情况下使用默认值8M。
memory_limit选项只有在PHP指定了enable-memory-limit方式编译时才会生效。
A.10. open_basedir
open_basedir选项会限制PHP只能在它指定的目录中打开文件。尽管它不能取代正确的输入过滤,但该选项能减少利用文件系统相关函数如include及require进行的攻击。
该选项的值会被当做前缀使用,因此当你想表示指定目录时请小心不要漏了最后的斜杠:
open_basedir = /path/to/
小提示
请确认enable_dl选项是关闭的,否则open_basedir的限制可能会被绕过。
安全人员 - 数据 加密 解密:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
加密 作为一本相关安全方面的书,通常加密是需要提及的话题。我之所以在本书的主体部分忽略了加密问题,是因为它的用途是狭窄的,而开发者应从大处着眼来考虑安全问题。过分依赖于加密常常会混淆问题的根源。尽管加密本身是有效的,但是进行加密并不会神奇地提高一个应用的安全性。 一个PHP开发人员应主要熟悉以下的加密方式: l 对称加密 l 非对称加密(公钥) l Hash函数(信息摘要) l 信息验证码 本附录主要关注于使用mcrypt扩展的对称加密算法。你需要参考的资料如下: 实用加密技术(Applied Cryptography), by Bruce Schneier (Wiley) http://www.schneier.com/blog/ http://wikipedia.org/wiki/Cryptography http://phpsec.org/articles/2005/password-hashing.html http://pear.php.net/package/Crypt_HMAC http://pear.php.net/package/Crypt_RSA C.1. 密码的存储 当你在数据库内存储的密码时,永远不要以明码方式存入,而是应该存储密码的hash值并同时使用附加字串: <?php /* $password contains the password. */ $salt = 'SHIFLETT'; $password_hash = md5($salt . md5($password . $salt)); /* Store password hash. */ ?> 当你需要确认一个密码是否正确时,以同样的方式计算出hash值并比较异同: <?php $salt = 'SHIFLETT'; $password_hash = md5($salt . md5($_POST['password'] . $salt)); /* Compare password hashes. */ ?> 如果hash值完全相同,你就有理由认为密码也是相同的。 如果使用了这个技巧,是不可能告诉用户他们的密码是什么的。当用户忘记密码时,你只能让他录入一个新密码并重新计算hash值存入数据库。当然,你需要非常小心地对用户进行身份确认——密码提醒机制是易受频繁攻击的目标,同时也是经常出现安全漏洞的源头。 C.2. 使用mcrypt PHP的标准加密扩展是mcrypt,它支持很多不同的加密算法。你可以通过mcrypt_list_algorithms( )函数来查看你的平台上支持的算法列表: <?php echo '<pre>' . print_r(mcrypt_list_algorithms(), TRUE) . '</pre>'; ?> 加密和解密分别由mcrypt_encrypt( ) 及 mcrypt_decrypt( )函数来实现。这两个函数都有5个参数,第一个参数是用于指定使用的算法: <?php mcrypt_encrypt($algorithm, $key, $cleartext, $mode, $iv); mcrypt_decrypt($algorithm, $key, $ciphertext, $mode, $iv); ?> 其中的加密键(第二个参数)是非常敏感的数据,因此你要确保把它存放在安全的地方。可以用第八章中保护数据库权限的方法来保护加密键。如果经济条件允许的话,硬件加密键是最好的选择,它提供了超级强大的安全性。 函数有多种模式可供选择,你可以使用mcrypt_list_modes( )来列出所有支持的模式: <?php echo '<pre>' . print_r(mcrypt_list_modes(), TRUE) . '</pre>'; ?> 第五个参数($iv)为初始化向量,可以使用mcrypt_create_iv( )函数建立。 下面的示例类提供了基本的加密解密方法: class crypt { private $algorithm; private $mode; private $random_source; public $cleartext; public $ciphertext; public $iv; public function __construct($algorithm = MCRYPT_BLOWFISH, $mode = MCRYPT_MODE_CBC, $random_source = MCRYPT_DEV_URANDOM) { $this->algorithm = $algorithm; $this->mode = $mode; $this->random_source = $random_source; } public function generate_iv() { $this->iv = mcrypt_create_iv(mcrypt_get_iv_size($this->algorithm, $this->mode), $this->random_source); } public function encrypt() { $this->ciphertext = mcrypt_encrypt($this->algorithm, $_SERVER['CRYPT_KEY'], $this->cleartext, $this->mode, $this->iv); } public function decrypt() { $this->cleartext = mcrypt_decrypt($this->algorithm, $_SERVER['CRYPT_KEY'], $this->ciphertext, $this->mode, $this->iv); } } ?> 上面的类会在其它示例中使用,下面是它的使用方法示例: <?php $crypt = new crypt(); $crypt->cleartext = 'This is a string'; $crypt->generate_iv(); $crypt->encrypt(); $ciphertext = base64_encode($crypt->ciphertext); $iv = base64_encode($crypt->iv); unset($crypt); /* Store $ciphertext and $iv (initialization vector). */ $ciphertext = base64_decode($ciphertext); $iv = base64_decode($iv); $crypt = new crypt(); $crypt->iv = $iv; $crypt->ciphertext = $ciphertext; $crypt->decrypt(); $cleartext = $crypt->cleartext; ?> 小提示 本扩展要求你在编译PHP时使用-mcrypt标识。安装指南及要求详见http://php.net/mcrypt。 C.3. 信用卡号的保存 我常常被问到如何安全地保存信用卡号。我的总是会首先询问他们是否确实有必要保存信用卡号。毕竟不管具体是如何操作的,引入不必要的风险是不明智的。同时国家法律还有关于信用卡信息处理方面的规定,我还时刻小心地提醒我并不是一个法律专家。 本书中我并不会专门讨论信用卡处理的方法,而是会说明如何保存加密信息到数据库及在读取时解密。该流程会导致系统性能的下降,但是确实提供了一层保护措施。其主要优点是如果数据库内容泄密暴露出的只是加密信息,但是前提是加密键是安全的。因此,加密键与加密的实现方法本身同样重要。 保存加密数据到数据的过程是,首先加密数据,然后通过初始向量与明文建立密文来保存到数据库。由于密文是二进制字符串,还需要通过base64_encode( )转换成普通文本字符串以保证二进制编码的安全存储。 <?php $crypt = new crypt(); $crypt->cleartext = '1234567890123456'; $crypt->generate_iv(); $crypt->encrypt(); $ciphertext = $crypt->ciphertext; $iv = $crypt->iv; $string = base64_encode($iv . $ciphertext); ?> 保存该字串至数据库。在读取时,则是上面流程的逆处理: <?php $string = base64_decode($string); $iv_size = mcrypt_get_iv_size($algorithm, $mode); $ciphertext = substr($string, $iv_size); $iv = substr($string, 0, $iv_size); $crypt = new crypt(); $crypt->iv = $iv; $crypt->ciphertext = $ciphertext; $crypt->decrypt(); $cleartext = $crypt->cleartext; ?> 本实现方法假定加密算法与模式不变。如果它们是不定的话,你还要保存它们以用于解密数据。加密键是唯一需要保密的数据。 C.4. 加密会话数据 如果你的数据库存在安全问题,或者部分保存在会话中的数据是敏感的,你可能希望加密会话数据。除非很有必要,一般我不推荐这样做,但是如果你觉得在你的情形下需要这样做的话,本节提供了一个实现方法的示例。 这个方案十分简单。实际上,在第八章中,已经说明了如何通过调用session_set_save_handler( )来执行你自己的会话机制。通过对保存和读取数据的函数的少量调整,你就能加密存入数据库的数据及在读取时解密数据: <?php function _read($id) { global $_sess_db; $algorithm = MCRYPT_BLOWFISH; $mode = MCRYPT_MODE_CBC; $id = mysql_real_escape_string($id); $sql = "SELECT data FROM sessions WHERE id = '$id'"; if ($result = mysql_query($sql, $_sess_db)) { $record = mysql_fetch_assoc($result); $data = base64_decode($record['data']); $iv_size = mcrypt_get_iv_size($algorithm, $mode); $ciphertext = substr($data, $iv_size); $iv = substr($data, 0, $iv_size); $crypt = new crypt(); $crypt->iv = $iv; $crypt->ciphertext = $ciphertext; $crypt->decrypt(); return $crypt->cleartext; } return ''; } function _write($id, $data) { global $_sess_db; $access = time(); $crypt = new crypt(); $crypt->cleartext = $data; $crypt->generate_iv(); $crypt->encrypt(); $ciphertext = $crypt->ciphertext; $iv = $crypt->iv; $data = base64_encode($iv . $ciphertext); $id = mysql_real_escape_string($id); $access = mysql_real_escape_string($access); $data = mysql_real_escape_string($data); $sql = "REPLACE INTO sessions VALUES ('$id', '$access', '$data')"; return mysql_query($sql, $_sess_db); }