According to OWASP, cross-site scripting (XSS) is one of the most widespread security risks in web applications. It occurs when an attacker injects malicious code into the client-side of an application. This normally happens when an application accepts untrusted (or user-supplied) data on a web page without escaping or validating it properly.
A successful XSS attack occurs when the browser executes the malicious scripts from in a manner determined by the threat actor. Generally, XSS attacks will require some form of interaction from the victim, either through social engineering or request to visit a particular page.
If an attacker exploits XSS vulnerabilities, they could perform malicious actions like account tampering, data theft, remote control, or even malware distribution.
- Validating and sanitizing input from users to ensure it only contains acceptable characters that cannot be used to launch XSS attacks.
- Using safe methods such as innerText for manipulating the DOM. Unlike innerHTML, this method escapes dangerous content, thereby preventing DOM-based XSS.
- Using frameworks that automatically escape XSS vulnerabilities by design. For instance, Node JS has the encodeURI and encodeURIComponent global functions that help prevent XSS attacks. You should also consider using advanced packages like the xss-filters .
The following code snippet shows how to use XSS filters from the npm package in express applications.
SQL databases are vulnerable to injection attacks where query parameters are exploited to execute arbitrary instructions.
Below is an express framework router that is vulnerable to an SQL injection attack:
In the example above, the application gets user IDs from URLs and retrieves the corresponding email address by querying the database. Two things are wrong in the code snippet.
First, the database query is built using a string concatenation. The second issue is that the user input is concatenated to the query instead of being handled as untrusted data.
An attacker might craft a query string id parameter in such a way that it retrieves all tables or writes into the database. For instance, when the attacker supplies these string parameters:
This would result to a query like this one:
When this query is executed successfully, it would pull the list of all tables in the databases. An attacker can then retrieve any information they want.
To mitigate SQL injections, developers should always perform proper input validation. When input from the user fails the validation checks, the SQL query is not executed.
Another way of preventing SQL injection is using parameterized queries or prepared statements instead of concatenations. Parameterized queries are used to abstract the SQL syntax from the input parameters.
In the example below, parameterized queries are used to prevent potential SQL injection attacks.
Sensitive cookie exposure
The client-side script on every browser can access all the content returned by an application to the server. This includes cookies that often contain sensitive data such as session IDs.
Exposing session identifiers, whether in URLs, error messages, or logs is a bad practice that opens up an application to security issues like cross-site request forgery(CSRF), session hijacking and session fixation.
To prevent this, developers must consistently use HTTPS and implement HTTP-Only cookies. The HTTP-Only attribute in cookies tells the browser to prevent cookie access through the DOM. By doing this, client-side script attacks are prevented from accessing sensitive data stored in cookies.
Another way of securing user sessions is opting by per-requests as opposed to using per-session identifiers. Any time the client requests privileged access permissions, terminate the session and re-authenticate them before granting access.
Here is an example cookie that uses Express – Node.js and stores session data on the SQLite database using the connect-sqlite3 package. Notice how we use HTTP only secure cookies.
Components with known vulnerabilities
There are tons of security risks associated with the use of vulnerable application components. For instance, vulnerabilities in some libraries or other elements such as browser plugin code are a security loophole in your applications.
To ensure the components you’re using do not compromise your application’s security, always keep up with the current versions of all. Do not rely on unpatched components for building or integrating into your web application.
Instead, exercise caution and inspect every component of your application. You do not want to use any broken code that comes your way, or even worse, code with intentionally malicious scripts.
Putting it all together
- Never trust user input
- Use proper encoding/escaping
- Sanitize user input
- Define a content security policy
- Set secure cookies
- Secure API keys on the client-side
- Encrypt data transmitted between the client and the server
- Use secure components and APIs for development
- Use updated libraries and frameworks
- Conduct regular scans on your codebase
Following these best coding practices is usually the first step for securing your web applications.