kintesh / containerise

Firefox extension to automatically open websites in a container
MIT License
415 stars 53 forks source link

'No Container' Default Container Breaks Logging in on Sites That Use HTTP Redirects #147

Closed rhclayto closed 3 years ago

rhclayto commented 3 years ago

I have the following Containerise rule which containerizes a bank web site into the Banking container:

!*.somebank.com , Banking

I also have the Default Container option turned on, with the Container name set as 'No Container'.

The login form is at https://www.somebank.com. After submitting credentials, the web site uses a 303 Redirect HTTP response to redirect to https://secure.somebank.com.

What happens is that Containerize opens the https://secure.somebank.com that was redirected to in the same tab but in the default 'No Container', then quickly adds https://secure.somebank.com into the Banking container. This brief loss of the Banking container from before the redirect loses the session, the log in is unsuccessful.

In short, a 303 Redirect inside a Containerised container with Default Container of 'No Container' doesn't keep both pages inside the same container, even though both are matched by the glob rule, but instead opens the redirect in the 'No Container' default & then switches back to the rule-matched container. This kills sessions.

The log in with redirect works inside the Banking container with:

1) Containerise disabled 2) The Default container option turned off 3) The Default container option turned on, but the Default Container name set to Banking.

The desired behavior would be to treat redirected-to sites that match the same glob rule as the site redirected-from, as internal navigation, not as a redirection to another site, triggering the default container, then rematching the rule & being put back into the original container.

ghost commented 3 years ago

I'm not sure how active the development here is, but I wrote the Default container feature a while ago and somehow still get notifications :/ Anyway, thanks for the detailed report.

The default container should only trigger as a backup when a match hasn't been found

https://github.com/kintesh/containerise/blob/071fb4e30c7ae1c37c65f96e0306ffb4a54e3ca5/src/containers.js#L77

What I'm guessing is actually happening is that there are multiple redirects (probably 2) of which the first one is to a site not covered by your glob and then the second one is. To confirm this, could you please enable persistent logs in the network tab of the developer tools?

(How, just in case you don't know)

Then repeat your actions verify the 3xx requests.

rhclayto commented 3 years ago

Thanks for your response.

In a private window with all extensions disabled & with persistent logging enabled, I fill in the credentials & click the login button on www.somebank.com. Here is what happens:

1) A POST request to https://secure.somebank.com/tob/live/usp-core/app/initialLogin - status 200. That page is a loading page which makes GET requests to a loader GIF, & two javascript files, both status 200. 2) One of the javascripts initiates a POST request to https://secure.somebank.com/cdn-cgi/bm/cv/result, & the POST payload looks like it is some kind of browser fingerprinting (it contains screen dimensions amongst other things) for the 'We don't recognize this device' stuff. 3) The other loaded javascript makes a POST request to https://secure.somebank.com/tob/live/usp-core/app/redirectInitialLogin, which receives a 303 See Other status from the server. The Location header in the 303 response points to /tob/live/usp-core/app/mfa. 4) The GET request to the redirect page https://secure.somebank.com/tob/live/usp-core/app/mfa is status 200. The page displayed is a 'we don't recognize this device, confirm identity with e-mail or text' page. By this point, when Containerise is enabled, the container has already switched to default No Container & the session is lost, i.e., this identity confirmation page is never actually displayed. The container then switches back to Banking & the page is redirected to the home page https://www.somebank.com. 5) After confirming my identity via SMS, the confirmation page issues a POST request to https://secure.somebank.com/tob/live/usp-core/app/postLogin, which receives a 303 status with Location header /tob/live/usp-core/app/home, which successfully GETS the online banking dashboard page.

If I have previously confirmed my device & registered it, the login process goes directly from redirectInitialLogin which issues a 303 redirect to postLogin which also issues a 303 which redirects to the dashboard.

With Containerise enabled as described in my first post, it looks like at the first 303 it opens a new default tab beside the Banking containerized tab I am working in, then quickly deletes the original banking tab & containerizes the new tab into Banking. (This is very rapid but is visibly perceptible.) I lose the developer tools panel when this happens, so I can't see how the login flow works in this scenario. For certain though without Containerise, there are two 303 redirects, but they are both to secure.somebank.com, which is matched by the glob rule. Also without Containerise & default tab option, it doesn't do the thing where it makes a new tab, deletes the first, then switches the new tab to the container. I believe this is where the breakage of login flow works (the login state is in the tab that gets deleted). I had a look at the code to see what might cause this behavior, but haven't been able to wrap my head around it yet.

