PlaidCTF2019刚结束,一共两个Web题目,目前官方还未放出Writeup,但是已经给出了exploit。做完的感受就是:国际赛真好玩儿啊。

初步分析

1
2
3
4
5
6
I set up a little quotes server so that we can all share our favorite quotes with each other. I wrote it in Flask, but I decided that since it's mostly static content anyway, I should probably put some kind of caching layer in front of it, so I wrote a caching reverse proxy. It all seems to be working well, though I do get this weird error when starting up the server: 
* Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
I'm sure that's not important.
Oh, and don't bother trying to go to the /admin page, that's not for you.

从官方给的描述来看,后端逻辑是用Flask写的,并使用了一个反向代理缓存。同时将这个代理服务的可执行文件给了我们,我们可以通过逆向来还原缓存策略。

题目功能也较为简单:

  1. 创建Quote
  2. 提交Quote给admin(report)
  3. 给Quote投票
  4. 浏览Quote
  5. 浏览report

反向代理缓存服务的缓存策略则是当请求方法为GET,并且响应是’HTTP/1.0 200 OK’时,则对该页面进行缓存。因此,当我们用GET方式访问一个页面,并且响应是’HTTP/1.0 200 OK’时,会缓存这个页面,当我们再次打开这个页面便会命中缓存。

而根据题目描述中的信息,还存在一个页面/admin,打开之后会有两个api,分别为获取Flag及访问下一个Quote。其中获取Flag的接口需要以POST方式请求,这就需要我们利用XSS。

但是在Quote以及report功能里,输出的内容都经过html实体编码,因此是无法构造出有效XSS的payload的,只能另寻他路。

从这里的思路就分为了两种,队里的师傅使用的是利用内存Leak的非预期解来完成攻击的,另一种则是官方解法。

官方解法中,是通过利用HTTP/0.9协议没有响应头这个feature进行了缓存投毒,下面分析官方的做法。

深入利用

前面讲到,当我们访问的是一个已缓存的页面时,会命中缓存并直接返回缓存的内容。如果我们能将含有XSS的payload的页面进行缓存,便能在其他用户用户访问被缓存的恶意页面时,触发攻击,完成缓存投毒攻击。

另一个关键点同样也是前面讲到的,当响应是HTTP/1.0 200 OK时,会进行缓存处理,那么现在可以理一下已有的条件:

  1. HTTP/0.9没有响应头
  2. 可以通过Quote功能输出自定义文本
  3. 返回内容是HTTP/1.0 200 OK时进行缓存

攻击思路已经呼之欲出:通过Quote功能,将HTTP/1.0 200 OK放入Quote的内容中,并添加上正确的响应头、响应体。

完成之后,继续用HTTP/0.9协议来访问这个Quote,由于正常返回的数据缺少响应头,因此在反向代理缓存进行处理时,会将Quote中Content处的HTTP/1.0 200 OK识别为响应头并进行缓存,此时我们便能控制页面的内容。

接下来只剩下最后一个问题:如何绕过过滤进行XSS。响应头中有一个名为Content-Encoding的头,它与请求头中的Accept-Encoding是HTTP中用来对「采用何种编码格式传输正文」进行协定的一对头部字段。

因此我们可以使用另一种编码方式来传输将要进行XSS的payload,这里就直接给出exploit中的利用:

1
2
3
4
5
6
7
wow = 'D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3SUUnUUUwCiudIbEAtwwwEtswGpDttpDDwt3ww03sG333333swwG03333sDDdFPiOMwSgoZOwMYzcoogqffVAaFVvaFvQFVaAfgkuSmVvNnFsOzyifOMwSgoy4'


data = {
'quote': 'HTTP/1.0 200 OK\r\nHTTP/1.0 302 OK\r\nContent-Encoding: deflate\r\nContent-Type: text/html;\r\nContent-Lexngth: {length}\r\n\r\n'.format(length=len(wow)) + wow,
'attribution': ''
}

其中wow是攻击页面的内容,data是构造出的响应头,重点是Content-Encoding: deflate这一部分,通过将payload进行编码后进行缓存,当受害者打开页面,由于受害者的响应头不再是deflate编码,因此解码后的XSS payload成功触发。

至此缓存投毒攻击结束。

亮点

这个题目的亮点可以分为三部分:

  1. 分析反向代理缓存的具体缓存逻辑
  2. 利用HTTP/0.9的feature
  3. 绕过Quote内容的html实体编码

当然最有意思的莫过于将这些看似零碎的点进行组合利用的过程了,真真是Hack For Fun.