Secure Web Application Cheat Sheet

This article is intended as a cheat sheet for web developers. It describes some basic steps and measures to create a secure web application protected from the most widely spread threats.

User input encoding

What is that?

If the user input is not supposed to contain any active content, such as HTML markup, CSS styles, or JavaScript, it should be properly encoded before usage. All special characters used with HTML, JavaScript, etc., should be replaced with the encoded values so that the content is treated as a plain text and not as an active content. The main reason to do that is to protect it against XSS.

What to do?

No data at rest should be encoded. You need to do the encoding with the client consuming data, as close to the output as possible. This is mainly because only the client knows in which context the data is going to be used and thus, which type of encoding should be applied.

The encoding may be applied in the following contexts:

  • HTML
  • HTML attribute
  • JavaScript
  • URL.

Encoded data at rest may also lead to double encoding issues.

Many frameworks perform encoding by default, e.g. ASP .NET Core for views or razor pages. Some frameworks also provide built-in methods for encoding.

Examples

  • HTML encoding
    System.Net.WebUtility.HtmlEncode("<b>Markup</b>");
    // &lt;b&gt;Markup&lt;/b&gt
  • HTML attribute encoding
    System.Web.HttpUtility.HtmlAttributeEncode("Quotation marks \" are encoded but not safe in case of single quoted attributes '"); '")
    // Quotation marks &quot; are encoded but not safe in case of single quoted attributes &#39
  • JavaScript encoding
    System.Text.Encodings.Web.JavaScriptEncoder.Default.Encode("Quotes ', \" are encoded. HTML-sensitive characters such as <, >, & are also encoded.'");.'")
    // Quotes \u0027, \u0022 are encoded. HTML-sensitive characters such as \u003C, \u003E, \u0026 are also encoded.\u0027
  • JSON encoding
    System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping.Encode("Quotation \" are escaped and HTML-sensitive characters such as <, >, & are not encoded");ed")
    // Quotation \" are escaped and HTML-sensitive characters such as <, >, & are not encoded
  • URL encoding
    System.Net.WebUtility.UrlEncode("Spaces and question mark?")
    // Spaces+and+question+mark%3F

Input sanitizing

What is that?

When the user input is not supposed to contain any active content, such as HTML markup, CSS styles, or JavaScript), you need to sanitize it to either remove or encode such content, including stripping HTML tags and attributes. The main reason for that is to prevent possible XSS issues.

What to do?

The preferable way to do that is to use the white-list approach by explicitly enlisting what is allowed. The exact logic and list of allowed content varies as per the application basis. It is a relatively complex task to implement safe input sanitation, which is why it is generally preferable to use the existing frameworks and libraries. For instance, for ASP.NET it might be HTML Agility Pack, an HTML parser the required sanitation logic can be implemented with.

Examples

User can leave comments containing some simple HTML tags, e.g., <strong>.

Comment before sanitation:

<strong>Important</strong> comment <script>alert(1)</script>

Comment after sanitation:

<strong>Important</strong> comment alert(1)

Secure handling of arbitrary input data

What is that?

You also need to validate the user input according to business rules. Validation might be performed on the client side and absolutely must be performed on server side. The client-side validation brings enhanced user experience, although it is not secure enough by itself. The server-side validation is the one which brings security in place and should always be done, irrespective of the client-side validation. Not all data can be validated though. For example, the system might use a message that comes, say, from an external system. If there is no predefined list of such messages or validation rules, such an input is actually an arbitrary one and should be handled specifically. Failing to do makes the system open to threats, such as phishing.

What to do?

  • User input validation

    Application accepts error message as a part of the URL:

    https://some.example?error=<script>alert(1)<sript>

    This message then might be displayed as a part of HTML:

    <p>Error happenend: {error}<p>

    Note: In this case, system is also vulnerable to XSS, which can be mitigated by proper encoding.

    Since the message in this case is arbitrary, the system is opened to phishing. E.g.:

    https://some.example?error=Please follow https://ssome.example/pwd_change for password change

    If the set of possible error messages is known, it would be more secure to transfer error codes using enumeration:

    https://some.example?errorType=1

    The application logic then maps the error code against the predefined error message, which is shown to the end user.

  • Special treatment of the data that did not pass validation
    When arbitrary input and no control over the data, you should explicitly emphasize that this data is external and was not validated.

    Example

    Continuing our previous example, when an error message comes from a third-party system, e.g., when it is integrated, a message may be displayed as a part of the user interface, explaining that the source of this message is extremal and is displayed ‘as is’.

