Tumblr Phishing Vulnerability

I get a lot of emails about Waltz. We encourage people to send us requests of sites they'd like to use Waltz on, and people take us up on that request quite often. About two months ago, though, I got one that stood out - the subject line had "Tumblr" in it, which is odd because Waltz has supported Tumblr from the day it launched. Interested, I opened the email:

Was trying to set up Waltz on Tumblr today, problem being whenever I go to enter my Tumblr password the website throws a pop up going "Oh holy flaming jesus never ever ever enter your tumblr password anywhere but on TUMBLR DOT COM". This happens on the first keystroke in the password box and doesn't go away

[...]

Do you guys have a workaround or is that Tumblr's issue?

Well that's by far the weirdest bug report I've seen regarding Waltz! I putzed around for awhile on the Tumblr login page and wasn't able to recreate the issue, so I reached out to one of the other major Waltz contributors, Jesse Pollak. Jesse pointed out that I was doing it wrong - the user was having trouble logging in from a user's blog. One of Waltz's main features is the ability to login from any page - regardless if that page is actually a login page. This gave Waltz users the ability to login from an actual Tumblr blog, which is usually not allowed.

Tumblr, recognizing the gem of a phishing attempt that their cusomtizable subdomain'd user blogs created, had put in some javascript that would alert the user if it looked like they were falling victim to a phishing scam.

Tumblr Alert

This is a great effort on Tumblr's part, and greatly reduces the phishing potential that came along with their services. While it makes for a less-than-ideal experience for the user, I had no intention of bypassing Tumblr's anti-phishing tools. It wouldn't necessarily create a security issue for Waltz users (we log you in behind the scenes), but working around their alert would introduce a one-off hack, and also just felt like the wrong way to respond to Tumblr's good security principles.

However, I was a little bothered about the user's report that the alert doesn't go away, even after they dismissed it. That's unnecessary, sounds like a bug, and creates a very terrible experience for the user. I started investigating and found that Tumblr injects a javascript file in all of their user's sites, and that it gets loaded before any other scripts on the page.

I beautified that file, and found this snippet at the very front:

(function () {
    var a = translated_warning_string;
    var b = function (d) {
        d = d || window.event;
        var c = d.target || d.srcElement;
        if (c.type == "password") {
            if (confirm(a)) {
                b = function () {}
            } else {
                c.value = "";
                return false
            }
        }
    };
    if (typeof document.addEventListener != "undefined") {
        document.addEventListener("keypress", b, true)
    }
})();

It's hard to read at first glance because of the obfuscation, but the gist is that they are creating a function, b, and calling it any time a keypress occurs on a password field. If the user presses "OK" on the warning dialog, b should reset itself to a function that does nothing: function() {}. It's easy to miss, but document.addEventListener doesn't actually cause b to be run on every key press - it causes the function that was stored in b at the time of binding to be run on every keypress. Updating the value of b later in the execution doesn't actually change anything about the event handler. The correct way to unbind would have been to use document.removeEventListener.

That's a minor issue, and one worth sending off to Tumblr, but reading through this source raised another major issue. Tumblr is binding to document's keypress event. This is likely so that interacting with any input on the page - regardless of when it gets added to the DOM - will cause the event handler to get called. However, if you know how the javascript event order works, you'll notice an issue.

The javascript event order triggers an event on every element between window and the element that actually caused the event. It actually does two passes - one starting from window and moving deeper, and then another one going in the opposite direction. The tricky part here is that any event handler in that flow can tell the event to stop propagating, which will cause the event order to exit without hitting any further elements.

I created a PoC, which was just a page styled to look like Tumblr's login page, but with this script tag added to the bottom of the page:

window.addEventListener("keypress", function(e) { e.stopPropagation(); }, true);

function stealPasswords(e) {
    var username = document.getElementById("username").value;
    var password = document.getElementById("password").value;

    alert("I just stole your password.  Username: "+username+"  Password: "+password);
}

I then bound the sign up button to call stealPasswords. To my great displeasure, this worked - typing my password into the password input didn't cause any phishing alerts to pop up, and when I hit the sign in button, my password was alerted. It could have just as easily been transferred off to some malicious user's servers.

I quickly wrote up a description of the issue and sent it off to Tumblr. Tumblr has a security bug bounty program, and while my vulnerability didn't quite fit their requirements, this was a really easy way to submit the issue.

This weekend I received an email from HackerOne informing me that Tumblr had awarded me a $100 bounty for the bug report. It had been nearly two months since I reported the issue, so I was quite surprised - I assumed they hadn't deemed the vulnerability major enough to warrant a bounty.

I pulled up the new version of the pre_tumblog.js file to see if the issue had been fixed, which it has. Thanks Tumblr!

(function () {
    var b = translated_warning_string;
    var a = window.confirm;

    function c(f) {
        var d = f.target || f.srcElement;
        if (d.type !== "password") {
            return
        }
        if (a(b)) {
            window.removeEventListener("keypress", c, true)
        } else {
            d.value = "";
            return false
        }
    }
    if (typeof window.addEventListener !== "undefined") {
        window.addEventListener("keypress", c, true)
    }
})();
Jesse Pollak from Clef was a great help in investigating this vulnerability. When I submitted the bug I promised that I would split the bounty with Clef if I was awarded one. Clef has decided to join me in donating the $100 bounty to Safe Families for Children. Thanks Clef!
Thanks for reading by . Want to read more?
comments powered by Disqus