En esta sección, explicaré cómo supervisar continuamente las amenazas durante un periodo prolongado sin intervención manual. El objetivo es sencillo: crear objetivos con prioridades específicas, definiendo el tipo de datos que se van a recopilar y la frecuencia de la supervisión.

Este módulo es lo más parecido a enseñar técnicas de vigilancia que puedo hacer legalmente. Mi intención no es promover la vigilancia; sin embargo, la supervisión es una práctica habitual en la inteligencia sobre amenazas. Muchas suites de inteligencia sobre amenazas para las fuerzas del orden incluyen la supervisión multiplataforma en múltiples foros y sitios web para rastrear la actividad de los usuarios, pero aquí no exploraremos ese nivel de complejidad.

Los temas de esta sección incluyen lo siguiente:

  1. Componentes del rastreador de perfiles
  2. Modelos de bases de datos
  3. Backend de la lista de vigilancia
  4. Plantilla para crear listas de vigilancia
  5. Plantilla para mostrar los resultados de la lista de vigilancia
  6. Pruebas

Componentes del rastreador de perfiles

En el tornet_forum, los perfiles de los usuarios muestran los comentarios y las publicaciones en una tabla, lo que nos permite ver toda la actividad de los usuarios y acceder a los enlaces de las publicaciones que han comentado o creado.

Aquí hay un ejemplo de una página de perfil:

Perfil del foro Tornet

En app/scrapers/profile_scraper.py, la función scrape_profile acepta un parámetro llamado scrape_option, que define la prioridad de raspado: everything (todo), comments (solo comentarios) o posts (solo publicaciones).

Los datos del perfil se extraen según la frecuencia especificada, por ejemplo, cada 5 minutos, cada hora o cada 24 horas.

1. scrape_profile:

  • Finalidad: extrae los detalles del perfil, las publicaciones y los comentarios de una URL de perfil especificada mediante el rastreo web con BeautifulSoup.
    • Parámetros clave:
      • url: URL de la página de perfil que se va a extraer.
      • session_cookie: Cookie de autenticación para acceder a la página.
      • user_agent: Cadena de agente de usuario para los encabezados de solicitud HTTP.
      • tor_proxy: Dirección proxy para el enrutamiento Tor.
      • scrape_option: Especifica qué extraer: «comments», «posts» o «everything» (por defecto).
    • Devuelve: Diccionario serializable en JSON con los detalles del perfil, las publicaciones, los comentarios y su recuento, o un diccionario de errores si falla el rastreo.

Más adelante, utilizaremos esta función para rastrear los datos del perfil. Ten en cuenta que nos centramos en extraer los títulos de las publicaciones, las URL y las marcas de tiempo, no el contenido completo de las publicaciones o los comentarios.


Modelos de base de datos

Necesitamos dos tablas: una para gestionar todos los objetivos y otra para almacenar los datos de cada objetivo. Puedes encontrar estas tablas definidas en app/database/models.py:

class Watchlist(Base):
    __tablename__ = "watchlists"

    id = Column(Integer, primary_key=True, index=True)
    target_name = Column(String, unique=True, index=True)
    profile_link = Column(String)
    priority = Column(String)
    frequency = Column(String)
    timestamp = Column(DateTime, default=datetime.utcnow)


class WatchlistProfileScan(Base):
    __tablename__ = "watchlist_profile_scans"
    id = Column(Integer, primary_key=True, index=True)
    watchlist_id = Column(Integer, ForeignKey("watchlists.id"), nullable=False)
    scan_timestamp = Column(DateTime, default=datetime.utcnow)
    profile_data = Column(JSON)

Almacenamos todos los datos del perfil de usuario como una única cadena JSON completa.


Backend de la lista de seguimiento

El código del backend se encuentra en app/routes/watchlist.py. Aunque el código es extenso y complejo, céntrate en los dos diccionarios clave siguientes:

# Map stored frequency values to labels
FREQUENCY_TO_LABEL = {
    "every 5 minutes": "critical",
    "every 1 hour": "very high",
    "every 6 hours": "high",
    "every 12 hours": "medium",
    "every 24 hours": "low"
}

