你不知道的文件上传漏洞php代码分析
2018-09-07 17:41
漏洞描述
开发中文件上传功能很常见,作为开发者,在完成功能的基础上我们一般也要做好安全防护。
文件处理一般包含两项功能,用户上传和展示文件,如上传头像。
文件上传攻击示例
upload.php
<?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
<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]);?>
恶意用户可以通过访问 如来执行远程命令
Content-type验证
upload.php
<?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
<?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
<?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
<?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; } ?>
用户不可以直接通过来访问文件,必须通过view.php来访问
view.php
<?php $uploaddir = d:/uploads/; $name = $_GET[name]; readfile($uploaddir.$name); ?>
查看文件代码未验证文件名,用户可以通过例如,查看指定的文件
解决漏洞示例
upload.php
<?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
<?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.文件上传限制文件大小,个人上传数量等
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。