WSTG-INPV (Input Validation Testing), which is a section of the OWASP Web Security Testing Guide (WSTG). Input validation testing is a critical component of web application security testing that focuses on how applications handle user inputs.
The OWASP WSTG Input Validation Testing section typically covers testing methodologies for various input validation vulnerabilities, including:
SQL Injection
Cross-site Scripting (XSS)
HTTP Parameter Pollution
Command Injection
Server-side Template Injection
XML Injection
SSI Injection
LDAP Injection
ORM Injection
Buffer Overflows
These tests help security professionals identify weaknesses in how applications validate, filter, sanitize, and process user inputs, which are common attack vectors for exploiting web applications.
Input validators:
// input-validators.ts
/**
* Next.js 14 Input Validation Library
* Based on OWASP WSTG-INPV (Input Validation Testing) categories
*/
// Type definitions for validation results
type ValidationResult = {
isValid: boolean;
message?: string;
};
/**
* SQL Injection Prevention
* Validates input against common SQL injection patterns
*/
export function validateSqlInjection(input: string): ValidationResult {
if (!input) return { isValid: true };
// Check for common SQL injection patterns
const sqlPatterns = [
/('|"|;|--|\/\*|\*\/|@@|@|\bAND\b|\bOR\b|\bUNION\b|\bSELECT\b|\bFROM\b|\bWHERE\b|\bDROP\b|\bTABLE\b|\bINSERT\b|\bDELETE\b|\bUPDATE\b)/i,
/(ALTER|CREATE|DELETE|DROP|EXEC(UTE){0,1}|INSERT( +INTO){0,1}|MERGE|SELECT|UPDATE|UNION( +ALL){0,1})/i
];
for (const pattern of sqlPatterns) {
if (pattern.test(input)) {
return {
isValid: false,
message: "Potential SQL injection detected"
};
}
}
return { isValid: true };
}
/**
* Cross-site Scripting (XSS) Prevention
* Checks for potential XSS attacks in input
*/
export function validateXSS(input: string): ValidationResult {
if (!input) return { isValid: true };
// Check for common XSS patterns
const xssPatterns = [
/<script[^>]*>.*?<\/script>/is,
/javascript:[^\s]*/i,
/onerror\s*=|onload\s*=|onclick\s*=|onmouseover\s*=/i,
/<iframe[^>]*>/i,
/<img[^>]*>/i
];
for (const pattern of xssPatterns) {
if (pattern.test(input)) {
return {
isValid: false,
message: "Potential XSS attack detected"
};
}
}
return { isValid: true };
}
/**
* Command Injection Prevention
* Validates against OS command injection attempts
*/
export function validateCommandInjection(input: string): ValidationResult {
if (!input) return { isValid: true };
// Check for common command injection patterns
const cmdPatterns = [
/[;&|`\(\)$]/,
/\b(cat|cd|chmod|curl|echo|exec|find|grep|kill|ls|mkdir|mv|nc|ping|rm|sh|sleep|touch|wget)\b/i
];
for (const pattern of cmdPatterns) {
if (pattern.test(input)) {
return {
isValid: false,
message: "Potential command injection detected"
};
}
}
return { isValid: true };
}
/**
* HTTP Parameter Pollution Prevention
* Validates for duplicate parameters that could lead to HPP
*/
export function validateHPP(params: Record<string, string[]>): ValidationResult {
for (const [key, values] of Object.entries(params)) {
if (values.length > 1) {
return {
isValid: false,
message: `HTTP Parameter Pollution detected for parameter: ${key}`
};
}
}
return { isValid: true };
}
/**
* Server-side Template Injection Prevention
* Validates against template injection patterns
*/
export function validateTemplateInjection(input: string): ValidationResult {
if (!input) return { isValid: true };
// Check for common template injection patterns
const templatePatterns = [
/\$\{.*?\}/,
/\{\{.*?\}\}/,
/#\{.*?\}/,
/<\%.*?\%>/
];
for (const pattern of templatePatterns) {
if (pattern.test(input)) {
return {
isValid: false,
message: "Potential template injection detected"
};
}
}
return { isValid: true };
}
/**
* XML Injection Prevention
* Validates XML input against common XML injection patterns
*/
export function validateXMLInjection(input: string): ValidationResult {
if (!input) return { isValid: true };
// Check for XML injection patterns
const xmlPatterns = [
/<!DOCTYPE|<!ENTITY|<!ELEMENT/i,
/<!\[CDATA\[.*?\]\]>/i,
/\bXXE\b/i
];
for (const pattern of xmlPatterns) {
if (pattern.test(input)) {
return {
isValid: false,
message: "Potential XML injection detected"
};
}
}
return { isValid: true };
}
/**
* SSI Injection Prevention
* Validates against Server-Side Include injection patterns
*/
export function validateSSIInjection(input: string): ValidationResult {
if (!input) return { isValid: true };
// Check for SSI injection patterns
const ssiPatterns = [
/<!--#include|<!--#exec|<!--#echo|<!--#config|<!--#flastmod|<!--#fsize/i
];
for (const pattern of ssiPatterns) {
if (pattern.test(input)) {
return {
isValid: false,
message: "Potential SSI injection detected"
};
}
}
return { isValid: true };
}
/**
* LDAP Injection Prevention
* Validates against LDAP injection patterns
*/
export function validateLDAPInjection(input: string): ValidationResult {
if (!input) return { isValid: true };
// Check for LDAP injection patterns
const ldapPatterns = [
/[()&|!*]/,
/\)(cn|ou|dc|o)=/i
];
for (const pattern of ldapPatterns) {
if (pattern.test(input)) {
return {
isValid: false,
message: "Potential LDAP injection detected"
};
}
}
return { isValid: true };
}
/**
* ORM Injection Prevention
* Validates against ORM injection patterns (like NoSQL injection)
*/
export function validateORMInjection(input: string): ValidationResult {
if (!input) return { isValid: true };
// Check for NoSQL injection patterns
const ormPatterns = [
/\$where|findOne|find\(/i,
/{.*\$ne.*}|{.*\$gt.*}|{.*\$lt.*}|{.*\$exists.*}/i
];
for (const pattern of ormPatterns) {
if (pattern.test(input)) {
return {
isValid: false,
message: "Potential ORM/NoSQL injection detected"
};
}
}
return { isValid: true };
}
/**
* General Input Length Validation
* Ensures input is within acceptable length limits
*/
export function validateInputLength(input: string, maxLength: number = 1000): ValidationResult {
if (!input) return { isValid: true };
if (input.length > maxLength) {
return {
isValid: false,
message: `Input exceeds maximum length of ${maxLength} characters`
};
}
return { isValid: true };
}
/**
* Comprehensive input validation
* Runs all validators on a single input
*/
export function validateInput(input: string): ValidationResult {
const validators = [
validateSqlInjection,
validateXSS,
validateCommandInjection,
validateTemplateInjection,
validateXMLInjection,
validateSSIInjection,
validateLDAPInjection,
validateORMInjection
];
for (const validator of validators) {
const result = validator(input);
if (!result.isValid) {
return result;
}
}
return { isValid: true };
}
/**
* Sanitizes user input by removing potentially harmful characters
*/
export function sanitizeInput(input: string): string {
if (!input) return '';
// Remove script tags and potentially harmful HTML
let sanitized = input
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '')
.replace(/javascript:/gi, 'removed:')
.replace(/on\w+=/gi, 'data-removed=');
// Encode HTML entities
sanitized = sanitized
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
return sanitized;
}
These validators in a Next.js 14 API route:
// app/api/user/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { validateInput, sanitizeInput } from '@/utils/input-validators';
export async function POST(request: NextRequest) {
try {
// Parse the request body
const body = await request.json();
// Validate each input field
const fields = ['username', 'email', 'comment'];
for (const field of fields) {
if (body[field]) {
const validation = validateInput(body[field]);
if (!validation.isValid) {
return NextResponse.json({
success: false,
error: `${validation.message} in field '${field}'`
}, { status: 400 });
}
// Sanitize the input
body[field] = sanitizeInput(body[field]);
}
}
// Process valid input...
// For example, save to database
return NextResponse.json({
success: true,
message: 'Data processed successfully'
});
} catch (error) {
console.error('Error processing request:', error);
return NextResponse.json({
success: false,
error: 'Invalid request format'
}, { status: 400 });
}
}
Here's how you can use these validators with a form component:
// components/SecureForm.tsx
'use client';
import { useState, FormEvent } from 'react';
import { validateInput } from '@/utils/input-validators';
export default function SecureForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
comment: ''
});
const [errors, setErrors] = useState<Record<string, string>>({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [message, setMessage] = useState('');
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
// Real-time validation
const validation = validateInput(value);
if (!validation.isValid) {
setErrors(prev => ({ ...prev, [name]: validation.message || 'Invalid input' }));
} else {
setErrors(prev => {
const newErrors = { ...prev };
delete newErrors[name];
return newErrors;
});
}
};
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
setIsSubmitting(true);
setMessage('');
// Validate all fields before submission
let hasErrors = false;
const newErrors: Record<string, string> = {};
Object.entries(formData).forEach(([field, value]) => {
const validation = validateInput(value);
if (!validation.isValid) {
newErrors[field] = validation.message || 'Invalid input';
hasErrors = true;
}
});
if (hasErrors) {
setErrors(newErrors);
setIsSubmitting(false);
return;
}
try {
const response = await fetch('/api/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});
const data = await response.json();
if (data.success) {
setMessage('Form submitted successfully!');
// Reset form
setFormData({ username: '', email: '', comment: '' });
} else {
setMessage(`Error: ${data.error}`);
}
} catch (error) {
setMessage('Failed to submit form. Please try again.');
} finally {
setIsSubmitting(false);
}
};
return (
<div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow-md">
<h2 className="text-xl font-bold mb-4">Secure Form</h2>
{message && (
<div className={`p-3 mb-4 rounded ${message.includes('Error') ? 'bg-red-100 text-red-700' : 'bg-green-100 text-green-700'}`}>
{message}
</div>
)}
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label htmlFor="username" className="block mb-1 font-medium">
Username
</label>
<input
type="text"
id="username"
name="username"
value={formData.username}
onChange={handleChange}
className={`w-full p-2 border rounded ${errors.username ? 'border-red-500' : 'border-gray-300'}`}
required
/>
{errors.username && (
<p className="mt-1 text-sm text-red-600">{errors.username}</p>
)}
</div>
<div className="mb-4">
<label htmlFor="email" className="block mb-1 font-medium">
Email
</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
className={`w-full p-2 border rounded ${errors.email ? 'border-red-500' : 'border-gray-300'}`}
required
/>
{errors.email && (
<p className="mt-1 text-sm text-red-600">{errors.email}</p>
)}
</div>
<div className="mb-4">
<label htmlFor="comment" className="block mb-1 font-medium">
Comment
</label>
<textarea
id="comment"
name="comment"
value={formData.comment}
onChange={handleChange}
className={`w-full p-2 border rounded ${errors.comment ? 'border-red-500' : 'border-gray-300'}`}
rows={4}
required
/>
{errors.comment && (
<p className="mt-1 text-sm text-red-600">{errors.comment}</p>
)}
</div>
<button
type="submit"
disabled={isSubmitting || Object.keys(errors).length > 0}
className="w-full py-2 px-4 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-blue-300"
>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
</div>
);
}
How to Use This Code
Setup:
Create a
utils
folder in your Next.js 14 projectAdd the
input-validators.ts
file to this folderCreate your API routes and form components as shown in the examples
Key Features:
Comprehensive validation against all OWASP WSTG-INPV vulnerability types
Client-side validation with real-time feedback
Server-side validation as a security best practice
Input sanitization to remove potentially harmful content
Detailed error messages for developers and users
Security Best Practices:
Always validate inputs on both client and server sides
Sanitize inputs before storing or processing them
Use specific error messages for development, but consider using generic messages in production
Implement rate limiting and CSRF protection (not shown in these examples)