防范 XSS 的注意事项

与最佳实践

@CSS魔法

2015. 12. 30.

  • XSS 是什么
  • 漏洞是如何形成的
  • 如何防范

(45~60 分钟)

XSS 是什么?

XSS

Cross Site Scripting

“跨站脚本攻击”

  • 我们的网站
  • 攻击者插入了脚本
  • 运行在受害者的浏览器上

三个要点:

  • 脚本在浏览器中可以做什么
  • 攻击者的脑洞有多大

危害程度取决于:

千万不要看我用 alert 来演示,  

就以为 XSS 只能 alert……  

  漏洞是如何形成的?

从 HTML 说起:

<div class="topic">
    <h1>防范 XSS 的注意事项与最佳实践</h1>
    <p class="description">
        XSS 的含义是 “跨站脚本攻击”。简单来
        说,就是我们的网页……
    </p>
</div>

原则:

让内容

尤其是用户生成的内容

保持绿色属性

但是,HTML 本身是不带颜色的……

<div class="topic">
    <h1>如何使用 <p> 标签</h1>
    <p class="description">
        ……
        ……
    </p>
</div>

如果标题叫作《如何使用 <p> 标签》:

<div class="topic">
    <h1>本期分享的标题</h1>
    <p class="description">
        本期分享的讲师、背景、内容介绍等
        信息…… <scrip>/*...*/</script>
    </p>
</div>

如果内容来自用户:

如何解决?

转义

Escape

原始内容

HTML 代码

显示结果

<
&#60;
<
<
&lt;
<

网页作者

浏览器

HTML 实体

<

因此,特殊字符都需要转义:

>
'
"
&

...

再来看 HTML 中的 JS:

<script>
var postTitle = '二手 iPhone 6 九成新'
</script>

再来看 HTML 中的 JS:

<script>
var postTitle = '<?= $data->postTitle ?>'
</script>

用户输入的帖子标题是:

'; /* ... */; ' 

“SQL 注入” 的即视感?

因此:

  • 不仅要操心
    HTML 构造是否被破坏
    的问题
  • 还要操心
    JS 构造是否被破坏
    的问题

  如何防范

要点:

写数据时统一转义?

  1. 要转义!
  2. 如果你需要时刻记住 “要转义”,那你肯定会忘

内容的存储策略:

  • 写库时写原文,
    读库输出时转义
  • 写库前统一转义,
    读库输出时不操心

?

“写库前转义” 的问题:

还有一个问题……

还记得这个例子吧:

<script>
var postTitle = '<?= $data->postTitle ?>'
</script>

但是……

<script>
var keyword = '<?= $_GET["query"] ?>'
</script>

你还是要操心转义的问题!

数据并不一定总是来自数据库:

或者,来自第三方……

甚至还要操心哪些该转,哪些不该转!

所以:

  1. 写库时写原文,
    读库输出时转义
  2. 你需要 “最佳实践”

什么叫 “最佳实践”:

在面对需求时

条件反射般地采用 “成熟的对策”

即可确保不出问题

No Zuo, No Die!

插值

需求一

(把后端变量输出到向 HTML 中

对策:

模板引擎

div(class=$data->var1)
    = 我的名字叫 #{$data->var2} ……

PHP-Jade

div @class=var1
    "我的名字叫 {var2} ……"

Jedi

传值

需求二

(后端通过模板向前端脚本传递数据)

需求 2.1

传递单个变量

<script>
var postTitle = '<?= $data->postTitle ?>'
</script>
div.post
    h2.post-title #{$data->postTitle}
    p ...

对策 2.1

PHP-Jade

var postTitle = $('h3.post-title').text()

JS

script = [postTitle]
    !
        window.__postTitle = data[0]

对策 2.1

Jedi

伪需求

传递 URL 参数

<script>
var keyword = '<?= $_GET["query"] ?>'
</script>

对策

var keyword = _.url.getParam('query')

JS

JS 本身是有解析 URL 参数的能力的!

需求 2.2

传递复杂数据

<script>
var list = '<?= json_encode($data->postList) ?>'
</script>
script#json-list(type='text/json').
    !{$data->jsonList}

对策 2.2

PHP-Jade

var list = JSON.parse($('#json-list').html())

JS

PHP (Controller)

$postList = [/* ... */];
$templateData->jsonList = json_stringify($postList);

json_stringify() 是我们对 json_encode() 的包装,已针对 XSS 做作优化,默认处理所有必要的特殊字符。

script = [postList]
    !
        window.__list = data[0]

对策 2.2

Jedi

PHP (Controller)

$templateData->postList = [/* ... */];

填值

需求三

(前端脚本拿到数据后更新页面)

需求 3.1

填入单个变量

$('h3.post-title').html(postTitle)

JS

对策 3.1

$('h3.post-title').text(postTitle)

JS

需求 3.2

把复杂数据渲染为 HTML

var html = ''
_.each(list, function (item) {
    html += '<li>' + item.title + '</li>'
})

$('ul.post-list').html(html)

JS

对策 3.2

前端模板引擎

Underscore

// 定义模板
_.template.add('list-items', [
    '<% _.each(data, function (item) { %>',
        '<li><%= item %></li>',  // 这里会自动转义
    '<% }) %>',
].join('\n'))

JS

// 定义模板
_.template.add('list-items', [
    '<% _.each(data, function (item) { %>',
        '<li><%= item %></li>',  // 这里会自动转义
    '<% }) %>',
].join('\n'))

// 用模板渲染数据,得到 HTML 片断
var html = _.template.render('list-items', list)

JS

// 定义模板
_.template.add('list-items', [
    '<% _.each(data, function (item) { %>',
        '<li><%= item %></li>',  // 这里会自动转义
    '<% }) %>',
].join('\n'))

// 用模板渲染数据,得到 HTML 片断
var html = _.template.render('list-items', list)

// 更新页面
$('ul.post-list').html(html)

JS

  如何作死

番外篇

PHP 注入

死法一

div
    = $data->var

PHP-Jade

PHP-Jade

div
    - echo $data->var;

前后端代码混合

死法二

extends layout

block append scripts
    script.
        function handleData() {/* 一些业务逻辑 */}

        var data = {
            user: "<?= $data->user->username ?>",  // 妥妥地 XSS
            desc: "<?= $data->user->descr ?>"  // 妥妥地 XSS
        };

        handleData(data);

PHP-Jade

  • 永远不要相信用户输入!
  • 前端永远不要相信后端传来的数据! 

Q & A

Thank You!

防范 XSS 的注意事项与最佳实践

By CSS魔法

防范 XSS 的注意事项与最佳实践

防范 XSS 的入门教程,日常开发必读指南。

  • 1,462