利用Relative Path Overwrite(RPO)构造xss的几种姿势

一年半过去了...夹点私货吧..也算是给自己一点交代(笑)

讲道理我也不知道这一年多到底做了些什么...彻底变成一条致癌咸鱼惹

一直摸鱼一直爽(并不

总之咸了很久叭..一部分原因是把精力放在一些奇怪的地方了吧...比如学校的沙雕课程..密码学什么的还是值得花一点精力学的,还有开发向的东西...碰了下Rust、Elixir这些....还有就是补全了一些奇怪的和之前的方向不怎么相关的基础吧...形式语言自动机这些 我全都要.jpg

嘛...之前的我大概对技术之外的东西不怎么感兴趣的样子..摸了一年现在 ((

姑且也算半个飞友和铁道宅了吧...哲学精神病学心理学天文学什么的都了解一点 (错乱) ((w

要说为什么会变成这样的话大概最主要的原因还是因为一些感情上的事情叭..还有生病什么的...还有就是SNS什么的真的很浪费时间...本该用来提升自己的时间..嘛..都过去了就是。(大概吧

总之2018对于我来说更像是失去的一年,希望现在重新开始继续努力还来得及叭..

好了..私货到此为止吧..只是想找个地方把这些东西记录下来..目前又没有写日记的习惯就记录在这好啦w


<!doctype html>
<html>
  <body>
    <h1>test.html here!</h1>
    <script src="main.js"></script>
  </body>
</html>

test.html

alert("Welcome!");

main.js

首先丢一个问题叭..像上面这样的东西是否可能产生xss呢...

看起来绝对不可能对吧...

首先介绍一下相对路径覆盖(RPO)这种攻击方式,使用这种方式可有效扩大xss的攻击面,这种攻击方式也不算新鲜了,网上的资料不是很多但也有不少师傅写得很详细了我这里就不过多赘述了..

这里就只简单讲一讲实现的原理和一些前置条件吧..

RPO的原理和产生的根本原因

浏览器在通过相对路径加载静态资源时(其实也不一定非要是静态资源啦..也可以劫持使用相对路径进行的跳转什么的,可能意义不大,看场景来),由于浏览器和部分webserver实现上的差异导致部分特殊构造的路径的语义出现歧义,攻击者可以通过构造特殊的url,来控制页面的base-path,让浏览器加载攻击者控制的任意静态资源。

浏览器和部分webserver实现上的差异

部分webserver(如nginx在遇到编码后的/(%2f)后会自动解码然后继续处理),就是说在url里构造/和%2f对于webserver来说没有差别,/foo/bar/foo%2fbar对于webserver来说是同一个东西,即__foo目录下的bar文件__。

而对于大部分浏览器来说(我这边只实测了chrome和firefox,不知道ie什么情况),并不会自动解码%2f,唯一的用来作为目录分隔符的东西就是/本身(这里不考虑反斜杠\, 其实无所谓),所以对于浏览器来说/foo/barfoo目录下的bar文件,而/foo%2fbar是根目录下的foo%2fbar文件,浏览器以这两种方式中任意一种去请求webserver得到的结果都是同一个东西,但是微妙的区别就在于使用/foo/bar去请求的话浏览器会把/foo目录作为相对路径的base-url,而对于第二种/foo%2fbar,浏览器会认为相对路径的base-url是根目录/.

此时我们假设/foo/bar文件是一个html,通过相对路径加载src="main.js"。对于正常的请求/foo/bar,相对路径的base-url是/foo所以浏览器会加载/foo/main.js

相对的,如果通过/foo%2fbar去访问,虽然webserver返回的html是一样的,但是因为浏览器的实现不会decode %2f,而是把foo%2fbar整个当成了文件名,自然根目录/就被当成了相对路径的base-url,因此浏览器会去加载/main.js

进一步利用

在浏览器通过相对路径加载资源的过程中(base url+relative path),我们实际上可控的是base-url。
比如下面的payload/child/vul.php%3fvul=alert('RPO')%23/..%2ftest.html,这个在webserver看来和/child/n0t3x1st/../test.html是一样的,就是/child/test.html的内容。
而对于浏览器来说这是/child/vul.php%3fvul=alert('RPO')%23/目录下的..%2ftest.html文件,其要通过相对路径加载的js自然就是/child/vul.php%3fvul=alert('RPO')%23/main.js辣,而/child/vul.php?vul=alert('RPO')的响应就是我们反射回来的payload

RPO的利用条件

  • 目标页面使用相对路径加载资源(必须,且不能有base标签)
  • 同站存在可控的输入点(必须,无论是否有过滤,无论是储存的还是反射的)
  • webserver需能够把%2f解码为/并继续处理(Nginx默认配置下是可以的,Apache默认配置下 AllowEncodedSlashes的值是Off,会404哒)
  • 在http server解析完路径后再进行一次urldecode(看起来很苛刻,但不排除在某些边缘case里会遇到,而且如果同站有地方可以存储用户输入(无论是否有过滤)那么这个条件是非必须的。这个只影响到能不能用反射型的输入点来构造payload,最后还会提到)

exploit!

注意:需修改webserver配置支持pathinfo
目录结构
目录结构
main.js和test.html就是一开始那两个

<?php
  parse_str(parse_url(urldecode($_SERVER[REQUEST_URI]), PHP_URL_QUERY), $_GET);
  echo htmlentities($_GET['vul']);

vul.php

利用姿势1

Payload:
//localhost/father/child/vul.php%3fvul=alert('RPO')//..%2ftest.html
Payload1
tricks: 使用//注释掉浏览器在后面拼接上的main.js

利用姿势2

Payload:
//localhost/father/child/vul.php%3fvul=alert('RPO')&/..%2ftest.html
Payload2
tricks: 使用&把后面的/main.js变成query string里无用的参数

利用姿势3

//localhost/father/child/vul.php%3fvul=alert('RPO')%23/..%2ftest.html
Payload3
tricks: 使用#(hashtag),浏览器应该加载的静态资源的uri是localhost/father/child/vul.php%3fvul=alert('RPO')#/main.js,因为#后面的main.js作为segment只在本地处理,不会被包含在浏览器发送的request里,实际发送的请求见上图。

讲道理我觉得这种姿势是最优雅的...

附录

为什么不能有base标签...
base标签已经指定了相对路径的默认base,那还构造个鬼...

  <head> 
    <base href="http://localhost/father/child/">
  </head>

比如这里,无论我们怎么构造,浏览器加载静态资源时的行为就是base指定的base-path(和我们在url里构造出来的无关)+relative path(比如这里的main.js),两者我们都不可控,且似乎没有覆盖base标签指定的这个默认值的方法,所以和绝对路径一样,gg。

urldecode的小问题
要用反射型的输入点来构造的话问号需编码,不然后面构造的东西都被webserver当成参数了而不是路径,且编码后的?(%3f)必须被decode,否则在未使用pathinfo的环境下编码后的%3f会被当成文件名的一部分然后直接404(和正则具体怎么写的有关),在使用pathinfo的环境中这个似乎还会影响到webserver对传给php的QUERY_STRING的取值,还有很多很多其他的环境变量,最优解可能是在webserver层处理一下吧,不过这里我是在parse_str(parse_url(urldecode($_SERVER[REQUEST_URI]), PHP_URL_QUERY), $_GET);这里处理了一下,不是很优雅就是。使用path代替query string来传参的话似乎可以避开这个坑。

点击右边的按钮加载评论,如果无法加载那估计是被墙啦..你看着办w