Compare commits
2 commits
7cf5253c70
...
5aa4d720c7
Author | SHA1 | Date | |
---|---|---|---|
|
5aa4d720c7 | ||
|
e1e538a3e2 |
4 changed files with 137 additions and 26 deletions
|
@ -61,6 +61,8 @@ IOC_TIPOS_OMITIR = [
|
|||
|
||||
WORKERS_THR = 4
|
||||
|
||||
# Organizaciones que no se guardaran en BD (No se refleja conteo de IoC)
|
||||
ORG_OMITIR = []
|
||||
|
||||
|
||||
```
|
||||
|
@ -71,6 +73,8 @@ config.py se divide en :
|
|||
- KTIP_CONFIG: Configuración de conexión a servicio de Lookup de Kaspersky. Se debe solo configurar "api_key".
|
||||
- IOC_TIPOS_OMITIR: Tipos de Atributos que se omiten en el conteo, generalmente metadata.
|
||||
- WORKERS_THR: Cantidad de hilos que utiliza la aplicación en ejecución. Por defecto utiliza 4. Este parametro depende de la capacidad de CPU en relación a la cantidad de núcleos (4 Cores => 4 Hilos)
|
||||
- ORG_OMITIR: Organizaciones que no se contemplaran para mostrar conteo de IOC (datos). De igual forma se realiza limpieza de IoC asociados a esas organizaciones
|
||||
|
||||
|
||||
En el archivo run_daily.py se puede configurar por ejemplo, el maximo de ioc a comtemplar por evento en la variable "max_ioc":
|
||||
|
||||
|
|
|
@ -36,3 +36,6 @@ IOC_TIPOS_OMITIR = [
|
|||
|
||||
WORKERS_THR = 4
|
||||
|
||||
# Organizaciones que no se guardaran en BD (No se refleja conteo de IoC)
|
||||
ORG_OMITIR = []
|
||||
|
||||
|
|
128
defs.py
128
defs.py
|
@ -8,13 +8,14 @@ import ipaddress
|
|||
import json
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from pymisp import PyMISP
|
||||
from pymisp.exceptions import PyMISPError
|
||||
import config
|
||||
import urllib3
|
||||
import requests
|
||||
from models import Base, Registro, Usuario
|
||||
from models import Base, Registro, Usuario, ModificadosEv
|
||||
from datetime import datetime, timedelta
|
||||
import threading
|
||||
|
||||
|
@ -288,6 +289,10 @@ class MISPProcessorTop:
|
|||
if int(evento['Event']['attribute_count']) > 0:
|
||||
self.misp.publish(int(e['Event']['id']))
|
||||
logging.info("Publicando cambios de evento #" + e['Event']['id'])
|
||||
|
||||
# Se guarda uuid de evento que fue modificado en BD, manteniendo el timestamp de publish
|
||||
self.guardar_bd_procesados_mod(e['Event']['uuid'], int(e['Event']['publish_timestamp']), datetime.now())
|
||||
|
||||
else:
|
||||
# Se elimina evento con cero atributo...
|
||||
self.misp.delete_event(int(e['Event']['id']))
|
||||
|
@ -296,6 +301,20 @@ class MISPProcessorTop:
|
|||
logging.error(str(err))
|
||||
return resultados
|
||||
|
||||
@staticmethod
|
||||
def convert_value(value):
|
||||
try:
|
||||
# Intentar convertir a fecha y retornar un número representativo
|
||||
return int(datetime.strptime(value, "%Y%m%d").strftime("%Y%m%d"))
|
||||
except ValueError:
|
||||
# Si falla, convertir a entero directamente
|
||||
return int(value)
|
||||
|
||||
def find_max(self, data):
|
||||
# Llamar al método estático directamente
|
||||
max_value = max(data, key=self.convert_value)
|
||||
return max_value
|
||||
|
||||
def calcula_calidad_iocs(self, desde: str, hasta: str, a_por_evento=None):
|
||||
try:
|
||||
# Para salida
|
||||
|
@ -306,10 +325,6 @@ class MISPProcessorTop:
|
|||
|
||||
puntos = {k: 0 for k in self.creators_accounts.keys()}
|
||||
|
||||
# Para filtrar versión de Warninglist donde buscar, se toma desde fecha "desde"
|
||||
actual = datetime.now().year
|
||||
version = str(actual)
|
||||
|
||||
# Fechas para sacar la mayor...
|
||||
fechas = []
|
||||
|
||||
|
@ -328,12 +343,11 @@ class MISPProcessorTop:
|
|||
wl = self.misp.warninglists()
|
||||
|
||||
for l in wl:
|
||||
if str(l['Warninglist']['version']).startswith(str(actual)):
|
||||
fechas.append(l['Warninglist']['version'])
|
||||
fechas.append(str(l['Warninglist']['version']))
|
||||
|
||||
if fechas:
|
||||
# Saca la versión más alta...
|
||||
version = max(fechas)
|
||||
# Saca la versión más alta...
|
||||
#version = max(fechas, key=self.convert_value)
|
||||
version = self.find_max(fechas)
|
||||
|
||||
for l in wl:
|
||||
if str(l['Warninglist']['version']) == version:
|
||||
|
@ -342,11 +356,11 @@ class MISPProcessorTop:
|
|||
lista.append(self.misp.get_warninglist(int(l['Warninglist']['id'])))
|
||||
|
||||
if lista:
|
||||
|
||||
# Promedio por defecto
|
||||
prom = 0
|
||||
|
||||
logging.info("Warninglist de MISP Cargadas : " + str(len(lista)))
|
||||
logging.info("Versión de Warninglists a utilizar: " + str(version))
|
||||
|
||||
# Rango completo de fechas....
|
||||
logging.info("Buscando IoC Desde :" + desde + " Hasta :" + hasta)
|
||||
|
@ -364,10 +378,22 @@ class MISPProcessorTop:
|
|||
# Se seleccionan eventos para establecer limite de fechas
|
||||
for e in eventos_tmp:
|
||||
if datetime.fromtimestamp(int(e['publish_timestamp'])).date() <= datetime.strptime(hasta, '%Y-%m-%d').date():
|
||||
try:
|
||||
# se verifica que evento no haya sido procesado en periodo anterior
|
||||
prev_evento = self.obtener_evento_mod_db(e['uuid'])
|
||||
|
||||
# Event get
|
||||
ev = self.misp.get_event(int(e['id']))
|
||||
eventos.append(ev)
|
||||
# Si no existe en modificados....
|
||||
if not prev_evento:
|
||||
# Event get
|
||||
ev = self.misp.get_event(int(e['id']))
|
||||
eventos.append(ev)
|
||||
elif datetime.fromtimestamp(int(e['publish_timestamp'])) > prev_evento['pub_mod_fecha'] + timedelta(hours=1):
|
||||
# Verificar fecha con desfase, si es mayor se agrega para procesar
|
||||
# Event get
|
||||
ev = self.misp.get_event(int(e['id']))
|
||||
eventos.append(ev)
|
||||
except Exception as err_c:
|
||||
logging.error("Error al tratar de agregar evento +"+e['id']+" :"+str(err_c))
|
||||
|
||||
# Atributos por evento es None, se calcula promedio...
|
||||
if a_por_evento is None:
|
||||
|
@ -428,6 +454,80 @@ class MISPProcessorTop:
|
|||
except Exception as e:
|
||||
logging.info("Error al escribir JSON :" + str(e))
|
||||
|
||||
|
||||
def obtener_evento_mod_db(self, e_uuid: str):
|
||||
# Output inicial vacío
|
||||
output = {}
|
||||
|
||||
try:
|
||||
# Ruta de la base de datos
|
||||
ruta_base_datos = os.path.join(self.dir_data, "registros.db")
|
||||
engine = create_engine(f'sqlite:///{ruta_base_datos}')
|
||||
Base.metadata.create_all(engine)
|
||||
Session = sessionmaker(bind=engine)
|
||||
session = Session()
|
||||
|
||||
try:
|
||||
# Consultar el último registro con el evento_uuid específico
|
||||
ultimo_procesado = (
|
||||
session.query(ModificadosEv)
|
||||
.filter_by(evento_uuid=e_uuid)
|
||||
.order_by(ModificadosEv.pub_mod_fecha.desc()) # Ordenar por fecha descendente
|
||||
.first() # Obtener solo el primer registro
|
||||
)
|
||||
|
||||
# Si se encuentra un registro, convertirlo a dict
|
||||
if ultimo_procesado:
|
||||
output = ultimo_procesado.to_dict()
|
||||
|
||||
except SQLAlchemyError as db_error:
|
||||
logging.error(f"Error en la consulta a la base de datos: {db_error}")
|
||||
session.rollback()
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
except Exception as err:
|
||||
logging.error(f"Error general al conectarse a la base de datos: {err}")
|
||||
|
||||
# Retornar el resultado
|
||||
return output
|
||||
|
||||
|
||||
def guardar_bd_procesados_mod(self, e_uuid: str, timestamp_fecha: int, mod_fecha: datetime):
|
||||
try:
|
||||
# Ruta de la base de datos
|
||||
ruta_base_datos = os.path.join(self.dir_data, "registros.db")
|
||||
engine = create_engine(f'sqlite:///{ruta_base_datos}')
|
||||
Base.metadata.create_all(engine)
|
||||
Session = sessionmaker(bind=engine)
|
||||
session = Session()
|
||||
|
||||
# Crear el nuevo registro
|
||||
nuevo_registro = ModificadosEv(
|
||||
evento_uuid=e_uuid,
|
||||
publicado_fecha=datetime.fromtimestamp(timestamp_fecha),
|
||||
pub_mod_fecha=mod_fecha
|
||||
|
||||
)
|
||||
|
||||
try:
|
||||
session.add(nuevo_registro)
|
||||
session.commit()
|
||||
logging.info(f"Registrado UUID de evento modificado: {e_uuid}")
|
||||
except IntegrityError:
|
||||
# Manejo específico para violación de restricciones
|
||||
logging.warning(f"Registro duplicado: {e_uuid} ya existe con la misma fecha.")
|
||||
session.rollback()
|
||||
except Exception as e:
|
||||
logging.error(f"Error al guardar los datos en la base de datos: {e}")
|
||||
session.rollback()
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
except Exception as err:
|
||||
logging.error(f"Error general al guardar en la base de datos: {err}")
|
||||
|
||||
|
||||
def guardar_bd(self, data: list, fecha: str):
|
||||
try:
|
||||
ruta_base_datos = os.path.join(self.dir_data, "registros.db")
|
||||
|
|
24
run_daily.py
24
run_daily.py
|
@ -1,5 +1,6 @@
|
|||
from defs import MISPProcessorTop
|
||||
import calendar
|
||||
from config import ORG_OMITIR
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
|
@ -25,16 +26,19 @@ obj = MISPProcessorTop()
|
|||
|
||||
# Se llama a calcular fecha
|
||||
#iocs = obj.calcula_calidad_iocs(desde_fecha, hasta_fecha, 1000)
|
||||
|
||||
iocs = obj.calcula_calidad_iocs(fecha_anterior, fecha_anterior, max_ioc)
|
||||
|
||||
if iocs:
|
||||
# Solo para efectos de backup fisico por dia...
|
||||
#obj.guarda_ioc_json(iocs, fecha_anterior.strftime('%Y%m%d')"_"+".json")
|
||||
|
||||
# Se imprime estructura
|
||||
try:
|
||||
iocs = obj.calcula_calidad_iocs(fecha_anterior, fecha_anterior, max_ioc)
|
||||
print(iocs)
|
||||
|
||||
if iocs:
|
||||
# Solo para efectos de backup fisico por dia...
|
||||
#obj.guarda_ioc_json(iocs, fecha_anterior.strftime('%Y%m%d')"_"+".json")
|
||||
output = []
|
||||
for x in iocs:
|
||||
if x['comunidad'] not in ORG_OMITIR:
|
||||
output.append(x)
|
||||
|
||||
# Guarda en BD (SQLite)
|
||||
obj.guardar_bd(iocs,fecha_anterior)
|
||||
# Guarda en BD (SQLite)
|
||||
obj.guardar_bd(output,fecha_anterior)
|
||||
except Exception as e:
|
||||
print(str(e))
|
Loading…
Add table
Reference in a new issue