致远OA getshell 漏洞分析

漏洞触发点在

/seeyon/htmlofficeservlet

公布的POC为

执行poc以后,会在/seeyon/下产生一个名叫test123456.jsp的文件,直接访问该文件页面返回笑脸则成功写入webshell
-w717

但是文件存在的情况下,再次执行该poc并且更改poc内容就无法写入了,因为文件已经存在了,所以无法对其进行覆盖写入。

poc中有几个字段,分别为:

DBSTEP
OPTION
currentUserId
CREATEDATE
RECORDID
originalFileId
originalCreateDate
FILENAME
needReadFile
originalCreateDate

并且value值像是base64编码(猜对了一半)。

打开seeyon的源码,找到/seeyon/htmlofficeservlet所在的类。可以查看下执行流程。
-w744

package com.seeyon.ctp.common.office;

import DBstep.iMsgServer2000;
import com.seeyon.ctp.common.AppContext;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class HtmlOfficeServlet
  extends HttpServlet
{
  private static Log log = LogFactory.getLog(HtmlOfficeServlet.class);
  
  public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException
  {
    AppContext.initSystemEnvironmentContext(request, response);
    
    HandWriteManager handWriteManager = (HandWriteManager)AppContext.getBean("handWriteManager");
    HtmlHandWriteManager htmlHandWriteManager = (HtmlHandWriteManager)AppContext.getBeanWithoutCache("htmlHandWriteManager");
    
    iMsgServer2000 msgObj = new iMsgServer2000();
    try
    {
      handWriteManager.readVariant(request, msgObj);
      
      msgObj.SetMsgByName("CLIENTIP", request.getRemoteAddr());
      
      String option = msgObj.GetMsgByName("OPTION");
      if ("LOADFILE".equalsIgnoreCase(option)) {
        handWriteManager.LoadFile(msgObj);
      } else if ("LOADSIGNATURE".equalsIgnoreCase(option)) {
        htmlHandWriteManager.loadDocumentSinature(msgObj);
      } else if ("LOADMARKLIST".equalsIgnoreCase(option)) {
        handWriteManager.LoadSinatureList(msgObj);
      } else if ("SIGNATRUEIMAGE".equalsIgnoreCase(option)) {
        handWriteManager.LoadSinature(msgObj);
      } else if ("SAVESIGNATURE".equalsIgnoreCase(option)) {
        htmlHandWriteManager.saveSignature(msgObj);
      } else if ("SAVEHISTORY".equalsIgnoreCase(option)) {
        htmlHandWriteManager.saveSignatureHistory(msgObj);
      } else if ("SIGNATRUELIST".equalsIgnoreCase(option)) {
        handWriteManager.LoadSinatureList(msgObj);
      } else if ("SHOWHISTORY".equalsIgnoreCase(option)) {
        htmlHandWriteManager.getSignatureHistory(msgObj);
      }
      handWriteManager.sendPackage(response, msgObj);
    }
    catch (Exception e)
    {
      log.error("", e);
      msgObj = new iMsgServer2000();
      msgObj.MsgError("htmoffice operate err");
      handWriteManager.sendPackage(response, msgObj);
    }
    AppContext.clearThreadContext();
  }
  
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException
  {
    doGet(request, response);
  }
}

发现主要是调用msgObj.GetMsgByName来获取解码以后的明文,而GetMsgByNameDBstep.iMsgServer2000下的一个方法,找到DBStep.jar包并用jd-gui打开看一下其中的逻辑

public String GetMsgByName(String paramString)
  {
    int i = 0;
    int j = 0;
    String str1 = "";
    
    String str3 = paramString.trim().concat("=");
    i = this.FMsgText.indexOf(str3);
    if (i != -1)
    {
      j = this.FMsgText.indexOf("\r\n", i + 1);
      i += str3.length();
      if (j != -1)
      {
        String str2 = this.FMsgText.substring(i, j);
        str1 = DecodeBase64(str2);
        return str1;
      }
      return str1;
    }
    return str1;
  }

其中调用了DecodeBase64来对数据进行解码,跟踪DecodeBase64方法看看。
-w873

发现其就是一个变异base64编码,直接引用该jar包,然后调用DecodeBase64方法即可实现对poc中编码的明文进行解码。

-w1088

如果要生成xxx.jsp,只需要对xxx.jsp进行编码即可。

-w527

然后将编码后的字符串更改poc中的FILENAME的值。
对于自定义包内容,他会吃掉数据内容的前几位。
自定义jsp的时候需要前面加<%=Class.for暂时没研究是为什么。

完整poc

最后修改:2021 年 11 月 22 日
如果觉得我的文章对你有用,请随意赞赏