CWE-234: Failure to Handle Missing Parameter
Overview
Failure to handle missing parameters occurs when applications don't validate that required input parameters are present, leading to null pointer exceptions, logic errors, security bypass (missing authentication tokens), undefined behavior, or crashes when code assumes parameters exist.
OWASP Classification
A10:2025 - Mishandling of Exceptional Conditions
Risk
Medium: Missing required parameters causes null pointer exceptions (DoS), logic bypass (missing auth checks), incorrect defaults being used, security control bypass, uninitialized variables, and application crashes. Especially dangerous when missing security-critical parameters like tokens or permissions.
Remediation Steps
Core principle: Use correct encoding/interpretation for comparisons and policy checks; canonicalize before validation.
Locate Missing Parameter Handling Issues
When reviewing security scan results:
- Find parameter access without checks: Identify where code accesses request parameters without verifying they exist
- Check authentication/authorization flows: Look for missing token/permission parameter checks
- Review API endpoints: Find required parameters that aren't validated
- Trace null pointer exceptions: Identify crashes from missing parameters
- Check default values: Find where missing parameters get unsafe defaults
Common problematic patterns:
// No check if 'id' exists
const id = req.params.id;
db.findById(id); // Crashes if id is undefined
// Missing auth token not validated
const token = req.headers.authorization;
jwt.verify(token); // Error if token missing
// Assuming query parameter exists
const page = parseInt(req.query.page);
// page is NaN if req.query.page is undefined
// Missing required field not checked
const email = req.body.email;
sendEmail(email); // Fails if email undefined
Validate Required Parameters Are Present (Primary Defense)
const express = require('express');
const app = express();
// VULNERABLE - no parameter validation
app.get('/user/:id', (req, res) => {
// Attack: /user/ (no id) causes undefined
const userId = req.params.id;
// Crashes or returns null if id missing
const user = db.findById(userId);
res.json(user);
});
app.post('/api/transfer', (req, res) => {
// Attack: missing 'amount' or 'to' fields
const amount = req.body.amount;
const toAccount = req.body.to;
// No validation - transfers undefined amounts!
transfer(amount, toAccount);
});
// SECURE - validate all required parameters
app.get('/user/:id', (req, res) => {
// Validate parameter exists
if (!req.params.id) {
return res.status(400).json({
error: 'Missing required parameter: id'
});
}
const userId = req.params.id;
// Additional validation
if (!/^[0-9]+$/.test(userId)) {
return res.status(400).json({
error: 'Invalid user ID format'
});
}
const user = db.findById(parseInt(userId));
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
app.post('/api/transfer', (req, res) => {
// Validate required fields present
const requiredFields = ['amount', 'to', 'from'];
const missing = requiredFields.filter(field => !req.body[field]);
if (missing.length > 0) {
return res.status(400).json({
error: 'Missing required fields',
missing: missing
});
}
const { amount, to, from } = req.body;
// Validate values
if (typeof amount !== 'number' || amount <= 0) {
return res.status(400).json({ error: 'Invalid amount' });
}
transfer(amount, to, from);
res.json({ status: 'success' });
});
Why this works: Explicitly checking for missing parameters prevents null/undefined values from causing crashes, logic errors, or security bypasses. Fail fast with clear error messages.
Use Input Validation Frameworks
const Joi = require('joi');
const express = require('express');
// VULNERABLE - manual validation, easy to miss fields
app.post('/register', (req, res) => {
const { username, email, password } = req.body;
// Incomplete validation - missing many checks
if (username && email && password) {
createUser({ username, email, password });
}
});
// SECURE - schema-based validation
const registerSchema = Joi.object({
username: Joi.string()
.alphanum()
.min(3)
.max(30)
.required(), // Explicitly required
email: Joi.string()
.email()
.required(),
password: Joi.string()
.min(8)
.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
.required(),
age: Joi.number()
.integer()
.min(13)
.optional() // Explicitly optional
});
function validateRequest(schema) {
return (req, res, next) => {
const { error, value } = schema.validate(req.body, {
abortEarly: false, // Get all errors
stripUnknown: true // Remove extra fields
});
if (error) {
const errors = error.details.map(d => ({
field: d.path.join('.'),
message: d.message
}));
return res.status(400).json({
error: 'Validation failed',
details: errors
});
}
req.validatedBody = value;
next();
};
}
app.post('/register', validateRequest(registerSchema), (req, res) => {
// req.validatedBody guaranteed to have all required fields
const { username, email, password } = req.validatedBody;
createUser({ username, email, password });
res.json({ status: 'success' });
});
Handle Security-Critical Parameters Correctly
from flask import Flask, request, jsonify
from functools import wraps
app = Flask(__name__)
# VULNERABLE - missing auth token not checked
@app.route('/api/data')
def get_data_bad():
# Attack: no Authorization header sent
token = request.headers.get('Authorization')
# verify_token() fails with None!
user = verify_token(token)
return jsonify(get_user_data(user))
# SECURE - validate auth token present
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
# Check token present
token = request.headers.get('Authorization')
if not token:
return jsonify({'error': 'Missing authorization token'}), 401
# Validate token format
if not token.startswith('Bearer '):
return jsonify({'error': 'Invalid authorization format'}), 401
token = token.replace('Bearer ', '')
# Verify token
try:
user = verify_token(token)
if not user:
return jsonify({'error': 'Invalid token'}), 401
except Exception as e:
return jsonify({'error': 'Token verification failed'}), 401
# Pass user to endpoint
return f(user, *args, **kwargs)
return decorated
@app.route('/api/data')
@require_auth
def get_data_safe(user):
# user guaranteed to be valid
return jsonify(get_user_data(user))
# Check permission parameter
@app.route('/admin/delete/<int:user_id>')
@require_auth
def delete_user(user, user_id):
# NEVER default permissions to true!
# Explicitly check permission exists and is true
if not hasattr(user, 'is_admin') or not user.is_admin:
return jsonify({'error': 'Insufficient permissions'}), 403
delete_user_record(user_id)
return jsonify({'status': 'deleted'})
Provide Safe Defaults Only for Optional Parameters
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Min;
import javax.validation.constraints.Max;
import org.springframework.web.bind.annotation.*;
@RestController
public class ProductController {
// VULNERABLE - defaulting security-critical parameter
@GetMapping("/products")
public List<Product> getProducts(
@RequestParam(required = false) String category,
@RequestParam(required = false, defaultValue = "true") Boolean includePrivate
) {
// Attack: omit includePrivate to default to true!
// Shows private products by default!
return productService.getProducts(category, includePrivate);
}
// SECURE - safe defaults, security params required
@GetMapping("/products")
public List<Product> getProductsSafe(
@RequestParam(required = false, defaultValue = "all") String category,
@RequestParam(required = false, defaultValue = "10") @Min(1) @Max(100) Integer limit,
@RequestParam(required = false, defaultValue = "0") @Min(0) Integer offset
) {
// Safe defaults: pagination parameters
// Default category "all" is safe
// Never default security-critical parameters
return productService.getProducts(category, limit, offset);
}
// Separate endpoint for private products - explicit permission required
@GetMapping("/products/private")
@PreAuthorize("hasRole('ADMIN')")
public List<Product> getPrivateProducts() {
return productService.getPrivateProducts();
}
}
// Data class with validation
public class CreateUserRequest {
@NotNull(message = "Username is required")
private String username;
@NotNull(message = "Email is required")
@Email(message = "Invalid email format")
private String email;
@NotNull(message = "Password is required")
@Size(min = 8, message = "Password must be at least 8 characters")
private String password;
// Optional field with safe default
private Boolean receiveNewsletter = false;
// Getters and setters
}
Test Missing Parameter Handling
const request = require('supertest');
const app = require('./app');
describe('Missing Parameter Handling', () => {
test('Returns 400 when required parameter missing', async () => {
// Missing 'id' parameter
const response = await request(app)
.get('/user/')
.expect(400);
expect(response.body.error).toContain('Missing required parameter');
});
test('Returns 400 when required body field missing', async () => {
// Missing 'amount' field
const response = await request(app)
.post('/api/transfer')
.send({
to: 'account123',
from: 'account456'
// Missing: amount
})
.expect(400);
expect(response.body.missing).toContain('amount');
});
test('Returns 401 when auth token missing', async () => {
// No Authorization header
const response = await request(app)
.get('/api/data')
.expect(401);
expect(response.body.error).toContain('authorization');
});
test('Accepts request with all required parameters', async () => {
const response = await request(app)
.post('/api/transfer')
.send({
amount: 100,
to: 'account123',
from: 'account456'
})
.expect(200);
expect(response.body.status).toBe('success');
});
test('Uses safe defaults for optional parameters', async () => {
const response = await request(app)
.get('/products')
// No limit or offset specified
.expect(200);
// Should use default limit of 10
expect(response.body.length).toBeLessThanOrEqual(10);
});
test('Rejects request with security parameter omitted', async () => {
const response = await request(app)
.post('/register')
.send({
username: 'testuser',
email: 'test@example.com'
// Missing required: password
})
.expect(400);
expect(response.body.details).toContainEqual(
expect.objectContaining({
field: 'password',
message: expect.stringContaining('required')
})
);
});
});
Common Vulnerable Patterns
- Not checking if request.params.id exists
- Assuming query parameters are always present
- Missing null checks before using parameters
- Silent failures when parameters missing
- Using null/undefined values without validation
Security Checklist
- All required parameters validated before use
- Clear error messages for missing parameters
- Auth tokens/permissions explicitly required (not defaulted)
- Schema validation used for complex inputs
- Optional parameters have safe defaults
- Security-critical parameters never defaulted
- Tests verify missing parameter rejection