Создание правил
При создании правил Snort следует пользоваться приведенными ниже рекомендациями.
Проверка содержимого
Вторая версия машины детектирования Snort на первой фазе поиска выполняет проверку соответствия заданным шаблонам (pattern). Чем длиннее искомая последовательность, тем точнее будет результат поиска. Правила без опции content (или uricontent) замедляют работу системы в целом.
Хотя некоторые опции (например, pcre и byte_test) проверяют соответствие для полей данных пакета, эти опции не используют машину setwise pattern matching (“горизонтальный” поиск). Поэтому следует по возможности использовать вместо таких опций правило content.
Бороться с причиной, а не проявлением
Старайтесь писать правила так, чтобы они противодействовали использованию уязвимостей, а не конкретным эксплойтам.
Например, разумно искать уязвимые команды со слишком длинными аргументами, нежели shell-коды.
Правила для предотвращения использования уязвимостей менее уязвимы с точки зрения их обхода путем простого изменения кода эксплойта.
Учитывайте в правилах случаи нетипичного использования протоколов
Многие службы (например, FTP) обычно передают команды с использованием символов верхнего регистра. В случае FTP для передачи имени пользователя клиент будет посылать строку:
user username_here
Простое правило для детектирования попыток подключения к серверу FTP пользователя root может иметь вид:
alert tcp any any -> any any 21 (content:"user root";)
Такая задача может показаться тривиальной, однако хорошо написанное правило будет учитывать не только очевидное написание, но и множество (или все) возможные варианты. Например, для упомянутого случая строка может иметь вид:
user root
user root
userroot
Для того, чтобы предусмотреть в правиле все варианты строки, которые могут быть восприняты сервером, правило придется усложнить. Хороший пример реализации такого правила показан ниже:
alert tcp any any -> any 21 (flow:to_server,established; content:"root";
pcre:"/users+root/i";)
Для этого правила следует отметить несколько важных моментов:
-
правило включает опцию flow, проверяющую, что трафик относится к существующему соединению;
-
правило включает опцию content, которая используется для поиска слова root, являющегося самым длинным в данном случае идентификатором атаки; эта опция использует машину поиска setwise pattern match и ускоряет работу Snort;
-
правило включает опцию pcre, которая ищет слово user, сопровождаемое по крайней мере одним пробельным символом (включая символ табуляции), без учета регистра символов в слове root.
Оптимизация правил
Система поиска по содержимому (content matching) в некоторых случаях использует рекурсию, поэтому неаккуратно написанные правила могут приводить к тому, что программа Snort будет тратить ненужное время на дублирование проверок.
Рекурсия работает следующим образом – если заданный шаблон найден, но любое из условий проверки, расположенных после него, не выполняется, начинается новый поиск по шаблону. Повторение происходит до тех пор, пока соответствие не будет найдено снова или не закончится список опций.
На первый взгляд такой подход может показаться неразумным, однако он обусловлен необходимостью. Рассмотрим для примера правило:
alert ip any any -> any any (content:"a"; content:"b"; within:1;)
Программа будет искать в пакетах символ “a”', непосредственно за которым следует символ “b”. Без использования рекурсии строка “aab” даст отрицательный результат, хотя она и содержит символ “a” за которым непосредственно следует b. Отказ обусловлен тем, что за первым символом “a” не следует сразу же “b”.
Рекурсия важна для детектирования, но реализации механизма рекурсии недостаточно эффективна. Ниже приведен пример неоптимизированного правила:
content:"|13|"; dsize:1;
На первый взгляд может показаться, что это правило просто будет искать в пакете байт 0x13. Однако, по причине использования рекурсии для пакета, содержащего 1024 байта 0x13, будет выполняться 1023 попытки поиска этого байта и 1023 проверки dsize. Почему? Код 0x13 будет быть найден в первом байте, проверка dsize даст отрицательный результат и по причине использования рекурсии поиск кода 0x13 будет продолжен с того места, где было обнаружено предыдущее вхождение этого кода, с проверкой значения dsize, пока код 0x13 не будет найден снова.
Смена порядка опций, при которой опция с дискретной проверкой (в данном случае dsize) будет перемещена вперед, приведет к значительному ускорению работы правила Snort. Оптимизированное правило будет иметь вид:
dsize:1; content:"|13|";
Для пакета, содержащего 1024 байта 0x13 отрицательный результат будет получен незамедлительно, поскольку сначала проверяется дискретная опция dsize.
Перечисленные ниже опции являются дискретными и их следует размещать в начале правил:
-
dsize
-
flags
-
flow
-
fragbits
-
icmp_id
-
icmp_seq
-
icode
-
id
-
ipopts
-
ip_proto
-
itype
-
seq
-
session
-
tos
-
ttl
-
ack
-
window
-
resp
-
sameip
Проверка числовых значений
Опции byte_test и byte_jump предназначены для создания правил для протоколов, использующих данные указанного в пакете размера (прежде всего, для протокола RPC).
Для того, чтобы понять смысл использования byte_test и byte_jump рассмотрим попытку использования эксплойта для сервиса sadmind. Ниже показан дамп пакета для этого эксплойта:
89 09 9c e2 00 00 00 00 00 00 00 02 00 01 87 88 ................
00 00 00 0a 00 00 00 01 00 00 00 01 00 00 00 20 ...............
40 28 3a 10 00 00 00 0a 4d 45 54 41 53 50 4c 4f @(:.....metasplo
49 54 00 00 00 00 00 00 00 00 00 00 00 00 00 00 it..............
00 00 00 00 00 00 00 00 40 28 3a 14 00 07 45 df ........@(:...e.
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 06 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 04 ................
7f 00 00 01 00 01 87 88 00 00 00 0a 00 00 00 04 ................
7f 00 00 01 00 01 87 88 00 00 00 0a 00 00 00 11 ................
00 00 00 1e 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 3b 4d 45 54 41 53 50 4c 4f .......;metasplo
49 54 00 00 00 00 00 00 00 00 00 00 00 00 00 00 it..............
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 06 73 79 73 74 65 6d 00 00 ........system..
00 00 00 15 2e 2e 2f 2e 2e 2f 2e 2e 2f 2e 2e 2f ....../../../../
2e 2e 2f 62 69 6e 2f 73 68 00 00 00 00 00 04 1e ../bin/sh.......
Рассмотрим более подробно поля пакета и способ обнаружения эксплойта. Отметим сначала некоторую специфику RPC:
-
целые числа записываются в формате uint32s (4 байта); например, число 26 будет иметь вид 0x0000001a.
-
строка представляется значением uint32, задающим размер, собственно строкой и нулевыми байтами (от 0 до 3), служащими для выравнивания по 4-байтовой границе; строка “bob” будет иметь вид 0x00000003626f6200.
89 09 9c e2 - уникальный идентификатор запроса (случайное число uint32)
00 00 00 00 - тип rpc (call = 0, response = 1)
00 00 00 02 - версия rpc (2)
00 01 87 88 - программа rpc (0x00018788 = 100232 = sadmind)
00 00 00 0a - версия программы rpc (0x0000000a = 10)
00 00 00 01 - процедура rpc (0x00000001 = 1)
00 00 00 01 - разновидность “мандата” (1 = auth_unix)
00 00 00 20 - размер данных auth_unix (0x20 = 32)
## следующие 32 байта содержат данные auth_unix
40 28 3a 10 - unix timestamp (0x40283a10 = 1076378128 = feb 10 01:55:28 2004 gmt)
00 00 00 0a – длина имени клиентской машины (0x0a = 10)
4d 45 54 41 53 50 4c 4f 49 54 00 00 - строка “metasploit”
00 00 00 00 - uid запрашивающего пользователя (0)
00 00 00 00 - gid запрашивающего пользователя (0)
00 00 00 00 – идентификаторы дополнительных групп (0)
00 00 00 00 - разновидность верификатора (0 = auth_null – нет верификатора)
00 00 00 00 - размер верификатора (0, нет верификатора)
Остальная часть пакета содержит запрос, передаваемый процедуре 1 программы sadmind.
Известно, что уязвимость sadmind заключается в доверии к значениям uid, приходящим от клиента. Программа sadmind выполняет все запросы, полученные от клиента с uid = 0 (root).
Мы уже имеем данные, достаточные для создания правила. Сначала убедимся, что пакет содержит вызов RPC.
content:"|00 00 00 00|"; offset:4; depth:4;
После этого проверим, что пакет адресован программе sadmind.
content:"|00 01 87 88|"; offset:12; depth:4;
Далее нужно удостовериться, что пакет является вызовом уязвимой процедуры 1.
content:"|00 00 00 01|"; offset:16; depth:4;
Также нужно проверить, что пакет содержит “мандат” auth_unix.
content:"|00 00 00 01|"; offset:20; depth:4;
Нам не нужно имя хоста, но нужно проверить числовое значение, расположенное вслед за этим именем. В этом случае будет полезна опция byte_test. Начиная с поля размера имени хоста мы имеем последовательность байтов:
00 00 00 0a 4d 45 54 41 53 50 4c 4f 49 54 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00
Нам нужно прочитать первые 4 байта, преобразовать их в число и пропустить соответствующее этому числу количество байтов, а также байты заполнения. Сделав это, мы получим последовательность байтов:
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00
которая начинается с интересующего нас значения uid.
Словами это можно выразить так – мы хотим прочесть 4 байта, расположенных со смещением 36 от начала пакета, преобразовать эти байты в целое число и пропустить соответствующее количество байтов плюс байты заполнения. Соответствующее правило Snort будет иметь вид:
byte_jump:4,36,align;
после этого мы проверяем равенство нулю значения uid
content:"|00 00 00 00|"; within:4;
теперь соберем вместе созданные компоненты правила.
content:"|00 00 00 00|"; offset:4; depth:4;
content:"g00 01 87 88|"; offset:12; depth:4;
content:"|00 00 00 01|"; offset:16; depth:4;
content:"|00 00 00 01|"; offset:20; depth:4;
byte_jump:4,36,align;
content:"|00 00 00 00|"; within:4;
Третья и четвертая строки обеспечивают проверку содержимого последовательных байтов и их можно объединить:
content:"|00 00 00 00|"; offset:4; depth:4;
content:"|00 01 87 88|"; offset:12; depth:4;
content:"|00 00 00 01 00 00 00 01|"; offset:16; depth:8;
byte_jump:4,36,align;
content:"|00 00 00 00|"; within:4;
если служба sadmind уязвима к переполнению буфера, вместо определения длины имени хоста и пропуска соответствующего числа байтов, следует проверить не слишком ли велик размер имени хоста. Для этого мы считаем 4 байта, начиная с байта 36, преобразуем их в целое число и проверим, что это значение не превышает разумное (скажем, 200). Правило Snort будет иметь вид:
byte_test:4,>,200,36;
И результирующее правило получает форму:
content:"|00 00 00 00|"; offset:4; depth:4;
content:"|00 01 87 88|"; offset:12; depth:4;
content:"|00 00 00 01 00 00 00 01|"; offset:16; depth:8;
byte_test:4,>,200,36;
RFC1518: Y. Rekhter, T. Li., An Architecture for IP Address Allocation with CIDR, 1993
|