buttercup / locust

Login form location & automation utility
MIT License
20 stars 6 forks source link

Autotyping problems with Vue & Firefox #47

Open Sjikke opened 1 month ago

Sjikke commented 1 month ago

I am running into issues with the automated inputting of usernames and passwords on Vue-based web apps when using the browser extension in Firefox

When I trigger the auto type, it will start typing and the "progress" is visible. However, the actual data in the input fields is not updated. When I focus an input field, the old data will be back (if any).

From the console I get different error messages on different Vue-based apps.

Example website: https://demos.themeselection.com/materio-vuetify-vuejs-admin-template-free/demo/login

image

On other Vue-based apps, I have also seen the error below: image

The line that throws this specific error is from Vuejs: https://github.com/vuejs/vue/blob/73486cb5f5862a443b42c2aff68b82320218cbcd/src/platforms/web/runtime/modules/events.ts#L71

My tentative conclusion is that the custom InputEvent that is emitted by this library causes security issues when the document is trying to read information from it. Something with the prototype originating from code that runs with different privileges perhaps.


Edit to add environment details.

Firefox 127.0.2, on Ubuntu. Buttercup extension 3.2.0

perry-mitchell commented 1 month ago

Thanks for posting this, I'm seeing the same, but don't think it's limited to Vue.. at least I'm not certain. It happens on an array of sites I use and sometimes only for one out of the two inputs (user and pass). It feels like some state update issue and that maybe the events aren't getting the data in. But as you might've seen this library is quite thorough in terms of how it tries to set the value.

Would you happen to be able to test this further in your case? Maybe you find a method that works for you?

I wonder if we might have to do feature detection if it is library based.

Sjikke commented 1 month ago

I think my issue with Vue & Firefox is not necessarily the same as the problem where the inputs do not always properly get the data as you mentioned, although they do look the same. The difference here in my opinion is that in my scenario there are actual permission errors that cause a binding framework (Vue) to malfunction in processing inputs. Let me focus on my observations regarding the permission problems first.

If I remove the use of LocustInputEvent and instead simply add the source field to an InputEvent Vue does not run into the permission denied error in Firefox, while the extension can still read the evt['source'] for its own purposes. The document (i.e. Vue) cannot access this field, but that's not a problem.

function createInputEvent(source: InputEventTrigger, type: string, eventInitDict?: InputEventInit): InputEvent {
    const result = new InputEvent(type, eventInitDict);
    result["source"] = source;
    return result;
}

I don't really know how the extension works or how Firefox handles them, but I think this makes sense in my limited knowledge: the Prototype of LocustInputEvent originates from a cross-origin source (the extension, if my assumption is correct here). Firefox also considers the Prototype for these "input"/"change" events as Restricted in the debugger. Due to the errors during event handling Vue does not update its internal state and pushed out old information in a new DOM render cycle.
I do not understand why this works without problems in Chrome, but maybe they do some magic with Events crossing security boundaries, or they do not consider this to be cross-origin in the first place.

I am unsure if moving away from this LocustInputEvent class is a viable solution for you, or whether there a different solution that allows this to work properly in Firefox...


As for other scenarios where the input does not "stick"... One example of this was the 2 staged login for Microsoft Office 365 where you first select your username (from a previous login) and then have to type the password.

image

After a bit of digging it it looks to me like that this extension is filling the password field, but also a hidden username field. This last bit causes issues I think... Once the "changed" fires (I think, not 100% sure on the exact timing) for the username, the password field is emptied by Microsoft.

The inputs in this scenario are sent and "caught" correctly I think, but the inputs trigger unexpected reactions from the original document.

A possible fix would be to only fill fields that are visible at the time of triggering the typing. That appears to fix it for this specific login page at least.

    async fillUsername(username: string): Promise<void> {
        if (this.usernameField && isVisible(this.usernameField)) {
            await typeIntoInput(this.usernameField, username);
        }
    }

That being said, it just sounds like "another exception", instead of a broader fix to this type of problem.