В первой части статьи мы начали разбирать тему безопасной разработки на языке Python, поговорили о том, что нельзя допускать бесконтрольное выполнение команд, вводимых пользователем, также нельзя позволять вводить ключи к некоторым командам. Также, вспомнили про SQL инъекции и рассмотрели общие рекомендации по защите. Однако, в той или иной степени все приведенные в первой статье уязвимости свойственны и другим языкам программирования. В этой статье мы рассмотрим более свойственные именно для языка Python уязвимости. И начнем мы с такой интересной темы как десериализация.
Метод Pickle
Итак, рассмотрим, что такое сериализация и десериализация в Python. Сериализация это способ преобразования структуры данных в линейную форму, которую можно сохранить в файле или передать по сети. Обратный процесс преобразования сериализованного объекта в исходную структуру данных называется десериализацией. Прежде, чем начать говорить о недостатках данного метода мы разберем примеры того, как работает сериализация.
Для сериализации данных в Python используется метод Pickle. Это собственный формат сериализации объекта в Python. Интерфейс метода pickle содержит четыре метода: dump, dumps, load, и loads. Метод dump()
сериализует в открытый файл (файл-подобный объект). Метод dumps()
сериализует в строку. Метод load()
десериализует из открытого файлового объекта. Метод loads()
десериализует из строки.
Посмотрим на примере.
>>> import pickle
>>> data = {
... 'a': [1, 2.0, 3, 4+6j],
... 'b': ("character string", b"byte string"),
... 'c': {None, True, False}
... }
>>>
>>> with open('data.pickle', 'wb') as f:
... pickle.dump(data, f)
...
>>> with open('data.pickle', 'rb') as f:
... data_new = pickle.load(f)
...
>>> print(data_new)
Как видно в data_new мы получили набор данных data в сериализованном виде. Метод Pickle поддерживает по умолчанию текстовый протокол, но имеет также двоичный протокол, который более эффективен, но не удобен для чтения при отладке. Обычно при хранении данных используют как раз двоичный вариант Pickle.
Десериализация без проверок
Серьезным недостатком метода Pickle является то, что при выполнении методов load и loads не происходит проверка содержимого файла, который необходимо десериализовать. То есть существует возможность составить файл pickle таким образом, что в результате его десериализации злоумышленник сможет выполнить произвольный код с правами пользователя, запустившего данный скрипт.
Ниже приводится пример скрипта для Python 2.0, который готовит специальный Pickle файл. Этот файл после десериализации запускает командную оболочку.
import cPickle
import subprocess
import base64
class Exploit(object):
def __reduce__(self):
fd = 20
return (subprocess.Popen,
(('/bin/sh',), # args
0, # bufsize
None, # executable
fd, fd, fd # std{in,out,err}
))
print base64.b64encode(cPickle.dumps(Exploit()))
Как видно, в классе Exploit используется уже знакомая нам команда Popen, которая собственно и запускает шелл. Метод Pickle дампит результаты работы Exploit, и затем двоичный дамп кодируется Base64.
Таким образом, совершенно очевидно, что метод Pickle не является безопасным. У этого метода отсутствует контроль за целостностью данных и объектов, отсутствует контроль над размером данных или системных ограничений, код оценивается без мер безопасности, а строки кодируются и что еще важнее декодируются без проверки. И собственно, официальная документация по Pickle говорит нам следующее:
Warning: The pickle module is not intended to be secure against erroneous or maliciously constructed data. Never unpickle data received from an untrusted or unauthenticated source.
Рекомендации по безопасности
При десериализации из ненадежного источника, например если вы получаете сериализованный файл от пользователя никогда не используйте метод Pickle. Альтернативой может стать более безопасный метод JSON. Никогда не доверяйте данным, передаваемым пользователями. Никогда не производите десериализацию данных из ненадёжного источника с помощью pickle.
И немного про XML
Текстовый формат хранения данных XML получил широкое распространение. И практически в каждом языке есть специальные библиотеки для парсинга документов данного формата. Не является исключением и Python. При парсинге документов XML в Python обычно используют стандартную библиотеку XML. Однако, стандартная библиотека уязвима к некоторым DoS атакам. В частности, злоумышленник может отправить на разбор XML бомбу, которая может вызвать отказ в работе скрипта Python. Простейшая XML бомба имеет следующую структуру:
Здесь мы сначала формируем вхождение e содержащее длинную текстовую строку, а затем внутри тэга <bomb> многократно повторяем данное вхождение.
На этом принципе построена атака Billion Laughs. В ней мы сначала формируем вхождения по принципу прогрессии, когда каждое следующее вхождение состоит из предыдущих (lol2 из lol, lol3 из lol2 и так далее). Таким образом, когда мы публикуем lol9, результирующий тег будет иметь очень большой размер, в результате чего возможен отказ в обслуживании.
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ELEMENT lolz (#PCDATA)>
<!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>
И не только бомбы
Помимо XML при обработке XML в Python можно также столкнуться с другими видами атак. Например, возможны варианты, когда в XML документе будут выполнены обращения к посторонним веб ресурсам, как в примере ниже. Или, что еще хуже, возможен вывод на экран какого-либо текстового файла.
<!DOCTYPE external [
<!ENTITY ee SYSTEM "http://www.python.org/some.xml">
]>
<root>ⅇ</root>
<!DOCTYPE external [
<!ENTITY ee SYSTEM "file:///PATH/TO/simple.xml">
]>
<root>ⅇ</root>
Безопасный XML
Для борьбы с такими атаками через XML файлы рекомендуется использовать специализированные библиотеки, например xml.etree.ElementTree
или defusedxml.ElementTree
.
Работать с этими библиотеками довольно просто:
>>> from xml.etree.ElementTree import parse
>>> et = parse(xmlfile)
>>> from defusedxml.ElementTree import parse
>>> et = parse(xmlfile)
Заключение
В этой статье мы подробно рассмотрели вопросы, связанные с уязвимостями сериализации в Python, а также, рассмотрели все, что связано с безопасной и не очень обработкой XML. В следующей статье мы поговорим о проблемах безопасной эксплуатации кода, написанного на Python.
В заключение рекомендую начинающим разработчикам посетить открытый урок, посвященный важным алгоритмам Python. На этом занятии вы узнаете:
- что такое алгоритмы и зачем они нужны;
- разбор основных алгоритмов сортировки: пузырьковая, сортировка выбором, сортировка вставками, быстрая сортировка;
- разбор основных алгоритмов поиска: линейный поиск, бинарный поиск;
- разбор примеров рекурсивных алгоритмов: вычисление факториала, чисел Фибоначчи;
- примеры использования алгоритмов: сортировка списка, поиск элемента в массиве, нахождение наибольшего общего делителя.
Записаться на открытый урок можно по ссылке.