Разбор логов Snort с помощью PyParsing

Возникла проблема с разбором логов Snort с помощью модуля pyparsing.

Проблема заключается в разделении журнала Snort (который содержит многострочные записи, разделенные пустой строкой) и получении pyparsing для разбора каждой записи как целого фрагмента, а не чтения строки за строкой и ожидании, что грамматика будет работать с каждой строкой (очевидно, , это не.)

Я попытался преобразовать каждый фрагмент во временную строку, удалив новые строки внутри каждого фрагмента, но он отказывается правильно обрабатываться. Я могу быть полностью на неправильном пути, но я так не думаю (аналогичная форма отлично работает для журналов типа syslog, но это однострочные записи, и поэтому они поддаются вашему базовому итератору файлов/обработке строк)

Вот пример журнала и кода, который у меня есть:

[**] [1:486:4] ICMP Destination Unreachable Communication with Destination Host is Administratively Prohibited [**]
[Classification: Misc activity] [Priority: 3] 
08/03-07:30:02.233350 172.143.241.86 -> 63.44.2.33
ICMP TTL:61 TOS:0xC0 ID:49461 IpLen:20 DgmLen:88
Type:3  Code:10  DESTINATION UNREACHABLE: ADMINISTRATIVELY PROHIBITED HOST FILTERED
** ORIGINAL DATAGRAM DUMP:
63.44.2.33:41235 -> 172.143.241.86:4949
TCP TTL:61 TOS:0x0 ID:36212 IpLen:20 DgmLen:60 DF
Seq: 0xF74E606
(32 more bytes of original packet)
** END OF DUMP

[**] ...more like this [**]

И обновленный код:

def snort_parse(logfile):
    header = Suppress("[**] [") + Combine(integer + ":" + integer + ":" + integer) + Suppress("]") + Regex(".*") + Suppress("[**]")
    cls = Optional(Suppress("[Classification:") + Regex(".*") + Suppress("]"))
    pri = Suppress("[Priority:") + integer + Suppress("]")
    date = integer + "/" + integer + "-" + integer + ":" + integer + "." + Suppress(integer)
    src_ip = ip_addr + Suppress("->")
    dest_ip = ip_addr
    extra = Regex(".*")

    bnf = header + cls + pri + date + src_ip + dest_ip + extra

    def logreader(logfile):
        chunk = []
        with open(logfile) as snort_logfile:
            for line in snort_logfile:
                if line !='\n':
                    line = line[:-1]
                    chunk.append(line)
                    continue
                else:
                    print chunk
                    yield " ".join(chunk)
                    chunk = []

    string_to_parse = "".join(logreader(logfile).next())
    fields = bnf.parseString(string_to_parse)
    print fields

Любая помощь, указатели, RTFM, вы делаете это неправильно и т. д., очень ценятся.


person Sam Halicke    schedule 04.08.2010    source источник


Ответы (3)


import pyparsing as pyp
import itertools

integer = pyp.Word(pyp.nums)
ip_addr = pyp.Combine(integer+'.'+integer+'.'+integer+'.'+integer)

def snort_parse(logfile):
    header = (pyp.Suppress("[**] [")
              + pyp.Combine(integer + ":" + integer + ":" + integer)
              + pyp.Suppress(pyp.SkipTo("[**]", include = True)))
    cls = (
        pyp.Suppress(pyp.Optional(pyp.Literal("[Classification:")))
        + pyp.Regex("[^]]*") + pyp.Suppress(']'))

    pri = pyp.Suppress("[Priority:") + integer + pyp.Suppress("]")
    date = pyp.Combine(
        integer+"/"+integer+'-'+integer+':'+integer+':'+integer+'.'+integer)
    src_ip = ip_addr + pyp.Suppress("->")
    dest_ip = ip_addr

    bnf = header+cls+pri+date+src_ip+dest_ip

    with open(logfile) as snort_logfile:
        for has_content, grp in itertools.groupby(
                snort_logfile, key = lambda x: bool(x.strip())):
            if has_content:
                tmpStr = ''.join(grp)
                fields = bnf.searchString(tmpStr)
                print(fields)

snort_parse('snort_file')

урожаи

[['1:486:4', 'Misc activity', '3', '08/03-07:30:02.233350', '172.143.241.86', '63.44.2.33']]
person unutbu    schedule 04.08.2010
comment
Ты Бог. Это решение выходит за рамки моего опыта, но скоро будет реализовано, как только я разберусь во всех рабочих частях. Спасибо! - person Sam Halicke; 04.08.2010
comment
+1 - Хороший ответ ~ unutbu, побей меня! (Ваш групповой код выглядит довольно сумасшедшим, мне придется разобраться с ним, когда у меня будет несколько минут.) - person PaulMcG; 04.08.2010
comment
Спасибо всем за добрые слова :) - person unutbu; 05.08.2010
comment
Не могли бы вы заменить groupby на has_content = True; grp = ifilter(str.strip, snort_logfile)? - person jfs; 18.03.2012
comment
@J.F.Sebastian: ifilter вернет плоский итератор всех непустых строк. groupby объединяет строки в группы. - person unutbu; 18.03.2012
comment
@unutbu: да. поэтому я прямо указал: has_content=True, вы обрабатываете только одну группу, созданную groupby. в порядке. У меня так: файл не отсортирован по ключу. - person jfs; 18.03.2012

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

