Improper Use of Validation Framework

Description

Improper Use of Validation Framework occurs when a product does not use, or incorrectly uses, an input validation framework that is provided by the source language or an independent library. Modern programming languages and frameworks typically offer built-in input validation capabilities designed to streamline validation processes and reduce errors. These tools automatically verify input against specified requirements and route execution to error handlers for invalid data. Failing to use or misusing these frameworks leads to inconsistent validation, increased likelihood of vulnerabilities, and reduced maintainability.

Risk

Improper use of validation frameworks has significant security implications. Manual validation is more error-prone than framework validation. Inconsistent validation across the codebase creates security gaps. Input validation bypass opportunities increase. Common vulnerabilities like XSS, SQL injection, and command injection become more likely. Validation logic is scattered and hard to audit. Changes to validation rules may not be applied consistently. Framework security updates may not be leveraged. Custom validation code may have undiscovered bugs.

Solution

Use established validation frameworks provided by the language or reputable libraries. Configure validation rules declaratively where possible. Apply validation consistently at system boundaries. Use the framework's built-in validators before creating custom ones. Ensure validation errors are properly handled. Keep validation frameworks updated. Document validation requirements in schema definitions. Test validation rules thoroughly. Use server-side validation even with client-side validation. Centralize validation logic where possible.

Common Consequences

ImpactDetails
IntegrityScope: Integrity

Unexpected State - Unchecked input leads to cross-site scripting, process control, SQL injection vulnerabilities, and other injection attacks.
AvailabilityScope: Availability

Denial of Service - Improper validation may allow malformed input that crashes the application.

Example Code

Vulnerable Code

// Vulnerable: Not using Java Bean Validation framework

public class UserController {

    // Manual validation - error-prone and inconsistent
    @PostMapping("/users")
    public ResponseEntity<?> createUser(@RequestBody Map<String, Object> userData) {
        // Scattered manual validation - easy to miss cases
        String username = (String) userData.get("username");
        String email = (String) userData.get("email");
        String password = (String) userData.get("password");
        Integer age = (Integer) userData.get("age");

        // Inconsistent validation - different patterns each place
        if (username == null || username.isEmpty()) {
            return ResponseEntity.badRequest().body("Username required");
        }
        if (username.length() < 3 || username.length() > 50) {
            return ResponseEntity.badRequest().body("Username must be 3-50 chars");
        }

        // Missing validation for special characters in username!

        if (email == null || !email.contains("@")) {  // Insufficient email validation
            return ResponseEntity.badRequest().body("Invalid email");
        }

        // Password validation scattered and incomplete
        if (password == null) {
            return ResponseEntity.badRequest().body("Password required");
        }
        // Missing: length check, complexity requirements!

        if (age != null && age < 0) {  // What about age > 150?
            return ResponseEntity.badRequest().body("Invalid age");
        }

        // Process user - validation may have gaps
        return createUserInternal(username, email, password, age);
    }

    // Another endpoint with different (inconsistent) validation
    @PutMapping("/users/{id}")
    public ResponseEntity<?> updateUser(@PathVariable Long id,
                                        @RequestBody Map<String, Object> userData) {
        String email = (String) userData.get("email");

        // Different email validation here!
        if (email != null && email.indexOf("@") < 0) {  // Different check
            return ResponseEntity.badRequest().body("Bad email");
        }

        // Password validation completely missing here!

        return updateUserInternal(id, userData);
    }
}
# Vulnerable: Not using Python validation libraries like Pydantic or Marshmallow

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/api/orders', methods=['POST'])
def create_order():
    # Manual validation - scattered and incomplete
    data = request.json

    # No schema validation - accepts any structure
    if not data:
        return jsonify({'error': 'No data'}), 400

    # Manual field validation - easy to miss fields
    customer_id = data.get('customer_id')
    if not customer_id:
        return jsonify({'error': 'customer_id required'}), 400

    # Type checking missing - what if customer_id is not an integer?

    items = data.get('items')
    if not items:
        return jsonify({'error': 'items required'}), 400

    # No validation of item structure!
    # What fields should each item have?
    # What are valid values?

    total = data.get('total')
    if total is not None:
        # What if total is negative?
        # What if total doesn't match items?
        pass

    # Process order - potentially with invalid data
    return process_order(data)


@app.route('/api/users', methods=['POST'])
def create_user():
    data = request.json

    # Different validation approach than orders endpoint
    email = data.get('email', '')

    # Regex validation prone to errors
    import re
    if not re.match(r'^[\w\.-]+@[\w\.-]+\.\w+$', email):  # Incomplete regex
        return jsonify({'error': 'invalid email'}), 400

    # Missing: username validation, password validation, etc.

    return create_user_internal(data)