Edit: Confirmation of my observation that Containerise opens the redirect in a new default tab, deletes the original tab, & switches the new tab to the Banking container, all in rapid succession. If I turn on the 'Keep old tabs' option, when I submit the login form, a new default 'No Container' tab is opened next to the original tab, then switched to a Banking container. The login form tab remains in place, & in the network tab of developer tools I see that the POST request to https://secure.somebank.com/tob/live/usp-core/app/initialLogin has been denied, with the message under the Transferred field of 'Blocked by Containerise'. This is before any 303 response redirects. No more network requests after that. Whatever is causing this behavior is the source of the problem.

It looks like the code in containers.js is using the underlying Firefox browser.tabs.create() API method:

https://github.com/kintesh/containerise/blob/071fb4e30c7ae1c37c65f96e0306ffb4a54e3ca5/src/containers.js#L97

https://github.com/kintesh/containerise/blob/071fb4e30c7ae1c37c65f96e0306ffb4a54e3ca5/src/containers.js#L32-L39

https://github.com/kintesh/containerise/blob/071fb4e30c7ae1c37c65f96e0306ffb4a54e3ca5/src/Tabs/index.js#L11-L13

Could this be causing this behavior? And I see there is a browser.tabs.update method as well: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/update Would this perhaps allow updating the location in the same tab, rather than the 'create new tab, delete old tab, containerize new tab' dance?

rhclayto commented 3 years ago

New comment to lessen wall of text effect.

I built the xpi from source with some console.log lines thrown in. I noticed that when clicking the login button, which sends the POST request from step 1 in the previous comment, the hostIdentity variable is undefined, which causes Containerise to think it's going to an unmatched URL, so it tries to do it in a default container. It's getting hostIdentity undefined because the hostMap variable is an empty object for the request in question. Normal navigation, such as from https://www.somebank.com to https://www.somebank.com/login, does populate the hostMap object. hostMap is set here:

https://github.com/kintesh/containerise/blob/071fb4e30c7ae1c37c65f96e0306ffb4a54e3ca5/src/containers.js#L64-L68

(P.S.: I have tried it with 'Match domain only' preference set & unset, same result either way.)

Here is the whole console log for a single attempt (i.e., click the login button, new default tab opens, old tab closes, new default tab is containerized into the container the old tab was in). The log titles are the same as the variables being logged.

url:  https://secure.somebank.com/tob/live/usp-core/app/initialLogin index.js:1:11386

tabId:  16 index.js:1:11409

creatingTabs:  
Object {  }
index.js:1:11450

creatingUrl:  undefined index.js:1:11482

hostMap:  
Object {  }
index.js:1:11688

identities:  
(5) […]
?
0: Object { name: "Personal", icon: "fingerprint", iconUrl: "resource://usercontext-content/fingerprint.svg", … }
?
1: Object { name: "Work", icon: "briefcase", iconUrl: "resource://usercontext-content/briefcase.svg", … }
?
2: Object { name: "Banking", icon: "dollar", iconUrl: "resource://usercontext-content/dollar.svg", … }
?
3: Object { name: "Shopping", icon: "cart", iconUrl: "resource://usercontext-content/cart.svg", … }
?
4: Object { name: "No Container", icon: "circle", iconUrl: "resource://usercontext-content/circle.svg", … }
?
length: 5
?
<prototype>: Array []
index.js:1:11715

currentTab:  
{…}
?
active: true
?
attention: false
?
audible: false
?
cookieStoreId: "firefox-container-3"
?
discarded: false
?
favIconUrl: "data:image/x-icon;base64,<redacted>"
?
height: 1095
?
hidden: false
?
highlighted: true
?
id: 16
?
incognito: false
?
index: 3
?
isArticle: false
?
isInReaderMode: false
?
lastAccessed: 1612058898604
?
mutedInfo: Object { muted: false }
?
pinned: false
?
sharingState: Object { camera: false, microphone: false, screen: undefined }
?
status: "loading"
?
successorTabId: -1
?
title: "Some Bank Login"
?
url: "https://www.somebank.com/login"
?
width: 958
?
windowId: 1
?
<prototype>: Object { … }
index.js:1:11745

