flask session 安全问题 和 python 格式化字符串漏洞

admin 2023年2月16日23:30:19评论50 views字数 7499阅读24分59秒阅读模式



flask session 安全问题 和 python 格式化字符串漏洞



前言

ctf题中遇到了伪造session和python的格式化字符串漏洞
这里做个小结

1、flask session 安全问题

flask 是非常轻量级的 Web框架
其 session 存储在客户端中(可以通过HTTP请求头Cookie字段的session获取)

1、flask对session的防护

flask对session的防护如下

  • 新建了URLSafeTimedSerializer类 ,用它的dumps方法将类型为字典的session对象序列化成字符串,然后用response.set_cookie将最后的内容保存在cookie

  • json.dumps 将对象转换成json字符串,作为数据

  • 如果数据压缩后长度更短,则用zlib库进行压缩

  • 将数据用base64编码

  • 通过hmac算法计算数据的签名,将签名附在数据后,用“.”分割

这就解决了用户篡改session的问题,在不知道secret_key的情况下,是无法伪造签名的


class SecureCookieSessionInterface(SessionInterface):    """The default session interface that stores sessions in signed cookies    through the :mod:`itsdangerous` module.    """    #: the salt that should be applied on top of the secret key for the    #: signing of cookie based sessions.    salt = 'cookie-session'    #: the hash function to use for the signature. The default is sha1    digest_method = staticmethod(hashlib.sha1)    #: the name of the itsdangerous supported key derivation. The default    #: is hmac.    key_derivation = 'hmac'    #: A python serializer for the payload. The default is a compact    #: JSON derived serializer with support for some extra Python types    #: such as datetime objects or tuples.    serializer = session_json_serializer    session_class = SecureCookieSession
def get_signing_serializer(self, app): if not app.secret_key: return None signer_kwargs = dict( key_derivation=self.key_derivation, digest_method=self.digest_method ) return URLSafeTimedSerializer(app.secret_key, salt=self.salt, serializer=self.serializer, signer_kwargs=signer_kwargs)
def open_session(self, app, request): s = self.get_signing_serializer(app) if s is None: return None val = request.cookies.get(app.session_cookie_name) if not val: return self.session_class() max_age = total_seconds(app.permanent_session_lifetime) try: data = s.loads(val, max_age=max_age) return self.session_class(data) except BadSignature: return self.session_class()
def save_session(self, app, session, response): domain = self.get_cookie_domain(app) path = self.get_cookie_path(app) # Delete case. If there is no session we bail early. # If the session was modified to be empty we remove the # whole cookie. if not session: if session.modified: response.delete_cookie(app.session_cookie_name, domain=domain, path=path) return # Modification case. There are upsides and downsides to # emitting a set-cookie header each request. The behavior # is controlled by the :meth:`should_set_cookie` method # which performs a quick check to figure out if the cookie # should be set or not. This is controlled by the # SESSION_REFRESH_EACH_REQUEST config flag as well as # the permanent flag on the session itself. if not self.should_set_cookie(app, session): return httponly = self.get_cookie_httponly(app) secure = self.get_cookie_secure(app) expires = self.get_expiration_time(app, session) val = self.get_signing_serializer(app).dumps(dict(session)) response.set_cookie(app.session_cookie_name, val, expires=expires, httponly=httponly, domain=domain, path=path, secure=secure)
class Signer(object): # ... def sign(self, value): """Signs the given string.""" return value + want_bytes(self.sep) + self.get_signature(value)
def get_signature(self, value): """Returns the signature for the given value""" value = want_bytes(value) key = self.derive_key() sig = self.algorithm.get_signature(key, value) return base64_encode(sig)

class Serializer(object): default_serializer = json default_signer = Signer # .... def dumps(self, obj, salt=None): """Returns a signed string serialized with the internal serializer. The return value can be either a byte or unicode string depending on the format of the internal serializer. """ payload = want_bytes(self.dump_payload(obj)) rv = self.make_signer(salt).sign(payload) if self.is_text_serializer: rv = rv.decode('utf-8') return rv
def dump_payload(self, obj): """Dumps the encoded object. The return value is always a bytestring. If the internal serializer is text based the value will automatically be encoded to utf-8. """ return want_bytes(self.serializer.dumps(obj))

class URLSafeSerializerMixin(object): """Mixed in with a regular serializer it will attempt to zlib compress the string to make it shorter if necessary. It will also base64 encode the string so that it can safely be placed in a URL. """ def load_payload(self, payload): decompress = False if payload.startswith(b'.'): payload = payload[1:] decompress = True try: json = base64_decode(payload) except Exception as e: raise BadPayload('Could not base64 decode the payload because of ' 'an exception', original_error=e) if decompress: try: json = zlib.decompress(json) except Exception as e: raise BadPayload('Could not zlib decompress the payload before ' 'decoding the payload', original_error=e) return super(URLSafeSerializerMixin, self).load_payload(json)
def dump_payload(self, obj): json = super(URLSafeSerializerMixin, self).dump_payload(obj) is_compressed = False compressed = zlib.compress(json) if len(compressed) < (len(json) - 1): json = compressed is_compressed = True base64d = base64_encode(json) if is_compressed: base64d = b'.' + base64d return base64d

