Validation Security — Injection Prevention, XSS, and Sanitization
In this tutorial, you'll learn about Validation Security. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Validation security ensures that input processing does not introduce vulnerabilities. Validation rejects invalid data, while sanitization removes dangerous content from valid data.
What You'll Learn
By the end of this lesson, you will distinguish validation from sanitization, prevent injection attacks through input handling, protect against XSS via data content, and build a secure input pipeline.
Why It Matters
Validation alone cannot stop all attacks. SQL injection, XSS, Command Injection, and NoSQL injection can all be delivered through syntactically valid data. A secure input pipeline must validate first, then sanitize.
Real-World Use
Durga Antivirus Pro validates file uploads by type and size, then sanitizes filenames to prevent path traversal attacks before writing to disk. Both layers are required.
Validation vs Sanitization Flow
flowchart LR
Input[Raw Input] --> Validate{Validation}
Validate -->|Invalid| Reject[Reject with 400]
Validate -->|Valid| Sanitize[Sanitization]
Sanitize --> Strip[Strip Dangerous Content]
Strip --> Escape[Escape for Context]
Escape --> Safe[Safe Output]
Safe --> DB[Database]
Safe --> Render[HTML Render]
Safe --> Shell[Shell Command]
SQL Injection Prevention via Validation
# sql_injection_prevention.py
import re
from typing import Any, Dict, List, Optional
class SQLInjectionPrevention:
DANGEROUS_PATTERNS = [
r"['\";]", # quotes and semicolons
r"--", # SQL comment
r"/\*", # block comment open
r"UNION\s+ALL", # UNION attacks
r"UNION\s+SELECT", # UNION SELECT
r"OR\s+1\s*=\s*1", # tautology
r"OR\s+'1'\s*=\s*'1'",
r"DROP\s+TABLE", # DDL
r"DELETE\s+FROM", # DML
r"INSERT\s+INTO",
r"EXEC\(", # SQL Server exec
r"xp_cmdshell", # extended stored proc
r"pg_sleep", # PostgreSQL time-based
r"BENCHMARK\s*\(", # MySQL time-based
]
@staticmethod
def contains_sql_injection(value: str) -> bool:
if not isinstance(value, str):
return False
upper = value.upper()
for pattern in SQLInjectionPrevention.DANGEROUS_PATTERNS:
if re.search(pattern, upper):
return True
return False
@staticmethod
def safe_username(value: str) -> Optional[str]:
if not isinstance(value, str):
return "Username must be a string"
if SQLInjectionPrevention.contains_sql_injection(value):
return "Username contains invalid characters"
if not re.match(r'^[a-zA-Z0-9_]{3,30}$', value):
return "Username: 3-30 alphanumeric characters or underscores"
return None
@staticmethod
def safe_search_term(value: str) -> Optional[str]:
if not isinstance(value, str):
return "Search term must be a string"
if SQLInjectionPrevention.contains_sql_injection(value):
return "Search term contains invalid characters"
if len(value) > 200:
return "Search term too long"
return None
sp = SQLInjectionPrevention()
tests = [
("admin' --", sp.safe_username),
("Robert'); DROP TABLE Students;--", sp.safe_username),
("alice_dev", sp.safe_username),
("1 OR 1=1", sp.safe_search_term),
("safe search query", sp.safe_search_term),
]
for value, validator in tests:
error = validator(value)
status = "VALID" if not error else error
print(f" {str(value):45s} -> {status}")
Expected output:
admin' -- -> Username contains invalid characters
Robert'); DROP TABLE Students;-- -> Username contains invalid characters
alice_dev -> VALID
1 OR 1=1 -> Search term contains invalid characters
safe search query -> VALID
XSS Prevention in Validation
# xss_prevention.py
import re
from html import escape
from typing import Any, Dict, List, Optional
class XSSPrevention:
DANGEROUS_TAGS = [
r"<script[^>]*>",
r"<iframe[^>]*>",
r"<object[^>]*>",
r"<embed[^>]*>",
r"<svg[^>]*onload",
r"<img[^>]*onerror",
r"<img[^>]*onload",
r"<[^>]*onclick",
r"<[^>]*onmouseover",
r"<[^>]*onfocus",
r"<[^>]*onchange",
r"<[^>]*onsubmit",
r"javascript:",
r"onerror\s*=",
r"onload\s*=",
r"expression\s*\(",
]
@staticmethod
def contains_xss(value: str) -> bool:
if not isinstance(value, str):
return False
lower = value.lower()
for pattern in XSSPrevention.DANGEROUS_TAGS:
if re.search(pattern, lower):
return True
return False
@staticmethod
def sanitize_html(value: str) -> str:
return escape(value, quote=True)
@staticmethod
def sanitize_url(value: str) -> Optional[str]:
allowed_schemes = ["http", "https", "mailto", "tel"]
lower = value.lower().strip()
for scheme in allowed_schemes:
if lower.startswith(scheme + ":"):
return value
if ":" in lower and not lower.startswith("/"):
return None
return value
@staticmethod
def validate_comment(value: str) -> Optional[str]:
if not isinstance(value, str):
return "Comment must be a string"
if XSSPrevention.contains_xss(value):
return "Comment contains disallowed HTML"
if len(value) > 1000:
return "Comment too long (max 1000 chars)"
return None
xp = XSSPrevention()
test_inputs = [
"Hello, this is a safe comment!",
"<script>alert('xss')</script>",
"<img src=x onerror=alert(1)>",
"<a href=\"javascript:alert(1)\">click</a>",
"Safe URL: https://example.com",
]
for inp in test_inputs:
error = xp.validate_comment(inp)
status = "VALID" if not error else error
sanitized = xp.sanitize_html(inp)
print(f" Input: {inp[:50]:50s}")
print(f" Status: {status}")
print(f" Sanitized: {sanitized[:50]:50s}")
print()
Expected output:
Input: Hello, this is a safe comment!
Status: VALID
Sanitized: Hello, this is a safe comment!
Input: <script>alert('xss')</script>
Status: Comment contains disallowed HTML
Sanitized: <script>alert('xss')</script>
Input: <img src=x onerror=alert(1)>
Status: Comment contains disallowed HTML
Sanitized: <img src=x onerror=alert(1)>
Input: <a href=\"javascript:alert(1)\">click</a>
Status: Comment contains disallowed HTML
Sanitized: <a href="javascript:alert(1)">click</a>
Input: Safe URL: https://example.com
Status: VALID
Sanitized: Safe URL: https://example.com
Command Injection Prevention
# command_injection.py
import re
import shlex
from typing import Optional
class CommandInjectionPrevention:
DANGEROUS_CHARS = r'[;&|`$(){}[\]!#~<>]'
DANGEROUS_COMMANDS = [
"rm", "sudo", "eval", "exec", "system(", "passthru",
"shell_exec", "popen", "curl", "wget", "bash",
"sh -c", "cmd.exe", "powershell",
]
@staticmethod
def is_safe_filename(value: str) -> Optional[str]:
if not isinstance(value, str):
return "Filename must be a string"
if re.search(CommandInjectionPrevention.DANGEROUS_CHARS, value):
return "Filename contains invalid characters"
if value.strip() == "" or value.startswith("."):
return "Invalid filename"
if len(value) > 255:
return "Filename too long"
return None
@staticmethod
def is_safe_path_component(value: str) -> Optional[str]:
if not isinstance(value, str):
return "Path component must be a string"
if re.search(r'[\\/]', value):
return "Path component cannot contain slashes"
if value == ".." or value == ".":
return "Invalid path component"
return None
@staticmethod
def sanitize_filename(value: str) -> str:
safe = re.sub(r'[^a-zA-Z0-9._-]', '_', value)
safe = re.sub(r'_{2,}', '_', safe)
return safe.strip('_.')
cip = CommandInjectionPrevention()
test_names = [
"report.pdf",
"file; rm -rf /",
"`cat /etc/passwd`",
"../etc/passwd",
"file$(whoami).txt",
"valid-script-1.0.sh",
]
for name in test_names:
error = cip.is_safe_filename(name)
sanitized = cip.sanitize_filename(name)
print(f" Original: {name:30s}")
print(f" Safe: {'YES' if not error else 'NO'}")
if error:
print(f" Error: {error}")
print(f" Sanitized: {sanitized}")
print()
Expected output:
Original: report.pdf
Safe: YES
Sanitized: report.pdf
Original: file; rm -rf /
Safe: NO
Error: Filename contains invalid characters
Sanitized: file__rm_-rf_
Original: `cat /etc/passwd`
Safe: NO
Error: Filename contains invalid characters
Sanitized: _cat__etc_passwd_
Original: ../etc/passwd
Safe: NO
Error: Filename contains invalid characters
Sanitized: __etc_passwd
Original: file$(whoami).txt
Safe: NO
Error: Filename contains invalid characters
Sanitized: file_whoami_.txt
Original: valid-script-1.0.sh
Safe: YES
Sanitized: valid-script-1.0.sh
Secure Input Pipeline
# secure_input_pipeline.py
from typing import Any, Callable, Dict, List, Optional, Tuple
class PipelineStage:
def __init__(self, name: str, process: Callable[[Any], Tuple[bool, Any, Optional[str]]]):
self.name = name
self.process = process
class SecureInputPipeline:
def __init__(self):
self.stages: List[PipelineStage] = []
def add(self, stage: PipelineStage):
self.stages.append(stage)
def process(self, data: Any) -> Dict:
current = data
for stage in self.stages:
success, result, error = stage.process(current)
if not success:
return {"success": False, "stage": stage.name, "error": error, "data": current}
current = result
return {"success": True, "stage": "complete", "error": None, "data": current}
pipeline = SecureInputPipeline()
pipeline.add(PipelineStage("type_check", lambda v: (isinstance(v, str), v, None if isinstance(v, str) else "Must be string")))
pipeline.add(PipelineStage("length_check", lambda v: (True, v, None) if 1 <= len(v) <= 100 else (False, v, "Length must be 1-100")))
pipeline.add(PipelineStage("sql_injection_check", lambda v: (False, v, "SQL injection detected") if re.search(r"['\"--]", v) else (True, v, None)))
pipeline.add(PipelineStage("xss_check", lambda v: (False, v, "XSS detected") if re.search(r"<script|<img.*onerror", v, re.I) else (True, v, None)))
pipeline.add(PipelineStage("trim", lambda v: (True, v.strip(), None)))
import re
print(pipeline.process("Hello world"))
print(pipeline.process("<script>alert(1)</script>"))
print(pipeline.process("Robert'; DROP TABLE Students;--"))
print(pipeline.process(""))
print(pipeline.process(12345))
Expected output:
{'success': True, 'stage': 'complete', 'error': None, 'data': 'Hello world'}
{'success': False, 'stage': 'xss_check', 'error': 'XSS detected', 'data': '<script>alert(1)</script>'}
{'success': False, 'stage': 'sql_injection_check', 'error': 'SQL injection detected', 'data': "Robert'; DROP TABLE Students;--"}
{'success': False, 'stage': 'length_check', 'error': 'Length must be 1-100', 'data': ''}
{'success': False, 'stage': 'type_check', 'error': 'Must be string', 'data': 12345}
Common Mistakes
1. Confusing Validation with Sanitization
Validation rejects bad data. Sanitization cleans good data. Doing only one leaves gaps. Always do both.
2. Blacklisting Instead of Whitelisting
Blocking specific dangerous patterns is fragile. Attackers find new patterns. Whitelist allowed characters and structures instead.
3. Not Contextualizing Output Encoding
HTML-encoding for the database does not stop XSS in HTML output. Encode for each context: HTML, URL, JavaScript, CSS.
4. Relying on Client-Side Security Only
JavaScript validation is invisible to attackers. All security validation must happen on the server.
5. Not Handling NoSQL Injection
MongoDB $where, $gt, and $ne operators can be injected through JSON input. Validate types and reject operator keys.
Practice Questions
1. What is the difference between validation and sanitization?
Validation rejects invalid input. Sanitization removes or escapes dangerous parts of valid input. Both are required.
2. How do you prevent SQL injection in input?
Use parameterized queries. Also validate input for SQL metacharacters as a defense-in-depth layer.
3. What is XSS and how do you prevent it via input handling?
XSS injects JavaScript into web pages. Validate to block script tags, sanitize with HTML escaping (html.escape), and use CSP headers.
4. Why is whitelisting better than blacklisting for security?
Blacklists need constant updates. Whitelists define exactly what is allowed and reject everything else.
Challenge
Build a secure input pipeline for a file upload API: validate MIME type, sanitize filename (path traversal, command injection), scan for malware signatures, check file size, and store with a safe randomly-generated filename.
FAQ
Mini Project: Secure Input Handler
# secure_input_handler.py
import re
from typing import Any, Dict, List, Optional, Tuple
class SecureInputHandler:
def __init__(self):
self.validators: List[callable] = []
self.sanitizers: List[callable] = []
def add_validator(self, fn: callable):
self.validators.append(fn)
def add_sanitizer(self, fn: callable):
self.sanitizers.append(fn)
def process(self, value: Any) -> Tuple[bool, Any, Optional[str]]:
for validator in self.validators:
error = validator(value)
if error:
return False, value, error
for sanitizer in self.sanitizers:
value = sanitizer(value)
return True, value, None
handler = SecureInputHandler()
handler.add_validator(lambda v: None if isinstance(v, str) else "Must be string")
handler.add_validator(lambda v: None if len(v) <= 500 else "Too long")
handler.add_sanitizer(lambda v: v.strip())
handler.add_sanitizer(lambda v: re.sub(r'<[^>]*>', '', v))
tests = ["<b>Hello</b>", "<script>alert(1)</script>", 123, "A" * 600]
for test in tests:
valid, result, error = handler.process(test)
print(f" Input: {str(test)[:30]:30s} Valid: {valid} Result: {str(result)[:30]:30s} Error: {error}")
Expected output:
Input: <b>Hello</b> Valid: True Result: Hello Error: None
Input: <script>alert(1)</script> Valid: True Result: alert(1) Error: None
Input: 123 Valid: False Result: 123 Error: Must be string
Input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Valid: False Result: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Error: Too long
What's Next
You understand validation security. Next, learn honeypot validation and bot detection, then data validation complete project.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro