web前端安全基础与***(续篇)
继续前面的内容,由于转发到一些论坛时,限制了每个帖子中,添加图片链接的数量,而前篇包含的图片数量已经达到上限,因此开一个新帖继续讨论。
还记得在前篇中有一段针对使用 IE 浏览器用户的***载荷,如下所示 :
上面代码中,使用 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安全一书》,略作修改)
XssPayloadTest
这个在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的响应头。)
前面提到,当没有启用 CORS 时,浏览器的同源策略禁止 XHR 对象跨域读写资源;但是 iframe 标签则不受浏览器的同源策略约束,可以随意跨域加载资源,细节容后作介绍,这里先来看看一个 iframe 标签的效果:
XssPayloadTest
上面文档是为了兼容早期不支持 iframe 标签的浏览器量身定做的:具体而言,不支持 iframe 的浏览器在碰到这个标签的时候,将无法跨域加载页面资源,而是显示标签内部的数据,就本例而言,会提示用户需要使用支持 iframe 的浏览器才能正常显示内容;相反,当前的浏览器多数都支持 iframe 标签了,使用它们打开这个文档,就不会显示提示信息,以 FireFox 为例:
通常以大写的 X 开头的头部(包括请求与响应头)都是 HTTP 规范中的“扩展”头部;
上图中的 X-XSS-Protection 响应头是用于告诉客户端浏览器开启内置的 XSS 过滤机制(其值为 1 时,表示开启),只要浏览器实现了标准中定义的 X-XSS-Protection 规范,那么应该都能识别这个响应头(同时还需要目标站点的 web 服务器能够意识到传递这个响应头对于客户端安全的重要性,作为对比,百度的 web 服务器就不会传递这个响应头)
X-XSS-Protection 起到的防御效果是有限的,仅能阻止反射型 XSS,也就是当***载荷出现在 URL 中提交至服务器时,如果服务器没有过滤并且直接在响应中将***载荷“反射”回客户端,那么得益于先前或本次响应设置的 X-XSS-Protection 头部,浏览器将检查并过滤危害字符;X-XSS-Protection 无法防御存储型 XSS。
上面一系列图片中出现的 X-Frame-Options 已经介绍过,主要是用来防止***者通过 iframe 跨域嵌入页面内容,构造出点击劫持(ClickJacking)的伪装 UI ,欺骗用户点击。如果将 X-Frame-Options 头部的值设为 DENY ,那么任何页面都不能通过 iframe 嵌入其响应体中的 HTML 文档(如上所述,实际上设置了 X-Frame-Options 头部的服务器不会在响应体中跨域发送 HTML 文档),即便是同源(相同域)的页面也不行。
由此可见,google 的安全策略还是做的比较完善的,毕竟是世界领先的互联网云计算,云存储公司,以及信息检索服务提供商。
《有效阻止各种类型XSS的CSP(内容安全策略)》
以 facebook 服务器为例,当浏览器向其请求一个页面文档时,支持 CSP 的服务器在携带该文档的 HTTP 响应头部会添加一个 content-security-policy 响应头,支持 CSP 的浏览器在解析,渲染该文档之前,必须先读取该头部的值,以了解服务器对该页面资源设置的客户端安全加载策略,这样,即便页面中包含***载荷,也会因为该***载荷没有从 CSP 允许的域加载资源,而使***失败。另外,XSS ***通常无法注入,修改响应头部,因此该字段可以被用于正确判断服务器的本意:
content-security-policy: default-src *; script-src https://*.facebook.com http://*.facebook.com https://*.fbcdn.net http://*.fbcdn.net *.facebook.net *.google-analytics.com *.virtualearth.net *.google.com 127.0.0.1:* *.spotilocal.com:* 'unsafe-inline' 'unsafe-eval' https://*.akamaihd.net http://*.akamaihd.net *.atlassolutions.com chrome-extension://lifbcibllhkdhoafpjfnlhfpfgnpldfl; style-src * 'unsafe-inline'; connect-src https://*.facebook.com http://*.facebook.com https://*.fbcdn.net http://*.fbcdn.net *.facebook.net *.spotilocal.com:* https://*.akamaihd.net wss://*.facebook.com:* ws://*.facebook.com:* http://*.akamaihd.net https://fb.scanandcleanlocal.com:* *.atlassolutions.com http://p_w_upload.fbsbx.com https://p_w_upload.fbsbx.com;
对于上面的 CSP ,浏览器将会作出如下解释,并用其作为判断是否加载页面资源的标准:
1。 default-src * 表示允许从任何域加载任何资源,在这条指令中引入通配符星号是相当危险的,虽然该指令的优先级最低(策略冲突时,以其它指令为准,而不是 default-src),但一般也不推荐用通配符,这里应该是 facebook 前端开发工程师犯的低级安全错误;
2。script-src 指令表示允许从哪些域加载脚本文件,这里允许的域有:
https://*.facebook.com
http://*.facebook.com
https://*.fbcdn.net
http://*.fbcdn.net
*.facebook.net(估计租下了 .net 通用顶级域下的这个子域,才敢如此设置)
*.google-analytics.com
*.virtualearth.net
*.google.com(google 一向是web安全的模范站点)
127.0.0.1:*(这是多余的吧?)
*.spotilocal.com:*(允许该域服务器上的任何端口。。。可以对目标进行端口扫描了)
'unsafe-inline'
(表示允许该页面中内嵌的脚本,即通过 script 标签引入的,由于***者也可以注入 script 标签,这就说明 facebook 有一套良好的 XSS 过滤机制,才敢使用 unsafe-inline 这个参数;另外,如果没有显式指定这个参数,则默认禁止内嵌脚本,但这就无法执行“善良的”script 标签内代码)
'unsafe-eval'
(表示允许执行 eval() 家族的函数,后者通常用于在“严峻”的已过滤环境中执行恶意脚本,这表明 facebook 对自己的 XSS 防火墙很有自信)
https://*.akamaihd.net
http://*.akamaihd.net
*.atlassolutions.com
3。style-src * 同样表示允许从任何域加载样式表文件(危险!);
'unsafe-inline' 表示允许内嵌样式,即通过 style 标签引入的;
4。connect-src 表示允许通过 XHR 对象向哪些域发起跨域请求并读写响应,这里
允许“跨”的域有:
https://*.facebook.com
http://*.facebook.com
https://*.fbcdn.net
http://*.fbcdn.net
*.facebook.net
*.spotilocal.com:*
https://*.akamaihd.net
http://*.akamaihd.net
wss://*.facebook.com:*
ws://*.facebook.com:*
https://fb.scanandcleanlocal.com:*
*.atlassolutions.com
http://p_w_upload.fbsbx.com
https://p_w_upload.fbsbx.com
对于任何其它站点服务器返回的 CSP 策略,都可以按照类似上述方法来解释,推测出浏览器最终将只允许哪些内容。
《关于HTML文档的两种解析模式:HTML 与 XHTML 之间的差别》
浏览器根据HTML文档起始处的“”来判断应该使用何种解析模式,当xxxx的值为“html”时,表示为HTML解析模式,该模式对文档的语法要求比较宽松,浏览器的HTML解析引擎会容许并且自动修正绝大多数的语法错误,例如下面这个例子:
XssPayloadTest ' " ` 百度首页。。。![]()
可以看到,上述文档的HTML标签大小写混用;body元素与a元素没有闭合;
a元素的href属性的值(百度首页的URL)没有用双引号包含;存在一个没有起始(标签)的oops元素;img元素使用全大写而且没有闭合。。。。这些语法错误都可以在浏览器中得到修正,前提是使用HTML解析模式,以 IE 为例,打开上述文档,IE 会在其进程地址空间中构建DOM节点时,修正错误,缺少的元素:
另一方面,XHTML(衍生自XML)解析模式则非常严格,文档中存在上述任意一种类型的错误都会导致浏览器无法正常显示该文档,或者给出错误提示。
再者,采用HTML解析模式,遇到“
上例中,“与“]]>”字符串标签之间才是真正添加javascript代码的位置。 例如,BurpSuite Proxy 模块拦截到的从“博文视点”站点首页,返回的HTTP响应体的HTML文档中,就包含了对“”的使用: HTML 与 XHTML 解析模式差异总结: 使用XHTML解析时,仅遇到 script 或 style 标签,还无法切换进入到对应的解析模式(调用 javascript 或 css 解析引擎),而是必须添加“”数据标签,并且将脚本代码或样式表放在其中以实心句点表示的位置处,才能正常工作。 对于下面这个没有按照规范编写的img元素: “ 一共有4个属性:src,title,onerror,class。其中class属性的值没有使用双引号包含,三种浏览器各自使用不同的方式解析这个img元素: 从上面可以看出,只要是元素属性的值,无论是哪一种浏览器,在解析的时候都会自动向其添加双引号。另外,onerror 属性的值为一个javascript语句,运行结果是弹出提示框。 如果将包含javascript语句的双引号改成“反引号”(`),则三种浏览器在解析时,都会自动在反引号外侧再添加双引号,换言之,无论img元素属性的值为何,总是使用双引号包含,而这会导致添加反引号的javascript代码无法执行: 这里还有一个很重要的知识点:一般而言,浏览器在解析img元素的任意属性值时,无论原始文档中是否将属性值用双引号包含,浏览器都会自动将其包含。 但是,如果属性的值为以左尖括号开始的元素,例如script,并且原始文档中没有将其用双引号包含,那么浏览器在解析的时候会产生混乱(对于三大浏览器而言都一样),虽然这样并不会导致执行script标签中的javascript代码,但是浏览器会给出一些错误提示(IE),或者会干扰到对img元素其它属性值的正常解析(FireFox 与 chrome),例如原始文档中的代码:
从上面的解析结果还可以发现一个事实,那就是浏览器在解析时会自动忽略(不处理)分隔属性与属性值的等号(=)两侧的空格字符。在原始文档中,img元素的title属性后先接上一个空格符,然后依序是等号,第二个空格符,左尖括号。。。。而在浏览器解析时忽略了两侧的等号。 (img元素与第一个属性之间的空格符不会被省略,而且是必须的) 《浏览器如何解析HTML文档中,错误的元素标签嵌套》 以下面代码为例,其在body元素内部存在错误的标签嵌套: 通过前面的例子可知,浏览器不支持在元素属性名中使用特殊字符,那么,浏览器是否支持在元素名称中使用特殊字符,例如左尖括号以及等号呢?考虑下面这段代码: 在body元素内部,试图将第二个左尖括号开始的字符串作为 i 元素名称的一部分, 来看看浏览器们会如何解析这个文档: IE 中的情况与上述两者类似,这里就不截图说明了。大家可以自行尝试向 i 元素名称中添加等号(=),浏览器也会将其当成是元素名称的组成字符,从某种意义上而言,这种解析上的缺陷不得不视为当前版本的一个安全隐患。 在chrome的解析截图中,看到了将特殊字符进行HTML实体编码的用法,其实, 浏览器的HTML解析引擎应该都能识别在HTML文档的文本节点与元素属性值内,以HTML实体编码的字符序列,并将其解码,还原成可打印的ASCII字符。 这就是chrome将文本节点中的左尖括号编码的原因——它可以解码成明文字符。 这里隐含了一条重要的安全规则,浏览器在解析时应该遵守,否则就有可能在客户端产生xss漏洞,假设经由服务器端返回的HTML文档中,已经对文本节点与元素属性值进行了HTML实体编码(或者10进制,16进制编码),意味着这些被编码的字符是具有危害性的(一般而言,普通字符不需要编码) 那么浏览器就不能将解码后的危害性字符序列(如“
”
alert('xss in value of element properties'); class = examples>
其中title的属性值为以左尖括号起始的script标签,没有使用双引号包含,那么浏览器们会如何解析呢: