Web安全小总结:XSS,CSRF及其防御

3,691 阅读13分钟

前言

其实, 前端的安全并没有很多, 不过知道了, 起码后端兄弟不会那么累了。
本文主要讨论以下几种攻击方式 :

  • XSS方式
  • CSRF方式
  • 点击劫持

希望大家在阅读完文本之后, 能够很好地回答以下的几个问题:

  • 前端的攻击方式有哪些?
  • 什么是XSS方式? XSS攻击有几种类型?如何防范XSS攻击?
  • 什么是CSRF攻击?如何防范CSRF攻击?
  • 如何检测网站是否安全?

1. XSS方式

XSS(Cross-Site-Scripting),跨站脚本攻击是一种代码注入攻击。攻击者在目标网站上注入恶意代码,当被攻击者登录网站时就会执行这些恶意代码, 这些脚本可以读取 cookie ,session tokens , 或者其他敏感的网站消息,对用户进行钓鱼欺诈,甚至发起蠕虫攻击等。

XSS的本质:恶意代码未经过滤,与网站正常的代码混在一起;浏览器无法分辨哪些脚本是可信的,导致恶意脚本被执行。由于直接在用户的终端执行,恶意代码能够直接获取用户的信息,利用这些信息冒充用户向网站发起攻击

XSS的分类:

  • 存储型
  • 反射型
  • DOM 型

1.1 反射型XSS

当用户点击一个恶意链接, 或者提交一个表单, 或者进入一个恶意网站时, 注入脚本进入被攻击者的网站。Web 服务器将注入脚本,比如一个错误信息, 搜索结果等 , 未进行过滤直接返回到用户的浏览器上。

反射型XSS的攻击步骤:

  • 1.攻击者构造出特殊的URL, 其中包含恶意代码
  • 2.用户打开带有恶意代码的URL时, 网站服务端将恶意代码从 URL中取出, 拼接在 HTML中返回给浏览器
  • 3.用户浏览器接收到响应后解析执行, 混在其中的恶意代码也被执行。
  • 4.恶意代码窃取用户数据并发送到攻击者的网站, 或者冒充用户的行为, 调用目标网站接口执行攻击者指定的操作。

反射型 XSS 漏洞常见于 URL 传递参数的功能 , 如网站搜索 , 跳转等。由于需要用户主动打开恶意的 URL 才能生效 , 攻击者往往会结合多种手段诱导用户点击

POST 的内容也可以触发反射型 XSS , 只不过其触发条件比较苛刻 (需要构造表单提交页面 , 并引导用户点击),所以非常少见。

话不多说 , 我们来举个例子

//前端代码
// index.html
// 由于必须诱导用户点击,所以可能是这样的
<button id="btn" onclick="test()">
    //这是一张很好看的图片
    <img src="https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1136880418,2628684495&fm=15&gp=0.jpg" alt="">
</button>
<script>
    var test = function() {
    // 构造出的特殊的 url , 其中包含恶意代码
    let url = `/welcome?type=<script>alert('恶意内容')<\/script>`;
    location.href = url;
}
</script>

// 后端代码
// server.js
const express = require('express');
const app = express();
app.use(express.static(path.join(__dirname)))
app.get('/welcome',function(req, res) {
    // 把恶意代码当做字符串, 传输回浏览器
    res.send(`${req.query.type}`);
    res.end();
})

app.listen(3000, ()=> {
    console.log('server is running at port 4000')
});

如果不希望被前端拿到 cookie, 后端可以设置 httpOnly (不过这个不是 XSS 的解决方案 , 只能降低受损范围)

如何防范反射型 XSS攻击?

对字符串进行编码
对url的查询参数进行转义后再输出到页面

app.get('/welcome',function(req,res) {
    //对查询参数进行编码,避免反射型 XSS攻击
    res.send(`${encodeURIComponent(req.query.type)}`); 
})

总结:简单来说,前端向后端发送GET请求数据,后端返回结果之前,必须先对url查询参数进行编码再输出到页面

1.2 DOM型XSS

DOM型 XSS 攻击 , 实际上就是前端 javaScript 代码不够严谨 , 把不可信的内容插入到了页面 。在使用 .innerHTML , .outerHTML , appendChild , document.write()等 API 时要特别小心 , 不要把不可信的数据作为为 HTML 插入到页面上 , 尽量使用 .innerText , .textContent , setAttribute()等。

