All posts

Prevent XSS and Other Common Attacks on Your App

Introduction

Regardless of your application architecture or front-end type, there are a variety of common attack types that the application security architecture’s capabilities must protect against. I’ve talked about these topics briefly before here, here, and here.

Does your application have a documented security architecture? If not, you might want to rethink that situation; we’ll save that for another blog post. There are no guarantees that you have ever truly secured your application, but we can at least eliminate the low-hanging fruit and general sloppiness. The general strategy is usually something along the lines that you make it so time-consuming to break into an application that an attacker goes elsewhere.

There are never guarantees in life or your security posture, but some basic due diligence and relatively, non-invasive mitigation techniques can go a long way to protecting your application. However, there will almost always be dependencies that you have little control over other than routinely applying patches and best practices.

Depending on your budget, having multiple layers implementing each security capability can provide additional assurance. A classic example of this would be having two layers of internet-egress firewalls from different vendors that have more-or-less the same configuration. Having two WAFs may be overkill, but having a WAF and your application server doing basic input validation and sanitization may be wise.

Common Application Attacks

What are these common attacks?

Cross-Site Scripting (XSS) Attack: From OWASP, “Cross-Site Scripting (XSS) attacks are a type of injection, in which malicious scripts are injected into otherwise benign and trusted websites. XSS attacks occur when an attacker uses a web application to send malicious code, generally in the form of a browser side script, to a different end user. Flaws that allow these attacks to succeed are quite widespread and occur anywhere a web application uses input from a user within the output it generates without validating or encoding it.” For a detailed overview, check out the OWASP page on the subject.

SQL Injection: From OWASP, “a SQL Injection attack consists of insertion or “injection” of a SQL query via the input data from the client to the application. A successful SQL injection exploit can read sensitive data from the database, modify database data (Insert/Update/Delete), execute administration operations on the database (such as shutdown the DBMS), recover the content of a given file present on the DBMS file system and in some cases issue commands to the operating system. SQL injection attacks are a type of injection attack, in which SQL commands are injected into data-plane input in order to affect the execution of predefined SQL commands.”

Cross-Site Request Forgery (CSRF): From OWASP, “Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they’re currently authenticated. With a little help of social engineering (such as sending a link via email or chat), an attacker may trick the users of a web application into executing actions of the attacker’s choosing. If the victim is a normal user, a successful CSRF attack can force the user to perform state changing requests like transferring funds, changing their email address, and so forth. If the victim is an administrative account, CSRF can compromise the entire web application.”

Many others. The goal of this post isn’t to enumerate all the possible attack vectors for your application. If you want to dig into the taxonomy of web application vulnerabilities, then check out the OWASP Top 10 (the 2021 list is still the latest at the time this blog post was published) and its classification system with examples. Fascinating information and very much worth looking through, but not what we’re going to focus on here. Instead, I want to focus on security capabilities that provide high-value bang-for-the-buck that I always recommend be done.

In this post, we’re going to focus on the bare minimum one should be doing to prevent Cross-Site Scripting (XSS) Attacks. The techniques described below are also useful in mitigating SQL Injection Attacks. These mitigation techniques will not usually directly address CSRF attacks, but as the OWASP page on the topic points out, an XSS vulnerability can defeat all CSRF mitigation techniques.

Sanitize Your Inputs

Sanitize your input. Validate your input. Don’t trust the input. Even if it comes from a front end that you wrote or control, don’t trust it. You have no idea what has been done to that web front end, mobile app, native desktop app, or other client you’ve released. Or, if the request is even truly coming from where you think it is.

So, what does this mean in terms of mitigation techniques?

Create a schema that describes each input message. Maybe this is JSON Schema for XML, XSD for XML, GraphQL Schema for GraphQL, Protobuf for GRPC, etc. etc. etc. There are many other options covering a wide variety of use cases; some are proprietary, some are standards-based. Find one that makes sense for your situation and use it.

Whatever the data structure, whatever the schema language the following should be addressed:

  • required fields
  • optional fields (I don’t really like these, but sometimes unavoidable)
  • no additional fields (if it isn’t in the schema, it does not belong)
  • enforce data types.
  • enforce string formats (min/max length, regular expressions, dates)
  • expected number ranges
  • enumeration of finite possible values

Don’t embed serialized data types in blobs or strings. So, don’t take serialized (escaped) JSON text and place it into a string field. That only makes every other aspect of input validation more complicated.

For REST APIs, an OpenAPI spec could be created to address the request message schemas (JSON or XML) plus the rest of the request structure. Most of the major API Gateway products on the market today support enforcing API request structure based upon an OpenAPI document. This covers what is listed above in what is generally a cost effective (financially and system resources) manner. I’ll admit, this is probably the most common type of interface I implement these days, but the same basic idea can be accomplished using a variety of service implementation technologies.

I tend to be a proponent of performing this type of input validation near the edge / perimeter of the security stack or integration stack such as on an API Gateway. At several client sites, I’ve been confronted with architects or developers who believe that input validation of this type is business logic that belongs embedded in application logic. I could accept that, but then, 90%+ of all applications in the environment will not bother implementing input validation that even comes close to this level of fastidiousness (yes, I like this word and have been looking for a place to use it). So, yes, I tend to prefer a more centralized approach wherein it isn’t possible to have an API exposed to the outside world without being properly onboarded to an API Gateway (or similar system) that enforces this level of paranoia.

