This article is part of a series on the OWASP Top 10 for ASP.net Core. See below for links to other articles in the series.
A1 – SQL Injection | A6 – Sensitive Data Exposure (Coming Soon) |
A2 – Broken Authentication and Session Management | A7 – Insufficient Attack Protection (Coming Soon) |
A3 – Cross-Site Scripting (XSS) | A8 – Cross-Site Request Forgery (Coming Soon) |
A4 – Broken Access Control | A9 – Using Components with Known Vulnerabilities (Coming Soon) |
A5 – Security Misconfiguration (Coming Soon) | A10 – Underprotected APIs (Coming Soon) |
In previous iterations of this post, I had a big long text wall explanation of what exactly Cross Site Scripting (XSS) was. But after spending hours perfecting it, I think it’s easier to show you a simple screenshot that says it all.
This was simple to do. I have a “search” page for a user, and any query they type I relay back to them in the form of “Search Query {YourQueryHere}”. The code looks like this :
<h2>Search Results for "@Html.Raw(Context.Request.Query["query"])"</h2>
So we are taking whatever the user searched (Or put in the query string) and placing it directly on the page. Thus allowing the user to enter script tags, or really anything they want in there. This is essentially at the heart of what XSS is about. Taking unverified user input, and displaying it wholesale on a webpage.
For the duration of this post, I will refer back to “code I prepared earlier”. This code is up on Github if you want to take a look and test out some XSS yourself. You can download it here.
What Is XSS?
XSS is when a webpage enables an attacker to inject client side scripts (Typically javascript although other types of injections are possible) onto a webpage that is then subsequently shown to other users. Often these scripts seek to steal private data (For example cookies or browser storage), redirect a browser, or sometimes even just trick a user into doing an action that they wouldn’t normally do.
XSS is usually defined into two different types :
Reflected XSS
Reflected XSS is when cross site scripting occurs immediately as a result of the input from a user. An example might be when a user searches, and that search query is displayed immediately on the page. Typically the danger from XSS comes from the ability to send a link to an unsuspecting user, and that user see something completely unexpected.
Stored XSS
Stored XSS is when you are able to save something to a database or backend store, and have it relayed to users without having to send them a link. If we use an example of a blog that accepts comments on posts. If you are able to store a XSS exploit in a blog comment, then everyone who views that blog post from then on will be affected. Obviously this has the potential to be a much larger exploit than reflected XSS because it doesn’t depend on the user being sent a dodgy link or having to do anything extra on their part.
What Could Someone Do With XSS?
Javascript
The holy grail is of course to be able to inject script tags on a webpage. With this, the world really is the attackers oyster. They could do something as simple as redirecting the user to a different page (Where they then steal a users credentials), they could inject javascript to build a fake login form right there on the page (Where they then steal a users credentials), or they could even use it to steal a users login cookie (Where they then steal a users credentials). It can be devastating.
While injecting javascript on a page can be nothing short of devastating, protecting your site should be more than just disallowing the word “script” to be submitted anywhere. You can actually do some pretty dangerous stuff without CSS at all.
CSS
By injecting styles into a page, an attacker could change the entire layout of the page to trick the user into doing something they don’t want to do. A “clever” exploit I saw in the past was an attacker redesigning a page to trick the user into deleting their own account by moving the delete button around and changing the text (All now possible that you can add “content” inside CSS).
With CSS alone, you can pretty much rewrite the entire website. Let’s take a quick looking using a site I whipped up early. (Again, you can get the source code on Github here). By default it looks a bit like this :
Now let’s try something. Let’s try and inject the following CSS Payload in here :
<style> .container, .navbar{display:none;} body { padding: 5px } body:before { content:"We have moved. Head over to www.bogussite.com to continue"; } </style>
So the URL will look something like :
http://localhost:57423/?query=<style>.container,.navbar{display:none;}body {padding:5px}body:before{content:"We have moved. Head over to www.bogussite.com to continue";}</style>
Now when we view this URL :
IFrames
Injecting IFrames is an XSS exploit that can go undetected for quite some time because it can be essentially invisible to end users. IFraming can be as “harmless” as someone trying to rack up views on their own site that contain ads that pay per view, to something as harmful as IFraming a fake login form into the page.
HTML Encoding User Output
Now, if you’ve been looking at my sample code, you would have noticed something a bit iffy. I’m talking about this Html.Raw(Context.Request.Query[“query”]) . And I want to admit, I cheated a little. You see, by default, when ASP.net Core Razor outputs values onto a page, it always encodes them. If we remove this raw tag helper and try and inject a script tag, we instead see this on the page :
So why didn’t this actually run the script tag? It looks like it should right? Let’s actually look at the source code of the page then.
Look at how our script tag actually got written to the HTML. It’s been escaped for us meaning that the script tag hasn’t actually been ran! Hurrah! So for content that gets written directly to the page, we are actually somewhat protected by the framework.
How about other places we are displaying this data? Maybe we are building a SPA and not using ASP.net Core Razor at all. Every javascript library (Even jQuery) will actually encode data for you. But it pays to check whether this is an automatic or manual process, and anywhere you are outputting user input directly into HTML should be triple checked for valid encoding.
An interesting argument to note is that I have come across developers who insist on HTML Encoding things as they are stored in the database, and then displaying them as is on the webpage (Typically when you aren’t using Razor so you don’t get the auto encoding). This will work, but I think it’s a bad practice to follow. When you only encode data when you store it, you leave yourself open for reflected XSS attacks because this data is never stored anywhere (It’s shown directly back to the user).
URL Encoding User Input
While HTML encoding is fine when you are outputting user data directly into HTML. But at times you may need to accept user input and put it into a URL. URL’s do not encode with the same characters as HTML, so you may find yourself trying to override everything with the Raw tag helper. Do not do this! .NET Core has you covered with URL Encode.
To access it, you first need to install the the following nuget package from your package manager console in Visual Studio :
Install-Package Microsoft.AspNet.WebUtilities -Pre
You can then encode your URL’s in a view like so :
<a href="/linktosomething?query=@System.Net.WebUtility.UrlEncode(Context.Request.Query["query"])">Search Query</a>
Browser Protection
An interesting point to note is that browsers are jumping in to protect users against XSS. With a very simple payload of <script>alert(“this is an XSS”)</script> in Chrome, I actually end up with the following :
In saying that, you can basically find cheat sheets online that show you values to try and bypass these filters. A good writeup by a user trying to get around Chrome’s XSS filter can be found here https://blog.securitee.org/?p=37. Essentially, it’s always going to be an arms race and relying on a browser to protect your users would be fool hardy. Not to mention users who don’t update their browsers anyway.
X-XSS-Protection Headers
This is another way of protecting your users that fall into the basket of “great to have, but encode your output please”. Using the X-XSS-Protection header you are able to direct a browser how best to handle any XSS exploits it detects. The header has 4 different values you can user.
X-XSS-Protection: 0
Attempts to disable XSS Protection in the browser (Good if you want to try and test things out).
X-XSS-Protection: 1
Enables XSS protection but if XSS is detected, it will try and sanitize the output (e.g. Encode it or strip the characters). This can be dangerous to do because the resulting HTML could be just as dangerous.
X-XSS-Protection: 1; mode=block
Enables XSS protection and will block the page loading all together if any XSS exploit is detected.
X-XSS-Protection: 1; report=<reporting-uri>
This is a chromium only header that will allow you to report back to you any URL’s that were detected in having an XSS exploit.
Realistically the only option you should be using is block as this will protect you the best. You can set the header at the code or server level. For the code level, it’s as easy as adding an additional middleware in your pipeline. Something like so :
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.Use(async (context, next) => { context.Response.Headers.Add("X-Xss-Protection", "1"); await next(); }); app.UseMvc(); }
If you are interested in further reading, we have an entire article dedicated to just the X-XSS-Protection header, and another on 3 Security Headers That Every Site Should Have.
Summary
Cross Site Scripting is one of those exploits that refuses to die, mostly because of people not doing the basics right. As we’ve seen, in ASP.net Core our razor tag helpers are a great out of the box solution to protecting us, and indeed HTML encoding in general no matter the framework will solve a big deal of our problems. Browsers are making big strides in trying to protect people too, but this doesn’t mean developers can suddenly become complacent about their role in protecting end users.
In our next topic from the OWASP Top 10 – 2017, We will be tackling Broken Access Control.
I’m a big fan of this series of posts! Thanks for publishing this!