漏洞描述
开发中文件上传功能很常见,作为开发者,在完成功能的基础上我们一般也要做好安全防护。
文件处理一般包含两项功能,用户上传和展示文件,如上传头像。
文件上传攻击示例
upload.php
1
2
3
4
5
6
7
8
9
10
|
<?php
$uploaddir = 'uploads/' ;
$uploadfile = $uploaddir . basename ( $_files [ 'userfile' ][ 'name' ]);
if (move_uploaded_file( $_files [ 'userfile' ][ 'tmp_name' ], $uploadfile )){
echo "file is valid, and was successfully uploaded.\n" ;
}
else {
echo "file uploading failed.\n" ;
}
?>
|
upload.html
1
2
3
4
|
<form name= "upload" action= "upload1.php" method= "post" enctype= "multipart/formdata" >
select the file to upload: <input type= "file" name= "userfile" >
<input type= "submit" name= "upload" value= "upload" >
</form>
|
上述代码未经过任何验证,恶意用户可以上传php文件,代码如下
<?php eval($_get['command']);?>
恶意用户可以通过访问 如http://server/uploads/shell.php?command=phpinfo(); 来执行远程命令
content-type验证
upload.php
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<?php
if ( $_files [ 'userfile' ][ 'type' ] != "image/gif" ) { //获取http请求头信息中contenttype
echo "sorry, we only allow uploading gif images" ;
exit ;
}
$uploaddir = 'uploads/' ;
$uploadfile = $uploaddir . basename ( $_files [ 'userfile' ][ 'name' ]);
if (move_uploaded_file( $_files [ 'userfile' ][ 'tmp_name' ], $uploadfile )){
echo "file is valid, and was successfully uploaded.\n" ;
} else {
echo "file uploading failed.\n" ;
}
?>
|
该方式是通过http请求头信息进行验证,可通过修改content-type ==> image/jpg绕过验证,可以通过脚本或burpsuite、fiddle修改
如下
content-disposition: form-data; name="userfile"; filename="shell.php"
content-type: image/gif
图片类型验证
该方法通过读取文件头中文件类型信息,获取文件类型
备注:如jpeg/jpg文件头标识为ffd8
upload.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<?php
$imageinfo = getimagesize ( $_files [ 'userfile' ][ 'tmp_name' ]);
if ( $imageinfo [ 'mime' ] != 'image/gif' && $imageinfo [ 'mime' ] != 'image/jpeg' ) {
echo "sorry, we only accept gif and jpeg images\n" ;
exit ;
}
$uploaddir = 'uploads/' ;
$uploadfile = $uploaddir . basename ( $_files [ 'userfile' ][ 'name' ]);
if (move_uploaded_file( $_files [ 'userfile' ][ 'tmp_name' ], $uploadfile )){
echo "file is valid, and was successfully uploaded.\n" ;
} else {
echo "file uploading failed.\n" ;
}
?>
|
可以通过图片添加注释来绕过此验证。
如添加注释<?php phpinfo(); ?>,保存图片后将其扩展名改为php,则可成功上传。
上传成功后访问该文件则可看到如下显示
文件扩展名验证
通过黑名单或白名单对文件扩展名进行过滤,如下代码
upload.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<?php
$blacklist = array ( ".php" , ".phtml" , ".php3" , ".php4" );
foreach ( $blacklist as $item ) {
if (preg_match( "/$item\$/i" , $_files [ 'userfile' ][ 'name' ])) {
echo "we do not allow uploading php files\n" ;
exit ;
}
}
$uploaddir = 'uploads/' ;
$uploadfile = $uploaddir . basename ( $_files [ 'userfile' ][ 'name' ]);
if (move_uploaded_file( $_files [ 'userfile' ][ 'tmp_name' ], $uploadfile )){
echo "file is valid, and was successfully uploaded.\n" ;
} else {
echo "file uploading failed.\n" ;
}
?>
|
当黑名单不全,构造特殊文件名可以绕过扩展名验证
直接访问上传的文件
将上传文件保存在非web root下其他文件夹下,可以防止用户通过路径直接访问到文件。
upload.php
1
2
3
4
5
6
7
8
9
|
<?php
$uploaddir = 'd:/uploads/' ;
$uploadfile = $uploaddir . basename ( $_files [ 'userfile' ][ 'name' ]);
if (move_uploaded_file( $_files [ 'userfile' ][ 'tmp_name' ], $uploadfile )) {
echo "file is valid, and was successfully uploaded.\n" ;
} else {
echo "file uploading failed.\n" ;
}
?>
|
用户不可以直接通过http://localhost/uploads/ 来访问文件,必须通过view.php来访问
view.php
1
2
3
4
5
|
<?php
$uploaddir = 'd:/uploads/' ;
$name = $_get [ 'name' ];
readfile( $uploaddir . $name );
?>
|
查看文件代码未验证文件名,用户可以通过例如http://localhost/view.php?name=..//php/upload.php,查看指定的文件
解决漏洞示例
upload.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<?php
require_once 'db.php' ;
$uploaddir = 'd:/uploads/' ;
$uploadfile = tempnam( $uploaddir , "upload_" );
if (move_uploaded_file( $_files [ 'userfile' ][ 'tmp_name' ], $uploadfile )) {
$db =& db::connect( "mysql://username:password@localhost/database" );
if (pear::iserror( $db )) {
unlink( $uploadfile );
die "error connecting to the database" ;
}
$res = $db ->query( "insert into uploads set name=?, original_name=?,mime_type=?" ,
array ( basename ( $uploadfile , basename ( $_files [ 'userfile' ][ 'name' ]), $_files [ 'userfile' ][ 'type' ]));
if (pear::iserror( $res )) {
unlink( $uploadfile );
die "error saving data to the database. the file was not uploaded" ;
}
$id = $db ->getone( 'select last_insert_id() from uploads' );
echo "file is valid, and was successfully uploaded. you can view it <a href=\"view.php?id=$id\">here</a>\n" ;
} else {
echo "file uploading failed.\n" ;
}
?>
|
view.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<?php
require_once 'db.php' ;
$uploaddir = 'd:/uploads/' ;
$id = $_get [ 'id' ];
if (! is_numeric ( $id )) {
die ( "file id must be numeric" );
}
$db =& db::connect( "mysql://root@localhost/db" );
if (pear::iserror( $db )) {
die ( "error connecting to the database" );
}
$file = $db ->getrow( 'select name, mime_type from uploads where id=?' , array ( $id ), db_fetchmode_assoc);
if (pear::iserror( $file )) {
die ( "error fetching data from the database" );
}
if ( is_null ( $file ) || count ( $file )==0) {
die ( "file not found" );
}
header( "content-type: " . $file [ 'mime_type' ]);
readfile( $uploaddir . $file [ 'name' ]);
?>
|
上述代码文件名随机更改,文件被存储在web root之外,用户通过id在数据库中查询文件名,读取文件,可以有效的阻止上述漏洞发生
总结
通过以上示例分析,可总结一下几点
1.文件名修改,不使用用户上传的文件名
2.用户不可以通过上传路径直接访问文件
3.文件查看采用数据库获取文件名,从而在相应文件服务器读取文件
4.文件上传限制文件大小,个人上传数量等
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。