Рассол Python - как он ломается? - PullRequest
12 голосов
/ 09 ноября 2010

Все знают, что рассол не является безопасным способом хранения пользовательских данных.Это даже говорит об этом на коробке.

Я ищу примеры строк или структур данных, которые нарушают синтаксический анализ в текущих поддерживаемых версиях cPython >= 2.4.Есть ли вещи, которые можно мариновать, но не мариновать?Есть ли проблемы с конкретными символами Юникода?Действительно большие структуры данных?Очевидно, что у старого протокола ASCII есть некоторые проблемы, но как насчет самой современной двоичной формы?

Мне особенно любопытно, каким образом операция pickle loads может завершиться неудачей, особенно когда передается строка, созданная pickleсам.Существуют ли обстоятельства, при которых рассол будет продолжать разбирать после .?

Какие существуют крайние случаи?

Редактировать: Вот несколько примеровВот что я ищу:

  • В Python 2.4 вы можете выбрать массив без ошибок, но вы не можете его удалить.http://bugs.python.org/issue1281383
  • Вы не можете надежно выбирать объекты, которые наследуются от dict и вызывать __setitem__ до того, как переменные экземпляра будут установлены с помощью __setstate__.Это может быть затруднительно при выборе объектов Cookie.См. http://bugs.python.org/issue964868 и http://bugs.python.org/issue826897
  • Python 2.4 (и 2.5?) Вернет значение pickle для бесконечности (или близкие к нему значения, например 1e100000), но может (в зависимости от платформы) произойти сбой, когдапогрузка.См. http://bugs.python.org/issue880990 и http://bugs.python.org/issue445484
  • Этот последний элемент интересен тем, что показывает случай, когда маркер STOP фактически не прекращает синтаксический анализ - когда маркер существует как часть литерала, илив более общем случае, когда ему не предшествует символ новой строки.

Ответы [ 3 ]

6 голосов
/ 09 ноября 2010

Это очень упрощенный пример того, что Pickle не понравилось в моей структуре данных.

import cPickle as pickle

class Member(object):
    def __init__(self, key):
        self.key = key
        self.pool = None
    def __hash__(self):
        return self.key

class Pool(object):
    def __init__(self):
        self.members = set()
    def add_member(self, member):
        self.members.add(member)
        member.pool = self

member = Member(1)
pool = Pool()
pool.add_member(member)

with open("test.pkl", "w") as f:
    pickle.dump(member, f, pickle.HIGHEST_PROTOCOL)

with open("test.pkl", "r") as f:
    x = pickle.load(f)

Pickle, как известно, немного смешно с круговыми структурами, но если вы добавляете в микс пользовательские хэш-функции и задаете / диктуете, то все становится довольно странно.

В этом конкретном примере он частично раскрывает член и затем встречается с пулом. Таким образом, он частично распаковывает пул и встречает набор членов. Таким образом, он создает набор и пытается добавить частично не выбранный член в набор. В этот момент он умирает в пользовательской хэш-функции, потому что элемент не выбран частично. Страшно подумать, что могло бы произойти, если бы у вас было «if hasattr ...» в хэш-функции.

$ python --version
Python 2.6.5
$ python test.py
Traceback (most recent call last):
  File "test.py", line 25, in <module>
    x = pickle.load(f)
  File "test.py", line 8, in __hash__
    return self.key
AttributeError: ("'Member' object has no attribute 'key'", <type 'set'>, ([<__main__.Member object at 0xb76cdaac>],))
2 голосов
/ 22 июля 2014

Если вас интересует, как что-то не так с pickle (или cPickle, поскольку это просто немного другой импорт), вы можете использовать этот растущий список всех различных типов объектов в python, чтобы проверить его довольно легко.

https://github.com/uqfoundation/dill/blob/master/dill/_objects.py

Пакет dill включает в себя функции, которые обнаруживают, как объект не может выполнить выборку, например, перехватывая ошибку, которую он выдает, и возвращая ее пользователю.

dill.dill имеет следующие функции, которые вы также можете построить для pickle или cPickle, просто с помощью функции вырезать и вставить и import pickle или import cPickle as pickle (или import dill as pickle):

