未修复的权限绕过
还是之前那个遗留的问题,首先是权限绕过,首先还是做一个简单的回顾
关于登录的校验在org.apache.ofbiz.webapp.control.LoginWorker#checkLogin
做处理来判断用户是否登录,可以看到这里的判断逻辑非常简单,跳过前两个判断,在后面只需要login返回的不是error,则为认证成功
再来看看login的判断,在这里如果unPwErrMsglist
不为null则可以直接返回requirePasswordChange
因此知道这个其实你就可以去后台点一点就知道哪里能RCE了,但是我比较好奇OFBiz的路由处理,为什么最后能调用到ProgramExport.groovy
路由处理以及解惑
在web.xml当中可以看到仅有一个Servlet处理,继续跟入org.apache.ofbiz.webapp.control.ControlServlet
以doGET
为例,忽略一些无关的逻辑,最终都是通过handler.doRequest
做请求的处理
在这里我们先看看handler的获取处理,首先在servlet的初始化过程就进行了初始化操作
1 2 3 4 5 6 7 8 9 10 11 12
| @Override public void init() throws ServletException { ServletContext ctx = getServletContext(); if (Debug.infoOn()) { String path = ctx.getContextPath(); String webappName = path.isEmpty() ? path : path.substring(1); Debug.logInfo("Loading webapp [" + webappName + "], located at " + ctx.getRealPath("/"), module); }
RequestHandler.getRequestHandler(ctx); }
|
可以看到代码虽然不少,但其实就是从/WEB-INF/controller.xml
下获取了路由的配置,并根据配置文件初始化了两个工厂类ViewFactory
/EventFactory
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
| public static RequestHandler getRequestHandler(ServletContext servletContext) { RequestHandler rh = (RequestHandler) servletContext.getAttribute("_REQUEST_HANDLER_"); if (rh == null) { rh = new RequestHandler(servletContext); servletContext.setAttribute("_REQUEST_HANDLER_", rh); } return rh; }
private RequestHandler(ServletContext context) { this.controllerConfigURL = ConfigXMLReader.getControllerConfigURL(context); try { ConfigXMLReader.getControllerConfig(this.controllerConfigURL); } catch (WebAppConfigurationException e) { Debug.logError(e, "Exception thrown while parsing controller.xml file: ", module); } this.viewFactory = new ViewFactory(context, this.controllerConfigURL); this.eventFactory = new EventFactory(context, this.controllerConfigURL);
this.trackServerHit = !"false".equalsIgnoreCase(context.getInitParameter("track-serverhit")); this.trackVisit = !"false".equalsIgnoreCase(context.getInitParameter("track-visit")); hostHeadersAllowed = UtilMisc.getHostHeadersAllowed();
}
public static ControllerConfig getControllerConfig(WebappInfo webAppInfo) throws WebAppConfigurationException, MalformedURLException { Assert.notNull("webAppInfo", webAppInfo); String filePath = webAppInfo.getLocation().concat(controllerXmlFileName); File configFile = new File(filePath); return getControllerConfig(configFile.toURI().toURL()); }
public static ControllerConfig getControllerConfig(URL url) throws WebAppConfigurationException { ControllerConfig controllerConfig = controllerCache.get(url); if (controllerConfig == null) { controllerConfig = controllerCache.putIfAbsentAndGet(url, new ControllerConfig(url)); } return controllerConfig; }
|
接下来我们继续看org.apache.ofbiz.webapp.control.RequestHandler#doRequest
,首先根据请求路径获取对应处理的RequestMap
接着首先是一些预处理,感兴趣的可以自己看看
继续往下,之后在正式处理前会走到我们熟悉的登录校验由于第一部分已经简单做了分析,这里就不再重复
之后根据返回结果选择对应的response,上面也提到过requestMap与uri有关,同样response这部分与uri以及type有关
1 2 3 4 5
| <request-map uri="ProgramExport"> <security https="true" auth="true"/> <response name="success" type="view" value="ProgramExport"/> <response name="error" type="view" value="ProgramExport"/> </request-map>
|
接下来可以看到这里根据nextRequestResponse.type
的种类执行,和我们请求的路径在xml中对应的配置有关,在这里由于我们是view因此走入else执行后置处理器
最终执行视图渲染的处理,之后的处理感兴趣就自己跟跟
而我们后台漏洞的利用点ProgramExport,对应视图如下
1
| <view-map name="ProgramExport" type="screen" page="component://webtools/widget/EntityScreens.xml#ProgramExport"/>
|
因此也不难理解为什么通过路由最终能调用ProgramExport.groovy的处理了
利用时值得注意的点
在直接利用时你会发现在某些版本下无法打成功,其实是因为这里做了一些简单的检查
这里有一些禁止使用的token
可以看到这个配置来源于配置文件
看到拦截的内容简直抽象,不知道从哪里抄来的…不多吐槽,绕过也很简单,黑名单里的限制并不多很容易通过代码层面bypass,当然也可以用unicode编码更直白
Ps:当然还有另一个CVE是关于SSRF以及任意配置读取的利用点也可以自己翻翻后台功能,这里不多做分析了
参考链接
Apache OFBiz漏洞 CVE-2023-49070 的前世今生