// Vulnerable: Not using validation libraries like Joi or Yup

const express = require('express');
const app = express();

app.post('/api/products', (req, res) => {
    const { name, price, category, description } = req.body;

    // Manual validation - incomplete and inconsistent
    if (!name) {
        return res.status(400).json({ error: 'Name required' });
    }

    // No length validation for name

    if (!price) {
        return res.status(400).json({ error: 'Price required' });
    }

    // Type coercion issues - price could be a string
    if (price < 0) {  // What if price is "abc"?
        return res.status(400).json({ error: 'Invalid price' });
    }

    // category validation missing

    // description - no length limit = potential DoS

    // XSS risk - no sanitization of inputs
    createProduct({ name, price, category, description });

    res.json({ success: true });
});

// Different endpoint, different validation style
app.put('/api/products/:id', (req, res) => {
    const { name, price } = req.body;

    // Inconsistent validation - allows empty name here!
    if (price !== undefined && typeof price !== 'number') {
        return res.status(400).json({ error: 'Price must be number' });
    }

    // No validation of id parameter!
    updateProduct(req.params.id, { name, price });

    res.json({ success: true });
});

Fixed Code

// Fixed: Using Java Bean Validation (JSR-380) framework

import javax.validation.Valid;
import javax.validation.constraints.*;

// Define validation rules declaratively
public class UserRequest {

    @NotBlank(message = "Username is required")
    @Size(min = 3, max = 50, message = "Username must be 3-50 characters")
    @Pattern(regexp = "^[a-zA-Z0-9_]+$",
             message = "Username can only contain letters, numbers, and underscores")
    private String username;

    @NotBlank(message = "Email is required")
    @Email(message = "Invalid email format")
    private String email;

    @NotBlank(message = "Password is required")
    @Size(min = 8, max = 128, message = "Password must be 8-128 characters")
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).*$",
             message = "Password must contain uppercase, lowercase, and number")
    private String password;

    @Min(value = 0, message = "Age cannot be negative")
    @Max(value = 150, message = "Age must be realistic")
    private Integer age;

    // Getters and setters...
}

@RestController
@Validated
public class UserController {

    // Framework handles validation automatically
    @PostMapping("/users")
    public ResponseEntity<?> createUser(@Valid @RequestBody UserRequest request,
                                        BindingResult bindingResult) {
        // Framework has already validated - check for errors
        if (bindingResult.hasErrors()) {
            List<String> errors = bindingResult.getAllErrors().stream()
                .map(ObjectError::getDefaultMessage)
                .collect(Collectors.toList());
            return ResponseEntity.badRequest().body(errors);
        }

        // All validation passed - safe to process
        return createUserInternal(request);
    }

    // Consistent validation via shared UserRequest class
    @PutMapping("/users/{id}")
    public ResponseEntity<?> updateUser(@PathVariable @Positive Long id,
                                        @Valid @RequestBody UserRequest request) {
        // Same validation rules applied automatically
        return updateUserInternal(id, request);
    }

    // Custom validation with framework integration
    @PostMapping("/users/batch")
    public ResponseEntity<?> createUsers(@Valid @RequestBody List<@Valid UserRequest> requests) {
        // Framework validates each item in the list
        return createUsersInternal(requests);
    }
}

// Global exception handler for validation errors
@ControllerAdvice
public class ValidationExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, List<String>>> handleValidationErrors(
            MethodArgumentNotValidException ex) {

        List<String> errors = ex.getBindingResult().getFieldErrors().stream()
            .map(error -> error.getField() + ": " + error.getDefaultMessage())
            .collect(Collectors.toList());

        return ResponseEntity.badRequest().body(Map.of("errors", errors));
    }
}
# Fixed: Using Pydantic for validation

from pydantic import BaseModel, Field, EmailStr, validator
from typing import List, Optional
from fastapi import FastAPI, HTTPException

app = FastAPI()


# Define validation schema with Pydantic
class OrderItem(BaseModel):
    product_id: int = Field(..., gt=0, description="Product ID must be positive")
    quantity: int = Field(..., ge=1, le=100, description="Quantity 1-100")
    unit_price: float = Field(..., gt=0, description="Price must be positive")


