请注意,本文编写于 1975 天前,最后修改于 1095 天前,其中某些信息可能已经过时。
致远OA getshell 漏洞分析
漏洞触发点在
/seeyon/htmlofficeservlet
公布的POC为
执行poc以后,会在/seeyon/
下产生一个名叫test123456.jsp
的文件,直接访问该文件页面返回笑脸则成功写入webshell
但是文件存在的情况下,再次执行该poc并且更改poc内容就无法写入了,因为文件已经存在了,所以无法对其进行覆盖写入。
poc中有几个字段,分别为:
DBSTEP
OPTION
currentUserId
CREATEDATE
RECORDID
originalFileId
originalCreateDate
FILENAME
needReadFile
originalCreateDate
并且value值像是base64编码(猜对了一半)。
打开seeyon的源码,找到/seeyon/htmlofficeservlet
所在的类。可以查看下执行流程。
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
来获取解码以后的明文,而GetMsgByName
是DBstep.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
方法看看。
发现其就是一个变异base64编码,直接引用该jar包,然后调用DecodeBase64
方法即可实现对poc中编码的明文进行解码。
如果要生成xxx.jsp,只需要对xxx.jsp进行编码即可。
然后将编码后的字符串更改poc中的FILENAME的值。
对于自定义包内容,他会吃掉数据内容的前几位。
自定义jsp的时候需要前面加<%=Class.for
暂时没研究是为什么。
完整poc