class URLSafeTimedSerializer(URLSafeSerializerMixin, TimedSerializer): """Works like :class:`TimedSerializer` but dumps and loads into a URL safe string consisting of the upper and lowercase character of the alphabet as well as ``'_'``, ``'-'`` and ``'.'``. """    default_serializer = compact_json

2、安全问题

但问题也来了
flask仅对 session 进行了签名,缺少数据防篡改实现,这便很容易存在安全漏洞

假设现在我们有一串 session 值为: eyJ1c2VyX2lkIjo2fQ.XA3a4A.R-ReVnWT8pkpFqM_52MabkZYIkY
那么我们可以通过如下代码对其进行解密:

from itsdangerous import *s = "eyJ1c2VyX2lkIjo2fQ.XA3a4A.R-ReVnWT8pkpFqM_52MabkZYIkY"data,timestamp,secret = s.split('.')int.from_bytes(base64_decode(timestamp),byteorder='big')


P师傅的对session的解密脚本

#!/usr/bin/env python3import sysimport zlibfrom base64 import b64decodefrom flask.sessions import session_json_serializerfrom 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()))

可能危害:

  • 敏感信息泄露

  • 验证码绕过漏洞

  • session伪造及对象注入漏洞

作为一个开发者,如果我们使用的web框架或web语言的session是存储在客户端中,那就必须牢记下面几点:

  • 没有加密时,用户可以看到完整的session对象

  • 加密/签名不完善或密钥泄露的情况下,用户可以修改任意session

  • 使用强健的加密及签名算法,而不是自己造(反例discuz)

2、python的格式化字符串漏洞

在 python 中,提供了 4种 主要的格式化字符串方式,分别如下:

1、%操作符

%操作符沿袭C语言中printf语句的风格

>>> name = 'Bob'>>> 'Hello, %s' % name"Hello, Bob"

2、string.Template

使用标准库中的模板字符串类进行字符串格式化

>>> name = 'Bob'>>> from string import Template>>> t = Template('Hey, $name!')>>> t.substitute(name=name)'Hey, Bob!'

3、调用format方法

python3后引入的新版格式化字符串写法,但是这种写法存在安全隐患


#直接格式化字符串>>> 'My name is {}'.format('Hu3sky')'My name is Hu3sky'#指定位置>>> 'Hello {0} {1}'.format('World','Hacker')'Hello World Hacker'>>> 'Hello {1} {0}'.format('World','Hacker')'Hello Hacker World'#设置参数>>> 'Hello {name} {age}'.format(name='Hacker',age='17')'Hello Hacker 17'#百分比格式>>> 'We have {:.2%}'.format(0.25)'We have 25.00%'#获取数组的键值>>> '{arr[2]}'.format(arr=[1,2,3,4,5])'3'

存在安全隐患的事例代码:


>>> config = {'SECRET_KEY': '12345'}>>> class User(object):...   def __init__(self, name):...     self.name = name...>>> user = User('joe')>>> '{0.__class__.__init__.__globals__[config]}'.format(user)"{'SECRET_KEY': '12345'}"

从上面的例子中,我们可以发现:如果用来格式化的字符串可以被控制,攻击者就可以通过注入特殊变量,带出敏感数据

主要语句user.__class__.__init__.__globals__

4、f-Strings

这是python3.6之后新增的一种格式化字符串方式,其功能十分强大
可以执行字符串中包含的python表达式,安全隐患可想而知

>>> a , b = 5 , 10>>> f'Five plus ten is {a + b} and not {2 * (a + b)}.''Five plus ten is 15 and not 30.'>>> f'{__import__("os").system("id")}'uid=0(root) gid=0(root) groups=0(root)'0'


结语

这俩通常会一起出现
然后通过格式化字符串漏洞获取secret_key
再用secret_key伪造session
从而获取admin权限

参考

  • 客户端 session 导致的安全问题

  • Python 格式化字符串漏洞(Django为例)

  • 从两道CTF实例看python格式化字符串漏洞

  • Python Web之flask session&格式化字符串漏洞





红客突击队于2019年由队长k龙牵头,联合国内多位顶尖高校研究生成立。其团队从成立至今多次参加国际网络安全竞赛并取得良好成绩,积累了丰富的竞赛经验。团队现有三十多位正式成员及若干预备人员,下属联合分队数支。红客突击队始终秉承先做人后技术的宗旨,旨在打造国际顶尖网络安全团队。


原文始发于微信公众号(红客突击队):flask session 安全问题 和 python 格式化字符串漏洞

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年2月16日23:30:19
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   flask session 安全问题 和 python 格式化字符串漏洞http://cn-sec.com/archives/1275727.html

发表评论

匿名网友 填写信息