Improper Filtering of Special Elements
Description
Improper Filtering of Special Elements is an input validation vulnerability where software receives data from an upstream component but fails to filter or incorrectly filters special characters or elements before passing the data to a downstream component. Special elements include characters that have syntactic meaning in the target context, such as path separators, command delimiters, SQL operators, XML/HTML tags, or escape sequences. When filtering is absent or incomplete, attackers can inject malicious content that is interpreted as commands or markup by downstream processors.
Risk
Improper filtering enables a wide range of injection attacks depending on the downstream context. Path traversal becomes possible when directory separators (../) aren't properly filtered. Command injection occurs when shell metacharacters pass through. SQL injection results from unfiltered quotes and SQL keywords. XSS attacks succeed when HTML/JavaScript elements aren't neutralized. The risk is that attackers can break out of the intended data context to execute commands, access unauthorized resources, or manipulate application behavior. Incomplete filtering (like removing only single occurrences) is particularly dangerous as it gives false confidence while remaining exploitable.
Solution
Implement comprehensive input filtering or encoding appropriate for the target context. Use allowlist validation when possible—accept only known-good characters. If filtering special elements, ensure all instances are handled (use global replacement). Prefer output encoding over input filtering—encode data at the point of use for the specific output context. Use parameterized queries for SQL, DOM methods for HTML, and proper escaping for shell commands. Layer defenses: combine input validation, output encoding, and context-appropriate APIs. Test filtering implementations with various bypass techniques including double-encoding, alternate encodings, and recursive patterns.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Unexpected State - Application reaches unintended state due to improperly filtered input affecting control flow or data. |
| Confidentiality | Scope: Confidentiality Read Application Data - Path traversal or injection attacks may expose sensitive data. |
| Integrity | Scope: Integrity Execute Unauthorized Code - Command or code injection becomes possible when filtering fails. |
Example Code
Vulnerable Code
# Vulnerable: Only removes first occurrence of "../"
#!/usr/bin/perl
my $username = GetUntrustedInput();
# Vulnerable: Missing /g modifier - only removes first occurrence
$username =~ s/\.\.\///;
my $filename = "/home/user/" . $username;
open(FILE, $filename); # Path traversal possible!
# Attack: Input "....//....//etc/passwd"
# After single replacement: "../../../etc/passwd"
// Vulnerable: Incomplete filtering of SQL special chars
<?php
function vulnerable_escape($input) {
// Vulnerable: Only escapes single quote, not double quote or backslash
$filtered = str_replace("'", "''", $input);
return $filtered;
}
$username = vulnerable_escape($_GET['username']);
$query = "SELECT * FROM users WHERE name = '$username'";
// Still vulnerable to injection via double quotes or backslash
// Attack: Input: \" OR 1=1 --
// Vulnerable: Filtering only some HTML tags
public class VulnerableFilter {
public String filterXSS(String input) {
// Vulnerable: Only filters <script> tags
String filtered = input.replaceAll("<script>", "")
.replaceAll("</script>", "");
return filtered;
}
}
// Attack: <img src=x onerror=alert(1)>
// Attack: <SCRIPT>alert(1)</SCRIPT> (case variation)
// Attack: <scr<script>ipt>alert(1)</scr</script>ipt> (nested)
# Vulnerable: Single-pass replacement allows bypass
def vulnerable_filter_traversal(path):
# Vulnerable: Single replacement - nested sequences survive
filtered = path.replace('../', '')
return filtered
# Attack: "....//....//etc/passwd"
# After filtering: "../../etc/passwd" - traversal still works!
// Vulnerable: Filtering null bytes but not encoded forms
void vulnerable_filter_null(char* input, char* output) {
int j = 0;
for (int i = 0; input[i] != '\0'; i++) {
// Vulnerable: Only filters literal null byte
if (input[i] != '\0') {
output[j++] = input[i];
}
}
output[j] = '\0';
// Doesn't filter %00 or other encoded nulls
}
Fixed Code
# Fixed: Use global replacement and recursive filtering
#!/usr/bin/perl
my $username = GetUntrustedInput();
# Fixed: Use /g for global replacement
# And loop until no more matches
my $prev;
do {
$prev = $username;
$username =~ s/\.\.[\\/]//g; # Handle both / and \
} while ($username ne $prev);
# Better: Validate against allowlist
if ($username !~ /^[a-zA-Z0-9_]+$/) {
die "Invalid username";
}
my $filename = "/home/user/" . $username;
open(FILE, "<", $filename) or die "Cannot open file";
// Fixed: Use parameterized queries instead of filtering
<?php
function fixed_query($username) {
$pdo = new PDO($dsn, $user, $password);
// Fixed: Use prepared statement - no manual escaping needed
$stmt = $pdo->prepare("SELECT * FROM users WHERE name = ?");
$stmt->execute([$username]);
return $stmt->fetchAll();
}
// If filtering is needed, use comprehensive approach
function fixed_escape($input) {
// Use database-specific escape function
return pg_escape_string($input); // PostgreSQL
// Or: mysqli_real_escape_string($conn, $input); // MySQL
}
// Fixed: Comprehensive HTML filtering
import org.owasp.encoder.Encode;
public class FixedFilter {
public String filterXSS(String input) {
// Fixed: Use proper HTML encoding library
return Encode.forHtml(input);
}
// Alternative: Allowlist approach
public String filterAllowlist(String input) {
// Only allow alphanumeric and specific safe characters
return input.replaceAll("[^a-zA-Z0-9\\s.,!?-]", "");
}
}
# Fixed: Recursive filtering until stable
import os.path
def fixed_filter_traversal(path):
# Fixed: Recursively remove until no changes
while True:
filtered = path.replace('../', '').replace('..\\', '')
if filtered == path:
break
path = filtered
return filtered
# Better: Use canonical path comparison
def fixed_validate_path(base_dir, user_path):
# Resolve to absolute path
full_path = os.path.realpath(os.path.join(base_dir, user_path))
# Ensure it's still under base directory
if not full_path.startswith(os.path.realpath(base_dir) + os.sep):
raise ValueError("Path traversal detected")
return full_path
// Fixed: Comprehensive null byte filtering
#include <string.h>
#include <ctype.h>
int fixed_filter_null(const char* input, char* output, size_t output_size) {
size_t j = 0;
size_t i = 0;
while (input[i] != '\0' && j < output_size - 1) {
// Filter literal null (can't appear in input anyway due to C strings)
// Filter URL-encoded null %00
if (input[i] == '%' && input[i+1] == '0' && input[i+2] == '0') {
i += 3;
continue;
}
// Filter other potentially dangerous sequences
if (input[i] == '\\' && input[i+1] == '0') {
i += 2;
continue;
}
output[j++] = input[i++];
}
output[j] = '\0';
return (input[i] == '\0') ? 0 : -1; // -1 if truncated
}
// Better: Allowlist validation
int validate_input(const char* input) {
for (int i = 0; input[i] != '\0'; i++) {
// Only allow alphanumeric and specific punctuation
if (!isalnum(input[i]) && input[i] != '_' && input[i] != '-') {
return 0; // Invalid
}
}
return 1; // Valid
}
Related CWEs
- CWE-791: Incomplete Filtering of Special Elements (child weakness)
- CWE-22: Path Traversal (specific exploitation)
- CWE-89: SQL Injection (specific exploitation)
- CWE-79: Cross-site Scripting (specific exploitation)
References
- MITRE Corporation. "CWE-790: Improper Filtering of Special Elements." https://cwe.mitre.org/data/definitions/790.html
- OWASP. "Input Validation Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html
- OWASP. "Testing for Input Validation." OWASP Testing Guide.