天天看點

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 <class 'int'>" 

實質上,任何能夠控制格式字元串的人都有可能通路對象的各種内部屬性。

問題出在哪裡?

第一個問題是,如何控制格式字元串。可以從下列地方下手:

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

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: 

                objobj = 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 "<stdin>", line 1, in <module> 

attributeerror: __class__ 

小結

在本文中,我們對python引入的一種格式化字元串的新型文法的安全漏洞進行了深入的分析,并提供了相應的安全解決方案,希望對讀者能夠有所幫助。

作者:shan66

來源:51cto