hostIdentity:  undefined index.js:1:11862

Going to open https://secure.somebank.com/tob/live/usp-core/app/initialLogin in default container firefox-default No Container index.js:1:12071

url:  https://secure.somebank.com/tob/live/usp-core/app/initialLogin index.js:1:10798

newTabIndex:  4 index.js:1:10821

currentTabId:  16 index.js:1:10852

openerTabId:  undefined index.js:1:10884

cookieStoreId:  firefox-default index.js:1:10915

currentTab:  
Object { id: 16, index: 3, windowId: 1, highlighted: true, active: true, attention: false, pinned: false, status: "loading", hidden: false, discarded: false, … }
index.js:1:10974

url:  https://secure.somebank.com/tob/live/usp-core/app/initialLogin index.js:1:11386

tabId:  17 index.js:1:11409

creatingTabs:  
Object { 17: "https://secure.somebank.com/tob/live/usp-core/app/initialLogin" }
index.js:1:11450

creatingUrl:  https://secure.somebank.com/tob/live/usp-core/app/initialLogin index.js:1:11482

url:  https://www.somebank.com/ index.js:1:11386

tabId:  17 index.js:1:11409

creatingTabs:  
Object { 17: "https://secure.somebank.com/tob/live/usp-core/app/initialLogin" }
index.js:1:11450

creatingUrl:  https://secure.somebank.com/tob/live/usp-core/app/initialLogin index.js:1:11482
hostMap:  
Object { host: "!*.somebank.com", containerName: "Banking", cookieStoreId: "firefox-container-3", enabled: true }
index.js:1:11688

identities:  
Array(5) [ {…}, {…}, {…}, {…}, {…} ]
index.js:1:11715

currentTab:  
Object { id: 17, index: 3, windowId: 1, highlighted: true, active: true, attention: false, pinned: false, status: "loading", hidden: false, discarded: false, … }
index.js:1:11745

hostIdentity:  
Object { name: "Banking", icon: "dollar", iconUrl: "resource://usercontext-content/dollar.svg", color: "green", colorCode: "#51cd00", cookieStoreId: "firefox-container-3" }
index.js:1:11862

url:  https://www.somebank.com/ index.js:1:10798

newTabIndex:  4 index.js:1:10821

currentTabId:  17 index.js:1:10852

openerTabId:  undefined index.js:1:10884

cookieStoreId:  firefox-container-3 index.js:1:10915

currentTab:  
Object { id: 17, index: 3, windowId: 1, highlighted: true, active: true, attention: false, pinned: false, status: "loading", hidden: false, discarded: false, … }
index.js:1:10974

url:  https://www.somebank.com/ index.js:1:11386

tabId:  18 index.js:1:11409

creatingTabs:  
Object { 18: "https://www.somebank.com/" }
index.js:1:11450

creatingUrl:  https://www.somebank.com/ index.js:1:11482

url:  https://www.somebank.com/ index.js:1:11386

tabId:  18 index.js:1:11409

creatingTabs:  
Object { 18: "https://www.somebank.com/" }
index.js:1:11450

creatingUrl:  https://www.somebank.com/
rhclayto commented 3 years ago

Okay, progress.

I found the code that is causing this, & a change that remedies it. However, I'm not sure if there would be side effects from making the change.

The code is at:

https://github.com/kintesh/containerise/blob/071fb4e30c7ae1c37c65f96e0306ffb4a54e3ca5/src/Storage/HostStorage.js#L11-L24

And the change is:

  get(url, matchDomainOnly) {
    return super.getAll().then(maps => {
      const sorted = sortMaps(Object.keys(maps).map(key => maps[key]));
      // Sorts by domain length, then by path length
      return sorted.find((map) => {
        try{
          return matchesSavedMap( url, matchDomainOnly, map);
        } catch (e) {
          console.error('Error matching maps', map, url, matchDomainOnly, e);
          return false;
        }
      });
    });
  }

The return with OR statement was returning the fallback empty object. Removing the || {} makes this work. Will this have side effects?

I will investigate further to see why that's happening to begin with under these circumstances.

rhclayto commented 3 years ago

Okie dokie! After all that, I found the solution, & it was simply that the bank website was redirecting from https://www.somebank**bank**.com to https://secure.somebank.com. I simply overlooked that they indeed switched the domain name. Bangs head against wall.