侧边栏壁纸
博主头像
翻斗

开始一件事最好是昨天,其次是现在

  • 累计撰写 44 篇文章
  • 累计创建 42 个标签
  • 累计收到 3 条评论

Web安全之XSS

翻斗
2020-05-19 / 0 评论 / 0 点赞 / 2,447 阅读 / 10,448 字

XSS是什么?

XSS: Cross-Site Scripting Attack(跨站脚本攻击)是一种注入攻击,攻击者通过在目标网站上注入恶意脚本,伪装成受害用户的身份执行用户才能执行的操作。
按照字面简称,应该叫做CSS的才是是吧?可是已经有个CSS(Cascading Style Sheets)了,所以这里叫做XSS。

XSS是如何工作的?

一般情况下,一个站点对于前端数据(前端参数或者用户输入)不做校验,将前端的内容在某个条件下直接使用HTML页面进行了渲染,如果这部分前端数据含有恶意的Javascript内容,那么会被浏览器直接执行,这段恶意的Javascript可以模仿当前用户做一些只有该用户能做的操作。

我这边给它整理了两个必要条件:

  • 注入恶意Javascript
  • 浏览器执行恶意Javascript

找个有漏洞的地方注入Javascript是基础,但是如果只有恶意的Javascript注入,没有真正执行脚本的地方,那么也不构成威胁。

XSS的分类

按照注入恶意脚本的方式可以分为以下几种:

  • 反射型XSS
  • 存储型XSS
  • DOM型XSS

上面说的一些笼统的概念,可能看了没有什么感觉,下面会根据三种分类,分别举例说明,让大家更有实感。

反射型XSS

特点:前端传参没有经过校验,直接渲染(执行参数里面的恶意代码)

1、多出现在URL传参的条件下

2、需要引导用户主动点击恶意URL

3、后端从URL中取出数据拼接到页面

流程如下图:

举个例子,我们以前用到的JSP页面可能有如下代码:

直接在将URL的参数keyword渲染到页面上。

正常情况,URLhttp://xxx/search?keyword=someword,那么下面的div内容就是:

<div>
您搜索的关键词是:someword
<div>

那如果是有人恶意伪造一个URL,比如是

http://xxx/search?keyword="><script>alert('XSS');</script>

那么他就把keyword后面的全部内容注入到页面上,最终页面div内容是:

<div>
您搜索的关键词是:"><script>alert('XSS');</script>
<div>

对于很多浏览器而言,可能就是直接弹出一个对话框了,表明这段注入的Javascript执行了。

可能有的朋友要问了:

就这?这也没什么啊,弹一个对话框而已,不影响。

这里我想说的是,上面只是一个例子,它表示一个本来不应该被执行的代码段被执行了,那如果这个代码并不只是弹出一个对话框,而是一些恶意操作呢?

下面就有一个真实案例:2011年6月28日的新浪微博的反射型XSS蠕虫案例

在2011年6月28日这天,当大家在正常使用新浪微博的时候,有一个病毒正在快速传播

  • 20:00,有一部分人在私信里面看到了比较刺激的连接,点击它
  • 20:14,开始有大量带V的认证用户中招转发蠕虫,特点是:关注一个叫做hellosamy的用户,同时给自己的粉丝发私信,私信中携带这个链接,这样开始大量传播
  • 20:30,2kt.cn中的病毒页面无法访问
  • 20:32,新浪微博中hellosamy用户无法访问
  • 21:02,新浪漏洞修补完毕

注意,大家并不是没有防病毒心理,比如一个陌生连接,我们一般是不点击的,但是大家也看到了,上面这个链接,他开头就是http://weibo.com/pub/star,一看就是微博自己的连接,新浪微博官方的连接总不是病毒吧?于是就点击了。

上面的这个就是中了反射型XSS的招了,主要是微博自己间上面URL参数(就是pub/star/g/后面的内容)没有经过过滤,直接渲染到页面,那么里面内嵌的恶意脚本www.2kt.cn/images/t.js就被浏览器执行了,这个脚本通过cookie拿到用户信息,再利用微博公开API给粉丝发私信,这样就指数性的传播开来了。

该病毒作者明显只是想秀一下本事,他的名字hellosamy就是在致敬第一个XSS蠕虫作者samy, 蠕虫可以快速自我繁殖,这个又是依靠XSS做的,所以叫做XSS蠕虫

存储型XSS

特点:用户数据(包括恶意数据)通过一些接口被上传至服务器,在另一个地方不经校验的被渲染出来

