在乌云漏洞平台上发现很多开源PHP程序存在漏洞。大部分漏洞都是因为对PHP中的变量过滤不够严格和未转义造成,直接使用了$_GET,$_PUT,$_COOKIE传过来的值,造成了sql注入(sql injection)和跨站攻击(xss)。事实上这些漏洞完全可以避免的。
先说sql注入。通常注入情况发生在参数过滤不严谨和拼接sql,所以尽量不要用sql拼接,尽量使用PDO或Mysqli。先看下面一段代码(test.php):
1 2 3 4 5 6 7 8 9 10 11 12 | $database = "test" ; $duser = "root" ; $dpassword = "*********" ; $server = "localhost" ; $link =mysql_connect( $server , $duser , $dpassword ) or die ( "连接不上服务器:" .mysql_error()); mysql_select_db( $database , $link ) or die ( "不能选择数据库" ); $id = $_GET [ 'id' ]; $sql = "select id,account,nickname,password from user where id='" . $id . "'" ; echo $sql ; $res =mysql_query( $sql ); $arr =mysql_fetch_array( $res ,MYSQL_ASSOC); |
然后访问http://127.0.0.1/test.php?id=1,获取用户信息。这是可以通过构造参数进行攻击比如
1 2 3 4 5 6 7 | //将可以查询出所有的表数据。 $sql = "select id,account,nickname,password from user where id='1' OR 1 = 1--''" ; //访问:http://127.0.0.1/test.php?id=1%27%20UNION%20ALL%20%28SELECT%20NULL,NULL,NULL,%27%3C?php%20system%28$_GET[%22command%22]%29;%20?%3E%27%20INTO%20OUTFILE%20%27D:/www/shell.php%27%29%20--%20-%27 //在知道网站路径情况下,在网站目录下生成shell.php,将能够在对用户的网站进行挂马,提权的操作。 $sql = "select id,account,nickname,password from user where id='1' UNION ALL (SELECT NULL,NULL,NULL,'' INTO OUTFILE 'D:/www/shell.php') -- -''" ; |
甚至还可以进行表数据的增加,修改,删除,数据库的删除!所以永远不要相信用户的输入,永远要过滤输入,转义输出。对传递过来的数字型变量要进行严格的匹配,对字符串型的变量进行长度限制。注意过滤用户传递过来的变量是否包含引号,/*,#,—等特殊字符或转义字符,script,drop,不要给用户超级权限,最好进行分隔。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | //使用addslashes过滤单引号,双引号和斜杠 $uname = addslashes ( $_GET [ 'id' ] ); $query = 'SELECT username FROM users WHERE id = "' . $uname . '"; //使用mysql_real_escape_string过滤特殊字符,比如\x00, \n, \r, \, ', ",\x1a //mysql_real_escape_string使用的是本地单字节字符集,而我们传递多字节编码的变量时,有可能还是会造成SQL注入漏洞 //如chr(0xbf) . chr(0x27),所以mysql服务端与php客户端通信尽量使用utf-8而不是gbk $uname = mysql_real_escape_string( $_GET [ 'id' ] ); $query = 'SELECT username FROM users WHERE id = "' . $uname . '"; //使用is_numeric检查数字型变量 $id = $_GET [ 'id' ]; ( is_numeric ( $id ) ? TRUE : FALSE ); //使用sprintf格式化数字型变量 $id = $_GET [ 'id' ]; $query = sprintf( "SELECT username FROM users WHERE id = '%d' " , $id ); //使用htmlentities($var, ENT_QUOTES)转义所有的html标记 $id = $_GET [ 'id' ]; $id = htmlentities( $id , ENT_QUOTES, 'UTF-8' ); $query = 'SELECT username FROM users WHERE id = "' . $id . '"' ; //使用filter_var过滤script标签,防止xss //$str = strip_tags($input); $str = filter_var( $input , FILTER_SANITIZE_STRING); |
如果PHP支持PDO可以的话,就不要拼接sql,尽量使用PDO或Mysqli。使用PDO要在PDO的DSN中指定charset属性,要设置PDO::ATTR_EMULATE_PREPARES参数为false,禁止php进行本地转义,造成双字节转义攻击,如果没有必要,msyql服务端与php客户端应该都是用utf-8进行通信,而不是gbk。
1 2 3 4 5 6 7 8 9 10 11 | $pdo = new PDO( "mysql:host=192.168.0.1;dbname=test;" , "root" ); $pdo ->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $st = $pdo ->prepare( "select * from info where id =? and name = ?" ); $id = 21; $name = 'zhangsan' ; $st ->bindParam(1, $id ); $st ->bindParam(2, $name ); $st ->execute(); $st ->fetchAll(); |
除了过滤,变量务必初始化,不要自动获得变量,用户将可以构造任何变量进行攻击。
不要这样随意,将造成文件包含攻击
1 2 3 4 5 | <?php if ( $_GET [ 'page' ]) { //if(file_exists($_GET['page'])) {非严格的检测 http://localhost/index.php?page=/etc/passwd%00 include ( $_GET [ 'page' ]); } ?> |
要进行严格的匹配,或者做映射
1 2 3 4 5 | <?php if ( $_GET [ 'page' ] == 'news' ) { include ( 'news.php' ); } ?> |
关闭浏览器输出的错误报告,避免泄漏文件目录路径,错误报告应输出到日记文件中
1 2 3 4 5 | #php.ini display_errors = 'off' #apache2.conf php_flag display_errors off |
php当中
1 | ini_set ( 'display_errors' , false); |
页面的访问要设置权限,特别是执行查询的或者录入的页面,简单设置如下
1 2 | define( 'APP_ADMIN' , '1' ); if (!defined( 'APP_ADMIN' )) exit ; |
应该做一个更为完善的权限系统,如基于角色的权限管理系统,最好细致到页面(元素)。
eval函数针对php安全有很大杀伤力,为了防止挂马,应禁用eval函数。
下载的页面必须设置下载目录,对要下载的文件进行检测或做目录权限控制,生成临时下载链接,不允许下载任意的文件。对于系统配置文件,日记文件等要做分离,建议像zend framework那样设置web目录,程序与web目录分开,避免泄漏源码等。不要将.svn文件直接放置在web目录,会导致源码泄露。
上传的页面必须进行类型检测,不要只截取扩展名,设置权限,不允许可执行文件。这里是网友写的一个文件类型检查类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | /*通过文件名,获得文件类型* *@author chengmo QQ:8292669* *@copyright <a href="http://www.cnblogs.com/chengmo">http://www.cnblogs.com/chengmo</a> 2010-10-17 *@version 0.1 *$filename="d:/1.png";echo cFileTypeCheck::getFileType($filename); 打印:png */ class cFileTypeCheck { private static $_TypeList = array (); private static $CheckClass =null; private function __construct( $filename ) { self:: $_TypeList = $this ->getTypeList(); } /** *处理文件类型映射关系表* * * @param string $filename 文件类型 * @return string 文件类型,没有找到返回:other */ private function _getFileType( $filename ) { $filetype = "other" ; if (! file_exists ( $filename )) throw new Exception( "no found file!" ); $file = @ fopen ( $filename , "rb" ); if (! $file ) throw new Exception( "file refuse!" ); $bin = fread ( $file , 15); //只读15字节 各个不同文件类型,头信息不一样。 fclose( $file ); $typelist =self:: $_TypeList ; foreach ( $typelist as $v ) { $blen = strlen (pack( "H*" , $v [0])); //得到文件头标记字节数 $tbin = substr ( $bin ,0, intval ( $blen )); ///需要比较文件头长度 if ( strtolower ( $v [0])== strtolower ( array_shift (unpack( "H*" , $tbin )))) { return $v [1]; } } return $filetype ; } /** *得到文件头与文件类型映射表* * * @return array array(array('key',value)...) */ public function getTypeList() { return array ( array ( "FFD8FFE1" , "jpg" ), array ( "89504E47" , "png" ), array ( "47494638" , "gif" ), array ( "49492A00" , "tif" ), array ( "424D" , "bmp" ), array ( "41433130" , "dwg" ), array ( "38425053" , "psd" ), array ( "7B5C727466" , "rtf" ), array ( "3C3F786D6C" , "xml" ), array ( "68746D6C3E" , "html" ), array ( "44656C69766572792D646174" , "eml" ), array ( "CFAD12FEC5FD746F" , "dbx" ), array ( "2142444E" , "pst" ), array ( "D0CF11E0" , "xls/doc" ), array ( "5374616E64617264204A" , "mdb" ), array ( "FF575043" , "wpd" ), array ( "252150532D41646F6265" , "eps/ps" ), array ( "255044462D312E" , "pdf" ), array ( "E3828596" , "pwl" ), array ( "504B0304" , "zip" ), array ( "52617221" , "rar" ), array ( "57415645" , "wav" ), array ( "41564920" , "avi" ), array ( "2E7261FD" , "ram" ), array ( "2E524D46" , "rm" ), array ( "000001BA" , "mpg" ), array ( "000001B3" , "mpg" ), array ( "6D6F6F76" , "mov" ), array ( "3026B2758E66CF11" , "asf" ), array ( "4D546864" , "mid" )); } public static function getFileType( $filename ) { if (!self:: $CheckClass ) self:: $CheckClass = new self( $filename ); $class =self:: $CheckClass ; return $class ->_getFileType( $filename ); } } $filename = "22.jpg" ; echo $filename , "t" ,cFileTypeCheck::getFileType( $filename ), "rn" ; $filename = "11.doc" ; echo $filename , "t" ,cFileTypeCheck::getFileType( $filename ), "rn" ; |
对于暴力破解设置验证码,或者限制ip次数。
参考链接:
SQL Injection Attacks by Example
SQL Injection
Injection
PHP SQL Injection
PHP+MySQL环境下SQL Injection攻防总结
PDO防注入原理分析以及使用PDO的注意事项
SQL injection that gets around mysql_real_escape_string()
MySQL远程提权php版
XSS Filter Evasion Cheat Sheet
php通过文件头检测文件类型通用类(zip,rar…)
php 验证码【倾斜,正弦干扰线,黏贴,旋转