漏洞描述
开发中文件上传功能很常见,作为开发者,在完成功能的基础上我们一般也要做好安全防护。
文件处理一般包含两项功能,用户上传和展示文件,如上传头像。
文件上传攻击示例
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.文件上传限制文件大小,个人上传数量等
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。