1、攻击者先把恶意代码注入到库中

2、多存在于UGC地方

3、正常用户正常访问就中招

举个例子:
有一个论坛,用户可以做评论,可以刷评论,用户A在评论中输入了一些恶意代码

<script>alert(“您的机器被攻击了”)</script>

用户B在正常浏览页面,刷评论的时候,却弹出来一个框(黑人问号脸?)

DOM型XSS

特点:前端页面没有经过校验的直接渲染一些前端数据,仅限于前端内容

1、多出现在URL传参的条件下

2、需要引导用户主动点击恶意URL

3、前端JS从URL中取出数据放入页面

举个例子:

如果有如下代码,它是直接使用Javascript从前端URL中获取数据并且渲染出来

如果直接修改这个URL,将参数改变,那么就可以注入Javascript代码了

伪造一个URL后,发给别的人,别人点击后,就会弹出对话框了

上面几种XSS介绍完,这里做一个总结:

XSS的危害

介绍了上面几个类型的XSS,我们能感知到它的危害很大,具体有哪些危害呢?我这里大致总结一下:

  • 冒充或伪装成受害用户
  • 执行用户能够执行的任何操作
  • 读取用户能够访问的任何数据
  • 捕获用户的登录凭据
  • 将特洛伊木马功能注入网站

如何防治?

XSS危害比较大,十年前是互联网病毒的重灾区,我这里大致根据三个方面总结进行防治:

  • 数据转义
  • 内容过滤以及白名单
  • 使用CSP

数据转义

一般XSS都是注入一些特殊的标签内容,可以通过内容转义来避免

Vue中使用{{}}而不是v-html加载数据

vue中的大括号会把数据解释为普通文本。通常如果要解释成html代码则要用v-html。而此指令相当于innerHTML。虽然像innerHTML一样不会直接输出script标签,但也可以输出img,iframe等标签。

下面是官网上的教程:

React中自动转义

React 在渲染 HTML 内容和渲染 DOM 属性时都会将 "'&<> 这几个字符进行转义,转义部分源码如下:

for (index = match.index; index < str.length; index++) {
    switch (str.charCodeAt(index)) {
      case 34: // "
        escape = '&quot;';
        break;
      case 38: // &
        escape = '&amp;';
        break;
      case 39: // '
        escape = '&#x27;';
        break;
      case 60: // <
        escape = '&lt;';
        break;
      case 62: // >
        escape = '&gt;';
        break;
      default:
        continue;
    }
  }

这段代码是 React 在渲染到浏览器前进行的转义,可以看到对浏览器有特殊含义的字符都被转义了,恶意代码在渲染到 HTML 前都被转成了字符串,如下:

// 一段恶意代码
<img src="empty.png" onerror ="alert('xss')"> 
// 转义后输出到 html 中
&lt;img src=&quot;empty.png&quot; onerror =&quot;alert(&#x27;xss&#x27;)&quot;&gt; 

这样就有效的防止了 XSS 攻击。

React中的JSX 语法

JSX 实际上是一种语法糖,Babel 会把 JSX 编译成 React.createElement() 的函数调用,最终返回一个 ReactElement,以下为这几个步骤对应的代码:

// JSX
const element = (
  <h1 className="greeting">
      Hello, world!
  </h1>
);
// 通过 babel 编译后的代码
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);
// React.createElement() 方法返回的 ReactElement
const element = {
  $$typeof: Symbol('react.element'),
  type: 'h1',
  key: null,
  props: {
    children: 'Hello, world!',
        className: 'greeting'   
  }
  ...
}

我们可以看到,最终渲染的内容是在 Children 属性中,那了解了 JSX 的原理后,我们来试试能否通过构造特殊的 Children 进行 XSS 注入,来看下面一段代码:

const storedData = `{
    "ref":null,
    "type":"body",
    "props":{
        "dangerouslySetInnerHTML":{
            "__html":"<img src=\"empty.png\" onerror =\"alert('xss')\"/>"
        }
    }
}`;
// 转成 JSON
const parsedData = JSON.parse(storedData);
// 将数据渲染到页面
render () {
    return <span> {parsedData} </span>; 
}

这段代码中, 运行后会报以下错误,提示不是有效的ReactChild

Uncaught (in promise) Error: Objects are not valid as a React child (found: object with keys {ref, type, props}). If you meant to render a collection of children, use an array instead.

那究竟是哪里出问题了?我们看一下 ReactElement 的源码:

