昨天备份本博客的时候发现,上传目录下多了几个奇怪的.asp和.php文件。这些文件并非我自己上传的,很可能是通过某个漏洞传到了服务器上。这会有什么危害呢?下面简单介绍一下。
假设某人把一个功能为删除站点下所有文件的evil.php通过漏洞传到了http://abc.com/upload/下,然后访问http://abc.com/upload/evil.php,那么:
如果该站点不支持php脚本,是没什么危害的;
如果该站点支持php脚本,它的全部文件就会被删除。
这里有两个关键点,一是主动访问该文件才会触发脚本执行,二是服务器支持该类型脚本才能执行。而本博客刚好不具备这两个条件(后面再详细说明),所以没有造成任何损失。即便如此,这漏洞还是要修复的,不然被无休止地上传文件迟早会占满硬盘空间。
本博客基于express框架开发,上传功能是通过multer中间件实现的,且只有管理后台存在文件上传的功能。因为后台页面、接口都设有权限验证,是不可能被绕过的。最后发现问题出在multer的调用上,而这问题又要归咎于官方文档的错误引导了:
app.use(multer({
dest: './uploads/',
rename: function (fieldname, filename) {
return filename.replace(/\W+/g, '-').toLowerCase() + Date.now()
}
}))
app.use(multer({
dest: './public/upload/',
rename: function () {
var now = new Date();
// 重命名为 年+月+日+时+分+秒+5位随机数
return now.getFullYear() +
( '0' + (now.getMonth() + 1) ).slice(-2) +
( '0' + now.getDate() ).slice(-2) +
( '0' + now.getHours() ).slice(-2) +
( '0' + now.getMinutes() ).slice(-2) +
( '0' + now.getSeconds() ).slice(-2) +
parseInt(10000 + Math.random() * 90000);
}
}))
上面第一段代码为官方文档的示例,第二段代码是我在博客程序中的调用方法。而这样写就意味着所有http访问(包括404的访问)都会经过multer这个中间件。所以只要某个http访问中包含文件,该文件就会被multer保存到指定目录(dest)下。
安全的用法应该是,只对指定路径调用multer中间件,并且增加权限验证:
// 权限检查
function addPermissionChecking(handler) {
return function(req, res, next) {
// 假设用户信息保存在req.currentUser中
if (req.currentUser) {
handler.apply(this, arguments);
} else {
next('权限不足');
}
};
}
app.use(
'/upload',
addPermissionChecking(
multer({
dest: './public/upload/',
rename: function () {
var now = new Date();
// 重命名为 年+月+日+时+分+秒+5位随机数
return now.getFullYear() +
( '0' + (now.getMonth() + 1) ).slice(-2) +
( '0' + now.getDate() ).slice(-2) +
( '0' + now.getHours() ).slice(-2) +
( '0' + now.getMinutes() ).slice(-2) +
( '0' + now.getSeconds() ).slice(-2) +
parseInt(10000 + Math.random() * 90000);
}
})
)
);
就这样,问题解决了。最后再解释一下为什么本博客没有受到脚本文件的影响:
上传的文件经过重命名,且新文件名中包含随机数,也就是说攻击者就算上传了脚本文件也无法获知文件名,也就不知道访问路径;
在express中压根儿不支持ASP和PHP。