1.2.3 XSS进阶
1.CSP
内容安全策略(Content Security Policy, CSP)是一种计算机安全标准,由Robert Hansen于2004年提出,首先在Firefox 4中实现,并很快被其他浏览器采用。CSP用于防止XSS、点击劫持和其他由于在受信任的网页上下文中执行恶意代码而导致的代码注入攻击。CSP为网站所有者提供了一种标准方法来声明允许浏览器在该网站上加载资源,如JavaScript、CSS、HTML网页等。
CSP可以通过两种方式进行设置,在HTTP的消息头中设置,或者在HTML的Meta标签中设置。正常的CSP配置由多组策略组成,每组策略包含一个策略指令和一个内容源列表,如表1-3、表1-4所示,每组策略之间由分号分隔。CSP主要是通过限制JavaScript的执行、限制跨域请求来防御XSS的。
表1-3 CSP指令
表1-4 CSP指令值表
具体配置参见https://developers.google.com/web/fundamentals/security/csp/。
举个例子来看看CSP的效果。未设置CSP时,代码如图1-68所示。
图1-68 未设置CSP的代码
成功加载图片后的效果如图1-69所示。
图1-69 成功加载图片
我们配置一个常见的CSP,代码如下。允许执行内联JavaScript代码,但不允许加载外部资源。
可以看到,配置完CSP之后,获取图片失败了,如图1-70所示。
图1-70 CSP策略生效
2.绕过CSP
下面介绍如何利用CSP的配置缺陷。
在真实的开发环境中,常常不得已需要执行内联,我们可以借此执行内联JavaScript,当然也可以利用location跳转带外数据,代码如下。
利用unsafe-eval错误配置的代码如下。
不允许加载外部资源,并且不允许加载内联JavaScript代码,但是配置了unsafe-eval指令值,使用了data配置,可通过Base64编码Payload,代码如下,结果如图1-71所示。
图1-71 成功绕过CSP
如果网站设置了script nonce,在无法猜测nonce值且base-uri没有被设置的情况下,可以使用base标签设置默认地址为我们构造的恶意服务器地址。如果页面中的合法script标签采用了相对路径,那么最终加载的JavaScript代码就是针对base标签中设置的默认地址的相对路径,如图1-72所示。
图1-72 成功绕过
这样就会默认加载我们构造的恶意服务器上的main.js。
我们也可以通过link标签进行预加载。如下代码是一个简单的CSP规则,不允许加载外部资源,我们用img标签引入baidu.com的图片时就会被阻止,如图1-73所示。
图1-73 不允许加载外部资源
可以通过link标签的预加载来绕过,代码如下。大部分浏览器都约束了该标签,如图1-74所示。
图1-74 通过link标签的预加载绕过
外带数据可以使用如下代码绕过。
在浏览器的机制上,跳转也算是一种跨域行为,并且不受CSP约束,可以通过跳转绕过CSP,带出我们要的数据。部分Payload可参考以下代码。
CSP中原本有sandbox和child-src限制iframe的行为,但是当一个同源站点存在A页面和B页面,且A页面有CSP保护,而B页面没有CSP保护时,我们可以通过B页面新建iframe嵌套A页面,这样就可以绕过A页面的CSP获取A页面的数据,如图1-75所示。
图1-75 通过iframe标签绕过
通过站点允许访问的资源来构造XSS,最经典的案例就是利用www.google.analytics.com中提供自定义JavaScript代码的功能(因为Google会封装自定义的JavaScript,所以还需要unsafe-eval字段),绕过CSP,代码如下,如图1-76所示。
图1-76 利用站点可控静态资源绕过
浏览器解析html标签的规则为遇到左尖括号标签开始解析,直到遇到右尖括号结束,两者之间的数据都会被当成标签名或者属性。
在某些场景下,利用这个规则我们可以劫持别的标签的属性,代码如下。
CSP为不允许请求外部资源,仅允许属性nonce为test的标签执行JavaScript代码。
我们构造Payload:?xss=%3Cscript+src=data:text/plain,alert(1),结果如图1-77所示。
图1-77 绕过不完整的script标签
如图1-77所示,Payload拼接到页面上就是成功劫持了nonce='test'属性,并且执行了我们构造的alert函数。
JSONP其实就是一个跨域解决方案,JavaScript是不可以跨域请求JSON数据的,但是可以跨域请求JavaScript脚本。我们可以把数据封装成一个JavaScript语句,做一个方法的调用,跨域请求JavaScript脚本可以得到此脚本。因为程序得到JavaScript脚本之后会立即执行,所以把数据作为参数传递到方法中就可以获得数据,从而解决跨域问题。为了便于客户端使用数据,我们发明了一种非正式传输协议,JSONP。该协议的一个要点就是允许用户传递一个callback参数给服务端,服务端返回数据时会将这个callback参数作为函数名来包裹JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。
JSONP是用来解决跨域问题的,能够绕过CSP,构造如下Payload。如果返回的数据为JSON格式,此方法就无法利用了。
3.XSS升级为RCE
浏览器一般对JavaScript调用系统命令设置限制,但是在一些客户端上往往会忽略这个细节。当XSS在浏览器以外的客户端被触发时,攻击者可以利用XSS构造JavaScript去调用系统命令,这样一个简单的XSS漏洞可能就会升级为一个RCE漏洞。
举个例子,Electron是GitHub发布的跨平台桌面应用开发工具,支持Web技术开发桌面应用,其本身是基于C++开发的,图形用户界面核心来自Chrome。Electron相当于精简版的Chromium浏览器。Xmind、Slack、Atom、Visual Studio Code、Wordpress Desktop、GitHub Desktop、蚁剑和Mattermost等应用程序都是采用Electron框架构建的。
简单来说,Electron可以将一个Web应用转为桌面应用,在Web应用中可能会出现的XSS漏洞,在Electron开发的桌面应用中也会出现,并且这种XSS漏洞很容易升级成为一个RCE漏洞。Xmind、GitHub Desktop等都曾被爆出过XSS漏洞导致的任意命令执行。
我们先来看看JavaScript如何调用系统命令。
exec()是child_process模块里面最简单的函数,作用是执行一个固定的系统命令。
例如执行命令whoami。
构造执行命令的XSS Payload调用计算器,代码如下。
execSync()的作用同exec(),不同之处在于该函数在子进程完全关闭之前不会返回,代码如下。当遇到超时并发送killSignal时,该函数在进程完全退出之前不会返回。
execFile()函数的作用是运行可执行文件,函数参数信息如下。
execFile()函数用法如下。
execFileSync()的作用与execFile()相同,不同之处在于该函数在子进程完全关闭之前不会返回。当遇到超时并发送killSignal时,该方法在进程完全退出之前不会返回,代码如下。