The schema that describes input messages can be tied into “at-rest” database schemas, internal data model representations (programming language class structure, etc), and similar traditional constructs. There are data modeling tools that would allow one to build such a data model then export all the artifacts needed to drive the various pieces of the data architecture. One usually only finds this level of sophistication in a larger shop, but with some discipline can be applied in just about any project.

Once all of this is being done to the input / request messages, it doesn’t hurt to implement the same for response messages. I always get push back on this point, but it will catch a lot of incorrect and garbage results that your code is producing.

Use A Web Application Firewall (WAF)

Deploy a Web Application Firewall (WAF). A WAF will scan all of the incoming traffic and search for common / known malicious request patterns such as those involved in XSS attacks, SQL Injection attacks, Cross Site Request Forgery. It will maintain a list of known malicious IPs and block all requests — one WAF I used recently blocked 2 billion+ IPs by default. It may implement geo-fencing based on country of source IP address. WAF functionality tends to vary by vendor, but a core set of capabilities around OWASP Top 10 tends to be present.

Make it impossible to bypass the WAF layer. Typing a different URL/IP into the browser and pulling up the app sans WAF should not be possible.

WAF functionality comes in many form factors: SaaS solution, Cloud Provider service (Azure WAF, Google Cloud Armor WAF, AWS Web Application Firewall), on-prem appliance, open-source library embedded in your application.

Anything that provides a managed set of rules that doesn’t require any input validation other than applying what should be a small list of exceptions to address false positives would generally be a really good start. It isn’t my intention to recommend a specific product or service here. I have listed the ones that I have used before.

An opensource library that you can embed in your application code or as a pod in Kubernetes(K8S) may sound like a low-cost / low-touch solution at first, but then when your application / system finally does come under attack you will find that what should be a perimeter security service meant to protect your high value internal assets (including compute time) is now chewing up available K8S compute time (and money) doing something that could be blocked more cost effectively in the long run through a cloud-hosted SaaS WAF solution. Depending on the situation, one of the numerous on-prem appliance solutions might also be acceptable.

A WAF has the potential to produce many false positives. In many organizations, a WAF administrator could turn into a full-time job for one or more people. When false positives impact large numbers of users, there will be pressure to simply disable large numbers of rules to fix things quickly. You will get out of your WAF what you put into it. Even for a small app deployed on one of the major cloud providers, I use the available local WAF service.

There is a large population of bots, malware, scrappers, etc, etc on the public internet that are throwing requests at every accessible ip:port attempting to find something that can be exploited. If you’ve never seen WAF logs for an active website, you will be amazed at how much crap is simply filtered out that has no connection to your site / app.

Encode User-Generated Input

Escape special characters in responses being returned to callers — we’ll call this Output Encoding. Though, I have seen it referred to by several names. There are a number of libraries that will do this for you, but in general, turn the following characters into the corresponding HTML codes or HEX codes:

& — Ampersand (&)
< — Less Than (<)
> — Greater Than (>)
 “—Double Quote (")
‘ — Single Quote (')

There are others and again, there numerous libraries that can do this for you in every platform / language used for application development commonly in use today.

Anything that involves taking user input, storing it (this step isn’t even necessary, but is common), and later returning it in responses should have special characters escaped to avoid malicious scripts from being embedded in user input (or at least to have it ignored). If you want to take it a step further, do this even if the input comes from a trusted source such as a business partner.

It’s probably a good idea to do a similar special character encoding step in the user input validation process as well.

Summary

Everything in this post would be done on the backend. The backend system can be hardened to a security assurance level in a way that a browser environment or user’s device never can be, especially if that device lies outside your organization’s realm of control. There is plenty that should be done on the front end as well. There are numerous other recommendations in this category that are summarized here and elsewhere. For completeness, these typically include:

  • Any HTML attributes that use Javascript variables should be quoted. In fact, use quotes with all HTML attributes.
  • Use a modern framework that addresses common vulnerability vectors with HTML and Javascript.
  • Whatever Javascript framework you are using, follow its best practices and security recommendations.
  • Avoid legacy Javascript APIs and libraries that have known vulnerabilities.
  • Use cookie attributes effectively.
  • Set a Content Security Policy that restricts were scripts, images, and other resources can be loaded from (doing this with a website for the first time can be messy and frustrating).

In addition, you should always:

  • Use a Static Application Security Testing (SAST) tool to analyze your code base as part of your CI/CD/CT pipelines.
  • Use a package version dependency vulnerability check to do recommended package dependency version updates on your code base (generate reports from CI/CD/CT pipelines).
  • Use a container image scanning service to generate recommended package version upgrade reports (if applicable) from CI/CD/CT pipelines.

This is not a complete list; however, if the original three points of this article (input validation, use of a WAF, and escaping special characters in output) is used in all situations, the internet would be a safer place.

This article summarizes the three security capabilities I recommend be used by every application project that I am involved in. These are part of an effective XSS mitigation strategy. Just because you are doing these things doesn’t mean your system is secure. If you are not doing these things, there is cause for concern.

Originally published on Medium.