地址:​​http://www.phpthinking.com/archives/575​

php给了开发人员极大的灵活性,可是这也为安全问题带来了潜在的隐患,最近须要总结一下以往的问题。在这里借翻译一篇文章同一时候加上自己开发的一些感触总结一下。

简单介绍

当开发一个互联网服务的时候,必须时刻牢记安全观念,并在开发的代码中体现。

PHP脚本语言对安全问题并不关心。特别是对大多数没有经验的开发人员来说。

每当你讲不论什么涉及到钱財事务等交易问题时。须要特别注意安全问题的考虑。比如开发一个论坛或者是一个购物车等。

安全保护一般性要点

  • 不相信表单

对于一般的Javascript前台验证,因为无法得知用户的行为。比如关闭了浏览器的javascript引擎。这样通过POST恶意数据到server。须要在server端进行验证,对每一个php脚本验证传递到的数据。防止XSS攻击和SQL注入

  • 不相信用户

要如果你的站点接收的每一条数据都是存在恶意代码的。存在隐藏的威胁,要对每一条数据都进行清理

  • 关闭全局变量

在php.ini文件里进行下面配置:




​1​

​register_globals = Off​


假设这个配置选项打开之后。会出现非常大的安全隐患。比如有一个process.php的脚本文件。会将接收到的数据插入到数据库,接收用户输入数据的表单可能例如以下:




​1​

​<input name=​​​​"username"​​ ​​type=​​​​"text"​​ ​​size=​​​​"15"​​ ​​maxlength=​​​​"64"​​​​>​


这样,当提交数据到process.php之后,php会注冊一个$username变量。将这个变量数据提交到process.php。同一时候对于不论什么POST或GET请求參数,都会设置这种变量。假设不是显示进行初始化那么就会出现以下的问题:




​1​

​<?php​


​2​

​// Define $authorized = true only if user is authenticated​


​3​

​if​​ ​​(authenticated_user()) {​


​4​

​$authorized​​ ​​= true;​


​5​

​}​


​6​

​?​

 

​>​



此处。如果authenticated_user函数就是推断$authorized变量的值,如果开启了register_globals配置。那么不论什么用户都能够发送一个请求。来设置$authorized变量的值为随意值从而就能绕过这个验证。

全部的这些提交数据都应该通过PHP提前定义内置的全局数组来获取,包含$_POST、$_GET、$_FILES、$_SERVER、$_REQUEST等。当中$_REQUEST是一个$_GET/$_POST/$_COOKIE三个数组的联合变量。默认的顺序是$_COOKIE、$_POST、$_GET。

推荐的安全配置选项

error_reporting设置为Off:不要暴露错误信息给用户,开发的时候能够设置为ON

safe_mode设置为Off

register_globals设置为Off

将下面函数禁用:system、exec、passthru、shell_exec、proc_open、popen

open_basedir设置为 /tmp ,这样能够让session信息有存储权限,同一时候设置单独的站点根文件夹

expose_php设置为Off

allow_url_fopen设置为Off

allow_url_include设置为Off

SQL注入攻击

对于操作数据库的SQL语句,须要特别注意安全性。由于用户可能输入特定语句使得原有的SQL语句改变了功能。类似以下的样例:




​1​

​$sql​​ ​​= ​​​​"select * from pinfo where product = '$product'"​​​​;​


此时假设用户输入的$product參数为:

39′; DROP pinfo; SELECT ‘FOO

那么终于SQL语句就变成了例如以下的样子:




​1​

​select product from pinfo where product = ​​​​'39'​​​​; DROP pinfo; SELECT ​​​​'FOO'​


这样就会变成三条SQL语句,会造成pinfo表被删除,这样会造成严重的后果。

这个问题能够简单的使用PHP的内置函数解决:




​1​

​$sql​​ ​​= ​​​​'Select * from pinfo where product = '​​​​"'​


​2​

​mysql_real_escape_string(​​​​$product​​​​) . ​​​​'"'​​​​;​


防止SQL注入攻击须要做好两件事:

对输入的參数总是进行类型验证

对单引號、双引號、反引號等特殊字符总是使用mysql_real_escape_string函数进行转义

可是,这里依据开发经验,不要开启php的Magic Quotes。这个特性在php6中已经废除。总是自己在须要的时候进行转义。

 

防止主要的XSS攻击

XSS攻击不像其它攻击,这样的攻击在client进行。最主要的XSS工具就是防止一段javascript脚本在用户待提交的表单页面。将用户提交的数据和cookie偷取过来。

