Menu

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
¿Por qué la gestión de RR.HH. debe ser en la nube?
Sergio Darias Pérez  |  29 noviembre 2022

Te explicamos porque cada vez más departamentos de RR.HH. implementan tecnología cloud y aplicaciones basadas en datos para su gestión diaria

Leer más
Cómo configurar FSLogix en una cuenta de almacenamiento
Carlos Fernández Barreiro  |  09 noviembre 2022

En este post te explicamos como completar un proceso de configuración de una cuenta de almacenamiento para el despliegue de perfiles de Fslogix

Leer más
Microsoft Teams, mucho más que reuniones y videollamadas
Silvia Padilla Rodríguez  |  02 noviembre 2022

En este post te descubrimos porqué Microsoft Teams se ha convertido en el núcleo de muchas organizaciones para optimizar la operatividad diaria

Leer más