breakinferno / breakinferno.github.io

0 stars 0 forks source link

Web安全之XSS篇 #3

Open breakinferno opened 6 years ago

breakinferno commented 6 years ago

XSS

  1. 综述
  2. 浏览器解析
  3. 反射型XSS
    1. 概览
    2. 浏览器解析
    3. 例子
  4. 持久型XSS

XSS(Cross Site Scritp)跨站脚本攻击,它指的是恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意用户的特殊目的。个人感觉跟Sql注入差不多,原因都是利用网站对于用户的信任,认为用户的输入都是合法规范的,从而没有对用户的输入进行良好的有效的过滤,html界面能够被攻击者插入js脚本,从而使某些用户利用这些漏洞完成网站攻击操作。

XSS可以分为三种形式,一种是反射型的,也叫非持久性XSS。这种类型的XSS是由于服务器端未对客户端传入的数据进行存储和任何过滤,直接返回客户端被浏览器渲染导致的。一般的利用方式是攻击者找到网站XSS漏洞之后,通过发邮件等方法使用户访问一段可以触发XSS攻击脚本的链接,一旦用户访问该链接触发恶意脚本,那么攻击成功。更为常见的情景就是在url中利用编码绕过常见的参数过滤逻辑从而达到注入恶意脚本的目的。第二种形式是持久型的XSS。攻击者使恶意脚本绕过前后端检测存储到服务器端中,一旦有用户访问改页面,恶意脚本没有经过转义被浏览器直接渲染就会触发攻击。这种XSS攻击范围广。第三种是Dom-based 的XSS,这种就是单纯的客户端攻击,利用document.writeinnerHTML等输出可以直接触发恶意代码。

这三种方式导致XSS的发生,其原因就是没有对输入输出做有效的转义和过滤。所以要减少XSS的发生,应该做到客户端对服务器返回的数据完全不信任,服务器对客户端传递的数据也完全不能信任,对所有输入输出都做好转义过滤。

浏览器解析和字符编码

无论什么XSS攻击其代码都运行在浏览器环境,所以为了更好的防范XSS攻击,浏览器解析的过程只是是基本。我们都知道浏览器完成界面渲染的过程是解析DOM,生成CSSOM,然后生成渲染树,最后浏览器在解析HTML文档时无论按照什么顺序,主要有三个过程:HTML解析、JS解析和URL解析,每个解析器负责HTML文档中各自对应部分的解析工作。

HTML解析

解析可以分为两个子过程——语法分析及词法分析。

词法分析就是将输入分解为符号,符号是语言的词汇表——基本有效单元的集合。对于人类语言来说,它相当于我们字典中出现的所有单词。

语法分析指对语言应用语法规则。

解析器一般将工作分配给两个组件——词法分析器(有时也叫分词器)负责将输入分解为合法的符号,解析器则根据语言的语法规则分析文档结构,从而构建解析树,词法分析器知道怎么跳过空白和换行之类的无关字符

一些基本的概念:

  1. 字符实体

字符实体是一个转义序列,它定义了一般无法在文本内容中输入的单个字符或符号。一个字符实体以一个&符号开始,后面跟着一个预定义的实体的名称(实体名称),或是一个#符号以及字符的十进制数字(实体编号)。注意,某些实体并不存在实体名称,但都存在实体编号。

知道了什么是字符实体,那什么是html字符实体(8进制或者16进制)就简单了。html中预留了一些字符,比如<>,用于标签的分隔。我们想在页面中展示这两个字符怎么办呢,肯定不能直接在文本中插入这两个字符,不然可能导致dom结构出错。但我们可以通过使用&lt;&gt;这种方式来进行该字符的展示。这里这两个就叫做<>的html实体。

PS: <实体编码为&#60或者&#x3c

  1. HTML5大元素

html中有五大元素分别是:

空元素(Void elements): 如<area>,<br>,<base>等等

原始文本元素(Raw text elements): 有<script>和<style>

RCDATA元素(RCDATA elements): 有<textarea>和<title>

外部元素(Foreign elements): 例如MathML命名空间或者SVG命名空间的元素

基本元素(Normal elements): 即除了以上4种元素以外的元素

五类元素的区别如下:

空元素: 不能容纳任何内容(因为它们没有闭合标签,没有内容能够放在开始标签和闭合标签中间)。

原始文本元素: 可以容纳文本。

RCDATA元素: 可以容纳文本和字符引用。

外部元素: 可以容纳文本、字符引用、CDATA段、其他元素和注释

基本元素: 可以容纳文本、字符引用、其他元素和注释

好了了解完上面两个概念之后我们来了解一下html词法解析过程吧。

词法解析看这里

html解析器是一个状态机,其基本状态有如下四种:标签开始状态(Tag open state)、标签名状态(Tag name state)、结束标签打开状态”(End tag open state)、数据状态(Data State).注意每次发现一个完整的tag之后就会释放该token,其状态机如图:

Html-parser

这里举一个简单的栗子:

<html>  
    <body>  
    Hello world  
    </body>  
</html> 

