1

I am replacing navigator.credentials.create() and navigator.credentials.get() using chrome extension. navigator.credentials.create() for registering a 'security key' for 2nd-factor authentication. My replacement script works with some websites like Facebook and GitHub but doesn't work on some websites like Gmail, Twitter, Amazon AWS. What might be the issue? Why is there an inconsistency here?

content_script.ts

 const webauthnInject = document.createElement('script');
 webauthnInject.type = 'text/javascript';
 webauthnInject.src = 'chrome-extension://' + chrome.runtime.id + '/js/inject_webauthn.js';
 document.documentElement.appendChild(webauthnInject);

inject_webauthn.ts

(() => {
cKeyCredentials.create = async (options: CredentialCreationOptions): Promise<Credential | null> => {//code}

cKeyCredentials.get = async (options: CredentialRequestOptions): Promise<Credential | null | any> => {//code}

Object.assign(navigator.credentials, cKeyCredentials);
})();

manifest.json

"content_scripts": [
    {
      "all_frames": true,
      "matches": [
        "https://*/*",
        "http://*/*"
      ],
      "exclude_matches": [
        "https://*/*.xml"
      ],
      "run_at": "document_start",
      "js": [
        "js/content_script.js"
      ]
    }
  ],
"permissions": [
    "tabs",
    "storage"
  ],
  "web_accessible_resources": [
    "js/inject_webauthn.js",
    "img/*"
  ],
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",

Updates

The issue is likely because of new dynamic iframes as pointed out by wOxxOm and kaiido. So, I'm trying to use mutationObserver

var observer = new MutationObserver(function (mutations) {
    mutations.forEach(function (mutation) {
        [].filter.call(mutation.addedNodes, function (node) {
            return node.nodeName == 'IFRAME';
        }).forEach(function (node) {
            node.addEventListener('load', function (e) {
                console.log('loaded', node.src);
            });
        });
    });
});
observer.observe(document.documentElement, { childList: true, subtree: true });

I added the above observer in content_script.js. It still doesn't detect the relevant new IFRAME.

The script that call the credentials.create APi is load when I initiate 2FA. Is there a way to find why my injected code functions are not called here? enter image description here

tarun14110
  • 940
  • 5
  • 26
  • 57
  • Those sites probably use the API inside their service workers. Extensions can't run there. – wOxxOm May 22 '20 at 06:10
  • @wOxxOm Thanks for the comment. Is there a way to look at the service workers to make sure if they are using a service worker for it? Is there any way an extension can replace the returned response of the service worker? – tarun14110 May 22 '20 at 16:08
  • You can inspect service workers in devtools. I don't know if you can replace the response but if it's possible then I guess only via `chrome.debugger` API and [Fetch.takeResponseBodyAsStream](https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-takeResponseBodyAsStream) command. – wOxxOm May 22 '20 at 16:12
  • @wOxxOm I don't see any service worker from those origins, where I'm registering U2F. I think the issue might be related to something else. – tarun14110 May 22 '20 at 19:32
  • Do you look in the Application panel of devtools? Both gmail and twitter have a service worker, dunno about aws. Also what exactly do you mean by "doesn't work"? Does the script run at all? If you add console.log to the hooked methods do you see something? – wOxxOm May 22 '20 at 19:37
  • I see. I was opening the `https://myaccount.google.com` and didn't see any service worker. So, I assumed that this account page where those API supposed to call doesn't use service workers. I can see the log outside those methods. But don't see any console log output in `create` or `get` methods. So, the script is inserted correctly but these methods are never called. – tarun14110 May 22 '20 at 19:59
  • @wOxxOm is this API even available in SW? I thought it was a Window only feature, and [specs](https://w3c.github.io/webappsec-credential-management/#the-credential-interface) say the same. – Kaiido May 26 '20 at 01:56
  • Those APIs are available in Linux but I'm not sure about SW though. – tarun14110 May 26 '20 at 03:03
  • @Kaiido, it is available as can be seen in devtools when inspecting any service worker. @tarun14110, `Window` means a JavaScript interface for the `window` object, not Windows OS. – wOxxOm May 26 '20 at 04:33
  • @wOxxOm from which browser? My Chrome 83.0.4103.61 doesn't exposes it. It **correctly** exposes a WorkerNavigator as `self.navigator`, which **correctly** doesn't have a `.credentials` member, only Navigator does. – Kaiido May 26 '20 at 04:51
  • Oops, you're right, I didn't select the console context selector in addition to opening the worker. – wOxxOm May 26 '20 at 04:53
  • @tarun14110, 1) check `chrome://policy` - maybe you have runtime_blocked_hosts there, 2) also try disabling all other extensions and chrome://apps, 3) try to run the script as a literal textContent string (see [method 2](/a/9517879)) because currently it runs async after the page scripts. – wOxxOm May 26 '20 at 04:56
  • 1
    I'm not too much into chrome-extensions, but does this script also self-injects in iframes? A common trick to avoid script kiddies is to call sensitive methods from a new iframe, which would have a clean scope. – Kaiido May 26 '20 at 04:58
  • @Kaiido you may be right because even though the OP's content script runs in iframes thanks to `all_frames` but Chrome doesn't run it in src-less iframes or those with src="javascript:" (this is a bug or rather architectural deficiency) but it might make sense to add `"match_about_blank": true` anyway @tarun14110 – wOxxOm May 26 '20 at 04:59
  • 1
    FWIW, there's a way to defuse the iframe trick: spoof Document.prototype.createElement in [page context](/a/9517879). – wOxxOm May 26 '20 at 05:48
  • @wOxxOm I have tried all the three suggested ideas. I also added `"match_about_blank": true`. None of these worked. Can I debug through console somehow to find out how and from where exactly these websites are calling the `navigator.credentials.create()` API? – tarun14110 May 27 '20 at 06:14
  • 1
    The most advanced option was spoofing createElement, did you try it? Other than that, you'd have to reverse-engineer the site's code. Devtools also has a multifile search tool Ctrl-Shift-F. – wOxxOm May 27 '20 at 09:24
  • Sorry! I didn't understand the spoofing createElement part. Can you give me a pointer here? – tarun14110 May 28 '20 at 01:53
  • I am able to find the call to `navigator.credentials.create`. This script is dynamically retrieved and loaded when a user clicks the register key button. I couldn't find resources for doing Document.prototype.createElement spoofing. Is there a way to use mutationobserver to monitor new iframes? – tarun14110 May 28 '20 at 21:31
  • @wOxxOm Added some update, does it helps to find the probable cause? Also, I think my extension's code is injecting to all the iframes in the gmail at the time of the credentials.create call. I used console top section to look the iframes and could see my extension in all the iframes. – tarun14110 Jun 01 '20 at 01:14

0 Answers0