跨越语言的艺术:Flask Session 伪造

一颗小胡椒2023-10-26 10:49:02

0x01 概述

跨语言移植一直是技术领域内难以解决的问题,需要解决语言之间的约束,好在先前我们成功使用 Go 实现了 IIOP 协议通信,有了前车之鉴,所以这次我们将继续使用跨语言方式实现 Flask Session 伪造。本文以 Apache Superset 权限绕过漏洞(CVE-2023-27524) 为例讲述我们是如何在 Go 中实现 Flask 框架的 Session 验证、生成功能的。

最终,我们在 Goby 中成功使用跨语言方式实现了 CVE-2023-27524 漏洞的检测与利用效果,并加入了一键获取数据库凭据,一键反弹 shell 的利用方式:

0x02 漏洞原理

Session(会话)是一种服务器端管理的数据存储机制,用于在用户与 Web 应用程序之间保持持久性状态信息。它允许 Web 应用程序在不同 HTTP 请求之间存储和检索用户特定的数据。

当用户首次访问 Web 应用程序时,服务器会为每个用户创建一个唯一的会话标识(通常是一个会话 ID 或令牌),该标识存储在用户的浏览器中的 Cookie 中或通过 URL 参数传递。服务器使用此标识来跟踪特定用户的会话。

如果我们能创建具有特权用户的 Session ,则可以执行后台危险操作,比如执行命令、上传文件等操作。

在本例中,Apache Superset 所使用的 Flask 是一个微型的 Python Web 框架,在许多项目都有应用,比如 Flask-Chat、Flask-Blog、Flask-Admin 等,如果没有更改默认的 Flask 配置,则可能造成潜在的漏洞:

攻击者首先发送请求登录的包,将回显包的 Flask Session 复制下来,并通过 session.decode 方法解码 Session 中的 Session Data。其次通过 session.verify 方法来校验 Flask Session 是否采用了默认密钥。如果为默认密钥则使用 session.sign 方法将密钥和解码后的 Session Data 生成对应的 Flask Session,从而达到权限绕过的目的。

因此要使用该权限绕过漏洞,需要了解 Flask Session的组成结构:

Flask Session 的组成结构主要由三部分构成,第一部分为 Session Data ,即会话数据。第二部分为 Timestamp ,即时间戳。第三部分为 Cryptographic Hash ,即加密哈希。只有符合 Flask Session的组成结构的 Session 才会被 Flask 正确解析。

0x03 Flask Session

由于 Go 语言中目前没有现成的 Flask 框架 Session 生成机制,所以需要用 Go 复刻 Flask Session 的核心代码以作为最终的通用解决方案。

3.1session.decode

try:
        decoded = session.decode(session_cookie)
        print(f'Decoded session cookie: {decoded}')
    except:
        print('Error: Not a Flask session cookie')
        return

在 Python 代码中, session.decode 方法的作用为检验传入的 Session 值是否为 Flask Session。因此,在 Go 语言中实现同等逻辑即可,具体需要下断点调试,从源码的角度查看 session.decode 的执行流程。

session.verify 方法源码如下:

def decode(value: str) -> dict:
    try:
        compressed = False
        payload = value
        if payload.startswith('.'):
            compressed = True
            payload = payload[1:]
        data = payload.split(".")[0]
        data = base64_decode(data)
        if compressed:
            data = zlib.decompress(data)
        data = data.decode("utf-8")
    except Exception as e:
        raise DecodeError(
            f'Failed to decode cookie, are you sure '
            f'this was a Flask session cookie? {e}')
    def hook(obj):
        if len(obj) != 1:
            return obj
        key, val = next(iter(obj.items()))
        if key == ' t':
            return tuple(val)
        elif key == ' u':
            return UUID(val)
        elif key == ' b':
            return b64decode(val)
        elif key == ' m':
            return Markup(val)
        elif key == ' d':
            return parse_date(val)
        return obj
    try:
        return json.loads(data, object_hook=hook)
    except json.JSONDecodeError as e:
        raise DecodeError(
            f'Failed to decode cookie, are you sure '
            f'this was a Flask session cookie? {e}')

session.decode 方法在接收到传入的 Flask Session 后,首先以.号开头来判断是否为压缩的 Flask Session,如果为压缩的 Flask Session 则将开头的号去除,接着将 Flask Session 以.号进行分割来获取 Session Data ,最后进行 base64 解码。如果成功解码则说明目标使用的是 Flask Session 。

3.2 session.verify

for i, key in enumerate(SECRET_KEYS):
    cracked = session.verify(session_cookie, key)
    if cracked:
        break

在 Python 代码中,session.verify 方法的作用为根据密钥去验证 Flask Session 的正确性,如果验证成功则说明是正确的密钥。因此,在 Go 语言中实现同等逻辑即可,具体需要下断点调试,从源码的角度查看 session.verify 的执行流程。