# Map frequency labels to intervals (in seconds)
FREQUENCY_MAP = {
    "critical": 5 * 60,
    "very high": 60 * 60,
    "high": 6 * 60 * 60,
    "medium": 12 * 60 * 60,
    "low": 24 * 60 * 60
}

La frecuencia determina la periodicidad con la que se recopilan los perfiles. Una prioridad crítica activa escaneos cada 5 minutos, mientras que una prioridad baja indica un objetivo menos urgente, con perfiles recopilados cada 24 horas.

Funciones principales en watchlist.py

  1. schedule_all_tasks(db: Session):

    • Propósito: programa las tareas de rastreo para todos los elementos de la lista de seguimiento durante el inicio de la aplicación.
    • Funcionalidad: consulta todos los elementos de Watchlist de la base de datos y llama a schedule_task para cada elemento con el fin de configurar tareas de rastreo periódicas.
    • Parámetros clave:
      • db: sesión de la base de datos SQLAlchemy.
    • Devuelve: Ninguno. Registra el número de tareas programadas o errores.
    • Notas: Gestiona las excepciones para evitar fallos de inicio y registra los errores para su depuración.
  2. schedule_task(db: Session, watchlist_item: Watchlist):

    • Propósito: Programa una tarea de scraping recurrente para un elemento específico de la lista de seguimiento.
    • Funcionalidad: Asigna la frecuencia del elemento a un intervalo (por ejemplo, «cada 24 horas» a 86 400 segundos) y programa una tarea utilizando APScheduler para ejecutar scrape_and_save en el intervalo especificado.
    • Parámetros clave:
      • db: Sesión de la base de datos SQLAlchemy.
      • watchlist_item: objeto Watchlist que contiene los detalles del elemento.
    • Devuelve: Ninguno. Registra los detalles de la programación (por ejemplo, ID del elemento, intervalo).
    • Notas: Utiliza FREQUENCY_TO_LABEL y FREQUENCY_MAP para la asignación de frecuencias a intervalos.
  3. scrape_and_save(watchlist_id: int, db: Session = None):

    • Propósito: Realiza una única operación de scraping para un elemento de la lista de seguimiento y guarda los resultados.
    • Funcionalidad: Recupera el elemento de la lista de seguimiento y un bot aleatorio con el propósito SCRAPE_PROFILE de la base de datos, llama a scrape_profile con las credenciales del bot y almacena el resultado en WatchlistProfileScan. Crea una nueva sesión de base de datos si no se proporciona ninguna.
    • Parámetros clave:
      • watchlist_id: ID del elemento de la lista de seguimiento que se va a extraer.
      • db: Sesión de base de datos SQLAlchemy opcional.
    • Devuelve: Ninguno. Registra el éxito, los errores o los resultados vacíos y envía los datos a la base de datos.
    • Notas: Gestiona el análisis de las cookies de sesión, valida los resultados del rastreo y garantiza la limpieza adecuada de la sesión.
  4. get_watchlist(db: Session):

    • Propósito: Recupera todos los elementos de la lista de seguimiento.
    • Funcionalidad: Consulta la tabla Watchlist y devuelve todos los elementos como una lista de objetos WatchlistResponse.
    • Parámetros clave:
      • db: Sesión de la base de datos SQLAlchemy (a través de Depends(get_db)).
    • Devuelve: Lista de objetos WatchlistResponse.
    • Notas: Genera un error HTTP 500 con registro si la consulta falla.
  5. get_watchlist_item(item_id: int, db: Session):

    • Propósito: Recupera un único elemento de la lista de seguimiento por ID.
    • Funcionalidad: Consulta la tabla Watchlist en busca del item_id especificado y devuelve el elemento como un objeto WatchlistResponse.
    • Parámetros clave:
      • item_id: ID del elemento de la lista de seguimiento.
      • db: Sesión de la base de datos SQLAlchemy.
    • Devuelve: objeto WatchlistResponse o genera un error HTTP 404 si no se encuentra.
    • Notas: registra los errores y genera un error HTTP 500 si se producen problemas inesperados.
  6. create_watchlist_item(item: WatchlistCreate, db: Session):

    • Propósito: crea un nuevo elemento de la lista de seguimiento y programa su tarea de scraping.
    • Funcionalidad: Valida que el target_name sea único, crea una entrada Watchlist, ejecuta un escaneo inmediato si no existen escaneos y programa escaneos futuros utilizando schedule_task.
    • Parámetros clave:
      • item: Modelo Pydantic WatchlistCreate con detalles del elemento.
      • db: Sesión de base de datos SQLAlchemy.
    • Devuelve: Objeto WatchlistResponse para el elemento creado.
    • Notas: Genera un error HTTP 400 si target_name existe, y un error HTTP 500 para otros errores.
  7. update_watchlist_item(item_id: int, item: WatchlistUpdate, db: Session):

    • Propósito: Actualiza un elemento de la lista de seguimiento existente y reprograma su tarea de scraping.
    • Funcionalidad: verifica que el elemento existe y que target_name es único (excluyendo el elemento actual), actualiza los campos y llama a schedule_task para ajustar la programación del rastreo.
    • Parámetros clave:
      • item_id: ID del elemento de la lista de seguimiento.
      • item: modelo WatchlistUpdate de Pydantic con los detalles actualizados.
      • db: Sesión de la base de datos SQLAlchemy.
    • Devuelve: Objeto WatchlistResponse actualizado.
    • Notas: Genera un error HTTP 404 si no se encuentra el elemento, HTTP 400 si hay un target_name duplicado o HTTP 500 si se produce un error.
  8. delete_watchlist_item(item_id: int, db: Session):

    • Propósito: Elimina un elemento de la lista de seguimiento y sus exploraciones asociadas.
    • Funcionalidad: Elimina el elemento de Watchlist, sus exploraciones de WatchlistProfileScan y la tarea APScheduler correspondiente.
    • Parámetros clave:
      • item_id: ID del elemento de la lista de seguimiento.
      • db: Sesión de la base de datos SQLAlchemy.
    • Devuelve: respuesta JSON con mensaje de éxito.
    • Notas: genera un error HTTP 404 si no se encuentra el elemento y un error HTTP 500 si se produce un error. Ignora los trabajos del programador que faltan.
  9. get_profile_scans(watchlist_id: int, db: Session):

    • Finalidad: recupera todos los resultados de escaneo de un elemento de la lista de seguimiento.
    • Funcionalidad: Consulta WatchlistProfileScan en busca de escaneos que coincidan con watchlist_id, ordenados por marca de tiempo (descendente), y los devuelve como objetos WatchlistProfileScanResponse.
    • Parámetros clave:
      • watchlist_id: ID del elemento de la lista de seguimiento.
      • db: sesión de la base de datos SQLAlchemy.
    • Devuelve: lista de objetos WatchlistProfileScanResponse.
    • Notas: genera un error HTTP 500 si se producen errores en la consulta.
  10. download_scan(scan_id: int, db: Session):

    • Finalidad: descarga los datos del perfil de un escaneo como un archivo JSON.
    • Funcionalidad: recupera el escaneo por scan_id, escribe su profile_data en un archivo JSON temporal y lo devuelve como un FileResponse.
    • Parámetros clave:
      • scan_id: ID del escaneo que se va a descargar.
      • db: sesión de la base de datos SQLAlchemy.
    • Devuelve: FileResponse con el archivo JSON.
    • Notas: Genera un error HTTP 404 si no se encuentra el escaneo, y un error HTTP 500 si se produce un error.
  11. startup_event():

    • Propósito: Inicializa APScheduler al iniciar la aplicación.
    • Funcionalidad: comprueba si el programador no se está ejecutando, crea una sesión de base de datos, llama a schedule_all_tasks para programar todos los elementos de la lista de seguimiento e inicia el programador.
    • Parámetros clave: ninguno.
    • Devuelve: ninguno. Registra el estado del programador.
    • Notas: Asegura que el programador se inicia solo una vez para evitar trabajos duplicados.