XSS工具比SQL注入更加难以防护,各大公司站点都被XSS攻击过,尽管这样的攻击与php语言无关。但能够使用php来筛选用户数据达到保护用户数据的目的,这里主要使用的是对用户的数据进行过滤,一般过滤掉HTML标签,特别是a标签。

以下是一个普通的过滤方法:




​01​

​function​​ ​​transform_HTML(​​​​$string​​​​, ​​​​$length​​ ​​= null) {​


​02​

​// Helps prevent XSS attacks​


​03​

​// Remove dead space.​


​04​

​$string​​ ​​= trim(​​​​$string​​​​);​


​05​

​// Prevent potential Unicode codec problems.​


​06​

​$string​​ ​​= utf8_decode(​​​​$string​​​​);​


​07​

​// HTMLize HTML-specific characters.​


​08​

​$string​​ ​​= htmlentities(​​​​$string​​​​, ENT_NOQUOTES);​


​09​

​$string​​ ​​= ​​​​str_replace​​​​(​​​​"#"​​​​, ​​​​"#"​​​​, ​​​​$string​​​​);​


​10​

​$string​​ ​​= ​​​​str_replace​​​​(​​​​"%"​​​​, ​​​​"%"​​​​, ​​​​$string​​​​);​


​11​

​$length​​ ​​= ​​​​intval​​​​(​​​​$length​​​​);​


​12​

​if​​ ​​(​​​​$length​​ ​​> 0) {​


​13​

​$string​​ ​​= ​​​​substr​​​​(​​​​$string​​​​, 0, ​​​​$length​​​​);​


​14​

​}​


​15​

​return​​ ​​$string​​​​;​


​16​

​}​


这个函数将HTML的特殊字符转换为了HTML实体,浏览器在渲染这段文本的时候以纯文本形式显示。如<strong>bold</strong>会被显示为:

&lt;STRONG&gt;BoldText&lt;/STRONG&gt;

上述函数的核心就是htmlentities函数,这个函数将html特殊标签转换为html实体字符,这样能够过滤大部分的XSS攻击。

可是对于有经验的XSS攻击者。有更加巧妙的办法进行攻击:将他们的恶意代码使用十六进制或者utf-8编码。而不是普通的ASCII文本。比如能够使用以下的方式进行:




​1​

​<a href=​​​​"http://host/a.php?​

 

​variable=%22%3e %3c%53%43%52%49%50%54%3e%44%6f%73%6f%6d%65%74%68%69%6e%67%6d%61%6c%69%63%69%6f%75%73%3c%2f%53%43%52%49%50%54%3e"​​​​>​



这样浏览器渲染的结果事实上是:




​1​

​<a href=​​​​"http://host/a.php?variable="​​​​> <SCRIPT>Dosomethingmalicious</SCRIPT>​


这样就达到了攻击的目的。为了防止这样的情况,须要在transform_HTML函数的基础上再将#和%转换为他们相应的实体符号,同一时候加上了$length參数来限制提交的数据的最大长度。

使用SafeHTML防止XSS攻击

上述关于XSS攻击的防护很easy。可是不包括用户的全部标记,同一时候有上百种绕过过滤函数提交javascript代码的方法,也没有办法能全然阻止这个情况。

眼下。没有一个单一的脚本能保证不被攻击突破,可是总有相对来说防护程度更好的。一共同拥有两个安全防护的方式:白名单和黑名单。当中白名单更加简单和有效。

一种白名单解决方式就是SafeHTML。它足够智能可以识别有效的HTML,然后就行去除不论什么危急的标签。

这个须要基于HTMLSax包来进行解析。

安装使用SafeHTML的方法:

1、前往http://pixel-apes.com/safehtml/?

page=safehtml 下载最新的SafeHTML

2、将文件放入server的classes 文件夹,这个文件夹包括全部的SafeHTML和HTMLSax库

3、在自己的脚本中包括SafeHTML类文件

4、建立一个SafeHTML对象

5、使用parse方法进行过滤




​01​

​<?php​


​02​

​/* If you're storing the HTMLSax3.php in the /classes directory, along​


​03​

​with the safehtml.php script, define XML_HTMLSAX3 as a null string. */​


​04​

​define(XML_HTMLSAX3, ​​​​''​​​​);​


​05​

​// Include the class file.​


​06​

​require_once​​​​(​​​​'classes/safehtml.php'​​​​);​


​07​

​// Define some sample bad code.​


​08​

​$data​​ ​​= ​​​​"This data would raise an alert <script>alert('XSS Attack')</script>"​​​​;​


​09​

​// Create a safehtml object.​


