继续前面的内容,由于转发到一些论坛时,限制了每个帖子中,添加图片链接的数量,而前篇包含的图片数量已经达到上限,因此开一个新帖继续讨论。
还记得在前篇中有一段针对使用 IE 浏览器用户的攻击载荷,如下所示 :
<script>function XSS(){
a = new ActiveXObject('Microsoft.XMLHTTP');
a.open('get', 'http://www.baidu.com', false);
a.send();
b = a.responseText;
document.write(b);
}
XSS();
</script>
上面代码中,使用 IE 实现的XHR(XMLHttpRequest)对象访问百度首页并且通过javascript读取返回的数据(responseText),并且可以写入当前页面。
这是因为 IE 的同源策略没有很好地对XMLHttpRequest进行约束,例如,上述代码可能位于本地硬盘的一个HTML文本中(C:\Users\shayi\Desktop\XssPayloadTest.html)
我们已经在上一篇博文中看到,IE 默认允许XHR跨域加载并读写资源;对于其它浏览器(FireFox 与 chrome)而言,本地文件系统路径与“百度首页”是不同源的,因此它们会限制当前HTML文本所在源中的javascript读写从百度首页返回的数据,换言之,这两个浏览器的同源策略默认仅允许XHR加载,读写相同源中的数据,除非对浏览器以及目标站点的web服务器配置为启用HTML5规范中引入的“跨域资源共享”(CORS)。
下面的示例代码,通过使用非 IE 浏览器支持的XHR对象,尝试跨域加载资源并写入至当前页面DOM中的一个节点:
(引用自《白帽子讲web安全一书》,略作修改)
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-type" content="text/html;charset=utf-8" />
<title>XssPayloadTest</title>
<script type = "text/javascript">
var xmlhttp;
function LoadXMLDoc(url)
{
xmlhttp = null;
if (window.XMLHttpRequest)
{
xmlhttp = new XMLHttpRequest();
}
else if (window.ActiveXObject)
{
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
if (xmlhttp != null)
{
xmlhttp.onreadystatechange = state_Change();
xmlhttp.open("GET", url, true);
xmlhttp.send(null);
}
else
{
alert("your browser does not support XMLHTTP");
}
}
function state_Change()
{
if (xmlhttp.readyState == 4)
{
if (xmlhttp.status == 200)
{
document.getElementById('T1').innerHTML = xmlhttp.responseText;
}
else
{
alert("problem retrieving data:" + xmlhttp.statusText);
}
}
}
</script>
</head>
<body onload = "LoadXMLDoc('http://bbs.pediy.com/index.php')">
<div id = "T1" style = "border:1px solid black;height:40;width:300;padding:5"></div><br />
<button onclick = "LoadXMLDoc('http://shayi1983.blog.51cto.com')">Click</button>
</body>
</html>
这个在HTML文档头部(head元素内)引入的javascript定义了两个函数: LoadXMLDoc()通过浏览器支持的XHR类型来跨域发起HTTP GET请求并加载资源;
state_Change()检查对方web服务器返回的HTTP响应状态码,然后决定是读取响应数据的内容并写入当前页面(状态码为200);还是给出服务器端返回的错误信息(除200以外的其它状态码)。
在HTML文档体(body元素内),通过实际调用LoadXMLDoc()来对看雪论坛首页发起跨域请求(注意,当前“源”是本地文件系统上的测试用HTML页面),然后尝试将对方返回的“响应文本”(responseText)写入当前页面DOM的T1节点的内部HTML文本中,并且在文档体中添加一个按钮,用户点击时将再次通过XHR对象,发起对我的51cto博客页面的跨域请求。
上面代码在FireFox中的测试结果如下所示:
最后要指出一点,浏览器一般通过URL中的协议,主机名与端口号来判断一个页面文档所属的域,而在一个新打开的页面的URL中,只要3者之一与前面那个页面的URL不同,都会被浏览器的同源策略认定为属于不同域,从而阻止新开启页面上的脚本通过前者页面的URL来发起跨域请求,而符合HTML5 CORS 规范的XHR 发起的跨域请求则不在此限,当然前提是需要经过目标域(即要“跨”的域)中的服务器许可,如果目标域的web服务器在返回的 Access-Control-Allow-Origin 响应头中指定的值与发起跨域XHR所属的源始域相同,那么浏览器将允许这个XHR跨域请求。
为了更清楚说明XHR询问是否可以CORS的场景,假设一个HTML页面所属域为
http://www.webSite.com(即该页面通过这个URL加载),在该页面中,有一段生成跨域HXR的 javascript 代码如下:
var xhr = new XMLHttpRequest();
xhr.open("PUT", "http://www.friendlySite.com/index.html", true);
xhr.send();
xhr.open() 的第三个参数为 true,表明请求为异步发送,这是默认值,有些浏览器为了性能上的考量,甚至拒绝将该参数设置为 false(同步),如果你这么做了, Firebug 将会给出类似下面的提示:
遇到 xhr.send() 语句时,浏览器会向 www.friendlySite.com 发起一个HTTP 请求,用于“证实”www.friendlySite.com 是否愿意与 http://www.webSite.com 共享资源(通过后者打开的页面文档),请求头如下:
OPTIONS http://www.friendlySite.com/index.html HTTP/1.1
Host: www.friendlySite.com
User-Agent: Mozilla/5.0(Macintosh; Intel Mac OS X 10.7; rv:11.0) Gecko/20150101 Firefox/37.0
Accept: text/html,application/xhtml+xml,applicatiin/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.5
Origin: http://www.webSite.com
Accept-Control-Request-Method: PUT
以上HTTP请求中(这个包含 CORS 请求头的 HTTP 请求又称为“Preflight”请求),注意 Origin 与 Accept-Control-Request-Method 这2个头部,源始文档所属域为 www.webSite.com ,因此 Origin 头部就是这个值;源始文档中的XHR 对象请求的方法为 PUT,因此 Accept-Control-Request-Method 头部就是该值。还要注意浏览器使用的是 HTTP OPTIONS 方法,该方法在最初的HTTP协议规范中,用于查询web服务器支持的 HTTP 方法类型,现在则演变为用于查询对方的 CORS“许可策略”。
如果目标域 www.friendly.com 允许 CORS ,其返回的HTTP 响应头应该如下所示:
HTTP/1.1 200 OK
Date: Wed, 13 Apr 2015 06:51:53 GMT
Server: Tengine
Access-Control-Allow-Origin: http://www.webSite.com
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 10
Content-Length: 0
以上HTTP响应中,Access-Control-Allow-Origin 响应头的值与发出跨域请求的XHR 对象所在页面文档所属的域是同一个,表明目标域允许与 www.webSite.com
的 CORS ,如此一来,浏览器将允许 www.webSite.com 域页面中的XHR对象和后续的 javascript 代码读取,操纵和显示来自 www.friendly.com 域返回的数据,例如读写 xhr.responseText 的内容时,才不会发生错误。
另外,Access-Control-Max-Age 响应头的含义为,该响应的有效期,以秒为单位,例如其值为 3600 时,浏览器在一小时内对目标域的每次跨域访问,都不需要使用 Preflight 请求,这就减少在请求头中增加多余的头部造成的客户端与服务器端处理负担,以及链路带宽的浪费(每次少传输几十到上百字节总是好的);但是一小时后,浏览器必须再次发送 Preflight 请求至服务器,以更新目标域的 CORS 许可策略,一般而言,目标域为了其自身数据的安全考量,Access-Control-Max-Age 的值不会设的太长,但是像上例中的10秒那么短也不太实际,各位可以自行访问其它互联网上的站点,通过工具来测试这个值。
Access-Control-Allow-Credentials 请求头的值如果为 true,那么 www.webSite.com 域页面中的 XHR 对象以及 javascript 脚本代码,将能够读写
由目标域 www.friendly.com 设置的 cookie,HTTP 基本认证字串(base-64 格式的用户名与密码,即目标域赋予的用户会话令牌),摘要认证字串(MD5 或其它摘要算法加密的用户名与密码),以及由目标域 www.friendly.com 设置的客户端 SSL 证书(假设目标域是电子商务网站或网上银行,需要验证企业用户的身份,就有可能用到客户端 SSL 证书),以上这些证书和认证信息,以及 cookie 形式的会话令牌,都将能够被 www.webSite.com 域页面中的 XHR 对象以及 javascript 脚本代码读写访问;一般而言,如果目标域和源始域之间没有很好的友情和信任关系,Access-Control-Allow-Credentials 响应头的值应该为 false 会比较安全。
(作为对比,前面的本地文件系统上的测试HTML页面中的 javascript 代码无法将来自看雪以及51cto域的 xhr.responseText 写入至测试页面,就是因为浏览器没有收到来自看雪以及51cto域允许与“本地域”进行CORS的响应头。)《关于HTML文档的两种解析模式:HTML 与 XHTML 之间的差别》
浏览器根据HTML文档起始处的“<!DOCTYPE xxxx>”来判断应该使用何种解析模式,当xxxx的值为“html”时,表示为HTML解析模式,该模式对文档的语法要求比较宽松,浏览器的HTML解析引擎会容许并且自动修正绝大多数的语法错误,例如下面这个例子:
<!DOCTYPE html>
<htMl lang="en">
<head>
<meta http-equiv="Content-type" content="text/html;charset=utf-8" />
<title>XssPayloadTest</title>
</head>
<body>
' " `
<a href = http://www.baidu.com>百度首页。。。
</oops>
<IMG src = #>
</hTml>
可以看到,上述文档的HTML标签大小写混用;body元素与a元素没有闭合;
a元素的href属性的值(百度首页的URL)没有用双引号包含;存在一个没有起始(标签)的oops元素;img元素使用全大写而且没有闭合。。。。这些语法错误都可以在浏览器中得到修正,前提是使用HTML解析模式,以 IE 为例,打开上述文档,IE 会在其进程地址空间中构建DOM节点时,修正错误,缺少的元素:
另一方面,XHTML(衍生自XML)解析模式则非常严格,文档中存在上述任意一种类型的错误都会导致浏览器无法正常显示该文档,或者给出错误提示。
再者,采用HTML解析模式,遇到“<script>”,“<style>”,“<textarea>”,“<xmp>”等元素标签,都会致使浏览器切换到特殊解析模式,例如碰到script标签,调用javascript解析引擎;碰到style标签调用CSS解析器等等。
而在XML类文档中(包括XHTML),这些标签内部还需要嵌套一个前后不对称格式的数据标签,如下所示:
<script type="text/javascript">
<![CDATA[
//将此处注释掉的内容改成javascript代码,其它类型的特殊解析模式标签,以此类推
]]>
</script>
上例中,“<![CDATA[”与“]]>”字符串标签之间才是真正添加javascript代码的位置。
例如,BurpSuite Proxy 模块拦截到的从“博文视点”站点首页,返回的HTTP响应体的HTML文档中,就包含了对“<![CDATA[]]>”的使用:
HTML 与 XHTML 解析模式差异总结:
使用XHTML解析时,仅遇到 script 或 style 标签,还无法切换进入到对应的解析模式(调用 javascript 或 css 解析引擎),而是必须添加“<![CDATA[....]]>”数据标签,并且将脚本代码或样式表放在其中以实心句点表示的位置处,才能正常工作。
对于下面这个没有按照规范编写的img元素:
<img src= "http://www.baidu.com" title=""onerror="alert('xss')" class=examples>
一共有4个属性:src,title,onerror,class。其中class属性的值没有使用双引号包含,三种浏览器各自使用不同的方式解析这个img元素:
从上面可以看出,只要是元素属性的值,无论是哪一种浏览器,在解析的时候都会自动向其添加双引号。另外,onerror 属性的值为一个javascript语句,运行结果是弹出提示框。
如果将包含javascript语句的双引号改成“反引号”(`),则三种浏览器在解析时,都会自动在反引号外侧再添加双引号,换言之,无论img元素属性的值为何,总是使用双引号包含,而这会导致添加反引号的javascript代码无法执行:
这里还有一个很重要的知识点:一般而言,浏览器在解析img元素的任意属性值时,无论原始文档中是否将属性值用双引号包含,浏览器都会自动将其包含。
但是,如果属性的值为以左尖括号开始的元素,例如script,并且原始文档中没有将其用双引号包含,那么浏览器在解析的时候会产生混乱(对于三大浏览器而言都一样),虽然这样并不会导致执行script标签中的javascript代码,但是浏览器会给出一些错误提示(IE),或者会干扰到对img元素其它属性值的正常解析(FireFox 与 chrome),例如原始文档中的代码:
<img src = "#" title = <script>alert('xss in value of element properties');</script> class = examples>
其中title的属性值为以左尖括号起始的script标签,没有使用双引号包含,那么浏览器们会如何解析呢:
从上面的解析结果还可以发现一个事实,那就是浏览器在解析时会自动忽略(不处理)分隔属性与属性值的等号(=)两侧的空格字符。在原始文档中,img元素的title属性后先接上一个空格符,然后依序是等号,第二个空格符,左尖括号。。。。而在浏览器解析时忽略了两侧的等号。
(img元素与第一个属性之间的空格符不会被省略,而且是必须的)
《浏览器如何解析HTML文档中,错误的元素标签嵌套》
以下面代码为例,其在body元素内部存在错误的标签嵌套:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=utf-8" />
<title>XssPayloadTest</title>
</head>
<body>
<i <b>xss
</body>
</html>
通过前面的例子可知,浏览器不支持在元素属性名中使用特殊字符,那么,浏览器是否支持在元素名称中使用特殊字符,例如左尖括号以及等号呢?考虑下面这段代码:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=utf-8" />
<title>XssPayloadTest</title>
</head>
<body>
<i<"<b>">xss
</body>
</html>
在body元素内部,试图将第二个左尖括号开始的字符串作为 i 元素名称的一部分,
来看看浏览器们会如何解析这个文档:
IE 中的情况与上述两者类似,这里就不截图说明了。大家可以自行尝试向 i 元素名称中添加等号(=),浏览器也会将其当成是元素名称的组成字符,从某种意义上而言,这种解析上的缺陷不得不视为当前版本的一个安全隐患。
在chrome的解析截图中,看到了将特殊字符进行HTML实体编码的用法,其实,
浏览器的HTML解析引擎应该都能识别在HTML文档的文本节点与元素属性值内,以HTML实体编码的字符序列,并将其解码,还原成可打印的ASCII字符。
这就是chrome将文本节点中的左尖括号编码的原因——它可以解码成明文字符。
这里隐含了一条重要的安全规则,浏览器在解析时应该遵守,否则就有可能在客户端产生xss漏洞,假设经由服务器端返回的HTML文档中,已经对文本节点与元素属性值进行了HTML实体编码(或者10进制,16进制编码),意味着这些被编码的字符是具有危害性的(一般而言,普通字符不需要编码)
那么浏览器就不能将解码后的危害性字符序列(如“<script>”)当成指令执行,只能作为数据或字符串显示,或者索性不解码,在HTML文档中显示编码字符。
因为服务器端的xss过滤器已经意识到,危害性字符出现在文本节点与元素属性值中,可能导致xss漏洞,因此将其编码,而浏览器能做的就是,仅显示解码后的内容,而不是执行,或者保持原样输出。
为了更好地说明其中的微妙之处,来看一看下面的几个实例:
[CODE]<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=utf-8" />
<title>XssPayloadTest</title>
</head>
<body>→
[课程]FART 脱壳王!加量不加价!FART作者讲授!