some_stuff + Regex(".*") + 
                 Suppress(string_representing_where_you_want_the_regex_to_stop)

Каждый подпарсер внутри парсера pyparsing в значительной степени автономен и работает последовательно с входящим текстом. Таким образом, у термина Regex нет возможности заглянуть в следующее выражение, чтобы увидеть, где должно остановиться повторение '*'. Другими словами, выражение Regex(".*") будет просто читаться до конца строки, так как именно там ".*" останавливается без указания многострочного.

В pyparsing эта концепция реализована с помощью SkipTo. Вот как пишется ваша строка заголовка:

header = Suppress("[**] [") + Combine(integer + ":" + integer + ":" + integer) + 
             Suppress("]") + Regex(".*") + Suppress("[**]") 

Ваша проблема с ".*" решается путем изменения ее на:

header = Suppress("[**] [") + Combine(integer + ":" + integer + ":" + integer) + 
             Suppress("]") + SkipTo("[**]") + Suppress("[**]") 

То же самое для cls.

Последняя ошибка, ваше определение даты короче на один ':' + целое число:

date = integer + "/" + integer + "-" + integer + ":" + integer + "." + 
          Suppress(integer) 

должно быть:

date = integer + "/" + integer + "-" + integer + ":" + integer + ":" + 
          integer + "." + Suppress(integer) 

Я думаю, что этих изменений будет достаточно, чтобы начать синтаксический анализ ваших данных журнала.

Вот еще несколько советов по стилю:

У вас много повторяющихся Suppress("]") выражений. Я начал определять все мои подавляемые знаки препинания в очень компактном и простом в обслуживании операторе, подобном этому:

LBRACK,RBRACK,LBRACE,RBRACE = map(Suppress,"[]{}")

(разверните, чтобы добавить любые другие знаки пунктуации, которые вам нравятся). Теперь я могу использовать эти символы по их символическим именам, и я нахожу полученный код более легким для чтения.

Вы начинаете заголовок с header = Suppress("[**] [") + .... Мне никогда не нравилось видеть пробелы, встроенные в литералы таким образом, так как это обходит часть надежности синтаксического анализа, которую pyparsing дает вам с автоматическим пропуском пробелов. Если по какой-то причине пробел между «[**]» и «[» был изменен на использование 2 или 3 пробелов или табуляции, тогда ваш подавленный литерал не будет работать. Объедините это с предыдущим предложением, и заголовок будет начинаться с

header = Suppress("[**]") + LBRACK + ...

Я знаю, что это сгенерированный текст, поэтому вариации в этом формате маловероятны, но он лучше подходит для сильных сторон pyparsing.

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

cls = Optional(Suppress("[Classification:") + 
             SkipTo(RBRACK)("classification") + RBRACK) 

Позволит вам получить доступ к данным классификации, используя fields.classification.

person PaulMcG    schedule 04.08.2010
comment
да. Я признаю, что я определенно потянулся к молотку регулярных выражений в этом (вы должны видеть его, он довольно громоздкий) - но прошлой ночью погрузился в pyparsing, и это то, к чему я пришел. Определенно какое-то изменение парадигмы, но с огромным количеством данных и, более того, с разбросом данных и полей, pyparsing был единственным другим вариантом. Спасибо за ваше понимание! - person Sam Halicke; 04.08.2010
comment
И от автора pyparsing тем не менее! Еще раз спасибо Павел! - person Sam Halicke; 04.08.2010
comment
Комментарий с вопросом: больше не нужно использовать метод setResultsName() для именования полей? Это похоже на подразумеваемый ярлык выше, но я не могу найти его в документации. Спасибо! - person Sam Halicke; 04.08.2010
comment
Это было добавлено еще в версии 1.4.7, и есть упоминания об этом в файле CHANGES и в HowToUsePyparsing.html. Но я не вижу упоминания об этом в сгенерированных эпидоках, мне придется это исправить. Этот ярлык работает только в том случае, если для listAllMatches должно быть установлено значение по умолчанию False — если вы хотите передать его как True, вам нужно будет использовать полную форму вызова метода. - person PaulMcG; 05.08.2010

Ну, я не знаю Snort или pyparsing, так что заранее извиняюсь, если скажу глупость. Мне неясно, связана ли проблема с тем, что pyparsing не может обработать записи, или с тем, что вы не можете отправить их pyparsing в правильном формате. Если второе, то почему бы не сделать что-то подобное?

def logreader( path_to_file ):
    chunk = [ ]
    with open( path_to_file ) as theFile:
        for line in theFile:
            if line:
                chunk.append( line )
                continue
            else:
                yield "".join( *chunk )
                chunk = [ ]

Конечно, если вам нужно изменить каждый фрагмент перед его отправкой в ​​pyparsing, вы можете сделать это до того, как yield отправите его.

person Katriel    schedule 04.08.2010
comment
Спасибо, это намного чище, чем оригинал, однако все еще задыхается от ожидания [**] в качестве второй строки, а не первой строки следующего фрагмента. - person Sam Halicke; 04.08.2010
comment
Я все еще не уверен, что понимаю. Вы имеете в виду, что pyparsing не понимает куски? Я считаю, что он рассматривает новые строки как пробелы и игнорирует их. - person Katriel; 04.08.2010