2010-08-04 37 views
8

Có vấn đề với phân tích các bản ghi Snort bằng mô-đun pyparsing.Phân tích cú pháp Snort Nhật ký với PyParsing

Vấn đề là tách nhật ký Snort (có nhiều mục nhập, được phân cách bằng một dòng trống) và nhận được pyparsing để phân tích từng mục nhập dưới dạng toàn bộ đoạn, thay vì đọc từng dòng và mong đợi ngữ pháp hoạt động với mỗi dòng (rõ ràng là không.)

Tôi đã thử chuyển đổi từng đoạn thành một chuỗi tạm thời, loại bỏ các dòng mới trong mỗi đoạn, nhưng từ chối xử lý chính xác. Tôi có thể hoàn toàn theo dõi sai, nhưng tôi không nghĩ như vậy (một biểu mẫu tương tự hoạt động hoàn hảo cho nhật ký kiểu nhật ký hệ thống, nhưng đó là các mục nhập một dòng và do đó bạn tự vay để xử lý dòng/xử lý tệp cơ bản của mình)

Dưới đây là một mẫu của các bản ghi và mã tôi có cho đến nay:

[**] [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 [**] 

và mã Cập nhật:

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 

Bất kỳ sự giúp đỡ, gợi ý, RTFMs, bạn đang làm sai lầm, vv ., đánh giá cao.

Trả lời

13
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') 

mang

[['1:486:4', 'Misc activity', '3', '08/03-07:30:02.233350', '172.143.241.86', '63.44.2.33']] 
+0

Bạn là một vị thần. Đây là giải pháp vượt quá khả năng chuyên môn của tôi nhưng sẽ sớm được triển khai, ngay sau khi tôi tự mình hiểu tất cả các bộ phận hoạt động. Cảm ơn bạn! –

+0

+1 - Câu trả lời hay ~ unutbu, đánh tôi với cú đấm! (Mã nhóm của bạn trông khá điên rồ, tôi sẽ phải sắp xếp thông qua nó khi tôi nhận được một vài phút.) – PaulMcG

+0

+ nhiều chỉ cho việc sử dụng đáng yêu và thanh lịch của 'groupby'. – katrielalex

0

Vâng, tôi không biết Snort hoặc pyparsing, vì vậy xin lỗi trước nếu tôi nói điều gì đó ngu ngốc. Tôi không rõ liệu sự cố có phải là pyparsing không thể xử lý các mục nhập hay không hoặc bạn không thể gửi chúng đến pyparsing ở đúng định dạng. Nếu sau này, tại sao không làm điều gì đó như thế này?

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 = [ ] 

Tất nhiên, nếu bạn cần phải sửa đổi từng đoạn trước khi gửi nó cho pyparsing, bạn có thể làm như vậy trước khi yield ing nó.

+0

Cảm ơn, điều này sạch hơn rất nhiều so với bản gốc, tuy nhiên, vẫn bị nghẹt thở khi mong đợi [**] làm dòng thứ hai thay vì dòng đầu tiên của đoạn tiếp theo. –

+0

Tôi vẫn không chắc mình hiểu. Bạn có nghĩa là 'pyparsing' không hiểu các khối? Tôi tin rằng nó xử lý các dòng mới là khoảng trống và bỏ qua chúng. – katrielalex

4

Bạn có một số không học hỏi regex để làm, nhưng hy vọng điều này sẽ không quá đau đớn. Thủ phạm lớn nhất trong suy nghĩ của bạn là việc sử dụng cấu trúc này:

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

Mỗi thanh con trong trình phân tích cú pháp pyparsing khá độc lập và hoạt động liên tục qua văn bản đến. Vì vậy, thuật ngữ Regex không có cách nào để xem trước biểu thức tiếp theo để biết vị trí dừng lặp lại '*'. Nói cách khác, biểu thức Regex(".*") sẽ chỉ đọc cho đến cuối dòng, vì đó là nơi dừng ".*" mà không chỉ định đa dòng.

Trong pyparsing, khái niệm này được triển khai bằng SkipTo. Sau đây là cách dòng tiêu đề của bạn được viết:

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

của bạn "*" Vấn đề được giải quyết bằng cách thay đổi nó để:

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

điều tương tự cho cls.

Một lỗi cuối cùng, định nghĩa của bạn của ngày là ngắn bởi một ':' + số nguyên:

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

nên là:

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

Tôi nghĩ rằng những thay đổi này sẽ là đủ để bắt đầu phân tích của bạn dữ liệu nhật ký.

Dưới đây là một số gợi ý phong cách khác:

Bạn có rất nhiều lặp lại Suppress("]") biểu thức. Tôi đã bắt đầu xác định tất cả dấu chấm câu có thể ngăn chặn của tôi trong một tuyên bố rất nhỏ gọn và dễ bảo trì như sau:

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

(mở rộng để thêm bất kỳ ký tự dấu câu nào bạn thích). Bây giờ tôi có thể sử dụng các ký tự này bằng tên biểu tượng của chúng và tôi tìm thấy mã kết quả dễ đọc hơn một chút.

Bạn bắt đầu tiêu đề với header = Suppress("[**] [") + .... Tôi không bao giờ thích nhìn thấy các không gian được nhúng trong các văn chương theo cách này, vì nó bỏ qua một số các pyparsing mạnh mẽ phân tích cú pháp cho phép bạn bỏ qua khoảng trắng tự động của nó. Nếu vì lý do nào đó, khoảng cách giữa "[**]" và "[" đã được thay đổi để sử dụng 2 hoặc 3 dấu cách hoặc một tab, thì chữ bị chặn của bạn sẽ không thành công. Kết hợp điều này với đề xuất trước đó và tiêu đề sẽ bắt đầu bằng

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

Tôi biết đây là văn bản được tạo nên không thể thay đổi định dạng này, nhưng phát lại tốt hơn với điểm mạnh của pyparsing.

Khi bạn đã phân tích cú pháp các trường của mình, hãy bắt đầu gán tên kết quả cho các phần tử khác nhau trong trình phân tích cú pháp của bạn. Điều này sẽ làm cho số dễ dàng hơn để lấy dữ liệu sau đó. Ví dụ: thay đổi cls thành:

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

Sẽ cho phép bạn truy cập dữ liệu phân loại bằng cách sử dụng fields.classification.

+0

Có. Tôi thừa nhận tôi chắc chắn đã đạt tới cây búa regex trên cái này (Bạn sẽ thấy nó, nó khá khó sử dụng) - nhưng lại chìm vào trong đêm tối và đây là điều tôi đã đến. Chắc chắn một số mô hình chuyển dịch, nhưng với số lượng lớn dữ liệu và moreso, phương sai của dữ liệu và các trường, pyparsing là lựa chọn duy nhất khác. Cảm ơn sự thông cảm của bạn! –

+0

Và từ tác giả của pyparsing nontheless! Cảm ơn Paul lần nữa! –

+0

Nhận xét với một câu hỏi: không còn cần thiết phải sử dụng phương thức setResultsName() để đặt tên trường? Nó trông giống như một phím tắt ngụ ý ở trên, nhưng tôi không thể tìm thấy điều này trong tài liệu. Cảm ơn! –

Các vấn đề liên quan