其解析过程如下:

  (1)初始状态为'Data state',当遇到字符’<’, 当前状态切换到”标签打开状态”。

  (2)遇到字符’h’,创建一个新的起始标签标记,设置标记的标签名为空,当前状态切换至”标签名称状态”(Tag name state)。

  (3)重新从字符’h’开始解析,将解析的字符一个一个添加到创建的起始标签标记的标签名中,直到遇到字符’>’。此时当前状态切换至”数据状态”并释放当前标记,当前标记的标签名为’html’。

  (4)解析后续的’’和’’的方式与’<html>’一致,创建并释放对应的起始标签标记,解析完毕后,当前状态处于”数据状态”。</p> <p>  (5)遇到字符串’解析html文档’,针对每一个字符,创建并释放一个对应的字符标记,解析完毕后,当前状态仍然处于”数据状态”。</p> <p>  (6)遇到字符’<’, 进入”标签打开状态”。</p> <p>  (7)遇到字符’/‘, 进入”结束标签打开状态”(End tag open state)。</p> <p>  (8)遇到字符’t’,创建一个新的结束标签标记,设置标记的标签名为空,当前状态切换至”标签名称状态”(Tag name state)。</p> <p>  (9)重新从字符’t’开始解析,将解析的字符一个一个添加到创建的结束标签标记的标签名中,直到遇到字符’>’。此时当前状态切换至”数据状态”并释放当前标记,当前标记的标签名为’title’。</p> <p>  (10)解析’</head>’的方式与’’一样。

  (11)对于后续的html标签和文本的解析,可以参照(1)~(16)的流程来解析。

  (12)所有的html标签和文本解析完成后,状态切换至”数据状态”,一旦遇到文件结束标志符(EOF),则释放EOF标记。

HTML实体在如下三种情况下会被解码,在这些状态中HTML字符实体将会从“&#...”形式解码,对应的解码字符会被放入数据缓冲区中:

  1. 数据状态: 比如<div>&lt;span></span></div>。但是解析器在解析这个字符引用后不会转换到“tag open state”。正因为如此,就不会建立新标签.只会在div中将<span>输出`来
  2. RCDATA状态下: 比如<title>&lt;</title>会输出<.注意在浏览器解析RCDATA元素的过程中,解析器会进入“RCDATA状态”。在这个状态中,如果遇到“<”字符,它会转换到“RCDATA小于号状态”。如果“<”字符后没有紧跟着“/”和对应的标签名,解析器会转换回“RCDATA状态”。这意味着在RCDATA元素标签的内容中(例如”或者“”。因此在这中间并不会创建html元素,也不会执行脚本.比如<title><script>alert(1)</script></title>这段html中script代码并不会生效
  3. 属性状态下(上面的状态机是最简单的状态机,没有该状态). 比如<a href="javascript:&#x61;lert(0)"></a>会被解码为<a href="javascript:alert(0)"></a>

URL解析

url解析看这里,一般编码格式是%加上ascii码的两位数

URL解析器也是一个状态机模型,从输入流中进来的字符可以引导URL解析器转换到不同的状态。

URL资源类型必须是ASCII字母(U+0041-U+005A || U+0061-U+007A),不然就会进入“无类型”状态。例如,你不能对协议类型进行任何的编码操作,不然URL解析器会认为它无类型

URL编码过程使用UTF-8编码类型来编码每一个字符。如果你尝试着将URL链接做了其他编码类型的编码,URL解析器就可能不会正确识别。(不能对要进行URL解析的字符串进行其他编码)

Javascript解析

javascript编码的形式有:unicode编码(\uXXXX一样的字符),8进制,16进制,这里主要考虑unicode编码

特别注意:script标签里的文本的都是原始文本,不存在html实体的不会被解析和解码的过程。这意味者你将脚本进行html编码之后插入script标签也不会成功,只会原样返回.因为这里已经交由javascript解析器进行解析了。

我们可以将转义序列放在3个部分:字符串中,标识符名称中和控制字符中

  1. 字符串中

ECMAScript程序中,在字符串常量中出现的Unicode转义序列会被当作字符串常量中的一个Unicode字符,并且不会被解释成有可能结束字符串常量的换行符或者引号

所以console.log('\u0027)\\')(\u0027是'的unicode码)只会输出'字符,而不会和(后面的'组合从而结束参数

  1. 标识符中

    Unicode转义序列(如\u000A\u000B)同样被允许用在标识符名称中,被当作名称中的一个字符。而将'\'符号前置在Unicode转义序列串(如\u000A000B000C)并不能作为标识符名称中的字符。将Unicode转义序列串放在标识符名称中是非法的

所以我们这样是可行的<input type="button" name="demo" onclick="\u0061\u006c\u0065\u0072\u0074(1)">(alert的转码)

  1. 控制字符中

当用Unicode转义序列来表示一个控制字符时,例如单引号、双引号、圆括号等等,它们将不会被解释成控制字符,而仅仅被解码并解析为标识符名称或者字符串常量

所以这样是不行的<input type="button" name="demo" onclick="\u0061\u006c\u0065\u0072\u0074u0028\u0031\u0029">(alert(1)的转码)

总的来说,Unicode转义序列只有在标识符名称里不被当作字符串,也只有在标识符名称里的编码字符能够被正常的解析

解析顺序以及一些细节

解析顺序一般是html首先进行解析,javascript和url的解析顺序视情况而定。

比如:

Example A: <a href="UserInput"></a>
Example B: <a href=#   onclick="window.open('UserInput')"></a>

A中先html解析,然后进行url解析,如果userinput是js代码最后进行js解析。而B中则是先html解析,然后js解析,最后url解析

注意某些dom操作会导致html从新解析。

防御

chrome浏览器自带防御,可拦截反射性XSS(HTML内容和属性),js和富文本的无法拦截,所以我们必须得自己做一些防御手段。

  1. escape

  2. CSP(Content Security Policy)

内容安全策略(Content Security Policy,简称CSP)是一种以可信白名单作机制,来限制网站中是否可以包含某来源内容。默认配置下不允许执行内联代码( Githubissues.

  • Githubissues is a development platform for aggregating issues.