Request legitimacy validation

What is that?

The goal is to ensure that the request originates from a valid client. The main reason for performing it is to mitigate various threats, such as CSRF and phishing.

What to do?

  • Anti-forgery tokens
    Each request that changes data on the server must include a unique user session bound token. The token value should be arbitrary or otherwise encrypted.

    Example

    Potentially vulnerable form:

    <form action="/change_email">
      <label for="email">Email:</label><br>
      <input type="text" id="email" name="email" value="John">
      <input type="submit" value="Submit">
    </form>

    Form with a token included as a hidden field:

    <form action="/change_email">
      <label for="email">Email:</label><br>
      <input type="text" id="email" name="email" value="John">
      <input type="hidden" name="antiforgerytoken" value="GklQxnJiLk6LM4MMYZL5BQ==">
      <input type="submit" value="Submit">
    </form>

    For each request, the web application should validate the token on the server side.

  • SameSite attribute for session cookie
    If a cookie is used to store the user session identifier, we highly recommend setting the SameSite session cookie attribute to either Strict or Lax. This will effectively prevent sending authenticated requests from any origins other than the web application itself.

    Example

    <configuration>
      <system.web>
        <httpCookies sameSite="Strict" requireSSL="true" />
        <sessionState CookieSameSite="Lax" />
      <system.web>
    <configuration>
  • Action confirmation by the end user
    Any option that requires end-user confirmation of sensitive actions through a UI dialogue or 2FA would greatly reduce the risk of threat. However, since it requires additional actions from the end user, it might make sense to use this option for the most sensitive operations only.
  • Idempotent GET request
    HTTP GET requests must not change any data on the server. It is mainly due to the fact that GET requests can be forged much more easily than e.g. POST requests, while it is also much more complicated to secure such requests when they change the server state. Even using an anti-forgery token delivers less security in this case, since GET requests do not have any body, all their parameters are a part of an URL, , which adds to the risk of token leakage through browser history or logs.
  • Origin validation
    HTTP Origin and Referer header validation can be used as an auxiliary method to the techniques we covered above; it cannot be used as a primary one, though, as the value can easily be spoofed or can quite frequently be empty, e.g. due to VPN or firewall.

Framing control

What is that?

You should enforce an explicit policy regarding whether the web application or any of its parts may be displayed in a frame (<iframe> HTML tag), and when that may happen. The main reason for that is to protect the application against clickjacking attacks

What to do?

  • Content security policy (CSP)
    You can use the Frame-src directive of the CSP HTTP header to configure how the application should be displayed in iframe.

    Examples

    When no framing is allowed:

    Content-Security-Policy: frame-src: ‘none’

    When framing is allowed only for the resources of the same origin:

    Content-Security-Policy: frame-src: ‘self’
  • X-Frame-Options HTTP header
    This is not a standard HTTP header and much less flexible than CSP, but it still can be used in some cases, e.g. for browsers that do not support CSP.

    Examples

    When no framing is allowed:

    X-Frame-Options: DENY

    When framing is allowed only for the resources of the same origin:

    X-Frame-Options: SAMEORIGIN 

HTTP security headers

What is that?

There are various HTTP headers that can be used to configure security related aspects of HTTP communication.

