网鼎杯第二场 wafUpload

题目
<?phpm
#$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>

思路

审计源码可以知道,代码中用 end 函数取到上传文件的后缀并判断,用 reset 函数返回的值作为文件名

根据题目,需要绕过两层判断。

1.第一层,直接抓包修改 MIME 为 image/png 就行了。

2.第二层,构造 filename 字段为数组

仔细看 html 代码中提供了一个 filename 字段,在下面这句代码的判断中,会先查看是否有直接 post 提交的 filename 字段,如果有的话就使用这个字段的值

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

如果没有POST该字段,$file变量取上传时的name,即** $_FILES[‘file’][‘name’] **

若$file文件名不是数组,就对字符串中的点号. 进行explode分割,分割成数组
如上传aa.bb.php会被切为

[0] = > ‘aa’

[1] => ‘bb’

[2] => ‘php’

这样的数组

获取扩展名代码

$ext = end($file);

利用了end函数,这个函数可以返回数组的最后一项。

也就返回了最后的php作为$ext,再经过判断ext是否是jpg、png、gif的一种。

绕过

利用end reset函数的缺陷
举个例子:

<?php

$arr = array();

$arr[0] = 'first';
$arr[1] = 'second';
$arr[2] = 'third';

var_dump($arr);

echo "the result of reset: ".reset($arr)."n";

echo "the result of end: ".end($arr);
?>

end 函数原本的作用就是返回数组的最后一个元素,在上面看的是正常的。但是如果我们这里把对数组赋值的顺序换一下(先给 arr[2] 赋值),可以看到结果就变了。
继续尝试会发现 reset 函数也是一样,第一个给数组赋值的值就是 reset 函数返回的值,并不一定是arr[0]。

所以构造playload

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

end取的是png能通过校验
在后面拼接 $filename 时候,再一次拼接到后缀名,即

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

此时$file[count($file) - 1] 取到的就是$file[2-1]->$filename[1]
最后拼接出了 php.php,就达到了上传 shell 的目的。

https://www.anquanke.com/post/id/164561#h2-1