上传漏洞及防御

上传问题

  • 上传是web功能中必备的功能, 但是上传文件来源于用户,而且接下来可能立马就会被使用或访问
  • 我们并不知道用户会上传什么样的东西,如果在访问的过程中这个文件被当成了程序去解析就麻烦了
  • 比如像是php,asp这样的文件,再次去访问就会执行立马的代码,这实际上就会给了攻击者很大的后门
  • 攻击者可以把自己的逻辑在服务器上执行,那么他可以想干什么就干什么,所以上传漏洞会是一个大问题
  • 如果使用nodejs这类语言的话,上传文件被解析执行基本不会存在,我们仅仅做一些可能存在问题的示例
    
        phpinfo(); // 输出服务器信息
        var_dump($_SERVER); // 输入服务器内置变量信息
    ?>
    
  • 如上面的代码,如果上传文件路径和url是对应的,那么就会被执行这些代码
  • 在这里我们没有指定相关路由,php自动解析路径url, 所以这个漏洞在php中相当严重

上传漏洞演示

  • 此处我们使用nodejs做演示
  • 前端程序
    
    form(method="post", enctype="multipart/form-data", action="")
        input-field
            input(name="img", type="file")
    
  • 后端程序,我们首先要安装下支持form-data的body解析模块
    • $ cnpm i -S koa-body,之后在合适的地方引入
    • const bodyParser = require('koa-body'); 引入
    • app.use(bodyParse({multipart: true})); 配置
    • router.get('/uploadFile/*', site.uploadFile) 添加上传路由
  • 后端接收上传程序
    const fs = require('fs');
    const path = require('path');
    if(data.files) {
        let file = data.files.img;
        let ext = path.extname(file);
        let filename = Date.now() + ext;
        fs.renameSync(file.path, './static/upload/' + filename);
        
        data.fields.content += '+ filename +'" />';
        data = data.fields;
    
        // 模拟处理数据
        const result = await Comment.create({
            userId,
            postId: data.postId,
            content: data.content;
        });
        if(result) {
            ctx.redirect(`/post/${data.postId}`); // 跳转到文章详情页
        } else {
            ctx.body = 'Oops, something went wrong!'
        }
    }
    
  • 后端处理上传路径逻辑
    const fs = require('fs');
    exports.uploadFile = async function(ctx, next) {
        ctx.request.path;
        let filepath = ctx.request.path.replace('^\/uploadFile\//', '');
        filepath = './static/upload' + filepath; // 拼接成本地文件名称
        // 判断文件是否存在,不存在则返回404
        if(!fs.existsSync(filepath)) {
            ctx.status = 404;
            return;
        }
        let ext = path.extname(filepath);
        // 这里使用nodejs模拟php的自动运行文件行为
        var run = function(filepath) {
            return new Promise(function(resolve, reject) {
                // 使用child_procss这个模块去运行这个文件
                var child = require('child_procss').fork(filepath, [], {});
                var ret = '';
                child.stdout.on('data', function(data) {
                    console.log(data);
                    ret += data;
                });
                child.on('close', function(data) {
                    resolve(ret);
                });
                child.on('error', function(error) {
                    reject(error);
                })
            })
        }
        if(ext === '.js') {
            ctx.body = await run(filepath);
            return;
        }
        ctx.body = fs.readFileSync(filepath);
    }
    
  • 好的,程序到此为止,如果我们正常上传图片,它会正常展示,
  • 如果我们上传的是.js文件,它就会尝试运行.js文件, 加入上传的文件是下面的内容
    var dir = require('fs').readdirSync('/etc');
    console.log(dir);
    
  • 这时候就会泄露服务端很多信息,非常危险,其实他可以执行任何它想要去做的事情
  • 当然nodejs这类程序是我们自己有意写的,实际上nodejs中没有必要考虑这个漏洞
  • 在nodejs中这类场景是不存在的场景,不过在php,asp等环境是自动执行的
  • 所以,我们需要知道存在这个上传漏洞,也就是上传了服务器可执行文件,然后文件被执行

上传漏洞案例

  • 基于php框架的cms系统中图片裁剪上传,前端上传受限,但传输换包导致收到攻击
  • 基于jsp的3gQQ网站上传漏洞:上传文件被重命名,但验证码错误返回文件路径, 基于文件路径发起上传攻击

上传漏洞的防御

  • 上传之所以存在问题是因为
    • 文件由用户上传
    • 上传后通过url访问刚上传的文件
    • 在访问的时候,这个文件有可能会被当做程序去解析
  • 根据上面的因素,我们对应做出对策
    • 上传过程中限制用户上传程序
    • 访问时不能直接访问文件
    • 访问时,如果是程序则禁止执行
  • 具体措施,我们下面来聊聊

限制上传后缀

// 这里是后端来处理,当然前端也要做,后端做更为重要
// 但有时候,仅依靠后缀会不可靠
if(ext === '.js') {
    throw new Error('File Type Not Allowed!');
}

文件类型检查

// file.type是浏览器解析的时候给的,用户无法控制
// 但是攻击者可以会绕过浏览器给你发请求
// 所以这个在一定程度上也不可靠
if(file.type !== 'images/png') {
    throw new Error('不是png格式');
}

文件内容检查

// 一般情况下通过文件类型来判断具体是什么文件
// 但是像php,asp,jsp等混编的文件是无法正确判断的,因为其前几位可以自己编写
// 这个方法仍旧是存在不可靠因素
var fileBuffer = fs.readFileSync(file.path);
// 判断第一位
if(fileBuffer[0] == 0x5b) {
    // ...
}
// 判断第二位
// 判断第三位

直接读取文件而不执行

// 用程序直接读取之后返回给客户端
// php等语言同样可以做
// 这样不给文件执行的机会, 但是有个性能问题(读写过程)
ctx.body = fs.readFileSync(filepath);

权限控制

  • 可写,可执行互斥
  • 也就是一个文件/目录不能有上面这两种权限的同时存在
  • 脚本可正常工作是因为它可以被执行, 如果不能被执行就没有危害
  • 这是web服务器的一个配置原则,最低安全保障
  • 在部署应用的时候,使用的用户是低权限用户,满足部署需要即可,不能用root用户
  • 如果是静态文件存储,直接使用云存储会更好更安全,我们这里考虑的是自己服务器的处理方案

PHP 文件上传防御


if(strtolower($_SERVER['REQUEST_METHOD']) == 'post') {
    $content = $_POST['content'];
    // var_dump($_FILES['img']);
    $info = pathinfo($_FILES['img']['name']);
    // var_dump($info);
    // 限制上传后缀,也可以用数组的方式来处理
    if($info['extension'] !== 'png') {
        // TODO
    }

    // 文件类型的检查
    if($_FILES['img']['type'] !== 'image/png') {
        // TODO
    }

    // 文件内容的检查
    $info2 = finfo_file(finfo_open(), $_FILES['img']['tmp_name']);
    // var_dump($info2); // 根据前几个字符来判断 TODO
}

// 通过程序输出文件, 用户访问示例:/test.php?down=1
if($_GET['down']) {
    $file = file_get_contents('./test.php'); // 读取该文件,简单处理,示例而已
    header('Content-Type: text/plain');
    die($file);
}
?>

<form method="post" enctype="multipart/form-data">
    <input type="file" name="img" />
    <textarea name="content">hello</textarea>
    <button type="submit">提交</button>
</form>

你可能感兴趣的:(Web,php,web安全,前端)