8

使用 pyparsing 模块解析 Snort 日志时出现问题。

问题在于分离 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、您做错了等。

4

3 回答 3

14
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']]
于 2010-08-04T15:45:56.613 回答
4

您需要学习一些正则表达式,但希望这不会太痛苦。你的想法中最大的罪魁祸首是使用了这个结构:

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

于 2010-08-04T15:53:30.183 回答
0

好吧,我不知道 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,你可以在yielding 之前这样做。

于 2010-08-04T14:36:13.847 回答