RPO漏洞初探笔记 | Dayu's Blog

RPO漏洞初探笔记

RPO攻击简介

  • RPO(Relative Path Overwrite),意为相对路径覆盖,利用的是客户端浏览器与服务端url解析的差异而导致的漏洞。在引入相对路径的js/css文件处,通过一定的手法,加载我们可控的文件,按照js/css的语法执行,实现XSS等攻击。

解析差异分析

  • 本地测试目录结构如下
    roptest目录:
    roptest/xxx目录:
    roptest目录下的rop.php代码为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <html>
    <head>
    <title>ROP</title>
    <script src=./a.js></script>
    </head>
    <body>
    <?php echo "Hello dayu, this is roptest/rop.php" ?>
    </body>
    </html>
  • roptest目录下的a.js代码为

    1
    alert("I'm from roptest")
  • roptest/xxx目录下的a.js代码为

    1
    alert("I'm from xxx")
  • 当我们使用chrome访问 http://127.0.0.1/roptest/rop.php 时:

  • 访问 http://127.0.0.1/roptest/xxx/..%2frop.php 时:

  • 至此,我们已经可以看出这种攻击的条件:
    HTML代码中采用了相对路径来引入js或者css等文件
    服务端能够正常解析%2f等url编码(刚开始自测nginx可以构成漏洞,apache不行,后来查了一下:apache如果要支持%2f的话,需要将AllowEncodedSlashes设置为On。一般在配置了pathinfo后,会将这个选项打开。)
    客户端浏览器在寻找js资源的时候,并没有对%2f进行解码

第二届强网杯 Share your mind

  • 题目描述

    1
    2
    3
    4
    http://39.107.33.96:20000
    Please help me find the vulnerability before I finish this site!
    hint:xss bot使用phantomjs,版本2.1.1
    hint2: xss的点不在report页面
  • 这道题的漏洞点就在于RPO

  • 复现过程
    发表一个title为空,内容为alert(1)的article,访问为纯文本

  • 由于站点使用了pathinfo的url模式,

    1
    http://39.107.33.96:20000/index.php/view/article/1866
  • 类似相当于

    1
    http://39.107.33.96:20000/index.php?m=view&article=1866
  • 同时,浏览器按照phpinfo的格式来解析url的话,只会访问到能识别的地方

  • 所以,当我们访问

    1
    http://39.107.33.96:20000/index.php/view/article/1866/..%2f..%2f/
  • 浏览器会到达

    1
    http://39.107.33.96:20000/index.php/view/
  • 同时,该页面底部通过相对路径加载的两个js会去尝试加载

    1
    2
    http://39.107.33.96:20000/index.php/view/article/1866/../static/js/jquery.min.js
    和http://39.107.33.96:20000/index.php/view/article/1866/../static/js/bootstrap.min.js
  • 由于这两个文件不存在且加上上面的pathinfo原则,这两个<script src="">实际会加载

    1
    http://39.107.33.96:20000/index.php/view/article/1866
  • 所以此时会出现两个alert(1)弹窗,形成了xss攻击。

  • 最后,在report页面只能提交当前站点url,于是我们通过上述方法构造xss提交,”盲打”,获取到flag。

  • 附上解决md5的脚本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import multiprocessing
    from os import urandom
    from hashlib import md5
    import sys
    processor_number = multiprocessing.cpu_count()
    def work(cipher):
    for i in range(100):
    plain = urandom(16).encode('hex')
    if md5(plain).hexdigest()[:6] == cipher:
    print plain
    sys.exit(0)

    if __name__ == '__main__':
    cipher = raw_input('md5:')
    pool = multiprocessing.Pool(processes=processor_number)
    while True:
    plain = urandom(16).encode('hex')
    pool.apply_async(work, (cipher, ))
    pool.close()
    pool.join()