What to do?

  • Content-Security-Policy
    This header provides means to configure which content from which sources the web application can use, when the application can be displayed in a frame, how HTTPS is used, and other security related behavior scenarios. The stricter the CSP policy is, the better. Various CSP directives provide means to protect the app against various attacks; for instance, the script-src directive is crucial for protection against XSS, while the frame-src directive helps fight clickjaсking.

    Examples

    • Allowing scripts, styles, fonts, and embedded content from the files fo the same origin and denying inline scripts and styles
    • Denying framing
    • Allowing form submission only from the same origin
    • Enforcing all requests from the web application pages through HTTPS only.
    Content-Security-Policy:  style-src 'self'; script-src 'self'; object-src 'self'; img-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests;
  • Permissions-Policy (ex-Feature-Policy)
    This header provides means to configure which client APIs can be used by the web application. This can be especially handy to restrict using such sensitive APIs as camera, microphone, etc.

    Example

    You can deny using the microphone, camera, and payment APIs and allow location for the requests coming from the same origin as the web application:

    Feature-Policy: camera: 'none'; microphone 'none'; payment:'none'; geolocation 'self’
    Permissions-Policy: camera=(), microphone=(), payment=(), geolocation=(self)
  • Strict-Transport-Security
    This HTTP header helps enforce using HTTPS and can be especially useful in dealing with data leakage and man in the middle attacks (see Using HTTPS for more details).
  • Referrer-policy
    This HTTP header helps prevent data leakage due to redirects (see Preventing excessive and sensitive data leakage for more details).
  • X-Frame-Options
    This header provides means to define the framing policy (see Framing control for more details).
  • X-XSS-Protection
    This HTTP header configures how the browser should use browser specific anti XSS mechanism (if any): use none, block suspicious requests, or try to remove suspicious markup.

    Example

    Blocking pages containing suspicious (XSS wise) markup:

    X-XSS-Protection: 1; mode=block
  • X-Content-Type-Options
    This one is useful to fight attacks related to the files being executed by the browser. This may come in especially handy for web applications that allow file storage (see File storage and access management for more details).
  • HTTP headers should not be single or main line of defense
    How effective the HTTP headers mentioned above may be depends solely on the browser support. Thus, regardless of how much useful they may be, they should not be the only and main means of protection against the security threats.

Session management

What is that?

The user session mechanism allows web applications to recognize requests from the previously authenticated user, thus removing the need to re-enter user credentials for each request. A common approach is assigning each successfully logged in user a unique session identifier, which is then sent with each request from that user. Security wise, it is important to make the session mechanism secure against such attacks as session hijacking or session fixation.

What to do?

  • Session identifier should be random (not easily guessable)
    Session identifier should be random, not easily guessable or enumerable.
  • Session rotation
    After each successful login, a new session identifier should be issued for the user; the previous one should not be valid anymore.

    Example

    To prevent repeatedly using the session identifier in ASP.NET, the session cookie is explicitly invalidated:

    Session.Abandon();
    Response.Cookies.Add(new HttpCookie("ASP.NET_SessionId", ""));
  • Secure session cookie
    Use these attributes to make the session cookie more secure:
    • HttpOnly
      Helps prevent session identifier leakage via client-side code.
    • Secure
      Prevents cookie transmission via plain HTTP.
    • SameSite
      The Strict and Lax values prevent session cookie transmission with requests originated not from the web application, thus greatly reducing the CSRF risk.
    • No domain property
      If this property is set, it makes the cookie accessible to all sub-domains, which is not safe unless all subdomains are under control of the web application owner.

    Example

    Cookie related configuration in ASP.NET web.config file:

    <system.web>
    ...
      <httpCookies sameSite="Lax" requireSSL="true" httpOnlyCookies="true" />
      <sessionStatecookieSameSite="Lax" />
    ...
    <system.web>
  • Avoid session identifier transmission via URL
    Some web applications might transmit session identifier via query string parameter. If session identifier is not encrypted, this might lead to session hijacking, e.g. via logs or browser history. Cookie is the preferable way to store and transmit the session identifier.

Using HTTPS

What is that?

Any web application that uses plain HTTP is vulnerable to such attacks as man in the middle and content sniffing, since the data is transmitted in an unencrypted form.