DOM型XSS 的攻击步骤:

  • 1.攻击者构造出特殊数据 , 其中包含恶意代码
  • 2.用户浏览器执行恶意代码
  • 恶意代码窃取用户数据并发送到攻击者的网站 , 或者冒充用户的行为 , 调用目标网站接口执行攻击者指定的操作。

如何防范 DOM型XSS攻击

防范 DOM 型 XSS攻击的核心就是对输入内容进行转义 (DOM 中的内联事件监听器和链接跳转都能把字符串作为代码运行, 需要对其内容进行检查)

  1. 对于 url 链接(例如图片的src属性), 那么直接使用 encodeURIComponent来转义

  2. 非 url , 我们可以这样进行编码

     function encodeHtml(str) {
         return str.replace(/"/g, '&quot;')
                 .replace(/'/g, '&apos;')
                 .replace(/</g, '&lt;')
                 .replace(/>/g, '&gt;');
     }
    

DOM型 XSS攻击中 , 取出和执行恶意代码由浏览器端完成 , 属于前端 javaScript 自身的安全漏洞

再举个例子

//前端
<form onsubmit="return false">
    <div class="form-group">
        <label for="comments">商品评论</label>
        <input class="form-control" type="text" id="comments" />
    </div>
    <div class="form-group">
        <input class="btn btn-danger" type="button" id="attck" value="会被攻击" />
        <input class="btn btn-primary" type="button" id="security" value="我很安全" />
    </div>
</form>
<form>
    <ul class="list-group"></ul>
</form>
<script src="/node_modules/jquery/dist/jquery.js"></script>
<script>
    // DOM型 XSS 攻击
    $('#attck').click(function () {
        let commend = $('#comments').val();
        console.log(commend);
        if ($.trim(commend)) {
            $('.list-group').append(`<li class="list-group-item">${commend}</li>`);
            $('#comments').val('');
        }
    })
    // HTML 编码
    function encodeHtml(str) {
        return str.replace(/"/g, '&quot;')
            .replace(/'/g, '&apos;')
            .replace(/</g, '&lt;')
        replace(/>/g, '&gt;')
    }

    $('#security').click(function () {
        let commend = $('#comments').val();
        console.log(commend)
        // 对内容进行转义
        // 如果是url 地址 , 可以直接使用 encodeURIComponent 进行转义
        if ($.trim(commend)) {
            $('.list-group').append(`<li class="list-group-item">${encodeHtml(commend)}</li>`);
            $('#comments').val('');
        }
    })
</script>

总结:就是不要相信用户输入的内容,如果非要在页面上插入 HTML,就必须先转义再插入。对于URL可以使用encodeURIComponent,对于非URL可以使用上面的方法

1.3 存储型 XSS

恶意脚本永久存储在目标服务器上。当浏览器请求数据时, 脚本从服务器传回并执行,影响范围比反射型和 DOM型 XSS更大。 存储型XSS攻击的原因仍然是没有做好数据过滤;

  • 前端提交数据到服务端时 , 没有做好过滤;
  • 服务端在接受到数据时 , 在存储之前 , 没有做好过滤
  • 前端从服务端请求到数据 , 没有过滤输出。

存储型XSS的攻击步骤

  • 1.攻击者将恶意代码提交到目标网站的数据库中
  • 2.用户打开目标网站时 , 网站服务端将恶意代码从数据库取出, 拼接在 HTML中返回给浏览器
  • 3.用户浏览器接收到响应后解析执行, 混在其中的恶意代码也被执行。
  • 4.恶意代码窃取用户数据并发送到攻击者的网站, 或者冒充用户的行为, 调用目标网站接口执行攻击者指定的操作

这种攻击常见于带有用户保存数据的网站功能, 如论坛发帖, 商品评论, 用于私信等。

如何防范存储型 XSS攻击

  • 1.前端数据传递给服务器之前, 先转义/过滤(防范不了抓包修改数据的情况)
  • 2.服务端接收到数据, 在存储到数据库之前, 进行转义/过滤
  • 3.前端接收到服务器传递过来的数据, 在展示到页面前, 先进行转义/过滤

来一个例子, 可能长一点 , 登录用了一下 cookie

// 前端
// index.html
<div class="panel-heading">
    <h3>论坛</h3>
</div>
<ul class="list-group"></ul>
    <form>
        <div class="form-group">
            <label for="comments">打个招呼吧</label>
            <input class="form-control" type="text" id="comments" />
        </div>
        <div class="form-group">
            <input class="btn btn-primary" type="button" id="publish" value="发帖" />
        </div>
    </form>
<script src="/node_modules/jquery/dist/jquery.js"></script>
<script>
// 存储型 XSS 攻击
$(document).ready(function () {

    //获取帖子列表
    function getList() {
        $.get('/getComments2').then(res => {
            if (res.code === 0) {
                let lists = '';
                $.each(res.comments, (index, comment) => {
                    // 前端接收到服务器传递过来的数据, 在展示到页面之前, 进行转义
                    content = encodeHtml(comment.content);
                    console.log(comment.content , content)
                    lists += `<li class="list-group-item"><span>${comment.username}:</span> ${content}</li>`;
                });
                $('.list-group').html(lists);
            }
        });
    };
    getList();
    //Html 编码
    function encodeHtml(str) {
        return str.replace(/"/g, '&quot;')
            .replace(/'/g, '&apos;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;');
    }
    //Html 解码
    function decodeHtml(str) {
        return str.replace(/&quot;/g, '\"')
            .replace(/&apos;/g, '\'')
            .replace(/&lt;/g, '<')
            .replace(/&gt;/g, '>');
    }
    $('#publish').click(function () {
        let comment = $('#comments').val();
        // 前端数据传递给服务器之前 , 先转义/过滤(防范不了抓包修改数据的情况)
        $.post('/addComment2', { comment: encodeHtml(comment) }).then(res => {
            if (res.code === 1) {
                //跳去登录页
                location.href = '/login.html';
            } else {
                //获取新的帖子列表
                getList();
                $('#comments').val('');
            }
        });
    });
});
</script>

//前端 login.html
<div class="panel-heading">
    <h3>登录</h3>
</div>
<form onsubmit="return false">
    <div class="form-group">
        <label for="username">用户名</label>
        <input class="form-control" type="text" id="username" />
    </div>
    <div class="form-group">
        <label for="password">密码</label>
        <input class="form-control" type="text" id="password" />
    </div>
    <div class="form-group">
        <input class="btn btn-primary" type="submit" id="login" value="登录" />
    </div>
</form>
<script src="/node_modules/jquery/dist/jquery.js"></script>
<script>
    $(document).ready(function () {
        $('#login').click(function () {
            let username = $('#username').val();
            let password = $('#password').val();

            $.post('/api/login', {
                username,
                password
            }).then(res => {
                if (res.code === 0) {
                    //登录成功页
                    location.href = `/welcome?type=<script>alert('恶意内容')<\/script>`;
                } else {
                    //登录失败(如果是用户登录成功时,还能够获取到用户的cookie)
                    location.href = `/error?type=<script>alert('恶意内容')<\/script>`;
                }
            })
        })
    })
</script>

// 后端 server.js
const express = require('express');
const app = express();
const path = require('path');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');


//设置路径
app.use(express.static(path.join(__dirname, 'src')));
app.use(express.static(path.join(__dirname, '../')));
//将参数转换成对象
app.use(bodyParser.urlencoded({ extended: true }));
//req.cookie[xxx] 获取cookie
app.use(cookieParser());

//用户列表
let userList = [{ username: 'zs', password: '123456' }, { username: 'star', password: 'star' }];

let SESSION_ID = 'connect.sid';
let session = {};
//登录接口
app.post('/api/login', (req, res) => {
    let { username, password } = req.body;
    let user = userList.find(item => item.username === username && item.password === password);
    if (user) {
        //用户登录后,给一个标识(cookie登录)
        const cardId = Math.random() + Date.now();
        session[cardId] = { user };
        res.cookie(SESSION_ID, cardId);
        res.json({ code: 0 });
    } else {
        res.json({ code: 1, error: `${username} does not exist or password mismatch` });
    }

});

//1.反射型XSS攻击: http://localhost:3000/error?type=<script>alert('恶意内容')</script>
app.get('/error', function (req, res) {
    res.send(`${req.query.type}`); //拿到 url 上的 type 参数,并返回给前端    
});

app.get('/welcome', function (req, res) {
    //对查询参数进行编码,避免XSS攻击
    res.send(`${encodeURIComponent(req.query.type)}`);
    //对type查询参数进行编码,即可解决当前的XSS攻击(可重启服务查看)
    // res.send(`${encodeURIComponent(req.query.type)}`);
});

//安全的评论列表
let comments2 = [
    { username: 'zs', content: '我是zs' },
    { username: 'hw', content: '我是hw' },
    { username: 'star', content: '大家好,我是Star' },
]
app.get('/getComments2', function (req, res) {
    res.json({ code: 0, comments: comments2 });
});
function encodeHtml(str) {
    return str.replace(/"/g, '&quot;')
            .replace(/'/g, '&apos;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;');
}
app.post('/addComment2', function (req, res) {
    //cardId (req.cookies[SESSION_ID])要派上用场啦~
    let info = session[req.cookies[SESSION_ID]];
    if (info) {
        //用户已经登录
        let username = info.user.username;
        //  服务器接收到数据后 , 在存储到数据库之前 , 进行转义/过滤
        comments2.push({ username, content: encodeHtml(req.body.comment) });
        res.json({ code: 0, comments: comments2 });
    } else {
        res.json({ code: 1, error: 'user not logged in.' });
    }
});

app.listen(3000);

总结:

  • 1.恶意脚本如果未经过转换, 存储到了后台。任何用户访问此页面, 都会执行恶意脚本
  • 2.说白了就是增加字符串的过滤:
    • 前端输入时过滤
    • 服务端增加时过滤
    • 前端输出时过滤

1.4 JSONP中存在的 XSS 安全问题

    //一个 jsonp函数大概是这样
    function jsonp({ url, params, callback }) {
        return new Promise((resolve, reject) => {
            let script = document.createElement('script');
            params = JSON.parse(JSON.stringify(params));
            let arrs = [];
            for (let key in params) {
                arrs.push(`${key}=${params[key]}`);
            }
            arrs.push(`callback=${callback}`);
            script.src = `${url}?${arrs.join('&')}`;
            document.body.appendChild(script);
            console.log(callback)
            window[callback] = function (data) {
                resolve(data);
                document.body.removeChild(script);
            }
        })
    }
//如果我这样调用 ,就会 XSS安全问题
jsonp({
    url: 'http://localhost:3000/say',
    params: {
        wd: 'I Love you'
    },
    callback: 'alert(1)'
}).then(data => {
    alert(data.username)
    console.log(data)
})

简单来说就是把 jsonp中回调函数的参数设置为恶意代码

常见的预防操作

  • 将重要的cookie标记为http only,这样的话Javascript 中的document.cookie语句就不能获取到cookie了。

  • 只允许用户输入我们期望的回调参数

      let { callback} = req.query;
      if(callback === 'show') {
        callback = 'show';
      } else {
        res.end('error')
      }
    
  • 过滤或移除特殊的Html标签

      <script>,<iframe>等
    

2. CSRF方式

CSRF(Cross-site-request-forgery) 跨站请求伪造: 攻击者诱导受害者进入第三方网站, 在第三方网站中 , 向被攻击网站发起跨域请求。利用受害者在被攻击网站已经获取的注册凭证 , 绕过后台的用户验证, 达到冒充用户对被攻击的网站执行某项操作的目的。

典型的CSRF攻击流程

  • 1.受害者登录 A 站点 , 并保留了登录凭证(Cookie)
  • 2.攻击者诱导受害者访问了站点B
  • 3.站点B向站点A发送了一个请求, 浏览器会默认携带站点A的Cookie信息。
  • 4.站点A接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是无辜的受害者发送的请求。
  • 5.站点A以受害者的名义执行了站点B的请求
  • 攻击完成,攻击者在受害者不知情的情况下,冒充受害者完成了攻击

CSRF的特点

  • 1.攻击通常在第三方网站发起,如图上的站点B,站点A无法防止攻击发生。
  • 2.攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;并不会去获取cookie信息(cookie有同源策略)
  • 3.跨站请求可以利用各种方式:图片URL,超链接,CORS, Form提交等等(来源不明的连接,不要点击)

CSRF攻击防御

1.添加验证码(体验不好)

验证码能够防御CSRF攻击, 但是我们不可能每一次交互都需要验证码,否则用户的体验会非常差,但是我们可以在转账,交易等操作时, 增加验证码, 确保我们的账户安全。

2.判断请求的来源:检测Referer(并不安全,Referer可以被更改)

Referer 可以作为一种辅助手段,来判断请求的来源是否是安全的, 但是鉴于 Referer 本身是可以修改的 , 因此不能晋仅依赖于 Referer

3.使用Token(主流)

CSRF攻击之所以能够成功, 是因为服务器误把攻击者发送的请求当成了用户自己的请求。那么我们可以要求所有的用户请求都携带一个CSRF攻击者无法获取到的 Token。服务器通过校验请求是否携带正确的Token , 来把正常的请求和攻击请求区分开。跟验证码类似,只是用户无感知

步骤:

  • 服务端给用户生成一个token, 加密后传递给用户。
  • 用户在提交请求时, 需要携带这个token
  • 服务端验证token是否正确

这个比较常用,我就来举个例子吧

//login.html 登录页
<div class="panel-heading">
    <h3>登录</h3>
</div>
<div class="panel-body">
    <form onsubmit="return false">
        <div class="form-group">
            <label for="username">用户名</label>
            <input class="form-control" type="text" id="username" />
        </div>
        <div class="form-group">
            <label for="password">密码</label>
            <input class="form-control" type="text" id="password" />
        </div>
        <div class="form-group">
            <input class="btn btn-primary" type="submit" id="login" value="登录" />
        </div>
    </form>
</div>
<script src="/node_modules/jquery/dist/jquery.js"></script>
<script>
$('#login').click(function () {
    let username = $('#username').val();
    let password = $('#password').val();
    console.log(username,password)
    $.post('/api/login', {
        username,
        password
    }).then(res => {
        if(res.code === 0) {
            // 登录成功页
            location.href = '/safe3.html'
        } else {

        }
    })
})
</script>

//safe.html 显示页面
<div class="panel-heading">
    <h3>转账</h3>
    <h5>
        <p>用户:<span id='username'></span></p>
        <p>余额:<span id='account'></span></p>
    </h5>
</div>
<div class="panel-body">
    <form onsubmit="return false">
        <div class="form-group">
            <label for="payee">收款人</label>
            <input class="form-control" type="text" id="payee" />
        </div>
        <div class="form-group">
            <label for="amount">金额</label>
            <input class="form-control" type="text" id="amount" />
        </div>
        <div class="form-group">
            <input class="btn btn-primary" type="submit" id="transfer" value="转账" />
        </div>
    </form>
</div>
<button>
    <a href="/fish3.html">钓鱼连接</a>
</button>
</body>
<script src="/node_modules/jquery/dist/jquery.js"></script>
<script>
    function getUserInfo () {
        $.get('/api/userinfo').then(res => {
            if(res.code === 1) {
                location.href = '/login.html';
            } else {
                let { username , account } = res.info;
                $('#username').text(username);
                $('#account').text(account);
            }
        })
    }

    getUserInfo();
    $('#transfer').click(function() {
        let payee = $('#payee').val(); // 收款人
        let amount = $('#amount').val(); // 转账金额
        
        // 生成 token
        let token = document.cookie.match(/connect.sid=([^;]*)/) || [];
        
        $.post('/api/transfer3', {
            payee,
            amount,
            token : 'my_token_' + token[1]
        }).then(res => {
            if(res.code === 0) {
                // 重新获取用户信息
                getUserInfo();
            } else {
            }
        })
    })
    </script>

//钓鱼页面
<img width="600"
        src="http://b4-q.mafengwo.net/s12/M00/FE/AE/wKgED1v772uAa_VkAA3UNOJhbkE35.jpeg?imageMogr2%2Fthumbnail%2F1360x%2Fstrip%2Fquality%2F90" />
<p>你的钱不安全了~</p>
<a href="http://localhost:3001/safe.html">返回查看余额</a>
<iframe src="http://localhost:3002/fake1.html" style="display: none"></iframe>

//恶意站点
// fake1.html
<body>
<form name="sneak1" action="http://localhost:3001/api/transfer3" method="post">
    <input type="text" name="payee" value="loki" />
    <input type="text" name="amount" value="2000" />
</form>
</body>
<script>
    window.onload = function () {
        document.sneak1.submit();   //有验证码的情况
    }
</script>

//后端 server1.js
const express = require('express');
const app = express();
const path = require('path');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');

//将参数转换成对象
app.use(bodyParser.urlencoded({ extended: true }));
//req.cookie[xxx] 获取cookie
app.use(cookieParser());

//设置路径
app.use(express.static(path.join(__dirname, 'src')));


//用户列表
let userList = [{ username: 'hw', password: '123456', account: 1000 }, { username: 'loki', password: 'loki', account: 100000 }];

let SESSION_ID = 'connect.sid';
let session = {};
//登录接口
app.post('/api/login', (req, res) => {
    let { username, password } = req.body;
    let user = userList.find(item => item.username === username && item.password === password);
    if (user) {
        //用户登录后,给一个标识(cookie登录)
        const cardId = Math.random() + Date.now();
        session[cardId] = { user };
        res.cookie(SESSION_ID, cardId);
        res.json({ code: 0 });
    } else {
        res.json({ code: 1, error: `${username} does not exist or password mismatch` });
    }

});

//获取信息
app.get('/api/userinfo', (req, res) => {
    let info = session[req.cookies[SESSION_ID]];
    if (info) {
        //用户已经登录
        let username = info.user.username;
        res.json({ code: 0, info: { username : username, account: info.user.account} });
    } else {
        res.json({ code: 1, error: 'user not logged in.' });
    }
})

//转账前, 先验证 token
app.post('/api/transfer3', (req, res) => {
    let info = session[req.cookies[SESSION_ID]];
    if(info) {
        // 用户已经登录
        let { payee , amount , token } = req.body;
        console.log(token);
        console.log('my_token_' + req.cookies[SESSION_ID]);
        // 校验 token
         if(token === 'my_token_' + req.cookies[SESSION_ID] && Number(amount)) {
            // token 正确
            let username = info.user.username;
            userList.forEach(user => {
                if(user.username === username) {
                    user.account -= amount;
                }
                if(user.username === payee) {
                    user.account += amount;
                }
            });
            res.json({ code : 0})
        }
    } else {
        res.json({code : 1, error : 'user not logged in'})
    }
})


app.listen(3001, () => {
    console.log('Server is running at port 3001')
})

//后端 server2.js
const express = require('express');
const app = express();
const path = require('path');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');

//将参数转换成对象
app.use(bodyParser.urlencoded({ extended: true }));
//req.cookie[xxx] 获取cookie
app.use(cookieParser());

//设置路径
app.use(express.static(path.join(__dirname, 'dest')));

app.listen(3002, () => {
    console.log('Server is running at port 3002')
})

总结:

  • 在登录之后,进入 safe.html 转账页面
  • 在转账页面如果点击 钓鱼连接, 将被攻击, 第三方网站将由于携带 用户cookie, 所有可以绕开验证, 冒用被攻击者的身份, 做一些攻击者指定的操作。

4.Samesite Cookie属性

为了从源头上解决这个问题, Google 起草了一份草案来改进 HTTP协议 , 为 Set-Cookie 响应头新增 Samesite 属性 , 它用来表明这个 Cookie 是个 "同站Cookie" , 同站 Cookie只能作为第一方 Cookie, 不能作为第三方 Cookie, Samesite 有两个属性值 , 分别是 Strict 和 Lax。
部署简单, 并能有效防御 CSRF 攻击 , 但是存在兼容性问题

Samesite=Strict

Samesite=Strict 被成为是严格模式 , 表明这个 Cookie 在任何情况都不可能作为第三方的 Cookie, 有能力阻止所有 CSRF攻击。此时 , 我们在B 站点下发起对 A 站点的任何请求, A站点的 Cookie 都不会包含在 cookie请求头中。

安全扫描工具

在没有安全部门, 或者我们前端可以先进行粗略的安全扫描

  1. w3af
    w3af

  2. Arachni
    Arachni

最后

引用文章

寒冬求职之你必须要懂的Web安全
w3af的安装
Arachni的安装和简介