最近一个python字符串名目化的裂痕引起了我的留意,本日就来说一下Python引入的一种名目化字符串的新型语法的安详裂痕举办了深入的阐明,并提供了相应的安详办理方案。
当我们对不行信的用户输入利用str.format的时候,将会带来安详隐患——对付这个问题,其实我早就知道了,可是直到本日我才真正意识到它的严重性。因为进攻者可以操作它来绕过Jinja2沙盒,这会造成严重的信息泄露问题。同时,我在本文最后部门为str.format提供了一个新的安详版本。
需要提醒的是,这是一个相当严重的安详隐患,这里之所以撰文先容,是因为大大都人很大概不知道它是何等容易被操作。
焦点问题
从Python 2.6开始,Python受.NET开导而引入了一种名目化字符串的新型语法。虽然,除了Python之外,Rust及其他一些编程语言也支持这种语法。借助于.format()要领,该语法可以应用到字节和unicode字符串(在Python 3中,只能用于unicode字符串)上面,另外,它还能映射为越发具有可定制性的string.Formatter API。
该语法的一个特点是,人们可以通过它确定出字符串名目标位置和要害字参数,而且随时可以显式对数据项从头排序。另外,它甚至可以会见工具的属性和数据项——这是导致这里的安详问题的基础原因。
总的来说,人们可以操作它来举办以下工作:
>>> 'class of {0} is {0.__class__}'.format(42) "class of 42 is "
实质上,任何可以或许节制名目字符串的人都有大概会见工具的各类内下属性。
问题出在那边?
第一个问题是,如何节制名目字符串。可以从下列处所下手:
1.字符串文件中不行信的翻译器。我们很大概通过它们到手,因为很多被翻译成多种语言的应用措施城市用到这种新式Python字符串名目化要领,可是并非所有人城市对输入的所有字符串举办全面的审查。
2.用户袒露的设置。 由于一些系统用户可以对某些行为举办设置,而这些设置有大概以名目字符串的形式被袒暴露来。需要出格提示的是,我就见过某些用户可以通过Web应用措施来设置通知邮件、日志动静名目或其他根基模板。
危险品级
假如只是向该名目字符勾串报C表明器工具的话,倒是不会有太大的危险,因为这样的话,你最多会袒露一些整数类之类的对象。
然而,一旦Python工具被通报给这种名目字符串的话,那就贫苦了。这是因为,可以或许从Python函数袒露的对象的数量是相当惊人的。 下面是假想的Web应用措施的景象,这种环境下可以或许泄露密钥:
CONFIG = { 'SECRET_KEY': 'super secret key' } class Event(object): def __init__(self, id, level, message): self.id = id self.level = level self.message = message def format_event(format_string, event): return format_string.format(event=event)
假如用户可以在这里注入format_string,那么他们就能发明下面这样的奥秘字符串:
{event.__init__.__globals__[CONFIG][SECRET_KEY]}
将名目化作沙箱化处理惩罚
那么,假如需要让其他人提供名目化字符串,那该怎么办呢? 其实,可以操作某些未果真的内部机制来改变字符串名目化行为。
from string import Formatter from collections import Mapping class MagicFormatMapping(Mapping): """This class implements a dummy wrapper to fix a bug in the Python standard library for string formatting. See http://bugs.python.org/issue13598 for information about why this is necessary. """ def __init__(self, args, kwargs): self._args = args self._kwargs = kwargs self._last_index = 0 def __getitem__(self, key): if key == '': idx = self._last_index self._last_index += 1 try: return self._args[idx] except LookupError: pass key = str(idx) return self._kwargs[key] def __iter__(self): return iter(self._kwargs) def __len__(self): return len(self._kwargs) # This is a necessary API but it's undocumented and moved around # between Python releases try: from _string import formatter_field_name_split except ImportError: formatter_field_name_split = lambda \ x: x._formatter_field_name_split() {C} class SafeFormatter(Formatter): def get_field(self, field_name, args, kwargs): first, rest = formatter_field_name_split(field_name) obj = self.get_value(first, args, kwargs) for is_attr, i in rest: if is_attr: obj = safe_getattr(obj, i) else: obj = obj[i] return obj, first def safe_getattr(obj, attr): # Expand the logic here. For instance on 2.x you will also need # to disallow func_globals, on 3.x you will also need to hide # things like cr_frame and others. So ideally have a list of # objects that are entirely unsafe to access. if attr[:1] == '_': raise AttributeError(attr) return getattr(obj, attr) def safe_format(_string, *args, **kwargs): formatter = SafeFormatter() kwargs = MagicFormatMapping(args, kwargs) return formatter.vformat(_string, args, kwargs)
#p#分页标题#e#
此刻,我们就可以利用safe_format要领来替代str.format了:
>>> '{0.__class__}'.format(42) "" >>> safe_format('{0.__class__}', 42) Traceback (most recent call last): File "", line 1, in AttributeError: __class__
总结:
措施开拓中有这么一句话:任何时候不要相信用户的输入!此刻看来这句话说得很是有原理。所以列位同学要服膺!