URL Encoding Security: Preventing Injection Attacks via URL Parameters

URL encoding is usually discussed as a correctness issue — "your URL will be malformed if you do not encode it." But the security implications are more severe. Unencoded user input in URLs is a direct vector for injection attacks.

The Open Redirect Attack

This is the most common URL encoding vulnerability I find in production:

// Vulnerable: accepts any redirect URL
app.get("/redirect", (req, res) => {
  const target = req.query.url;
  res.redirect(target);
  // User visits: /redirect?url=https://evil.com/phish
  // They get redirected to a phishing site that looks like yours
});

The fix: validate and encode the redirect URL:

// Safer: validate and encode
const allowedDomains = ["mysite.com", "cdn.mysite.com"];

app.get("/redirect", (req, res) => {
  let target = req.query.url;

  // Validate
  try {
    const url = new URL(target);
    if (!allowedDomains.includes(url.hostname)) {
      return res.status(400).send("Invalid redirect domain");
    }
  } catch {
    return res.status(400).send("Invalid URL");
  }

  // Encode the URL properly
  target = encodeURI(target);
  res.redirect(target);
});

On Stack Overflow, open redirect vulnerability questions have 55,000 views and multiple examples of real-world exploits.

SQL Injection via URL Parameters

Even though parameterized queries prevent SQL injection, unencoded URL parameters can still slip through in dynamic query building:

// WRONG: string interpolation with URL parameter
app.get("/users", (req, res) => {
  const name = req.query.name;  // User sends: ?name=Alice' OR '1'='1
  const query = `SELECT * FROM users WHERE name = '${name}'`;
  // SQL injection!
});

But there is a less obvious vector: encoding bypass. If you filter for quotes but the URL encoder double-encodes the value:

// Attempted fix: filter quotes
const name = req.query.name.replace(/'/g, "''");
// User sends: ?name=%27%27%20OR%20%271%27%3D%271
// After URL decode: '' OR '1'='1
// After quote replacement: '' OR '1'='1' (still injects!)

The real fix is never build SQL by concatenation. But for logging or error messages where you display the URL parameter, always encode it:

// Safe for display: encode the value
app.use((err, req, res, next) => {
  console.error(`User searched for: ${encodeURI(req.query.name)}`);
  // Even if the value contains malicious characters,
  // they are encoded and harmless in the log output
});

A Reddit thread on r/netsec about URL parameter injection vectors has over 400 comments.

Parameter Pollution

// What happens when a user sends multiple parameters with the same name?
// ?debug=true&debug=false

// Express: takes the last value
req.query.debug === "false"

// Django: takes the last value
request.GET.get("debug") === "false"

// PHP: creates an array
$_GET["debug"] === ["true", "false"]

// ASP.NET: takes the first value
Request.QueryString["debug"] === "true"

Parameter pollution can bypass security checks:

// Hypothetical admin check
app.get("/admin/users", (req, res) => {
  if (req.query.admin === "false") {
    return res.status(403).send("Forbidden");
  }
  // User sends: ?admin=true&admin=false
  // In Express, req.query.admin === "false" (last value wins)
  // Access denied — correct behavior
  // But in ASP.NET: first value wins → "true" → access granted!
});

On Stack Overflow, HTTP parameter pollution has 30,000 views.

The Defense Checklist

// 1. Always encode user input in URLs
const userInput = req.query.search;
const encoded = encodeURIComponent(userInput);
fetch(`/api/search?q=${encoded}`);

// 2. Validate redirect URLs against a whitelist
function isAllowedRedirect(url) {
  const allowed = ["mysite.com", "app.mysite.com"];
  const parsed = new URL(url, "https://mysite.com");
  return allowed.includes(parsed.hostname);
}

// 3. Use URL constructors, not string concatenation
const params = new URLSearchParams({ q: userInput });
fetch(`/api/search?${params}`);

// 4. Validate parameter types
const page = parseInt(req.query.page, 10);
if (isNaN(page) || page < 1) {
  return res.status(400).send("Invalid page number");
}

Related Searches

  • url encoding security vulnerability
  • open redirect attack prevention
  • sql injection via url parameters
  • http parameter pollution
  • url encoding xss
  • url injection prevention
  • unencoded user input url
  • security url encoding best practices
  • url validation security
  • encodeuri component security

Frequently Asked Questions

Can URL encoding prevent XSS attacks?

URL encoding prevents user input from being interpreted as HTML or JavaScript when it appears in links or redirect URLs. However, the proper defense against XSS is HTML encoding (escaping HTML special characters), not URL encoding. Use both in their respective contexts.

What is HTTP parameter pollution?

Sending multiple HTTP parameters with the same name (like ?debug=true&debug=false). Different frameworks handle this differently — some take the first, some the last, some create an array. This inconsistency can be exploited to bypass security checks.

How do I prevent open redirect attacks?

Validate the redirect URL against a whitelist of allowed domains. Never redirect to an arbitrary URL provided by the user. If you must support dynamic redirects, parse the URL and check the hostname against an approved list.

Should I validate URL parameters on the client or server?

Both. Client-side validation provides immediate feedback. Server-side validation is mandatory. URL parameters can be manipulated by the user (modifying the URL, using tools like curl) regardless of any client-side validation.

Final Thoughts

URL encoding is a security control, not just a formatting concern. Unencoded user input in URLs creates vectors for open redirect, parameter pollution, and injection attacks. The fix is consistently encoding all user-supplied values with encodeURIComponent (JavaScript), quote_plus (Python), or url.QueryEscape (Go) before inserting them into URLs.