​10​

​$safehtml​​ ​​= ​​​​new​​ ​​safehtml();​


​11​

​// Parse and sanitize the data.​


​12​

​$safe_data​​ ​​= ​​​​$safehtml​​​​->parse(​​​​$data​​​​);​


​13​

​// Display result.​


​14​

​echo​​ ​​'The sanitized data is <br />'​​ ​​. ​​​​$safe_data​​​​;​


​15​

​?>​


SafeHTML并不能全然防止XSS攻击。仅仅是一个相对复杂的脚本来检验的方式。

使用单向HASH加密方式来保护数据

单向hash加密保证对每一个用户的password都是唯一的,并且不能被破译的,仅仅有终于用户知道password。系统也是不知道原始password的。

这种一个优点是在系统被攻击后攻击者也无法知道原始password数据。

加密和Hash是不同的两个过程。与加密不同。Hash是无法被解密的,是单向的;同一时候两个不同的字符串可能会得到同一个hash值,并不能保证hash值的唯一性。

MD5函数处理过的hash值基本不能被破解,可是总是有可能性的,并且网上也有MD5的hash字典。

使用mcrypt加密数据

MD5 hash函数能够在可读的表单中显示数据,可是对于存储用户的信用卡信息的时候。须要进行加密处理后存储,而且须要之后进行解密。

最好的方法是使用mcrypt模块。这个模块包括了超过30中加密方式来保证仅仅有加密者才干解密数据。




​01​

​<?php​


​02​

​$data​​ ​​= ​​​​"Stuff you want encrypted"​​​​;​


​03​

​$key​​ ​​= ​​​​"Secret passphrase used to encrypt your data"​​​​;​


​04​

​$cipher​​ ​​= ​​​​"MCRYPT_SERPENT_256"​​​​;​


​05​

​$mode​​ ​​= ​​​​"MCRYPT_MODE_CBC"​​​​;​


​06​

​function​​ ​​encrypt(​​​​$data​​​​, ​​​​$key​​​​, ​​​​$cipher​​​​, ​​​​$mode​​​​) {​


​07​

​// Encrypt data​


​08​

​return​​ ​​(string)​


​09​

​base64_encode​


​10​

​(​


​11​

​mcrypt_encrypt​


​12​

​(​


​13​

​$cipher​​​​,​


​14​

​substr​​​​(md5(​​​​$key​​​​),0,mcrypt_get_key_size(​​​​$cipher​​​​, ​​​​$mode​​​​)),​


​15​

​$data​​​​,​


​16​

​$mode​​​​,​


​17​

​substr​​​​(md5(​​​​$key​​​​),0,mcrypt_get_block_size(​​​​$cipher​​​​, ​​​​$mode​​​​))​


​18​

​)​


​19​

​);​


​20​

​}​


​21​

​function​​ ​​decrypt(​​​​$data​​​​, ​​​​$key​​​​, ​​​​$cipher​​​​, ​​​​$mode​​​​) {​


​22​

​// Decrypt data​


​23​

​return​​ ​​(string)​


​24​

​mcrypt_decrypt​


​25​

​(​


​26​

​$cipher​​​​,​


​27​

​substr​​​​(md5(​​​​$key​​​​),0,mcrypt_get_key_size(​​​​$cipher​​​​, ​​​​$mode​​​​)),​


​28​

​base64_decode​​​​(​​​​$data​​​​),​


​29​

​$mode​​​​,​


​30​

​substr​​​​(md5(​​​​$key​​​​),0,mcrypt_get_block_size(​​​​$cipher​​​​, ​​​​$mode​​​​))​


​31​

​);​


​32​

​}​


​33​

​?>​


mcrypt函数须要下面信息:

1、待加密数据

2、用来加密和解密数据的key

3、用户选择的加密数据的特定算法(cipher:如 MCRYPT_TWOFISH192,MCRYPT_SERPENT_256。 MCRYPT_RC2, MCRYPT_DES, and MCRYPT_LOKI97)

4、用来加密的模式

5、加密的种子,用来起始加密过程的数据,是一个额外的二进制数据用来初始化加密算法

6、加密key和种子的长度,使用mcrypt_get_key_size函数和mcrypt_get_block_size函数能够获取

假设数据和key都被盗取,那么攻击者能够遍历ciphers寻找开行的方式就可以,因此我们须要将加密的key进行MD5一次后保证安全性。同一时候因为mcrypt函数返回的加密数据是一个二进制数据。这样保存到数据库字段中会引起其它错误,使用了base64encode将这些数据转换为了十六进制数方便保存。