Defend your website against Cross-Site Scripting (XSS) input injection - ZTT series
7 minute read
In the previous article of the Zero Trust Thinking series, we understood what XSS was, and learned three ways of injecting XSS code into a website, also known as types of XSS.
In this and subsequent articles, we’ll learn practical techniques based on Zero Trust approach to protect our website from these XSS injections, starting from detecting injection in input, using samples on ASP.NET.
The Zero Trust Thinking series is brought to you by ASPSecurityKit, the first true security framework for ASP.NET and ServiceStack built from scratch on the Zero Trust model.
What Zero Trust has to do with XSS prevention?
Zero Trust isn’t a tool or a framework. Rather, it’s an approach; a mindset which proposes that we shouldn’t assume any trust on the credibility of data and caller especially based on the attributes such as the location from where they originate.
We already saw that with Stored XSS, your trusted database can become a permanent store of XSS code. So, as we’ll see, we have no option but to assume no (zero) trust and build the required defense at every possible layer that interfaces with the user – whether as input or output.
The samples used for this article are available in this repository.
Get ready with the code!
Follow the steps on the readme page of the repo to setup the code on your machine. Briefly, you would need Visual Studio 2019 or higher, clone or download the repo, and then open ZTT.XSS.Prevention
solution inside the cloned repository.
There are two projects in the solution: ZTT.XSS.Prevention.BasicDetection
and ZTT.XSS.Prevention.Full
. The latter is built on ASP.NET Core MVC using ASPSecurityKit’s MVC template. However, both are web apps based on the same concept: they represent a simple classified marketplace where registered users can post, browse and buy classified items or products.
You also need to create the database with the update-database
EF Core command. In the package manager console, execute this command for both the projects individually.
Detect input injection
Every layer that accepts input from the user should detect if the input has a potential XSS injection. In ASP.NET MVC5, we used to have this detection in-built, but starting from ASP.NET Core, it’s no longer available.
An XSS injection is usually a piece of HTML code that starts with an HTML special character, such as lessThan or ampersand. Armed with this knowledge, we have defined following regular expression pattern that matches such HTML special characters, in the constants.cs
file inside the BasicDetection
project.
public const string XssPattern = @"^(?:(?!(\<[a-zA-Z/!?])|(&[a-zA-Z#]))[\w\W])*$";
In the pattern, we’ve also defined a range of possible characters such as alphabet, hash etc. that should match the character subsequent to each starting HTML special character, so that other usage of these special characters is still possible.
We can now use the above pattern in a validator on every property we want to perform XSS validation. This is how we’ve done for the NewProduct model:
Try it out
We’ll now try to inject XSS code using the Stored XSS method we saw in the previous article. Follow the below steps to attempt it:
- Right click on the
BasicDetection
project and click on the ‘Set as Startup Project’ menu option. Press F5 to run the project. - Register the attacker user with an email like [email protected]; Email verification isn’t required.
- Go to the products page, and click on ‘Create New’.
- An attacker would usually enter a catchy name, to attract maximum number of users, so enter ‘New iPhone for just $199’.
- In the description, type ‘Brand new iPhone’, followed by this XSS code:
<script>alert(document.cookie.substr(document.cookie.indexOf('AspNetCore.Identity.Application')));</script>
Note
This XSS code reads your login (ASP.NET Identity) cookie, and simply shows it in an alert box. In the real world, the attacker would simply send this cookie to a remote server that belongs to the attacker, via an AJAX call or something similar, without the victim having any clue.
-
Now enter the cost $199, and click on the Create button.
-
Submission fails with XSS injection detected error message. Try injecting in the Name instead field and you shall receive the same error.
-
Stop debugging.
Scale it with ASPSecurityKit
As your web application grows, you’ll have more and more input string fields in various action methods that should be protected against XSS injection. Decorating every property manually with the XSS Regex validator is subject to manual omission, not to mention the verbosity it adds to your model, which is already cluttered with validators for domain/business rules.
What if you have an XSS detection component which can discover and validate every string value – whether defined as a property or in items of an IEnumerable type (including dictionaries, lists, arrays etc.) – regardless of the nesting level of such string value within the input parameter type members' hierarchy?
ASK’s provides exactly such a component part of its security pipeline, which comes with several other security checks, based on Zero Trust model.
Just by applying the ASPSecurityKit’s Protect attribute as global filter, all of your current and future action methods will get XSS detection check automatically.
For example, if you have an input parameter companyModel
, nested properties like model.Contact.PrimaryAddress.Line1
, model.Contact.Name
or model.Website.URLs
are discovered and validated for XSS injection automatically.
In case you want to enable HTML input for a particular property, you apply the AllowHtmlAttribute on that property.
And this is the true Zero Trust way of XSS detection in input: you assume that any value provided to you could have been compromised, so you aggressively verify everything.
Try it out
In the solution explorer, expand ZTT.XSS.Prevention.Full
project. Open the NewProduct model and verify that you don’t see any validation for XSS. Similarly, open the ProductController and you can verify there’s nothing mentioned for the XSS on Create or any other action method either.
Let’s run this project and see whether we have XSS protection in place.
- Right click on the
ZTT.XSS.Prevention.Full
project and click on the ‘Set as Startup Project’ menu option. Press F5 to run the project. - Register the attacker user with an email like [email protected]; Email verification isn’t required.
- Go to the products page, and click on ‘Create New’.
- Enter name as ‘New iPhone for just $199’.
- In the description, type ‘Brand new iPhone’, followed by this XSS code:
<script>alert(document.cookie.substr(document.cookie.indexOf('AskAuth')));</script>
-
Now enter the cost $199, and click on the Create button.
-
Submission fails with ASK’s XSS detection error message, mentioning the property name ‘Description’, and a portion of the input text containing the violating characters.
-
Try injecting in the Name field instead and you shall receive similar error this time for Name property.
-
Stop debugging.
Check out with a new addition
Let’s quickly add a search capability – by adding a term
parameter to Index Products action and see if we can inject using it.
- In the
ZTT.XSS.Prevention.Full
project, open ProductController. Modify the Index action as follows:
// GET: Products
[SkipActivityAuthorization]
public async Task<IActionResult> Index([FromQuery(Name = "term")] string term)
{
return View(await _context.Products.Where(x =>
string.IsNullOrEmpty(term) || x.Name.Contains(term) ||
x.Description.Contains(term))
.ToListAsync());
}
- Press F5 to run the project.
- Sign in as the attacker.
- Go to the products page, and update the URL by adding the search term ‘iPhone’ followed by the XSS code as follows:
https://[domain]:[port]/Product/?term=iPhone<script>alert(document.cookie.substr(document.cookie.indexOf(%27AskAuth%27)));</script>
- Hit enter to reload the page.
- Request fails with ASK’s XSS detection error message, mentioning the parameter name ‘term’, and a portion of the input text containing the violating characters.
Conclusion
In this article, we’ve learned about why Zero Trust approach is needed to holistically defend against XSS attacks. We then learned about a manual method of detecting XSS injection for input action parameters in ASP.NET using the RegularExpressionValidator. We also tried out the method using a sample.
We then learned about another approach of detecting XSS using ASPSecurityKit’s security pipeline, which complies with Zero Trust model, because it automatically discovers and detects every string value in your actions' parameters – regardless of how deeply nested it is in the type hierarchy – and thus, frees you from the worry and overhead of manually maintaining XSS detection check in your model/controller.
In the next article, we’ll learn about how to protect against XSS attacks by sanitizing output data.
The Zero Trust Thinking (ZTT) series
ZTT is brought to you by ASPSecurityKit, the first true security framework for ASP.NET and ServiceStack built from scratch on the Zero Trust model.
Leave your email below to receive a notification of new ZTT articles and videos right in your inbox, once every two weeks or so.
Credits
- Varun Om contributed with the walkthrough content, concepts and architecture for the sample.
- Abhilash contributed with images and sample source code.
Need help?
Looking for an expert team to build a reliable and secure software product? Or, seeking expert security guidance, security review of your source code or penetration testing of your application, or part-time/full-time assistance in implementation of the complete web application and/or its security subsystem?
Just send an email to [email protected] with the details or call us on +918886333058.