Menu

Creación de una regla en Microsoft Sentinel para detectar una infección de malware ZuoRAT

Creación de una regla en Microsoft Sentinel para detectar una infección de malware ZuoRAT

Recientemente el personal investigador de Lumen Technologies Inc.’s y Black Lotus Labs ha detectado un troyano de acceso remoto llamado ZuoRAT que, de momento, no suele ser detectado por antivirus. Este malware tiene como objetivo los siguientes modelos de router SOHO (siglas de Small Office Home Office, routers que están diseñados para oficinas en casa o pequeñas oficinas):

  • Cisco RV 320, 325 Y 420.
  • Asus RT-AC68U, RT-AC530, RT-AC68P y RT-AC1900U.
  • DrayTek Vigor 3900.
  • Dispositivos NETGEAR.

Aunque, según el informe realizado por el personal investigador, el malware ZuoRAT no solo podría limitarse a atacar estos modelos de router SOHO.

El malware llegaría a los dispositivos a través de la red, utilizando como vector de ataque vulnerabilidades en estos router que no hayan sido corregidas (como las CVE-2020-26878 y CVE-2020-26879). Según Incibe, una infección exitosa permitiría al ciberdelincuente recopilar información sobre la red en la que se encuentra el router para conseguir un acceso persistente en la red local y, posteriormente, realizar ataques Man-in-the-Middle y secuestro de DNS y HTTPS. Por lo que es primordial monitorizar los router y aplicar parches de seguridad cada vez que haya uno nuevo disponible.

En este post mostramos una consulta para Microsoft Sentinel que, configurada en el apartado de ‘Analytics rules’, permita alertarnos si nuestro dispositivo ha sido infectado con el malware ZuoRAT. Al final del post se añaden algunas recomendaciones para reducir la probabilidad sufrir una infección (spoiler: hay que actualizar el router).

Creación de la Analytic Rule en Microsoft Sentinel para la detección del malware ZuoRAT

Para crear la consulta en Microsoft Sentinel, nos desplazamos al apartado ‘Analytics’ y posteriormente seleccionamos ‘Create a scheduled query rule’.

A continuación, cumplimentamos la información general sobre la regla. Un ejemplo:

 zuorat-microsoft-sentinel

En la siguiente pestaña: ‘Set rule logic’ configuramos el funcionamiento de la regla. En este punto introducimos la consulta que Sentinel utilizará para buscar coincidencias en los logs y activar la alerta.

Para la consulta se han utilizado los indicadores de compromiso (IoC) ubicados en el repositorio de Black Lotus Labs disponible en el siguiente enlace y para añadir los IoC se ha utilizado un archivo .csv ubicado en el repositorio de Intelequia. Añado la consulta a continuación:

 

let iocs = externaldata(DateAdded: string, IoC: string, Type: string, TLP: string) [@"insertar-url-csv"] with (format="csv", ignoreFirstRecord=True);

let IPList = (iocs | where Type =~ "ip" | project IoC);

let sha256Hashes = (iocs | where Type == "sha256" | project IoC);

let FilePaths = (iocs | where Type =~ "FilePath" | project IoC);

let POST_URI = (iocs | where Type =~ "URI1" | project IoC);

let GET_URI = (iocs | where Type =~ "URI2" | project IoC);

