Security Fundamentals for Web Applications

This article covers the essential topics for securing web applications.

Broken Access Control

Broken access control is when the application does not ensure that the user is authenticated and is authorized to access data or perform a function.

An example is if a user can access a different user data via an application, or access functionality of a higher privileged role.

Two important definitions in this context:

  • Authentication: If a user proves they are who they say they are, like entering credentials to access a website
  • Authorization: If a user has got enough privileges to access the data or perform an operation

Protections against broken access control

Always validate if a user is authorized for the action it’s requesting to perform.

Make use of indirect object references to obfuscate the structure of your data to attackers. For example, when users give sequential IDs (/profile/67), it’s very easy for an attacker to guess the previously registered user is 66 and to iterate through the existing users to explore attack vectors. Using random digits makes it a lot harder to identify users.

JSON Web Token (JWT) is a JSON-based open standard for creating access tokens that assert several claims. For example, a server could generate a token that has the claim “logged in as admin” and provide that to a client. The client could use that token to prove it logged in as admin.

The tokens are signed by one party’s key (usually the servers) so that both parties can verify that the token is legitimate. JWT libraries can help verify token signatures either with a shared secret for symmetric signature algorithms or with public keys for asymmetric signature algorithms.

Each JWT is composed of three parts encoded with base64url. The header, payload, and signature.

Cryptographic Failures

Cryptographic failures can lead to sensitive data exposure. One of the most common examples of this is when a website does not use TLS security protocol from transferring data to and from users.

When a website does not use a secure protocol to transfer data if the communication is intercepted the data can be exposed.

Always make sure to use encryption in transit, and that your encryption methods stay up to date to address the latest vulnerabilities.

SQL Injection

A SQL injection attack happens when a bad actor is allowed to inject code into a program. This often leads to data theft or loss of data. Any application input that uses SQL to lookup data can be vulnerable to SQL Injections (SQLi) attacks

An example of how an SQLi attack can be performed. Note that syntax may change between different SQL engines;

query = "SELECT * FROM users WHERE name='" + user + "' AND password='" + password + "'";

An attacker can enter a password of (‘X OR ‘0’ = ‘0’’), which will evaluate to:

query = "SELECT * FROM users WHERE name='john’' AND password='X’ OR ‘0’ = ‘0’;

This expression will always pass because of the (OR ‘0’ = ‘0’), authenticating the attacker.

Preventing SQL injection attacks

Validate inputs before execution; if a function is expecting a number to be passed, for example, enforce that only a number can be passed.

Enforce the least privilege for execution; any execution should always happen with the minimum privileges that it requires. If an attacker circumvents any protections, then the least privileges reduce the attack’s blast radius.

User parametrized queries; These perform input validation and escaping of parameters that are being passed to SQL preventing would-be attackers from manipulating the underlying queries.

Reflected Cross-Site Scripting

All Cross-Site Scripting (XSS) vulnerabilities involve an attacker injecting code that is interpreted on the browser. The apply the best fix, first one has to understand and categorize where is the data interpreted as code.

A Reflected Cross-Site Scripting is a type of vulnerability where an attacker can inject data into a page response, which in turn is interpreted as code by the browser.

This attack normally exploits parts of the request data which are included in the response HTML. Typically these are form data, URL parts, query strings, etc

Remediating against Reflected Cross-Site Scripting

To avoid Reflected Cross-Site Scripting attacks you should never put data that be returned in the following elements:

  • Script text content
  • Style text content
  • Comments
  • Tag names
  • Attribute names
  • Unquoted attribute values
  • Any nested combination of the above

The only safe locations are:

  • HTML text content
  • Quoted attribute value

Both of these contexts require additional measures to prevent an attacker to break out of those contexts. One way of achieving this is to apply HTML encoding to any user input that will be returned to the page.

Stored Cross-Site Scripting

A Stored Cross-Site Scripting vulnerability happens when attacker-controlled data is mixed with the HTML on the server side.

One common example is a website that allows users to submit links. HREF attributes can contain Javascript, without protections an attacker can exploit this to inject Javascript code which will be stored on the server, and executed on the browser when an unsuspecting user browses a page that displays that element.

Protecting against Stored Cross-Site Scripting

Ensure that any user-submitted content that is stored and displayed to other users is properly validated and sanitized.

For example, one potential way of addressing the vulnerability above is to ensure that the link that is saved is valid, and starts with either the HTTP or HTTPS protocol.

DOM-Based Cross-Site Scripting

A DOM-based Cross-Site Scripting attack happens when data controlled by an attacker is interpreted by the client JavaScript as code in the DOM API.

An example of a DOM element that can provide an attack surface is the innerHTML property which enables HTML code to be injected via the DOM into page elements. An attacker can exploit this to inject malicious code into the user’s browser and execute it on the client.

Remediating DOM-Based Cross-Site Scripting

One way of remediating this type of attack is by avoiding harmful code such as element.innerHTML instead of using element.textContent.

Command Injection

A Command Injection vulnerability is one where an attacker can pass data that is executed in the underlying OS as a command. Through this vulnerability, an attacker can gain access to everything that the process has got permission to.