What to do?

  • HTTPS redirection and HTTP rejection
    All HTTP requests to web application should be redirected to HTTPS. This works for clients that can handle redirects (e.g. browsers).
  • Strict-Transport-Security HTTP header
    This one tells the browser that the resource should be accessed via HTPS only. After getting this header, all subsequent requests will be made via HTTPS.

    Example

    Strict-Transport-Security: max-age=63072000; includeSubDomains
  • HTTP strict transport security (HSTS) with preload
    The browser cannot determine in advance whether the resource should be accessed exclusively via HTTPS. Thus, the first request to the resource can still be made via plain HTTP and thus, be vulnerable. To mitigate this risk, the resource can be added to the preload list and the preload directive can be used (see https://hstspreload.org for more details).

    Example

    Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
  • HTTPS only API
    Clients other than browsers might not follow HTTPS redirect (e.g. via HSTS), which is why it is generally advisable to also deny any HTTP requests.

Secure cookies

What is that?

Cookies might contain sensitive information, such as the user session identifier. If such cookies are transferred through plain HTTP or can be accessed via the client side code (JavaScript), this might make the system vulnerable to some attacks, including man in the middle and session hijacking.

What to do?

  • HttpOnly flag
    Makes the cookie not accessible for the client side code. This flag should be set for all cookies that are not supposed to be accessed via JavaScript.
  • Secure flag
    This flag will prevent cookie transmission through plain HTTP. Secure HTTPS protocol should always be used, hence this flag should always be set as well.
  • SameSite attribute
    This attribute should be set to either Strict or Lax. This will prevent cookie transmission with requests originating not from the web application, thus greatly reducing the risk of CSRF.

Example

An HTTP header setting the session identifier cookie with all the flags and attributes mentioned above:

Set-Cookie: SessionId=fxy40phg0wejmfpnlwfwevmi; Secure; HttpOnly; SameSite=Strict

Configuration in web.config for ASP.NET:

<configuration>
 <system.web>
  <httpCookies sameSite="Strict" requireSSL="true" />
  <anonymousIdentification cookieRequireSSL="true" /> 
  <authentication>
    <forms cookieSameSite="Lax" requireSSL="true" />
  </authentication>
  <sessionState cookieSameSite="Lax" />
  <roleManager cookieRequireSSL="true" />
 <system.web>
<configuration>

File storage and access management

What is that?

When an application provides any file uploading options, it should be implemented with the security in mind to prevent any potential functionality abuse that might lead to various types of attacks, such as remote command execution.

What to do?

  • File name validation and sanitizing
    Names should be validated to at least exclude extensions, which might lead to code execution (e.g. .bat, .cmd, .vbs). File names should be generated by the system and should be random.
  • Restriction of allowed file extensions
    File types allowed for upload should be restricted, preferably through using the whitelist approach.
  • X-Content-Type-Options HTTP header
    This header helps prevent file execution when it is served by the browser.

    Example

    X-Content-Type-Options: nosniff
  • Different domain for file storage
    Files should be stored in a domain that is different from the one the web application uses. This should not be a subdomain but rather a completely different one. You may want to apply the strictest possible content security and permission (feature) policies to such a domain.

    Example

    // Web application
    https://mywebapp.com
    
    // File storage
    https://filesmywebapp.com

Preventing excessive and sensitive data leakage

What is that?

You should ensure that the data, such as personal data or data providing details about technical side of web application and its internals (e.g. original error messages and stack traces), are not leaked into client.

What to do?

  • Exception shielding
    This technique implies explicit control over the error messages sent to the client. The error messages should never be sent as is, since they might contain sensitive technical information, most likely useless for the client but quite useful for the potential attacker. You should have a predefined set of client error messages that are useful for the client and contain no irrelevant data, such as internal identifiers, information about internal data storage structure, stack traces, etc.

    Example

    Application_Error handler in Global.asax file used for an ASP.NET web application:

    void Application_Error(object sender, EventArgs e)
    {
       Response.Redirect("ErrorPage.aspx", true);
    }

    Exception handler used for an ASP.NET Core web application:

    public void Configure(IApplicationBuilder app)
    {
       app.UseExceptionHandler("/Error");
    }
  • URL should not contain unencrypted sensitive data
    URLs should not contain any sensitive data, since such data might quite easily get leaked via browser history, Referer HTTP header, or server logs. If you absolutely need to transfer such data in an URL, it should be encrypted.
  • Referrer-Policy HTTP header
    This HTTP response header provides means to restrict the data sent in the Referer HTTP header, thus preventing leakage of sensitive data via URL p>

    Example

    Referrer-Policy: no-referrer
  • Exclude HTTP response headers with technical details about web application implementation
    Some frameworks by default add some HTTP response headers with technical details, such as framework version, server type and version, etc. Such HTTP headers, if not important for the end user, should be excluded from the response.

    Example

    HTTP headers for ASP.NET and IIS sent with HTTP responses by default:

    Server: Microsoft-IIS/7.5
    
    X-AspNetMvc-Version: 3.0
    
    X-AspNet-Version: 4.0.30319
    
    X-Powered-By: ASP.NET

    Server HTTP header being removed via URL rewrite rule:

    <rewrite>    
      <outboundRules rewriteBeforeCache="true">
        <rule name="Remove Server header">
          <match serverVariable="RESPONSE_Serve-r" pattern=".+" />
          <action type="Rewrite" value="" />
        </rule>
      </outboundRules>
    </rewrite>

    You can remove X-Powered-By via IIS configuration (HTTP response headers item), while X-AspNetMvc-Version can be removed by placing this code in the Application_Start method in the Global.asax file.

    MvcHandler.DisableMvcResponseHeader = true;

    Finally, you can remove the X-AspNet-Version HTTP header via configuration in in the web.config file:

    <system.web>
      <httpRuntime enableVersionHeader="false" />
    </system.web>

External redirect under strict control

What is that?

In case your web application can redirect the user to external resources and such redirects are not validated, this exposes the system to phishing and open redirects, especially when the URL of an external resource is a part of user input (e.g. the URL query string parameter).

What to do?

  • Validation of redirects
    Redirect addresses should always be validated. If that is not possible for some reason, the user should be notified about such redirect via a page or a dialogue requiring explicit user confirmation.

Safe input usage in SQL queries

What is that?

If any SQL query accepts parameters, those should be encoded in SQL context before usage. Otherwise, system will get exposed to SQL injections. This is especially important for the SQL queries that use direct user input (form fields, URL query string, etc.) as a part of a query.

What to do?

  • Query parametrization
    Avoid using simple string concatenation with any user input other than raw. All input should be encoded either manually or used in a form of a parameter. There are many frameworks that provide such options; for example, Microsoft Entity Framework is not vulnerable to SQL injection by default, since all user input is converted into SQL parameters. At the same time, it is possible to create raw queries, in which case developer is responsible for SQL query parametrization.

    Example

    Vulnerable SQL query:

    using var connection = new SqlConnection(ConnectionString);
    connection.Open();
    string sqlExpression = $"SELECT * FROM Users WHERE Name='{name}'";
    var command = new SqlCommand(sqlExpression, connection);
    using var reader = command.ExecuteReader();

    Parametrized SQL query:

    using var connection = new SqlConnection(ConnectionString);
    connection.Open();
    string sqlExpression = $"SELECT * FROM Users WHERE Name=@Name";
    var command = new SqlCommand(sqlExpression, connection);
    command.Parameters.Add(new SqlParameter("@Name", SqlDbType.NVarChar) { Value = name });
    using var reader = command.ExecuteReader();
  • Least privilege principle
    This principle helps mitigate the consequences of possible SQL injection. For instance, using different accounts for queries (data read) and commands (data change) can prevent data change or destruction even in case of successful SQL injection.

Summary

This article is by no means exhaustive security manual. There are a lot of more exotic and sophisticated types of attacks; no system is 100% percent safe and can be attacked or abused one way or another. That said, the techniques described in this article provide a decent level of protection from the most known security threats and make your web application reasonably secure.

You Might Also Like

Blog Posts Techniques for Handling Service Failures in Microservice Architectures
October 13, 2021
This article may be useful for those who have suffered from the instability of external APIs: what are the strategies for handling failures and which way we found to deal with the problem.
Blog Posts How to Stop Getting Lost among Multiple Figma Mockups
August 20, 2021
In a large project, one does not only need to remember all mockup versions, but also store them in a file. The article tells about versioning tools in Figma.
Blog Posts Creating Truly Reliable Plugins Based on the Managed Add-In Framework
July 06, 2021
For one of our projects, Managed Add-In Framework (hereinafter MAF) became the best option for implementation of diverse customer requirements. Here are some nuances of working with MAF.