基本介绍
Gareth Heyes在2014年首次提出了一种新型攻击手法—RPO(Relative Path Overwrite)相对路径覆盖,该漏洞是一种利用相对URL路径覆盖目标文件的一种攻击手段,其主要依赖于服务器和浏览器的解析差异性并利用前端代码中加载的css/js的相对路径来加载其他文件,最终使得浏览器将服务器返回的不是css/js的文件当做css/js来解析,从而导致XSS,信息泄露等漏洞产生
原理概述
资源定位
资源的定位有相对路径和绝对路径两种方式,其中绝对路径以根目录为起点并完整地指定资源的路径,例如:http://www.example.com/index.html,其中"http://"表示使用的协议类型,"www.example.com"表示目标服务器的主机名称信息,"index.html"表示资源路径,通过这一个URL我们可以直接访问指定的资源,而相对路径并不会直接指定域或协议,它会使用现有的目标来确定协议和域,例如:public/somedirectory,相对URL将查找public并根据当前域名自动包含其前面的域,当下相对URL有两种重要的变化,其中第一种是我们可以使用当前路径并在其中查找一个目录,比如"xyz",第二种是我们可以使用常见的目录遍历技术,比如"../xyz"
下面的样式表使用示例中使用的一个常见的相对URL,其中link元素使用相对URL引用"style.css",具体被引用的文件取决于您在站点目录结构中的位置,它将基于该位置加载样式表,例如:如果您当前在一个名为"xyz"的目录中,那么样式表将会从"xyz/style.css"中加载,同时下面的"echo $_SERVER['PHP_SELF']"输出请求页面的当前URL,这里我们可以进行一些简易测试:
代码语言:javascript复制<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
<link href="styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
Hello <?php echo $_SERVER['PHP_SELF']?>
</body>
</html>
我们正常访问http://192.168.204.134/a.php时,浏览器会去请求http://192.168.204.134/style.css
如果我们访问http://192.168.204.134/a.php/{}*{background-color:red}/,那么此时就会请求http://192.168.204.134/a.php/{}*{background-color:red}/style.css
怪异模式
CSS user agents是指Web浏览器或其他Web客户端应用程序在呈现Web页面时使用的CSS样式表,每个Web浏览器都有自己的CSS user agents,CSS user agents包含了浏览器默认的CSS样式表,它们规定了浏览器在渲染HTML页面时所使用的样式,CSS规范规定了在某些情况下user agents必须忽略非法样式表的一部分,这也意味着user agents在解析非法部分时除非是明确匹配到了开始和结束,否则予以忽略,简单来说就是仅解析其中格式正确完整的部分,忽略非法语法,此类解析模式也被称之为"怪异模式(Quirks mode)",这也是上面的CSS文件返回一片红色的原因:
解析差异
下面我们先来了解一下Web Server的解码功能,在RPO目录下新建两个php文件apache.php和nginx.php,访问成功会分别输出Apache和Nginx,同时新建一个test空目录:
Apache中简单测试如下:
代码语言:javascript复制http://192.168.204.134/ROP/test/../apache.php
Nginx中简单测试如下:
代码语言:javascript复制http://192.168.204.134/RPO/test/../nginx.php
从以上测试结果可以看出来对于完全相似的URL,不同的服务器的处理方式是不同的:
- Apache服务器默认情况下不认识../这个符号,认为../apache.php是一个文件,所以无法找到该文件
http://192.168.204.134/RPO/test/../apache.php ————> ../apache.php文件在那?
- Nginx能自动地把../进行url解码,转化为../之后再去查找请求的文件,所以可以找到nginx.php文件:
http://192.168.204.134/RPO/test/../nginx.php ————> ../nginx.php 等价与../nginx.php,只后去上级目录下访问nginx.php文件
利用条件
ROP漏洞利用条件如下:
- CSS解析器忽略非法的内容
- 存在相对路径的JS或者CSS引用
- 后端使用Niginx服务器来搭建服务/Apache服务配置错误导致可以解析../(例如:开启AllowEncodedSlashes On)
备注:AllowEncodedSlashes仅在2.0.46版本之后可以使用,具体配置方式如下
Step 1:打开Apache的配置文件httpd.conf并找到以下代码
代码语言:javascript复制<Directory />
AllowOverride none
Require all denied
</Directory>
Step 2:在上面的代码块之下添加以下内容
代码语言:javascript复制<VirtualHost *:80>
AllowEncodedSlashes On
</VirtualHost>
Step 3:重启服务器即可
漏洞示例
跨目录读取JS
利用流程
在RPO目录下创建index.php代码如下:
代码语言:javascript复制 <html>
<head></head>
<body>
<script src=a.js>
</script>
</body>
</html>
<?php
echo "js in test folder";
?>
在index.php同目录下的test文件夹中有a.js,如果被调用就会弹出对话框,其代码如下:
代码语言:javascript复制alert("Read file successfully");
访问http://192.168.204.134/RPO/test/../index.php后你会惊奇的发现本来只能读取和自己在同一目录下的a.js的index.php居然成功访问到了test目录下的a.js(css也是一样的原理,不再赘述)
原理分析
下面我们来分析一下上面的弹窗究竟是怎么实现的:
Step 1:我们向服务器提交我们想请求的URL
代码语言:javascript复制http://192.168.204.134/RPO/test/../index.php
Step 2:服务器会把../自动进行URL解码,实际上服务器端看到你请求的URL如下
代码语言:javascript复制http://192.168.204.134/RPO/test/../index.php
Step 3: "../ "在URL中会被理解成上一层目录,所以服务器实际上认为你访问的是下面的URL并把index.php的内容返回给天真的浏览器
代码语言:javascript复制http://192.168.204.134/RPO/index.php
Step 4:接下来浏览器的工作就是根据URL的路径处理index.php中引用的使用相对地址的脚本,可是万万没想到浏览器它并不认识../,于是URL在它眼里依旧是最初的模样
代码语言:javascript复制http://192.168.204.134/RPO/test/../index.php
Step 5:此时无知的浏览器把../index.php当成了一个文件,可它还是严格按照脚本的要求加载当前目录下的a.js文件,而对它来说现在的当前目录已变成了test,自然而然test目录下的a.js被成功加载
按JS解析内容
通过上面的了解我们可以发现一点就是我们在利用RPO时所有的资源文件都是在服务器端一早就已经有了的,而我们要想通过RPO实现XSS攻击那么就必须得再页面中引入我们的攻击脚本,而由于环境并非我们自己开发所以没法控制服务器端的JS脚本内容以及其位置,下面我们介绍如何将内容按照JS来解析
URL重写
URL重写是一种通过修改URL的结构和参数,使得URL更加简洁、易读、易记的技术,同时也能够提高网站的可访问性和SEO优化效果,URL重写通常是通过在Web服务器上配置规则来实现的,这些规则可以将原始URL转换为新的URL或者将URL中的某些部分替换为其他内容,例如:原始URL链接http://www.example.com/product.php?id=123&name=apple通过URL重写后可以变更为http://www.example.com/product/123/apple,其中product用于代表页面类型,123代表商品ID,apple代表商品名称,这样的URL更加简洁明了,同时也更加易于搜索引擎的识别和收录
下面进行一个简单配置示例:
Step 1:首先检查Apache是否已经安装了rewrite模块
之后加载rewrite模块:
Step 2:启用URL重写
在httpd.conf文件中找到以下行,然后将其中的"None"改为"All"
代码语言:javascript复制AllowOverride None
Step 3:创建.htaccess文件并填写重写规则,随后重启Apache服务器
简单演示
首先在Apache中配置好URL_REWRITE,将http://192.168.204.134/RPO/index.php/page/1重写为http://192.168.204.134/RPO/test/1.html,同时构造一下index.php
代码语言:javascript复制<!DOCTYPE html>
<html>
<head>RPO attack test</head>
<body>
<script src="3.js"></script>
</body>
</html>
<?php
error_reporting(E_ALL^E_NOTICE^E_WARNING);
if($_GET['page'])
{
$a=$_GET['page'];
Header('Location:http://192.168.204.134/RPO/test/'."$a".'.html');
}
?>
3.html
代码语言:javascript复制alert("RPO attack");
从上面可以看到在index.php中引入了当前页面中的3.js,3.html中写入了一个没有<script>标签的JS语句,之后我们可以通过访问下面的URL后将3.html的文件中的没有<script>标签的js语句进行解析,完成攻击(笔者这里是因为重写配置不当导致的原因没有弹窗)~
原理分析
下面解释一下上面的过程:
Step 1:攻击者向服务器请求URL
代码语言:javascript复制http://192.168.204.134/RPO/index.php/page/3/../../../index.php
Setp 2:服务器收到请求
代码语言:javascript复制http://192.168.204.134/RPO/index.php/page/3/../../../index.php
Step 3:服务器返回index.php页面给浏览器
代码语言:javascript复制http://192.168.204.134/RPO/index.php
Step 4:浏览器加载index.php文件并加载同目录下的1.js,但是浏览器看到的URL是
代码语言:javascript复制http://192.168.204.134/RPO/index.php/page/3/../../../index.php
Step 5:浏览器认为../../../index.php是一个页面,自然而然加载的URL就是
代码语言:javascript复制http://localhost/RPO/index.php/page/3
Step 6:由于我们的请求是由<script src=…>生成的,所以返回给我们的东西都会被浏览器当做是JS解析,在这里由于http://192.168.204.134/RPO/index.php/page/3是一个能够请求的页面,所以之后的3.js会交给/3来进行一次处理,就像http://192.168.204.134/RPO/index.php/page/3/的内容会被index.php处理一样,然后/3返回给script标签,这也就是为什么3页面会被当做js解析的原因
扩展案例
执行案例1
如果我们可以在所在的页面制作样式表自引用,那么我们就可以使用CSS解析来忽略HTML并在IE兼容中执行我们的自定义CSS,当站点包含如下样式表时,我们直接访问URL会直接解析对应的页面:
代码语言:javascript复制<link href="styles.css" rel="stylesheet" type="text/css" />
此时我们只需要在URL末尾添加一个正斜杠,那么样式表最终在通过浏览器解析时会认为这是一个目录,但实际上是当前页面的目录加载原始页面:
代码语言:javascript复制http://challenge.hackvertor.co.uk/xss_horror_show/chapter7/rpo.php/
Meta元素将IE的文档模式强制转换为执行表达式所需的IE7兼容,我们的持久文本{}*{xss:expression(open(alert(1)))包含在页面上,在现实场景中它可能是个人资料页面或者可能是其他用户可以查看的共享状态更新,我们使用"开放"{来防止客户端重复执行警报的DoS,"rpo.php/"的简单请求使相关样式将页面本身作为样式表加载,实际请求是"/labs/xss_horror_show/chapter7/rpo.php/styles.css",浏览器认为还有另一个目录,但实际请求被发送到文档,这实际上就是RPO攻击的工作原理
执行案例2
RPO攻击并不仅限于诸如"styles.css"之类的相对URL,它还可以攻击诸如"../../styles.css"之类的 URL,但在这种情况下我们需要提供假目录的级别直到从当前文档加载样式,"../"表示往上看当前目录,我们需要三个级别的假目录
代码语言:javascript复制<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
<link href="../../styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
Hello {}*{xss:expression(open(alert(1)))}
</body>
</html>
我们访问下面的链接:
代码语言:javascript复制http://challenge.hackvertor.co.uk/xss_horror_show/chapter7/rpo2.php/fakedirectory/fakedirectory2/fakedirectory3
这次因为相对URL正在寻找比当前目录高两倍的目录,所以我们请求"/labs/xss_horror_show/chapter7/rpo2.php/styles.css"时意味着您也可以将文件定位到不同的目录中,但在这种情况下我们将其指向原始的html文件,请注意我们本可以只完成rpo2.php///,但为了清楚起见,我提供了假目录的文本,当然还有其他变体,例如:使用@import 命令,这在长度或字符有限时很有用,再次使用"}"忽略HTML,然后使用@import语句在IE上工作得很好,尽管从技术上讲以这种方式使用import语句是无效的语法,当然RPO也并不限于IE,我们可以在其他浏览器上使用该技术,但Chrome、Firefox、Opera或Safari上的CSS不支持JavaScript,另一个限制是文档类型不能包含在目标文档中,因为这会导致CSS的解析器停止在非IE浏览器上解析HTML文件
代码语言:javascript复制<html>
<head>
<link href="../../styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
Hello {}*{color:#ccc;}
</body>
</html>
之后通过在后面添加///后,可以将上面的文档将文本颜色更改为灰色,并且适用于所有浏览器,它的工作方式与之前的PoC相同,但这次使用纯CSS,没有表达式,如果文档中包含文档类型,则在每个浏览器上都会失败,除非IE处于兼容模式,RPO攻击适用于任何类型的文档,例如:可以更改图像文件的目标,但由于图像文件在文件的开头查找特定的字符串,而最终结果只是一个图像,因此此类RPO攻击的用处不大
修复建议
- 绝对路径:在代码中使用绝对路径来引用文件,这样可以避免使用相对路径,从而避免RPO漏洞的发生
- 输入验证:对用户输入的文件名进行严格的验证,确保输入的文件名只包含合法的字符,避免输入包含../等相对路径字符
- 增加文件名前缀:在文件名前面添加一个固定的前缀,这样即使攻击者使用相对路径也无法访问到系统中的文件,因为文件名不匹配
- 文件权限严格控制:对于敏感文件,应该设置严格的权限控制,只有授权用户才能访问文件,避免非授权用户通过RPO漏洞访问到文件