skip to Main Content
你的Python高效学习之道 025-5987-6503 contact@lessoncode.com

使用Python新式字符串格式

核心问题

从Python 2.6开始,一种新的格式字符串语法由.NET启发,这也是Rust和其他一些编程语言支持的相同语法。 它可以在.format()方法后面的字节和unicode字符串(在Python 3上的unicode字符串上),它也镜像在更可自定义的string.Formatter API中。

其中一个特点是您可以同时处理字符串格式的位置和关键字参数,您可以随时显式重新排列项目。 但是更大的特点是您可以访问对象的属性和项目。 后者是导致这个问题的原因。

基本上可以做以下事情:

>>> ‘class of {0} is {0.__class__}’.format(42)
“class of 42 is <class ‘int’>”

实质上:控制格式字符串的人可以访问对象的潜在内部属性。

哪里发生了?

第一个问题是为什么有人控制格式字符串。 有几个地方出现:

  • 字符串文件中不受信任的翻译。 这是一个很大的因为许多应用程序被翻译成多种语言将使用新式的Python字符串格式化,并不是每个人都会检查所有的字符串进来。
  • 用户暴露配置。 一些系统用户可能被允许配置一些行为,并且可能会以格式字符串的形式显示。 特别是我已经看到用户可以在Web应用程序中配置通知邮件,日志消息格式或其他基本模板。

危险等级

只要只有C解释器对象被传递到格式字符串,你有点安全,因为你可以发现的最糟糕的是一些内部reprs,就像上面是一个整数类的事实。

但是,当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.
“””

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()

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)

现在,您可以使用safe_format方法作为str.format的替代:

>>> ‘{0.__class__}’.format(42)
“<type ‘int’>”
>>> safe_format(‘{0.__class__}’, 42)
Traceback (most recent call last):
File “”, line 1, in
AttributeError: __class__

发表评论

电子邮件地址不会被公开。 必填项已用*标注

Back To Top