class OrderRequest(BaseModel):
    customer_id: int = Field(..., gt=0)
    items: List[OrderItem] = Field(..., min_items=1, max_items=50)
    total: Optional[float] = Field(None, ge=0)
    notes: Optional[str] = Field(None, max_length=500)

    @validator('total')
    def validate_total(cls, v, values):
        """Validate total matches items sum if provided."""
        if v is not None and 'items' in values:
            calculated = sum(item.quantity * item.unit_price
                           for item in values['items'])
            if abs(v - calculated) > 0.01:
                raise ValueError('Total does not match items sum')
        return v

    class Config:
        # Additional validation config
        extra = 'forbid'  # Reject unknown fields


class UserRequest(BaseModel):
    username: str = Field(..., min_length=3, max_length=50,
                          regex=r'^[a-zA-Z0-9_]+$')
    email: EmailStr  # Built-in email validation
    password: str = Field(..., min_length=8, max_length=128)

    @validator('password')
    def password_strength(cls, v):
        """Validate password complexity."""
        if not any(c.isupper() for c in v):
            raise ValueError('Password must contain uppercase letter')
        if not any(c.islower() for c in v):
            raise ValueError('Password must contain lowercase letter')
        if not any(c.isdigit() for c in v):
            raise ValueError('Password must contain digit')
        return v


# Endpoints with automatic validation
@app.post('/api/orders')
async def create_order(order: OrderRequest):
    # Pydantic has already validated - data is safe
    return await process_order(order.dict())


@app.post('/api/users')
async def create_user(user: UserRequest):
    # Validation happens automatically via Pydantic
    return await create_user_internal(user.dict())


# Error handling is automatic with FastAPI + Pydantic
# Invalid requests return 422 with detailed error messages
// Fixed: Using Joi validation library

const express = require('express');
const Joi = require('joi');

const app = express();
app.use(express.json());

// Define validation schemas
const productSchema = Joi.object({
    name: Joi.string()
        .min(1)
        .max(100)
        .required()
        .trim()
        .pattern(/^[a-zA-Z0-9\s\-]+$/)
        .messages({
            'string.empty': 'Name is required',
            'string.max': 'Name cannot exceed 100 characters',
            'string.pattern.base': 'Name contains invalid characters'
        }),

    price: Joi.number()
        .positive()
        .precision(2)
        .max(1000000)
        .required()
        .messages({
            'number.positive': 'Price must be positive',
            'number.max': 'Price exceeds maximum allowed'
        }),

    category: Joi.string()
        .valid('electronics', 'clothing', 'food', 'other')
        .required(),

    description: Joi.string()
        .max(1000)
        .optional()
        .trim()
});

// Validation middleware factory
const validate = (schema) => {
    return (req, res, next) => {
        const { error, value } = schema.validate(req.body, {
            abortEarly: false,  // Return all errors
            stripUnknown: true  // Remove unknown fields
        });

        if (error) {
            const errors = error.details.map(d => d.message);
            return res.status(400).json({ errors });
        }

        req.body = value;  // Use sanitized values
        next();
    };
};

// Endpoints with schema validation
app.post('/api/products', validate(productSchema), (req, res) => {
    // Data has been validated and sanitized by Joi
    const { name, price, category, description } = req.body;

    createProduct({ name, price, category, description });
    res.json({ success: true });
});

// Same schema for updates (with optional fields)
const productUpdateSchema = productSchema.fork(
    ['name', 'price', 'category'],
    (schema) => schema.optional()
);

app.put('/api/products/:id',
    validate(Joi.object({ id: Joi.number().positive().required() }).unknown(true)),
    validate(productUpdateSchema),
    (req, res) => {
        // Both params and body validated
        updateProduct(req.params.id, req.body);
        res.json({ success: true });
    }
);

CVE Examples

While this CWE itself is not directly mapped to CVEs, improper input validation is a root cause of many vulnerability classes:

  • SQL Injection (CWE-89): Caused by insufficient input validation
  • XSS (CWE-79): Caused by insufficient output encoding and input validation
  • Command Injection (CWE-78): Caused by insufficient input validation

  • CWE-20: Improper Input Validation (parent)
  • CWE-1215: Data Validation Issues (category member)
  • CWE-102: Struts: Duplicate Validation Forms (child)
  • CWE-105: Struts: Form Field Without Validator (child)
  • CWE-106: Struts: Plug-in Framework not in Use (child)

References

  1. MITRE Corporation. "CWE-1173: Improper Use of Validation Framework." https://cwe.mitre.org/data/definitions/1173.html
  2. OWASP Input Validation Cheat Sheet
  3. Java Bean Validation (JSR-380)
  4. Pydantic Documentation
  5. Joi Validation Library