(union isfuzzy=true

    (CommonSecurityLog

    | where isnotempty(SourceIP) or isnotempty(DestinationIP)

    | where SourceIP in (IPList) or DestinationIP in (IPList) or Message has_any (IPList) or FileHash in (sha256Hashes)

    | extend IPMatch = case(SourceIP has_any (IPList), "SourceIP", DestinationIP has_any (IPList), "DestinationIP", "Message")  

    | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated)

        by

        SourceIP,

        DestinationIP,

        DeviceProduct,

        DeviceAction,

        Message,

        Protocol,

        SourcePort,

        DestinationPort,

        DeviceAddress,

        DeviceName,

        IPMatch

    | extend

        timestamp = StartTimeUtc,

        IPCustomEntity = case(IPMatch == "SourceIP", SourceIP, IPMatch == "DestinationIP", DestinationIP, "IP in Message Field")  

    ),

    (OfficeActivity

    | extend SourceIPAddress = ClientIP, Account = UserId

    | where SourceIPAddress has_any (IPList)

    | extend

        timestamp = TimeGenerated,

        IPCustomEntity = SourceIPAddress,

        AccountCustomEntity = Account

    ),

    (_Im_Dns

    | where DstIPAddr has_any (IPList) or SrcIpAddr has_any (IPList)

    | extend DestinationIPAddress = ResponseName, Host = SrcIpAddr

    | extend

        timestamp = TimeGenerated,

        IPCustomEntity = DestinationIPAddress,

        HostCustomEntity = Host

    ),

    (_Im_NetworkSession

    | where DstIPAddr has_any (IPList) or SrcIpAddr has_any (IPList)

    | extend

        timestamp = TimeGenerated,

        IPCustomEntity = SrcIpAddr,

        HostCustomEntity = Hostname,

        AccountCustomEntity=User

    ),

    (_Im_NetworkSession

    | where DstIPAddr has_any (IPList) or SrcIpAddr has_any (IPList)

    | extend

        timestamp = TimeGenerated,

        IPCustomEntity = DstIpAddr,

        HostCustomEntity = Hostname,

        AccountCustomEntity = User

    ),

    (WireData  

    | where isnotempty(RemoteIP)

    | where RemoteIP has_any (IPList)

    | extend

        timestamp = TimeGenerated,

        IPCustomEntity = RemoteIP,

        HostCustomEntity = Computer

    ),

    (SigninLogs

    | where isnotempty(IPAddress)

    | where IPAddress has_any (IPList)

    | extend

        timestamp = TimeGenerated,

        AccountCustomEntity = UserPrincipalName,

        IPCustomEntity = IPAddress

    ),

    (AADNonInteractiveUserSignInLogs

    | where isnotempty(IPAddress)

    | where IPAddress has_any (IPList)

    | extend

        timestamp = TimeGenerated,

        AccountCustomEntity = UserPrincipalName,

        IPCustomEntity = IPAddress

    ),

    (W3CIISLog  

    | where isnotempty(cIP)

    | where cIP has_any (IPList) or (csMethod == 'GET' and csUriStem has_any (GET_URI)) or (csMethod == 'POST' and csUriStem has_any (POST_URI))

    | extend

        timestamp = TimeGenerated,

        IPCustomEntity = cIP,

        HostCustomEntity = Computer,

        AccountCustomEntity = csUserName

    ),

    (AzureActivity  

    | where isnotempty(CallerIpAddress)

    | where CallerIpAddress has_any (IPList)

    | extend

        timestamp = TimeGenerated,

        IPCustomEntity = CallerIpAddress,

        AccountCustomEntity = Caller

    ),

    (

    DeviceNetworkEvents

    | where isnotempty(RemoteIP)  

    | where RemoteIP has_any (IPList) or InitiatingProcessSHA256 has_any (sha256Hashes) or InitiatingProcessFolderPath has_any (FilePaths)

    | extend

        timestamp = TimeGenerated,

        IPCustomEntity = RemoteIP,

        HostCustomEntity = DeviceName  

    ),

    (

    AzureDiagnostics

    | where ResourceType == "AZUREFIREWALLS"

    | where Category == "AzureFirewallApplicationRule"

    | parse msg_s with Protocol 'request from ' SourceHost ':' SourcePort 'to ' DestinationHost ':' DestinationPort '. Action:' Action

    | where isnotempty(DestinationHost)

    | where DestinationHost has_any (IPList)  

    | extend DestinationIP = DestinationHost

    | extend IPCustomEntity = SourceHost

    ),

    (

    AzureDiagnostics

    | where ResourceType == "AZUREFIREWALLS"

    | where Category == "AzureFirewallNetworkRule"

    | parse msg_s with Protocol 'request from ' SourceHost ':' SourcePort 'to ' DestinationHost ':' DestinationPort '. Action:' Action

    | where isnotempty(DestinationHost)

    | where DestinationHost has_any (IPList)  

    | extend DestinationIP = DestinationHost

    | extend IPCustomEntity = SourceHost

    ),

    (Event

    | where Source == "Microsoft-Windows-Sysmon" and EventID == '7'

    | extend EvData = parse_xml(EventData)

    | extend EventDetail = EvData.DataItem.EventData.Data

    | extend ImageLoaded = EventDetail.., Hashes = EventDetail..

    | parse Hashes with * 'SHA256=' SHA256 '",' *

    | where ImageLoaded has_any (FilePaths) or SHA256 has_any (sha256Hashes)

    | project

        TimeGenerated,

        EventDetail,

        UserName,

        Computer,

        Type,

        Source,

        SHA256,

        ImageLoaded,

        EventID

    | extend

        Type = strcat(Type, ":", EventID, ": ", Source),

        Account = UserName,

        FileHash = SHA256,

        Image = EventDetail..

    | extend

        timestamp = TimeGenerated,

        HostCustomEntity = Computer,

        AccountCustomEntity = Account,

        ProcessCustomEntity = tostring(split(Image, '\\', -1)),

        AlgorithmCustomEntity = "SHA256",

        FileHashCustomEntity = FileHash

    ),

    (DeviceEvents

    | extend FilePath = strcat(FolderPath, '\\', FileName)

    | where InitiatingProcessSHA256 has_any (sha256Hashes) or FilePath has_any (FilePaths)

    | project

        TimeGenerated,

        ActionType,

        DeviceId,

        DeviceName,

        InitiatingProcessAccountDomain,

        InitiatingProcessAccountName,

        InitiatingProcessCommandLine,

        InitiatingProcessFolderPath,

        InitiatingProcessId,

        InitiatingProcessParentFileName,

        InitiatingProcessFileName,

        InitiatingProcessSHA256,

        Type

    | extend

        Account = InitiatingProcessAccountName,

        Computer = DeviceName,

        CommandLine = InitiatingProcessCommandLine,

        FileHash = InitiatingProcessSHA256,

        Image = InitiatingProcessFolderPath

    | extend

        timestamp = TimeGenerated,

        HostCustomEntity = Computer,

        AccountCustomEntity = Account,

        ProcessCustomEntity = InitiatingProcessFileName,

        AlgorithmCustomEntity = "SHA256",

        FileHashCustomEntity = FileHash

    ),

    (DeviceFileEvents

    | where FolderPath has_any (FilePaths) or SHA256 has_any (sha256Hashes)

    | project

        TimeGenerated,

        ActionType,

        DeviceId,

        DeviceName,

        InitiatingProcessAccountDomain,

        InitiatingProcessAccountName,

        InitiatingProcessCommandLine,

        InitiatingProcessFolderPath,

        InitiatingProcessId,

        InitiatingProcessParentFileName,

        InitiatingProcessFileName,

        InitiatingProcessSHA256,

        Type

    | extend

        Account = InitiatingProcessAccountName,

        Computer = DeviceName,

        CommandLine = InitiatingProcessCommandLine,

        FileHash = InitiatingProcessSHA256,

        Image = InitiatingProcessFolderPath

    | extend

        timestamp = TimeGenerated,

        HostCustomEntity = Computer,

        AccountCustomEntity = Account,

        ProcessCustomEntity = InitiatingProcessFileName,

        AlgorithmCustomEntity = "SHA256",

        FileHashCustomEntity = FileHash

    ),

    (DeviceImageLoadEvents

    | where FolderPath has_any (FilePaths) or SHA256 has_any (sha256Hashes)

    | project

        TimeGenerated,

        ActionType,

        DeviceId,

        DeviceName,

        InitiatingProcessAccountDomain,

        InitiatingProcessAccountName,

        InitiatingProcessCommandLine,

        InitiatingProcessFolderPath,

        InitiatingProcessId,

        InitiatingProcessParentFileName,

        InitiatingProcessFileName,

        InitiatingProcessSHA256,

        Type

    | extend

        Account = InitiatingProcessAccountName,

        Computer = DeviceName,

        CommandLine = InitiatingProcessCommandLine,

        FileHash = InitiatingProcessSHA256,

        Image = InitiatingProcessFolderPath

    | extend

        timestamp = TimeGenerated,

        HostCustomEntity = Computer,

        AccountCustomEntity = Account,

        ProcessCustomEntity = InitiatingProcessFileName,

        AlgorithmCustomEntity = "SHA256",

        FileHashCustomEntity = FileHash

    ),

    (Event

    | where Source == "Microsoft-Windows-Sysmon"

    | extend EvData = parse_xml(EventData)

    | extend EventDetail = EvData.DataItem.EventData.Data

    | parse EventDetail with * 'SHA256=' SHA256 '",' *

    | where EventDetail has_any (sha256Hashes)

    | project TimeGenerated, EventDetail, UserName, Computer, Type, Source, SHA256

    | extend

        Type = strcat(Type, ": ", Source),

        Account = UserName,

        FileHash = SHA256,

        Image = EventDetail..

    | extend

        timestamp = TimeGenerated,

        HostCustomEntity = Computer,

        AccountCustomEntity = Account,

        ProcessCustomEntity = tostring(split(Image, '\\', -1)),

        AlgorithmCustomEntity = "SHA256",

        FileHashCustomEntity = FileHash

    ),

    (imFileEvent

    | where TargetFileSHA256 has_any (sha256Hashes) or FilePath has_any (FilePaths)

    | extend

        Account = ActorUsername,

        Computer = DvcHostname,

        IPAddress = SrcIpAddr,

        CommandLine = ActingProcessCommandLine,

        FileHash = TargetFileSHA256

    | project Type, TimeGenerated, Computer, Account, IPAddress, CommandLine, FileHash

    ),

    (VMConnection

    | where Direction == 'outbound' and DestinationIp has_any (IPList)

    )

)

  

 