const symbolFor = Symbol.for;
REACT_ELEMENT_TYPE = symbolFor('react.element');
const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // 这个 tag 唯一标识了此为 ReactElement
    $$typeof: REACT_ELEMENT_TYPE,
    // 元素的内置属性
    type: type,
    key: key,
    ref: ref,
    props: props,
    // 记录创建此元素的组件
    _owner: owner,
  };
  ...
  return element;
}

注意到其中有个属性是 $$typeof,它是用来标记此对象是一个ReactElementReact在进行渲染前会通过此属性进行校验,校验不通过将会抛出上面的错误。React 利用这个属性来防止通过构造特殊的Children来进行的XSS攻击,原因是$$typeof 是个Symbol类型,进行JSON 转换后会Symbol值会丢失,无法在前后端进行传输。如果用户提交了特殊的Children,也无法进行渲染,利用此特性,可以防止存储型的XSS攻击。

服务器中转义

服务器中,比如Java服务器,可以在特定敏感接口(一般是包含UGC内容的接口,即用户输入的内容)中做内容转义。
Apachecommons-lang中,提供了一个比较好的转义工具类StringEscapeUtil,他可以防止各种注入类攻击(SQL注入和XSS等)

import org.apache.commons.lang.StringEscapeUtils;  

public class EscapeString {  

    public static void main(String[] args) throws Exception {  

        String str = "APEC召开时不让点柴火做饭";  

        System.out.println("用escapeJava方法转义之后的字符串为:"+StringEscapeUtils.escapeJava(str));  

        System.out.println("用unescapeJava方法反转义之后的字符串为:"+StringEscapeUtils.unescapeJava(StringEscapeUtils.escapeJava(str)));  

          

        System.out.println("用escapeHtml方法转义之后的字符串为:"+StringEscapeUtils.escapeHtml(str));  

        System.out.println("用unescapeHtml方法反转义之后的字符串为:"+StringEscapeUtils.unescapeHtml(StringEscapeUtils.escapeHtml(str)));  

          

        System.out.println("用escapeXml方法转义之后的字符串为:"+StringEscapeUtils.escapeXml(str));  

        System.out.println("用unescapeXml方法反转义之后的字符串为:"+StringEscapeUtils.unescapeXml(StringEscapeUtils.escapeXml(str)));  

          

        System.out.println("用escapeJavaScript方法转义之后的字符串为:"+StringEscapeUtils.escapeJavaScript(str));  

        System.out.println("用unescapeJavaScript方法反转义之后的字符串为:"+StringEscapeUtils.unescapeJavaScript(StringEscapeUtils.escapeJavaScript(str)));  

    /**输出结果如下: 

    用escapeJava方法转义之后的字符串为:APEC\u53EC\u5F00\u65F6\u4E0D\u8BA9\u70B9\u67F4\u706B\u505A\u996D

  用unescapeJava方法反转义之后的字符串为:APEC召开时不让点柴火做饭

  用escapeHtml方法转义之后的字符串为:APEC&#21484;&#24320;&#26102;&#19981;&#35753;&#28857;&#26612;&#28779;&#20570;&#39277;

  用unescapeHtml方法反转义之后的字符串为:APEC召开时不让点柴火做饭

  用escapeXml方法转义之后的字符串为:APEC&#21484;&#24320;&#26102;&#19981;&#35753;&#28857;&#26612;&#28779;&#20570;&#39277;

  用unescapeXml方法反转义之后的字符串为:APEC召开时不让点柴火做饭

  用escapeJavaScript方法转义之后的字符串为:APEC\u53EC\u5F00\u65F6\u4E0D\u8BA9\u70B9\u67F4\u706B\u505A\u996D

  用unescapeJavaScript方法反转义之后的字符串为:APEC召开时不让点柴火做饭**/

    }  

}  

内容过滤以及白名单

除了转义,我们还可以通过内容过滤来防止XSS,将可能涉及到XSS的数据进行过滤,再加上白名单机制,只允许一些指定的标签或者其他特殊字符通过,其他的标签等就会被剔除掉,这样内容会少一些,少的都是有危险的部分。

前端使用xss框架过滤

在项目中,XSS的安全漏洞很容易出现,例如在聊天模块和富文本模块很容易出现。
有时候你想实现富文本编辑器里编辑html内容的业务。可是又担心XSS恶意脚本的注入。此时可以使用一个xss工具。网址:https://github.com/leizongmin/js-xss

下载xss

npm i xss -S

(1)在页面中引入资源且生成XSS过滤器,对内容进行过滤

