正文 6611字数 113,338阅读

1. 输入输出安全

(1)关闭 register_global 选项(php4及以前),初始化所有变量,能防止调用未定义变量notice错误,也能够防范一些hacker行为

(2)接收任何变量的时候,务必记住:所有用户输入都是不安全的!如果确定用户输入数据是整数(比如ID之类),可以使用(int)或intval()函数强制类型转换,如果是字符串类型,使用 addslashes 或 mysql_escape_string(如果考虑数据库编码请连接数据库后使用mysql_real_escape_string更安全,针对GBK等编码,使用该函数能够防范目前很多GBK编码爆出的注入漏洞)注1

(3)输出数据的时候,如果害怕html,可以使用strip_tags全部过滤,或者使用 htmlspecialchars 进行html标签转义保证输出到页面不会构成威胁,另外,最好输入或者输出的时候过滤所有的<script><iframe><object>等标记和内容,有时候<style>或作为属性的style也最好过滤一下

注1:问题来自多字节编码。例如在GBK里,0xbf27并不是一个合法的双字节字符,因此addslash()会把它转义成0xbf5c27,碰巧0xbf5c是一个合法的双字节字符,由此可以注入一个0x27 (')。addslash()和mysql_escape_string无药可救。mysql_real_escape_string()可以根据字符集正确地转义,但是需要在建立数据库联接的时候指明“SET CHARACTER SET 'GBK'”。


2. php系统安全

(1)打开 safe_mode 是最重要的,同时设置 open_basedir, safe_mode_include_dir,safe_mode_exec_dir 等选项

(2)当要操作或者包含文件的时候,使用realpath 和 basename 检查文件是否是本地文件

(3)如果很多危险函数如果不使用,建议关闭,关闭函数修改disbale_functions选项,比如很多命令执行函数、eval、phpinfo 等函数

(4)如果不需要上传文件功能可以关闭,如果需要记得设置 post_max_size 选项为你合适的大小,否则文件系统很容易被塞满,并且move_uploaded_file来操作上传的文件,而且不是使用copy

(5)把库文件(.inc)和数据文件(数据信息,配置信息,sqlite数据库等)不要与web目录放在一起,防止被下载


3.小议urldecode等函数的安全使用
urldecode函数在很多程序中是经常使用的,对用户提交的某些字符进行URL解码,防止一些字符截断之类的问题出现,但是如果使用不当可能会出现一些安全问题,举两个例子
1,泄露绝对路径
这个问题比较典型了,但是需要PHP的错误回显打开才行。看这段小代码
<? $name[] = "abc"; echo $name[0]; echo $name[1]; echo $name[2]; $name1="def"; echo $name1[0]; echo $name1[1]; echo $name1[2]; echo urldecode($name); ?>
Run code
Cut to clipboard

    PHP的变量可以用数组的形式操作,那么是不是在任何地方都可以互相使用呢,这段代码的输出如下:
    abcdef
    Warning: urldecode() expects parameter 1 to be string, array given in C:\Apache Group\Apache2\htdocs\px\non48.php on line 10
    可见,WEB程序的绝对路径已经暴露出来了,在一些注入攻击或者一些文件包含漏洞中知道绝对路径对一些进一步猜测或者load_file()的作用还是很大的,有此类问题的函数还有:urlencode,urldecode,rawurlencode,rawurldecode,base64_encode,mysql_connect等等。实例PHPBB论坛绝对路径泄漏
    代码如下:
    if ( isset($HTTP_POST_VARS['folder']) || isset($HTTP_GET_VARS['folder']) ) { $folder = ( isset($HTTP_POST_VARS['folder']) ) ? $HTTP_POST_VARS['folder'] : $HTTP_GET_VARS['folder']; $folder = htmlspecialchars($folder); if ( $folder != 'inbox' && $folder != 'outbox' && $folder != 'sentbox' && $folder != 'savebox' ) { $folder = 'inbox'; } } else { $folder = 'inbox'; }
    Run code
    Cut to clipboard

      提交:http://localhost/phpBB2/privmsg.php?folder[]=
      回显:
      Warning: htmlspecialchars() expects parameter 1 to be string, array given in /www/phpbb2/privmsg.php on line 61
      Run code
      Cut to clipboard

        4,bypass GPC或addslashes注入
        在GPC或者addslashes的影响下,变量被放在一对单引号中,进行SQL注入攻击时首先就是要闭合单引号才能继续构造查询语句,这时单引号会被转义成\'导致攻击失败,由于urldecode函数对变量的解码处理导致我们给变量赋值%2527,浏览器会把%25解码为%,然后%27被带入urldecode函数处理,再解码为',这样的话,单引号就被顺利带入并绕过了GPC或者addslashes,实例wordpress的wp-admin/admin-ajax.php页面注入漏洞:
        define('DOING_AJAX', true); check_ajax_referer(); if ( !is_user_logged_in() ) die('-1');
        Run code
        Cut to clipboard

          ...漏洞出在check_ajax_referer(); 函数,进一步看下去
          function check_ajax_referer() { $cookie = explode('; ', urldecode(empty($_POST['cookie']) ? $_GET['cookie'] : $_POST['cookie'])); // AJAX scripts must pass cookie=document.cookie foreach ( $cookie as $tasty ) { if ( false !== strpos($tasty, USER_COOKIE) ) $user = substr(strstr($tasty, '='), 1); if ( false !== strpos($tasty, PASS_COOKIE) ) $pass = substr(strstr($tasty, '='), 1); } if ( !wp_login( $user, $pass, true ) ) die('-1');
          Run code
          Cut to clipboard

            注意这一段句:
            $cookie = explode('; ', urldecode(empty($_POST['cookie']) ? $_GET['cookie'] : $_POST['cookie'])); // AJAX scripts must pass
            Run code
            Cut to clipboard

              用urldecode函数处理cookie,而处理后的用户信息最终被带入数据库语句查询
              function get_userdatabylogin($user_login) { global $wpdb; ... if ( !$user = $wpdb->get_row("Select * FROM $wpdb->users Where user_login = '$user_login'") ) return false;
              Run code
              Cut to clipboard

                这样攻击者构造用户信息为%2527 and 1=1就会在urldecode函数作用下顺利闭合单引号,实现SQL注入攻击
                if ( !$user = $wpdb->get_row("Select * FROM $wpdb->users Where user_login = ‘’ and 1=1") )
                Run code
                Cut to clipboard

                  实例2PHP168SQL注入漏洞
                  if(!$keyword) { extract($db->get_one("SELECT keywords AS keyword FROM {$pre}article WHERE aid='$id'")); } if($keyword){ $SQL.=" AND ( "; $keyword=urldecode($keyword); $detail=explode(" ",$keyword); unset($detail2); foreach( $detail AS $key=>$value){ $detail2[]=” BINARY title LIKE ‘%$value%’ “; } $str=implode(” OR “,$detail2); $SQL.=” $str ) “; }else{ $SQL.=” AND 0 “; } $ORDER=’ list ‘; } if(!$webdb[viewNoPassArticle]){ $SQL.=’ AND yz=1 ‘; } $SQL=” WHERE $SQL ORDER BY $ORDER DESC LIMIT $rows”; $which=’*'; $listdb=list_article($SQL,$which,$leng);
                  Run code
                  Cut to clipboard

                    $keyword经过urldecode后进入SQL查询语句,漏洞由此产生

                    5.安全使用intval函数
                    关于这个函数的功能不再多说什么了,手册上写的很明白
                    intval最常用的是在程序中过滤进入数据库的变量,将其转换为整型,防止SQL注入攻击的产生
                    但是使用不当的话则会起不到检查的作用,下面就结合一个实例来说明这个问题
                    国内某个CMS系统,经过ZEND加密了,解密后某文件代码如下:
                    $id = isset( $_GET['id'] ) ? $_GET['id'] : 0; if ( intval( $id ) ) { $sql = "SELECT url FROM ".$tablepre."feed WHERE id={$id} AND uploader='{$SESSION['uid']}'";
                    Run code
                    Cut to clipboard

                      代码很简单,获取GET来的id用intval函数判断,如果是整型则带入数据库查询,看似逻辑上没有什么问题,但实际上这段代码没有起到任何的check作用,为什么呢?看如下脚本:
                      <? $var="20070601"; if (intval($var)) echo "it's safe"; echo '$var='.$var; echo "<br>"; $var1="1 union select 1,1,1 from admin"; if (intval($var1)) echo "it's safe too"; echo '$var1='.$var1; ?>
                      Run code
                      Cut to clipboard

                        运行以上脚本可以看到,两个判断的结果都是safe的,但实际上只有$var是安全的$var1变量后已经附带的有额外的SQL查询语句了,那么intval是如何判断的呢?这就需要看intval函数在PHP中是如何实现的,代码如下:
                        PHP_FUNCTION(intval) { zval **num, **arg_base; int base; switch (ZEND_NUM_ARGS()) { case 1: if (zend_get_parameters_ex(1, &num) == FAILURE) { WRONG_PARAM_COUNT; } base = 10; break; case 2: if (zend_get_parameters_ex(2, &num, &arg_base) == FAILURE) { WRONG_PARAM_COUNT; } convert_to_long_ex(arg_base); base = Z_LVAL_PP(arg_base); break; default: WRONG_PARAM_COUNT; } RETVAL_ZVAL(*num, 1, 0); convert_to_long_base(return_value, base); }
                        Run code
                        Cut to clipboard

                          intval函数只判断参数的第一个字符是否为整型,这样如果放在if中,只要满足变量第一个字符为整型,则返回值为ture,所以说用if来判断intval后的变量是不安全的。