An example is an application that takes a filename from user input. If the application suffixes it with the touch command into “touch filename” and evaluates the expressions then it is open to a Command Injection attack. An attacker can send a file name as “test; rm -f *;” and deleted all the files on the directory, given that the process executing the command has permissions

Remediating Command Injection attacks

It’s best practice to avoid using commands that just evaluate an arbitrary expression. When possible always use libraries and commands that execute only the desired task.

Taking the example above, a much preferable implementation is to use the respective language/library method to create files, which will have a narrower scope and have validations built-in.

When this is not possible, then it is required to whitelist what can be passed and to parse for special characters, such as the semicolon on the example above.

Insecure Design

Insecure Design covers vulnerabilities that originate from architecture or design problems.

A good example of this is an online banking application that allows users to download their bank statements by PDF. Generating a random link for the download is a good practice, but further to this the application also needs to ensure the requester has got valid authorization to perform that action.

Remediating Insecure Design

To remediate an Insecure Design vulnerability one always has to take a shift left approach, going back to the design and ensuring that the preventive measures are in place at the lowest level of the application.

XML External Entities (XXE)

XML External Entities attacks exploit the usage of XML parsers loading data from another source.

Like the previous vulnerabilities, attackers find opportunities under this vulnerability when they can pass code as data, so the thing to watch here is when the data is part of the server response.

As an example, suppose that a site contains a search that uses AJAX to search for users. This would send an XML payload to the server as this:

<search><userSearch>John Doe</userSearch></search>

An attacker could proxy the request and manipulate the UserName part of this search such has:

<!DOCTYPE test \[ <!ENTITY passwd SYSTEM “file:///etc/passwd” > \]>

<search><userSearch>&passwd;</userSearch></search>

Cause the server to output the contents of /etc/passwd

Preventing XXE attacks

The best line of defense against XML External Entities attacks is by disabling External Entities altogether.

Additionally, ensure that the relevant XML parser you are using is always up to date with the latest

Security Misconfiguration

Application misconfigurations can often lead to vulnerabilities that can let users gain access to resources that they should not have access to, leave systems unprotected or leak confidential data to an attacker.

Protecting against security misconfigurations

  • Don’t disclose any information about the underlying server and systems. For example, strip the server information from response headers
  • Don’t include verbose or debug logs on client responses
  • Don’t output directory listings
  • Protect administration portal URLs
  • Always change default credentials to secure passwords and if possible enable two-factor authentication
  • Keep server software up to date with the latest security patches, including any third-party libraries

Vulnerable and Outdated Components

When building software, developers often rely on third-party components and libraries. These components bring additional risks to an application because once their vulnerabilities become known, they introduce further attack vectors.

Many organizations are slow with releasing updates to the software, if the slow release cycle extends to patching then such an organization makes an easy target for attackers by running vulnerable components.

Protecting against Vulnerable and Outdated Components

Keeping your software and dependencies patched and up to date is the key to stopping this vulnerability.

Tools like Renovate can go a long way in helping achieve this goal by scanning GitHub repositories for libraries and raising PRs when new versions are available.

Identification and Authentication Failures

Most websites and applications nowadays require users to identify themselves to access data or advanced functionality. To achieve this, it’s not sufficient to tell who we are, often we need to provide proof of our identity (password). This is authentication.

After successful authentication, applications keep track of the user through requests, this is called a session.

When applications expose the session token to the client, this can potentially be hijacked by an attacker to impersonate the user whose original session was from.

Protecting against Identification and Authentication Failures

Applications should always validate on the server side that the request comes from a valid user, as client-side data can be tampered with.

Software and Data Integrity Failures

Software and Data Integrity Failures (aka Insecure Deserialization) are vulnerabilities that arise when code and/or infrastructure do not protect against integrity violations.

Applications often need to obtain complex data from the client. The process of converting the data to a portable format such as JSON or XML is called serialization. The reverse, converting data from transport to objects native to a language is called deserialization.

When the integrity of data is not validated, attackers can exploit the deserialization process to abuse an application’s logic to execute malicious code.

How to prevent Software and Data Integrity Failures

  • When possible, don’t use serialization/deserialization
  • Always perform validations to the data
  • Sign the object to detect if they have been tampered with
  • Run the executing code with the lowest required privileges

Security Logging and Monitoring Failures

These don’t represent a vulnerability on their own, but insufficient logging and monitoring can prevent can hinder the capacity of detecting malicious activities earlier, and consequently these from being mitigated.

Logging

Logs can record activities against an application. For example, through logging one can see multiple attempts of authentication against an admin portal with failed credentials. These can indicate that an attacker is attempting to brute force credential discovery.

Monitoring

Monitoring can record trends of certain events in an application. For the example above, it’s can record failed login attempts.

Monitoring is always the first stage, the one which alerts that a given event is out of trend.

Server-Side Request Forgery (SSRF)

This type of vulnerability exists when a server takes a user-provided URL and uses it to make a request, without any validation.

An example would be an application that takes user-provided URLs and allows the contents of these URLs to be viewed by the user.

Without validation, this can be used to access malicious URLs controlled by the attacker.

Remediating Server-Side Request Forgery

The best way to prevent this type of attack is by whitelisting what data can be passed to such functions.

References