Si no estás satisfecho con las frecuencias predeterminadas, puedes modificarlas en watchlist.py. Para mantener la coherencia, también tendrás que actualizar las plantillas en consecuencia. Sin embargo, si estás ajustando las frecuencias con fines de depuración, puedes modificarlas únicamente en watchlist.py sin alterar las plantillas.


Plantilla para crear listas de seguimiento

La plantilla que utilizaremos es una tabla CRUD sencilla, para que sea simple y funcional. Puede revisarla abriendo app/templates/watchlist.html.

  1. Creación de elementos de la lista de seguimiento:

    • Propósito: Añade un nuevo elemento a la lista de seguimiento para supervisar un perfil de destino.
    • Interacción con el backend:
      • El botón «Nueva amenaza» abre un modal (newThreatModal) con campos para el nombre del objetivo, el enlace del perfil (URL), la prioridad (todo, publicaciones, comentarios) y la frecuencia (cada 24 horas, cada 12 horas, etc.)..
      • El envío del formulario (newThreatForm) valida el enlace del perfil y envía una solicitud AJAX POST a /api/watchlist-api/items (gestionada por watchlist_api_router) con los datos del formulario.
      • El backend crea un registro «Watchlist», lo guarda en la base de datos y devuelve una respuesta de éxito. Si se realiza correctamente, se cierra el modal, se restablece el formulario y «loadWatchlist()» actualiza la tabla. Los errores activan una alerta.
  2. Listado y actualización de elementos de la lista de seguimiento:

    • Objetivo: muestra y actualiza una tabla con los elementos de la lista de seguimiento.
    • Interacción con el backend:
      • La función loadWatchlist(), llamada al cargar la página y al pulsar el botón «Actualizar tabla», envía una solicitud AJAX GET a /api/watchlist-api/items (gestionada por watchlist_api_router).
      • El backend devuelve una lista de registros Watchlist (ID, nombre del objetivo, enlace al perfil, prioridad, frecuencia, marca de tiempo). La tabla se rellena con estos detalles y muestra «No se han encontrado elementos» si está vacía. Los errores activan una alerta y muestran un mensaje de error en la tabla.
  3. Edición de elementos de la lista de seguimiento:

    • Finalidad: Actualiza un elemento existente de la lista de seguimiento.
    • Interacción con el backend:
      • El botón «Editar» de cada fila de la tabla recupera los datos del elemento mediante una solicitud AJAX GET a /api/watchlist-api/items/{id} (gestionada por watchlist_api_router) y rellena el editThreatModal con los valores actuales.
      • El envío del formulario (editThreatForm) valida el enlace del perfil y envía una solicitud AJAX PUT a /api/watchlist-api/items/{id} con los datos actualizados.
      • El backend actualiza el registro Watchlist. Si se realiza correctamente, se cierra el modal y loadWatchlist() actualiza la tabla. Los errores activan una alerta.
  4. Eliminación de elementos de la lista de seguimiento:

    • Objetivo: Elimina un elemento de la lista de seguimiento.
    • Interacción con el backend:
      • El botón «Eliminar» de cada fila de la tabla solicita confirmación y envía una solicitud AJAX DELETE a /api/watchlist-api/items/{id} (gestionada por watchlist_api_router).
      • El backend elimina el registro Watchlist. Si se realiza correctamente, loadWatchlist() actualiza la tabla. Los errores activan una alerta.
  5. Visualización de los resultados de la lista de seguimiento:

    • Finalidad: Redirige a una página de resultados para el perfil de un elemento de la lista de seguimiento.
    • Interacción con el backend:
      • El botón «Ver resultados» de cada fila de la tabla enlaza con /watchlist-profile/{id} (gestionado por main.py::watchlist_profile).
      • El backend renderiza una plantilla con los resultados de los datos de monitorización del elemento de la lista de seguimiento (por ejemplo, publicaciones o comentarios). No se realiza ninguna llamada AJAX directa, pero la redirección depende de la recuperación de datos del backend.