session.verify 方法的组成如图所示:

get_serializer(secret, legacy, salt):

def get_serializer(secret: str, legacy: bool, salt: str) -> URLSafeTimedSerializer:
    if legacy:
        signer = LegacyTimestampSigner
    else:
        signer = TimestampSigner
    return URLSafeTimedSerializer(
        secret_key=secret,
        salt=salt,
        serializer=TaggedJSONSerializer(),
        signer=signer,
        signer_kwargs={
            'key_derivation': 'hmac',
            'digest_method': hashlib.sha1})

get_serializer 方法的作用为返回一个 URLSafeTimedSerializer 的构造方法,其中的 secret 和 salt 参数是由session.verify(session_cookie, key) 传入的 。

在 signer_kwargs 参数中包含了两个键值对(key-value pair):

  1. 'key_derivation': 'hmac':这是字典中的第一个键值对。它将键 key_derivation 映射到值 hmac。这表示在某个上下文中,使用 HMAC(Hash-based Message Authentication Code)作为密钥派生方法。
  2. 'digest_method': hashlib.sha1:这是字典中的第二个键值对。它将键 digest_method 映射到值 hashlib.sha1。这表示在某个上下文中,使用 SHA-1 哈希算法作为摘要方法。

因此 Flask Session 使用 HMAC 算法作为密钥派生方法,使用 SHA-1 哈希算法作为摘要方法。

loads(value) 方法使用了 signer.unsign 方法,其中参数 s 为传入的 session 值:

跟进调试 signer.unsign(s, max_age=max_age, return_timestamp=True) 发现调用了 super().unsign(signed_value) ,此时 signed_value 的值为 Flask Session :

进入 super().unsign 方法调试可以发现 sig 变量为 Flask Session 组成结构中的 Cryptographic Hash ,value 变量为 Flask Session 组成结构中的 Session Data + "." + Timestamp :

至此,我们成功得知 Flask Session 结构中的 Session Data 和 Timestamp 是如何划分的,接下来我们需要得知 Flask Session 是如何进行校验的,重点在 self.verify_signature(value,sig) 方法上:

进入 self.verify_signature(value,sig) 方法进行调试:

def verify_signature(self, value: _t_str_bytes, sig: _t_str_bytes) -> bool:
    """Verifies the signature for the given value."""
    try:
        sig = base64_decode(sig)
    except Exception:
        return False
    value = want_bytes(value)
    for secret_key in reversed(self.secret_keys):
        key = self.derive_key(secret_key)
        if self.algorithm.verify_signature(key, value, sig):
            return True
    return False

主要分为两部分:

1. self.derive_key()
2. self.algorithm.get_signature(key, value)
  • 第一部分为 self.derive_key() 方法:
  • 前面分析 get_serializer(secret, legacy, salt) 得知 Flask Session 使用的是 HMAC 算法,所以 self.derive_key() 方法的作用为使用 secret_key 创建一个 HMAC 对象,使用 sha1 作为摘要算法,接着向 HMAC 对象中添加 self.salt 数据,在 Flask 中默认为 cookie-session ,最后返回 HMAC 对象的摘要,即 mac.digest() 的结果,也就是第二部分 self.algorithm.get_signature(key, value) 方法中的参数 key 。
  • 第二部分为 self.algorithm.get_signature(key, value) 方法:
def verify_signature(self, key: bytes, value: bytes, sig: bytes) -> bool:
        """Verifies the given signature matches the expected
        signature.
        """
        return hmac.compare_digest(sig, self.get_signature(key, value))

此时的 hmac.compare_digest(sig, self.get_signature(key,value)) 使用了 self.get_signature(key,value) 方法:

该方法的作用为使用给定的密钥和消息数据来生成一个 HMAC 签名,以帮助确保数据的完整性和真实性。此时的密钥为第一部分 self.derive_key() 方法的执行结果,消息数据为 Flask Session 组成结构中的 Session Data + "." + Timestamp 。最后将生成的 HMAC 签名和第一部分 self.derive_key() 方法生成的 key 使用 hmac.compare_digest 进行比较,当两个值完全相同时该密钥则为正确密钥。

3.3 session.sign

forged_cookie = session.sign({'_user_id': 1, 'user_id': 1}, key)

在 Python 代码中,session.sign 方法的作用为根据密钥和 Session Data 生成 Flask Session。因此,在 Go 语言中实现同等逻辑即可,具体需要下断点调试,从源码的角度查看 session.sign 的执行流程。

session.sign 方法的组成如图所示:

get_serializer 方法在实现 session.verify 方法中已分析。因此,主要查看 dumps(value) 方法,可以得知主要调用了 self.mak_signer(salt).sign(payload) ,而此时 payload 已经被 base64 编码:

这里将分两部分进行解析:

  • 第一部分 make_signer:

make_signer 为构造方法,依据传入的参数对 self.signer 进行初始化赋值。

  • 第二部分 .sign(payload) :

{'_user_id': 1, 'user_id': 1}已经在 dumps(value) 方法中被 base64 编码了,即 Flask Session 组成结构中的 Session Data,而 timestamp 为当前时间戳的 base64 编码。

此时将 Session Data + "." + Timestamp 作为 self.get_signature 方法的参数( self.get_signature 方法在实现 session.verify 方法中已解析),生成为 Flask Session 组成结构中的 Cryptographic Hash:

最后将 Flask Session 组成结构中的 Session Data、Timestamp、Cryptographic Hash以.号进行拼接为最终的 Flask Session 。

综上,我们成功分析完 Flask Session 的校验、生成过程,接下来以 Apache Superset 权限绕过漏洞(CVE-2023-27524)为例,实现效果如下:

0x04 总结

在白帽汇安全研究院,漏洞检测和利用是一项创造性的工作,我们致力于以最简洁,高效的方式来实现。为了在 Goby 中实现 Flask Session 生成和利用方法,我们花费大量精力去调试 Flask 源码,分析 Session 的构造过程。最终,我们成功在 Go 语言中实现了 Flask Session 校验和生成方法。为了验证方法的可靠性,我们以 Apache Superset 权限绕过漏洞(CVE-2023-27524)为例,在 Goby 上实现了漏洞攻击效果,并加入了一键获取数据库凭据,一键反弹 shell 的利用方式。

sessionhmac
本作品采用《CC 协议》,转载必须注明作者和本文链接
跨语言移植一直是技术领域内难以解决的问题,需要解决语言之间的约束,好在先前我们成功使用 Go 实现了 IIOP 协议通信,有了前车之鉴,所以这次我们将继续使用跨语言方式实现 Flask Session 伪造。本文以 Apache Superset 权限绕过漏洞(CVE-2023-27524) 为例讲述我们是如何在 Go 中实现 Flask 框架的 Session 验证、生成功能的。
今天分享一篇文章,涉及Redis未授权、SSRF漏洞、宽字节注入、JSONP劫持、CORS、CRLF注入等技能,并详细讲述了其原理和漏洞利用等。能够回连且权限够的话,写crontab利用计划任务执行命令反弹shell?图片加载与下载:通过URL地址加载或下载图片?禁用不需要的协议。禁止30x跳转Java和PHP的SSRF区别PHP支持的协议:
了解NTLM Realy 攻击
2023-11-17 14:52:26
详解Ntlm Relay
浅析JWT安全问题
2022-08-08 15:44:18
观察确定为JWT,将payload处字符base64解码得??把sub的wiener修改为administrator,重新传参??成功越权,然后就是删除用户即可?然后前往jwt.io生成我们需要的的jwt,把sub和secret进行修改??重新传包,成功?
NTLM是一种网络认证协议,与NTLM Hash的关系就是:NTLM网络认证协议是以NTLM Hash作为根本凭证进行认证的协议。其中也提到了NTLM协议是一种不安全的认证模式。同时将之前生成的随机数Challenge等信息发送给客户端。客户端接受到Challenge后,使用将要登录到账户对应的NTLM Hash加密Challenge生成Response,然后将Response等信息发送至服务器端。
S7 PLCs漏洞分析
2022-12-01 10:57:34
近期关注了文章“Vulnerability analysis of S7 PLCs:Manipulating the security mechanism”,文章对Siemens PLC环境进行了深入分析。
客户端通过服务端的公钥对私钥加密传给服务端的时候,服务端如何辨别不是黑客使用服务端公钥加密的自己的私钥?
写这个是因为考虑到在渗透过程中,对目标机器上的第三方软件的信息收集很大程度决定后续能不能横向移动,内网密码搜集的越多,横向渗透也就越方便,这里将列举常见的软件,远程控制,浏览器,常见数据库相关软件和系统的凭证获取方式。 而有时候因为环境特别严苛,在线解密的工具用不了的时候离线就显得特别重要,做个记录,也当了解一下各个软件之间不同的认证方法,可能有些许理解错误,看官们轻锤。
域渗透就是基于windows域环境的渗透,而域渗透涉及到的技术,如哈希传递(PTH)票据传递(PTT)委派攻击等,都是基于域环境下的认证机制来实现的,这也是为什么要了解Windows认证机制的原因之一Windows的认证包括三个部分,用户直接操作计算机登陆账户(本地认证),远程连接到工作组中的某个设备(网络认证),登陆到域环境中的某个设备(域认证)
一颗小胡椒
暂无描述