var xss = require("xss") 
const option={} //自定义设置
const myxss = new xss.FilterXSS(option);
const line='<script type="text/javascript">alert(1);</script>'
var html = myxss.process(line);
console.log(html); //输出:&lt;script type="text/javascript"&gt;alert(1);&lt;/script&gt;

(2)如果我想不过滤img标签的onerror属性,或者不过滤style标签。通过设置whiteList可选择性的保留特定标签及其属性,例如:

const option={
    whiteList:{
        img:['src','onerror'] //img标签保留src,onerror属性
        style:['type'] //style标签默认是不在whileList属性里的,现在添加上去
    }
}
const myxss = new xss.FilterXSS(option);
letline='<img src="./123.png" onerror="alert(1);" alt="123">'
let html = myxss.process(line);
console.log(html); //输出:<img src="./123.png" onerror="alert(1);">
line='<style type="text/css">color:white;</style>'
html = myxss.process(line);
console.log(html); //输出:<style type="text/css">color:white;</style>

xss默认的whiteList可以通过console.log(xss.whiteList)显示。
(3)如果想彻底过滤掉类似script,noscript标签,option可如下设置:

const option={
    stripIgnoreTagBody: ["script","noscript"],
}
const myxss = new xss.FilterXSS(option)
let line='<script type="text/javascript">alert(1);</script>'
let html = myxss.process(line)
console.log(html.length) //输出0,即html被转化为空字符串
line='<noscript>123</noscript>'
html = myxss.process(line)
console.log(html.length) //输出0,即html被转化为空字符串

后端使用JSoup白名单过滤

Jsoup 使用标签 白名单 的机制用来进行防止 XSS 攻击,假设白名单中只允许 p 标签存在,此时在一段 HTML 代码中,只能存在 p 标签 ,其他标签将会被清除只保留被标签所包裹的内容:

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Whitelist;
/**
 * 描述: 过滤 HTML 标签中 XSS 代码
 */
public class JsoupUtil {
    /** 
     * 使用自带的 basicWithImages 白名单
     * 允许的便签有 a,b,blockquote,br,cite,code,dd,dl,dt,em,i,li,ol,p,pre,q,small,span,strike,strong,sub,sup,u,ul,img  
     * 以及 a 标签的 href,img 标签的 src,align,alt,height,width,title 属性
     */
    private static final Whitelist whitelist = Whitelist.basicWithImages();
    /** 配置过滤化参数, 不对代码进行格式化 */
    private static final Document.OutputSettings outputSettings = new Document.OutputSettings().prettyPrint(false);
    static {
        // 富文本编辑时一些样式是使用 style 来进行实现的
        // 比如红色字体 style="color:red;"
        // 所以需要给所有标签添加 style 属性
        whitelist.addAttributes(":all", "style");
    }
    public static String clean(String content) {
        return Jsoup.clean(content, "", whitelist, outputSettings);
    }
}

使用CSP

CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置。

CSP 大大增强了网页的安全性。攻击者即使发现了漏洞,也没法注入脚本,除非还控制了一台列入了白名单的可信主机。

两种方法可以启用 CSP:

  • 通过HTTP头信息的Content-Security-Policy
  • 通过网页的标签

通过HTTP头信息的Content-Security-Policy

Content-Security-Policy: script-src 'self'; object-src 'none';
style-src cdn.example.org third-party.org; child-src https:

通过网页的标签

<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:">

上面代码中,CSP 做了如下配置。

  • 脚本:只信任当前域名
  • <object>标签:不信任任何URL,即不加载任何资源
  • 样式表:只信任cdn.example.orgthird-party.org
  • 框架(frame):必须使用HTTPS协议加载
  • 其他资源:没有限制

启用后,不符合 CSP 的外部资源就会被阻止加载。
Chrome的报错信息:

总结

XSS就是通过服务器/客户端中没做严格校验的漏洞,将恶意Javascript代码注入到站点中,漏洞会在渲染页面的时候执行这些恶意脚本,攻击者可以伪造用户来访问用户数据,使用用户身份做一些危害用户数据安全的事情。

因为其操作简单,危害很大,互联网开始的十多年是网络安全的重灾区。

要应对XSS,我们主要就是做好数据验证,包括数据转义和数据过滤(白名单),或者使用浏览器标准CSP等方法。

参考:
https://portswigger.net/web-security/cross-site-scripting
https://juejin.cn/post/6874743455776505870
https://ghthou.github.io/2018/01/13/Jsoup-防止-XSS-攻击/
https://www.cnblogs.com/wuguanglin/p/XSS.html

0

评论区