Plantilla para mostrar los resultados de la lista de seguimiento

Necesitamos una plantilla específica para mostrar los resultados de cada objetivo, ya que manejamos un gran volumen de datos que deben organizarse por fecha para mayor claridad.

  1. Visualización de los resultados del escaneo:

    • Finalidad: muestra los resultados del escaneo de un elemento de la lista de seguimiento en secciones de acordeón.
    • Interacción con el backend:
      • La plantilla recibe watchlist_item (nombre del objetivo, prioridad, frecuencia) y scans (lista de datos del escaneo con profile_data que contiene publicaciones y comentarios) de main.py::watchlist_profile.
      • Cada acordeón representa un escaneo y muestra la marca de tiempo del escaneo, el recuento de publicaciones y el recuento de comentarios (de «profile_data»). Aparece una insignia «Se han detectado nuevos resultados» si el recuento de publicaciones o comentarios del último escaneo difiere del escaneo anterior. Los datos se representan utilizando Jinja2 sin llamadas API adicionales.
  2. Tablas de publicaciones y comentarios:

    • Finalidad: muestra hasta 15 publicaciones y comentarios por escaneo en tablas separadas.
    • Interacción con el backend:
      • Para cada escaneo, las publicaciones (profile_data.posts) y los comentarios (profile_data.comments) se representan en tablas con columnas para el título, la URL y la fecha de creación (o el texto del comentario en el caso de los comentarios). Si no hay datos, se muestra el mensaje «No se han encontrado publicaciones/comentarios».
      • Los datos se precargan desde el backend a través de main.py::watchlist_profile, por lo que no se necesitan más solicitudes API para renderizar las tablas.
  3. Paginación para conjuntos de datos grandes:

    • Finalidad: Gestiona la paginación de los escaneos con más de 15 publicaciones o comentarios.
    • Interacción con el backend:
      • Para las tablas que superan los 15 elementos, se renderiza un grupo de botones de paginación con hasta 5 botones de página, utilizando data-items (publicaciones/comentarios codificados en JSON) y data-total-pages de profile_data.post_count o comment_count.
      • La función changePage() gestiona la paginación del lado del cliente, dividiendo los datos JSON para mostrar 15 elementos por página sin llamadas adicionales al backend. Actualiza dinámicamente los botones de página y el contenido de la tabla en función de la navegación del usuario (clics en anterior/siguiente o en el número de página).
  4. Descarga de los resultados del escaneo:

    • Objetivo: exportar los resultados del escaneo como un archivo.
    • Interacción con el backend:
      • Cada acordeón de escaneo incluye un botón «Descargar resultados» que enlaza con /api/watchlist-api/download-scan/{scan.id} (gestionado por watchlist_api_router).
      • El backend genera un archivo descargable (por ejemplo, JSON o CSV) que contiene los profile_data del escaneo (publicaciones y comentarios). El enlace activa una descarga directa sin AJAX, basándose en el procesamiento del backend.

