Our great sponsors
- Appwrite - The open-source backend cloud platform
- InfluxDB - Collect and Analyze Billions of Data Points in Real Time
- Onboard AI - Learn any GitHub repo in 59 seconds
-
DOMPurify
DOMPurify - a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG. DOMPurify works with a secure default, but offers a lot of configurability and hooks. Demo:
body, input.value property, or body are all different). If you need to insert untrusted input into raw HTML, use a well-tested sanitizer such as DOMPurify.
Setting a strong Content Security Policy without
unsafe-inline
orunsafe-eval
in thescript-src
ordefault-src
directives is an effective defense-in-depth) measure to prevent modern browsers from executing attacker code even if the attacker is able to insertelements into the page.
3. HTTP API injection
RESTful APIs, GraphQL, and other HTTP-based APIs are ubiquitous. When a web application makes an API call to another service, injection vulnerabilities are possible when that request includes untrusted input.
Consider a contrived example in which a web app integrates with a payments service that has a REST API endpoint for creating a subscription:
POST /subscriptions/{product_id}?price_usd=
whereprice_usd
is optional, and a pre-configured price is used if omitted. If an attacker controls the value ofproduct_id
and passes a value ofdesired_product_id?price_id=0
, the web app would end up making a request toPOST /subscriptions/desired_product_id?price_id=0
, which would allow the attacker to sign up for a free subscription.In JavaScript, the standard way to sanitize untrusted inputs in URL paths is
encodeURIComponent
, which replaces problematic characters such as?
and/
with safe percent-encoded sequences. When inserting untrusted input into URL query parameters,new URLSearchParams(queryParams)
provides a convenient, safe interface for building a query string from a JavaScript object of key-value pairs.4. Shell injection
Backend APIs sometimes need to execute external commands on the machine where they run. Consider an API that performs WHOIS lookups for a requested domain by executing the
whois
command locally.Consider the following vulnerable Node.js code:
const whois = child_process.execSync(`whois ${whoisRequest.domain}`);
If an attacker can pass the domain
reddit.com && rm -rf /
, the backend will execute the commandwhois reddit.com && rm -rf /
. Thechild_process.execSync
function passes the command string to the shell (/bin/sh
by default on Linux), which parses&& rm -rf /
as a subsequent command to wipe the filesystem.To avoid this issue, never pass untrusted input to a shell. Instead, use an interface such as
child_process.execFileSync
that executes a specific binary (which shouldn't be a shell!) and passes arguments as an array:const whois = child_process.execFileSync("whois", [whoisRequest.domain]);
Now, even if the user passes a domain
reddit.com && rm -rf /
, that entire string will be passed as the command-line argument towhois
, which will exit with an error but will not cause any harmful side-effects. Perhaps an even better solution would be to use a library to perform WHOIS queries without needing to execute a separate command.Astute readers may point out that validating the domain against a regex would also likely prevent shell injection in this case. However, avoiding the possibility of shell injection by using a safe interface that keeps untrusted input away from a shell's command parser is a more robust solution that avoids shell injection in all cases.
5. Path traversal
Finally, a path traversal vulnerability arises when an untrusted input is inserted into a filesystem path, which can cause the wrong file to be read or even written. Consider a backend API that reads a file at the path
/teams/${team_id}/${report_name}.csv
. If an attacker controls the value ofreport_name
but notteam_id
, they could pass areport_name
of../other_team_id/private.
This would cause the file/teams/team_id/../other_team_id/private.csv
(resolved to/teams/other_team_id/private.csv
) to be read, leaking data from a different team.To avoid path traversal vulnerabilities, never use untrusted input in file or directory names. It's safest always to control the names of files and directories, including IDs that you generate and control (e.g., UUIDs, KSUIDs, etc.). If the name of a file or directory absolutely must be derived from untrusted input, consider hashing it (e.g., using SHA-256) or at least encoding it into a format that doesn't include dots or slashes (e.g., URL-safe base64).
Know of good Node.js libraries for avoiding injection vulnerabilities? Let folks know in the comments!
-
The most robust protection against XSS is to use a client-side framework like React that lets you intuitively render untrusted inputs on a page without risking XSS. Be sure to avoid using dangerouslySetInnerHTML, which bypasses XSS protections.
-
Appwrite
Appwrite - The open-source backend cloud platform. Add Auth, Databases, Functions, and Storage to your product and build any application at any scale while using your preferred coding languages and tools.
-
The upcoming Sanitizer API - kinda like a native DOMPurify that provides el.setHTML() and Document.parseHTML()