Password managers: Please make sure AutoFill is secure!

Dear developers of password managers, we communicate quite regularly, typically within the context of security bug bounty programs. Don’t get me wrong, I don’t mind being paid for finding vulnerabilities in your products. But shouldn’t you do your homework before setting up a bug bounty program? Why is it the same basic mistakes that I find in almost all password managers? Why is it that so few password managers get AutoFill functionality right?

Of course you want AutoFill to be part of your product, because from the user’s point of view it’s the single most important feature of a password manager. Take it away and users will consider your product unusable. But from the security point of view, filling in passwords on the wrong website is almost the worst thing that could happen. So why isn’t this part getting more scrutiny? There is a lot you can do, here are seven recommendations for you.

1. Don’t use custom URL parsers

Parsing URLs is surprisingly complicated. Did you know that the “userinfo” part of it can contain an @ character? Did you think about data: URLs? There are many subtle details here, and even well-established solutions might have corner cases where their parser produces a result that’s different from the browser’s. But you definitely don’t want to use a URL parser that will disagree with the browser’s – if the browser thinks that you are on malicious.com then you shouldn’t fill in the password for google.com no matter what your URL parser says.

Luckily, there is an easy solution: just use the browser’s URL parser. If you worry about supporting very old browsers, the same effect can be achieved by creating an <a> element and assigning the URL to be parsed to its href property. You can then read out the link’s hostname property without even adding the element to the document.

2. Domain name is not “the last two parts of a host name”

Many password managers will store passwords for a domain rather than an individual host name. In order to do this, you have to deduce the domain name from the host name. Very often, I will see something like the old and busted “last two parts of a host name” heuristic. It works correctly for foo.example.com but for foo.example.co.uk it will consider co.uk to be the domain name. As a result, all British websites will share the same passwords.

No amount of messing with that heuristic will save you, things are just too complicated. What you need is the Public Suffix List, it’s a big database of rules which can be applied to all top-level domains. You don’t need to process that list yourself, there is a number of existing solutions for that such as the psl package.

3. Don’t forget about raw IP addresses

Wait, there is a catch! The Public Suffix List will only work correctly for actual host names, not for IP addresses. If you give it something like 192.168.0.1 you will get 0.1 back. What about 1.2.0.1? Also 0.1. If your code doesn’t deal with IP addresses separately, it will expose passwords for people’s home routers to random websites.

What you want is recognizing IP addresses up front and considering the entire IP address as the “domain name” – passwords should never be shared between different IP addresses. Recognizing IP addresses is easier said that done however. Most solutions will use a regular expression like /^\d{1-3}\.\d{1-3}\.\d{1-3}\.\d{1-3}$/. In fact, this covers pretty much all IPv4 addresses you will usually see. But did you know that 0xC0.0xA8.0x00.0x01 is a valid IPv4 address? Or that 3232235521 is also an IPv4 address?

Things get even more complicated once you add IPv6 addresses to the mix. There are plenty of different notations to represent an IPv6 address as well, for example the last 32 bits of the address can be written like an IPv4 address. So you might want to use an elaborate solution that considers all these details, such as the ip-address package.

4. Be careful with what host names you consider equivalent

It’s understandable that you want to spare your users disappointments like “I added a password on foo.example.com, so why isn’t it being filled in on bar.example.com?” Yet you cannot know that these two subdomains really share the same owner. To give you a real example, foo.blogspot.com and bar.blogspot.com are two blogs owned by different people, and you certainly don’t want to share passwords between them.

As a more extreme example, there are so many Amazon domains that it is tempting to just declare: amazon.<TLD> is always Amazon and should receive Amazon passwords. And then somebody goes there and registers amazon.boots to steal people’s Amazon passwords.

From what I’ve seen, the only safe assumption is that the host name with www. at the beginning and the one without are equivalent. Other than that, assumptions tend to backfire. It’s better to let the users determine which host names are equivalent, while maybe providing a default list populated with popular websites.

5. Require a user action for AutoFill

And while this might be a hard sell with your marketing department: please consider requiring a user action before AutoFill functionality kicks in. While this costs a bit of convenience, it largely defuses potential issues in the implementation of the points above. Think of it as defense in the depth. Even if you mess up and websites can trick your AutoFill functionality into thinking that they are some other website, requiring a user action will still prevent the attackers from automatically trying out a huge list of popular websites in order to steal user’s credentials for all of them.

There is also another aspect here that is discussed in a paper from 2014. Cross-Site Scripting (XSS) vulnerabilities in websites are still common. And while such a vulnerability is bad enough on its own, a password manager that fills in passwords automatically allows it to be used to steal user’s credentials which is considerably worse.

What kind of user action should you require? Typically, it will be clicking on a piece of trusted user interface or pressing a specific key combination. Please don’t forget checking event.isTrusted, whatever event you process should come from the user rather than from the website.