He elegido los acordeones para organizar los resultados del escaneo por marca de tiempo, ya que creo que es el enfoque más eficaz para gestionar grandes conjuntos de datos. Aunque es posible que prefieras otro método, el formato de acordeón ofrece una visualización clara y eficaz.

No es necesario modificar la plantilla, ya que puedes exportar los datos como JSON y visualizarlos en cualquier formato fuera de tornet_scraper.


Pruebas

Para comenzar las pruebas, configura los siguientes componentes:

  1. Configura una API para resolver CAPTCHA.
  2. Cree un perfil de bot con el propósito establecido en scrape_profile e inicie sesión para obtener una sesión.
  3. Cree una lista de seguimiento utilizando la página /watchlist.

Para crear una lista de seguimiento para supervisar una amenaza, proporcione lo siguiente:

  1. Nombre del objetivo: cualquier identificador de la amenaza.
  2. Enlace del perfil: en el formato «http://z3zpjsqox4dzxkrk7o34e43cpnc5yrdkywumspqt2d5h3eibllcmswad.onion/profile/N3tRunn3r».

Vaya al menú «Lista de vigilancia», haga clic en «Nueva amenaza» e introduzca los detalles. Al añadir un objetivo por primera vez, es posible que la ventana modal se detenga brevemente mientras el backend inicia un análisis inicial. Este análisis inicial al crear un objetivo no es el comportamiento predeterminado del programador de tareas en «watchlist.py», sino una función personalizada que he implementado.

Así es como se muestran los objetivos:

Perfiles de la lista de vigilancia

A continuación se muestran los resultados de la supervisión. Para realizar la prueba, ajusté la frecuencia de programación crítica de cada 5 minutos a cada 1 minuto:

Supervisión de la lista de vigilancia del usuario Netrunner

Puedes expandir cualquier acordeón para ver los resultados y descargarlos en formato JSON:

Acordeones de la lista de vigilancia