IMPORTANTE: En la primera línea de la consulta vemos [@"insertar-url-csv"], debemos introducir aquí entre las comillas la URL donde se ubica el archivo en formato .csv que vamos a utilizar (se puede utilizar la siguiente URL: https://raw.githubusercontent.com/intelequia/Security.IOCs/main/ZuoRAT_IoCs.csv). Por otra parte, esta consulta supera los 10.000 caracteres porque se trata de una genérica que aborda múltiples tablas para que sea posible aplicarla al mayor número de entornos posibles. En el momento de utilizarla, hay que eliminar las tablas que no se vayan a consultar. Un ejemplo: si en nuestro entorno no disponemos de un servidor IIS del cual tengamos logs que analizar, eliminamos la parte correspondiente a la tabla ‘W3CIISLog’.

A continuación, introducimos la consulta, eliminamos las tablas que no vayamos a utilizar y probamos si funciona pulsando en la opción ‘Test with current data’. Si no lanza ningún mensaje de error, continuamos configurando la regla.  

El siguiente paso consiste en configurar el mapeo de entidades (para agregar contexto en el caso de que se activase una alerta y poder investigar por Host, Account e IP en el caso de que hubiere más alertas asociadas):

mapeo-identidades-zuorat

Con esta parte finalizada, pasamos a la planificación de la consulta:

Cuando terminemos esta parte, pasamos directamente a la pestaña ‘Review and Create’ porque lo demás se puede quedar como viene por defecto.

¿Qué recomendaciones podemos llevar a cabo?

  • Revisar las comunicaciones de seguridad de cada fabricante afectado. Un ejemplo: Asus publicó un comunicado de seguridad el 30 de junio de 2022 relativo al malware ZuoRAT: ASUS Product Security Advisory | ASUS Global.
  • Aplicar las medidas de seguridad que recomiende el fabricante del router SOHO en relación con el malware ZuoRAT.
  • Instalar actualizaciones y parches de seguridad.
  • Considerar contratar servicios de seguridad gestionada para la supervisión de las comunicaciones.
  • Utilizar el servicio AntiBotnet de la Oficina de Seguridad del Internauta (OSI) para detectar si nuestra IP pública forma parte de una botnet.
Si tienes alguna consulta o crees que podemos ayudar a tu organización, no dudes en ponerte en contacto con nosotrosEstaremos encantados de ayudarte😊
 

 

Categorías
 
Posts relacionados
¿Qué es Microsoft Security Copilot? La IA que transforma la ciberseguridad
Publicado por José Félix Núñez Rodríguez  |  02 abril 2024

¿Qué es Microsoft Security Copilot y por qué está revolucionando el análisis de seguridad gracias a la IA? te lo contamos en este post

Leer más
¿Qué rol juega un servicio SOC en la eficiencia empresarial?
Publicado por Intelequia  |  07 noviembre 2023

Los servicios SOC se han convertido en una solución eficiente para la protección de datos, seguridad y que permite garantizar la operatividad empresarial

Leer más
La IA, la nueva aliada de la Ciberseguridad
Publicado por César Asin Mogica  |  06 noviembre 2023

Descubre aquí los últimos avances en inteligencia artificial en el campo de la ciberseguridad que conocimos en Madrid Tech Show.

Leer más