«

XSS攻击的原理和预防

codeez 发布于 阅读:997 随笔


XSS:跨站脚本攻击。

概念:页面被注入恶意代码,被不知情用户执行。

分三类:

存储型xss ->数据库

反射型xss -> URL

DOM型xss

一、反射型

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

// 点击按钮后获取localStorage
<body>
    <div id="t"></div>
    <input id="s" type="button" value="这是一个按钮" onclick="test()">
</body>
<script>
    function test() {
        const arr = ['自定义的数据1', '自定义的数据2', '自定义的数据3', '<img src="11" οnerrοr="console.log(window.localStorage)" />']
        const t = document.querySelector('#t')
            arr.forEach(item => {
            const p = document.createElement('p')
            p.innerHTML = item
            t.append(p)
        })
    }
</script>

二、存储型

存储型 XSS 会把用户输入的数据“存储”在服务器端。

比较常见的一个场景就是,黑客写下一篇包含有恶意 JavaScript 代码的博客文章,文章发表后,所有访问该博客文章的用户,都会在他们的浏览器中执行这段恶意的 JavaScript 代码。黑客把恶意的脚本保存到服务器端,所以这种 XSS 攻击就叫做 “存储型 XSS”。

<img src="11" onerror="alert(111)" />
三、DOM型

DOM 型 XSS跟前两种XSS的区别:DOM 型 XSS攻击中,取出和执行恶意代码由浏览器端完成,属于前端JavaScript自身的安全漏洞,而其他两种XSS都属于服务端的安全漏洞。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>XSS攻防演练</title>
</head>
<body>
    <h3>基于DOM的XSS</h3>
    <input type="text" id="input">
    <button id="btn">提交内容</button>
    <div id="div"></div>
</body>
<script>
    const input = document.getElementById('input');
    const btn = document.getElementById('btn');
    const div = document.getElementById('div');

    let inputValue;

    input.addEventListener('change', (e) => {
        inputValue = e.target.value;
    }, false);

    btn.addEventListener('click', () => {
        div.innerHTML = `<a href=${inputValue}>链接地址</a>`
    }, false);
</script>
</html>

我们再页面输入框中输入以下文本'' onclick=alert(/xss/),这里的''引号是为了关闭掉href属性,给它赋予了一个空值。然后点击提交内容按钮,则页面中的<div id="div"></div>标签包含了一下html内容

<a href onlick="alert(/xss/)">链接地址</a>
点击后就会弹出/ xss

防御办法:

一、HttpOnly 最早是由微软提出,并在IE 6中最先实现的,至今已经逐渐成为一个标准。浏览器将禁止页面的JavaScript访问带有HttpOnly属性的Cookie。所以我们需要在http的响应头set-cookie时设置httpOnly,让浏览器知道不能通过document.cookie的方式获取到cookie内容。

严格地说,HttpOnly 并非为了对抗 XSS——HttpOnly 解决的是XSS后的 Cookie 劫持攻击。所以说使用HttpOnly有助于缓解XSS攻击,但仍然需要其他能够解决XSS漏洞的方案;

二、XSS Filter

对于用户的输入内容我们需要持怀疑态度。在对输入不做任何过滤检查的情况下用户可能输入任何字符串。比如我们期望输入的内容是:hello word, 也许我们收到的内容是onclick=alert(/xss/)。

在XSS的防御上,输入检查一般是检查用户输入的数据中是否包含一些特殊字符,如<、>、’、”等。如果发现存在特殊字符,则将这些字符过滤或者编码。这种输入检查的方式,可以称为“XSS Filter”。互联网上有很多开源的“XSS Filter”的实现。比如一个简单的htmlencode转义:

const htmlEncode = function (handleString){
    return handleString
    .replace(/&/g,"&amp;")
    .replace(/</g,"&lt;")
    .replace(/>/g,"&gt;")
    .replace(/ /g,"&nbsp;")
    .replace(/\'/g,"&#39;")
    .replace(/\"/g,"&quot;");
}

但是输入检查也有弊端,比如:

攻击者绕过前端页面直接使用接口就可以提交恶意代码到远程库中。

输入数据,还可能会被展示在多个地方,每个地方的语境可能各不相同,如果使用单一的替换操作,则可能会出现问题。输入检查也需要有针对性,如果我们想表达的意思是一个数小于另一个数( 3 < 4),前端转义后的字符就变成3 < 4,当这个值被存到远端时后,再通过AJAX获取使用就会造成不必要的麻烦,比如我就进行数值计算等等。

三、输出检查

一般来说,除富文本的输出外,在变量输出到HTML页面时,可以使用编码或转义的方式来防御XSS攻击。

XSS的本质还是一种“HTML 注入”,用户的数据被当成了HTML代码一部分来执行,从而混淆了原本的语义,产生了新的语义。

如同输入检查一样,我们可以对输出进行编码转义。

1.在HTML中输出

比如我们的html代码中有这样一段代码:

<div>$htmlVar</div>

<a href="">$htmlVar</a>

如果输出的变量没有进行安全处理,直接使用并渲染在页面中,都能导致直接产生XSS。最终的结果可能生成一下代码:

<div><script>alert('我是一个XSS攻击者')</script></div>

<a href="#"><img href="" onclick="alert('我是另外一个XSS攻击者')"></a>

这个预防的方法就是对html进行转义检查。

2.在HTML属性中输出

如果我们的html属性时动态值,那么利用属性也可以被攻击;

<div id="testXSS" data-name=""></div>
现在往data-name属性中插入一段未转义的代码"><script>alert('我是一个XSS攻击者')</script><",结果如下:

<div id="testXSS" data-name=""><script>alert('我是一个XSS攻击者')</script><""></div>

  1. <script>标签中输出

<script>标签中输出时,首先应该确保输出的变量在引号中。

<script>

    // 假设userData是攻击者注入的数据

    let xssVar = userData;

</script>

攻击者需要先闭合引号才能实施XSS攻击:

<script>

    // 假设userData是攻击者注入的数据

    let xssVar = "";alert('我是一个script XSS攻击者');

</script>
  1. 在CSS中输出

在 CSS 和 style、style attribute 中形成 XSS 的方式非常多样化,所以,一般来说,尽可能禁止用户可控制的变量在“<style>标签”、“HTML标签的style属性”以及“CSS 文件”中输出。如果一定有这样的需求,则推荐使用一个关于CSS转义库。

四、防御DOM Based XSS

DOM Based XSS是一种比较特别的XSS漏洞,前文提到的几种防御方法都不太适用,需要特别对待。这个本质上,实际上就是网站前端JavaScript代码本身不够严谨,把不可信的数据当作代码执行了。

如果用 Vue/React 技术栈,并且不使用 v-html/dangerouslySetInnerHTML功能,就在前端render阶段避免innerHTML、outerHTML的 XSS隐患。稍后会有专门的Vue关于XSS的防御段落。

会触发DOM Based XSS的地方有很多,以下几个地方是JavaScript输出到HTML页面的必经之路。

document.write();

document.writeln();

xxx.innerHTML();

xxx.outerHTML();

xxx.innerHTML.replace();

document.attachEvent();

window.attachEvent();

window.location();

window.name;

所以开发者需要重点关注这几个地方的参数是否可以被用户控制。如果项目中有用到这些的话,一定要避免在字符串中拼接不可信数据。

五、Vue中的防御措施

不论使用模板还是渲染函数,Vue都会将插值的内容都会自动转义。也就是说对于这份模板:

<template>

    <p>{{userData}}</p>

</template>

<script>

    // 从远程获取的数据

    userData = "<script>alert('xss')</script>"

</script>

最终编译后页面显示的html源码内容如下:

<p>

    <script>alert('xss')</script>

</p>

原因是Vue帮我们对数据进行了转义,因此避免了脚本注入。该转义通过诸如 textContent 的浏览器原生的 API 完成,所以除非浏览器本身存在安全漏洞,否则不会存在安全漏洞。转义后的内容如下:

<script>alert("xss")</script>
注入HTML

如果你要动态注入远程的HTML内容,首先你应该确保这些内容是安全有效的,否则你应该采取一些防御措施,去过滤或转义掉一些危险的标签符号;例如你可以这样显示的渲染HTML:

<!-- 当使用模版时 -->
<div v-html="userProvidedHtml"></div>

<!-- 当使用渲染函数时 -->
<script>

    h('div', {

    domProps: {

        innerHTML: this.userProvidedHtml

    }

    })

</script>

<!-- 当使用JSX 的渲染函数时 -->

<div domPropsInnerHTML={this.userProvidedHtml}></div>
例如我们可以使用一个简单的方法(或者引用一个更加健壮的库/插件XSS来过滤一遍这个远程的userProvidedHtml数据内容,以确保安全;

// 一个简单的函数,通过转义<为&lt以及>为&gt来实现防御HTML节点内容

const escape = function(str){

    return str.replace(/</g, '&lt;').replace(/>/g, '&gt;')

}

样式注入
在使用Vue 要在模板内避免渲染 style 标签:

<style>{{ userProvidedStyles }}</style>
这是因为,一但通过userProvidedStyles,恶意用户仍可以提供 CSS 来进行“点击诈骗”,例如将链接的样式设置为一个透明的方框覆盖在“登录”按钮之上。然后再把 https://user-XSS-website.com/做成你的应用的登录页的样子,它们就可能获取一个用户真实的登录信息,所以Vue推荐使用对象语法且只允许用户提供特定的可以安全控制的property的值:

<!-- sanitizedUrl应为受控的地址 -->

<a

  v-bind:href="sanitizedUrl"

  v-bind:style="{

    color: userProvidedColor,

    background: userProvidedBackground

  }"

>

  click me

</a>

前端

请先 登录 再评论