2010-08-04 23 views
8

Tiene un problema con el análisis de los registros de Snort usando el módulo de pyparsing.Analizando Snort Logs con PyParsing

El problema es separar el registro de Snort (que tiene entradas de líneas múltiples separadas por una línea en blanco) y pellizcar para analizar cada entrada como un todo, en lugar de leer en línea y esperar que la gramática funcione con cada línea (obviamente, no).

He intentado convertir cada fragmento en una cadena temporal, quitando las líneas nuevas dentro de cada fragmento, pero se niega a procesar correctamente. Puede que esté completamente en el camino equivocado, pero no lo creo (una forma similar funciona perfectamente para registros tipo syslog, pero esas son entradas de una sola línea y se prestan para el procesamiento básico de iterador/línea de archivos)

he aquí una muestra del registro y el código que tengo hasta ahora:

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

y el código de actualización:

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 

Cualquier ayuda, punteros, RTFMs, lo están haciendo males, etc. ., apreciado enormemente.

Respuesta

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

produce

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

Eres un dios. Esta es una solución más allá de mi experiencia, pero pronto será implementada tan pronto como me haga entender todas las partes que trabajan. ¡Gracias! –

+0

+1 - Buena respuesta ~ unutbu, ¡pásame al golpe! (Tu código groupby se ve bastante loco, tendré que ordenarlo cuando tenga unos minutos.) – PaulMcG

+0

+ muchas solo por el uso encantador y elegante de 'groupby'. – katrielalex

0

Bueno, no sé Snort o pyparsing, así que me disculpo de antemano si digo algo estúpido. No estoy seguro de si el problema es pyparsing si no puede manejar las entradas o si no puede enviarlas al pyparsing en el formato correcto. Si es este último, ¿por qué no hacer algo como esto?

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

Por supuesto, si tiene que modificar cada trozo antes de enviarlo a pyparsing, puede hacerlo antes yield ing ella.

+0

Gracias, esto es mucho más limpio que el original, sin embargo, todavía se ahoga al esperar [**] como la segunda línea en lugar de la primera línea del siguiente fragmento. –

+0

Todavía no estoy seguro de entender. ¿Quieres decir que 'pyparsing' no está entendiendo los trozos? Creo que trata las nuevas líneas como espacios en blanco y las ignora. – katrielalex

4

Tiene alguna desaprendizaje expresión regular para hacer, pero espero que esto no va a ser muy doloroso. El mayor culpable en su forma de pensar es el uso de esta construcción:

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

Cada subparser dentro de un analizador pyparsing es más o menos independiente, y trabaja de forma secuencial a través del texto entrante. Por lo tanto, el término Regex no tiene forma de anticiparse a la siguiente expresión para ver dónde debe detenerse la repetición '*'. En otras palabras, la expresión Regex(".*") va a leer hasta el final de la línea, ya que es donde ".*" se detiene sin especificar multilínea.

En pyparsing, este concepto se implementa mediante SkipTo. Aquí es cómo su línea de cabecera está escrito: "*"

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

Su problema se resuelve cambiando a:

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

Lo mismo para cls.

Un último error, su definición de la fecha es corto por uno ':' + número entero:

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

debería ser:

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

Creo que esos cambios serán suficientes para empezar a analizar su Dato de registro.

Aquí están algunas sugerencias de estilo:

Usted tiene una gran cantidad de expresiones repetidas Suppress("]"). He empezado a definir todo mi puntuacion suppressable en una declaración muy compacto y fácil de mantener de esta manera:

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

(ampliar para agregar cualquier otros caracteres de puntuación se quiere). Ahora puedo usar estos caracteres por sus nombres simbólicos, y encuentro que el código resultante es un poco más fácil de leer.

Comience encabezado con header = Suppress("[**] [") + .... Nunca me gusta ver espacios incrustados en literales de esta manera, ya que omite parte de la robustez de análisis que pyparsing le ofrece con su salto de espacio en blanco automático. Si por alguna razón el espacio entre "[**]" y "[" se cambió para usar 2 o 3 espacios, o una pestaña, entonces su literal suprimido fallaría. Combine esto con la sugerencia anterior, y la cabecera se iniciaría con

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

Sé que esto se genera el texto, por lo que la variación de este formato es poco probable, pero juega mejor las fortalezas de pyparsing.

Una vez que haya analizado los campos, comience a asignar nombres de resultados a diferentes elementos dentro de su analizador. Esto lo hará un lote más fácil de obtener los datos después. Por ejemplo, cambiar CLS:

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

le permitirá acceder a los datos de clasificación utilizando fields.classification.

+0

Sí. Admito que definitivamente alcancé el martillo de expresión regular en este caso (deberías verlo, es bastante difícil de manejar), pero me puse a pipar la noche anterior y esto es a lo que había llegado. Definitivamente, algunos cambios de paradigma, pero con la gran cantidad de datos y más, la varianza de los datos y los campos, el pyparsing era la única otra opción. ¡Gracias por tu visión! –

+0

¡Y del autor de pyparsing no obstante! Gracias de nuevo Paul! –

+0

Comente con una pregunta: ¿ya no es necesario utilizar el método setResultsName() para nombrar campos? Parece un atajo implícito arriba, pero no puedo encontrarlo en la documentación. ¡Gracias! –