6. Isolate your content from the webpage

Why did I have to stress that the user needs to click on a trusted user interface? That’s because browser extensions will commonly inject their user interface into web pages and at this point you can no longer trust it. Even if you are careful to accept only trusted events, a web page can manipulate elements and will always find a way to trick the user into clicking something.

Solution here: your user interface should always be isolated within an <iframe> element, so that the website cannot access it due to same-origin policy. This is only a partial solution unfortunately as it will not prevent clickjacking attacks. Also, the website can always remove your frame or replace it by its own. So asking users to enter their master password in this frame is a very bad idea: users won’t know whether the frame really belongs to your extension or has been faked by the website.

7. Ignore third-party frames

Finally, there is another defense in the depth measure that you can implement: only fill in passwords in the top-level window or first-party frames. Legitimate third-party frames with login forms are very uncommon. On the other hand, a malicious website seeking to exploit an XSS vulnerability in a website or a weakness in your extension’s AutoFill functionality will typically use a frame with a login form. Even if AutoFill requires a user action, it won’t be obvious to the user that the login form belongs to a different website, so they might still perform that action.

Comments

  • Stephan Sokolow

        “To give you a real example, foo.blogspot.com and bar.blogspot.com are two blogs owned by different people, and you certainly don’t want to share passwords between them.”

    To be honest, I don’t go on blogspot enough to see the problem with this. I’d think that either you’d want to share passwords between them because you’re using the same account to comment on all blogspot blogs or it’s irrelevant if the login flow redirects you to a different domain.

        “Require a user action for AutoFill”

    I’ve often wondered whether any websites might be using the automatic auto-fill for Firefox’s built-in password manager to identify non-logged-in users.

        “This is only a partial solution unfortunately as it will not prevent clickjacking attacks. Also, the website can always remove your frame or replace it by its own. So asking users to enter their master password in this frame is a very bad idea: users won’t know whether the frame really belongs to your extension or has been faked by the website.”

    Definitely something that Mozilla needs to improve on, given that there’s no officially-blessed API for privileged popups that works in all circumstances and the recommended “open a new window” approach doesn’t really work for people like me who aggressively disable the opening of new windows/tabs without user input and uninstall extensions which manage to circumvent that as part of a frequently used feature.

    (Not to mention, depending on the destop window manager in question, opening a new window may not behave as Firefox expects. For example, there’s a reason that people with tiling window managers either use GIMP in single-window mode or drop by their WM’s wiki to grab a complex custom ruleset for GIMP.)

    Wladimir Palant

    To be honest, I don’t go on blogspot enough to see the problem with this. I’d think that either you’d want to share passwords between them because you’re using the same account to comment on all blogspot blogs or it’s irrelevant if the login flow redirects you to a different domain.

    Commenting on Blogspot is isolated within an iframe pointing to blogger.com, the blog itself cannot access it. There is a reason for this: these subdomains are no longer controlled by Blogspot, they are controlled by whoever owns the blog. The blog owner can run arbitrary JavaScript code on them for example. They can also add a login form, if they decide to add authenticated functionality of their own, and that login form will point to whatever backend they like. So if you are registered on foo.blogspot.com, you certainly don’t want bar.blogspot.com to be able to access these credentials. I’ve seen a password manager going as far as extending the Public Suffix List to treat blogspot.com, blogspot.de and the like as a public suffix – so the actual “domain name” would be one level below it. They had similar special casing for lots of other domains as well, mostly also blog hosters. That’s certainly one way of solving this issue, though one always has to wonder just how complete their list is.

    Definitely something that Mozilla needs to improve on, given that there’s no officially-blessed API for privileged popups

    Yes, I mentioned that issue to Mozilla staff on a number of occasions, also written https://palant.de/2017/11/11/on-web-extensions-shortcomings-and-their-impact-on-add-on-security on it. Unfortunately, nothing is happening from what I know. The current design is pushing extension developers into adopting problematic approaches.

  • Ed

    Do you have a preferred password manager? I stumbled upon your “Maximizing password manager attack surface” and looked through a few more articles. I’m curious if there is one in particular you recommend?

    Wladimir Palant

    Obviously, I prefer the password manager that I wrote myself. But other than that, 1Password seems pretty good. While I cannot claim that I was very thorough in my review, they didn’t have any of the usual issues and the product generally seems well thought out security-wise.

  • Marcel

    A bit nerdy and not the one to use for the masses, but what do you think of
    https://github.com/passff/passff

    Wladimir Palant

    Usually, when you have some third party create a browser extension for an offline password store, the security of that extension is far worse than that of the password store. I didn’t spend much time looking into this extension, but I saw something that looks like a critical security issue.