def copy(obj, *args, **kwds):
    """use pickling to 'copy' an object"""
    return loads(dumps(obj, *args, **kwds))


# quick sanity checking
def pickles(obj,exact=False,safe=False,**kwds):
    """quick check if object pickles with dill"""
    if safe: exceptions = (Exception,) # RuntimeError, ValueError
    else:
        exceptions = (TypeError, AssertionError, PicklingError, UnpicklingError)
    try:
        pik = copy(obj, **kwds)
        try:
            result = bool(pik.all() == obj.all())
        except AttributeError:
            result = pik == obj
        if result: return True
        if not exact:
            return type(pik) == type(obj)
        return False
    except exceptions:
        return False

и включает их в dill.detect:

def baditems(obj, exact=False, safe=False): #XXX: obj=globals() ?
    """get items in object that fail to pickle"""
    if not hasattr(obj,'__iter__'): # is not iterable
        return [j for j in (badobjects(obj,0,exact,safe),) if j is not None]
    obj = obj.values() if getattr(obj,'values',None) else obj
    _obj = [] # can't use a set, as items may be unhashable
    [_obj.append(badobjects(i,0,exact,safe)) for i in obj if i not in _obj]
    return [j for j in _obj if j is not None]


def badobjects(obj, depth=0, exact=False, safe=False):
    """get objects that fail to pickle"""
    if not depth:
        if pickles(obj,exact,safe): return None
        return obj
    return dict(((attr, badobjects(getattr(obj,attr),depth-1,exact,safe)) \
           for attr in dir(obj) if not pickles(getattr(obj,attr),exact,safe)))

def badtypes(obj, depth=0, exact=False, safe=False):
    """get types for objects that fail to pickle"""
    if not depth:
        if pickles(obj,exact,safe): return None
        return type(obj)
    return dict(((attr, badtypes(getattr(obj,attr),depth-1,exact,safe)) \
           for attr in dir(obj) if not pickles(getattr(obj,attr),exact,safe)))

и эта последняя функция, которую вы можете использовать для проверки объектов в dill._objects

def errors(obj, depth=0, exact=False, safe=False):
    """get errors for objects that fail to pickle"""
    if not depth:
        try:
            pik = copy(obj)
            if exact:
                assert pik == obj, \
                    "Unpickling produces %s instead of %s" % (pik,obj)
            assert type(pik) == type(obj), \
                "Unpickling produces %s instead of %s" % (type(pik),type(obj))
            return None
        except Exception:
            import sys
            return sys.exc_info()[1]
    return dict(((attr, errors(getattr(obj,attr),depth-1,exact,safe)) \
           for attr in dir(obj) if not pickles(getattr(obj,attr),exact,safe)))
0 голосов
/ 09 ноября 2010

Возможна выборка экземпляров классов.Если бы я знал, какие классы использует ваше приложение, то я мог бы подорвать их.Придуманный пример:

import subprocess

class Command(object):
    def __init__(self, command):
        self._command = self._sanitize(command)

    @staticmethod
    def _sanitize(command):
        return filter(lambda c: c in string.letters, command)

    def run(self):
        subprocess.call('/usr/lib/myprog/%s' % self._command, shell=True)

Теперь, если ваша программа создает Command экземпляров и сохраняет их, используя pickle, и я могу подорвать или вставить в это хранилище, тогда я могу запустить любую команду, которую выберу, установив self._command напрямую.

На практике мой пример никогда не должен проходить для безопасного кода.Но обратите внимание, что если функция sanitize безопасна, то и весь класс - за исключением возможного использования маринадов из ненадежных данных, нарушающих это.Следовательно, существуют программы, которые являются безопасными, но могут стать небезопасными из-за неправильного использования pickle.

Опасность заключается в том, что ваш код, использующий рассол, может быть подорван по тому же принципу, но в невинно выглядящем коде, гдеуязвимость гораздо менее очевидна.Лучше всего всегда избегать использования pickle для загрузки ненадежных данных.

...