A05: Security Misconfiguration
Learn about security misconfiguration vulnerabilities, common mistakes in configuring applications and infrastructure, and how to implement secure configurations.
Security Misconfiguration is one of the most common vulnerabilities and often the easiest to exploit. It occurs when security settings are not defined, implemented, or maintained properly. Even small misconfigurations can lead to significant breaches.
What is Security Misconfiguration?
Security misconfiguration can happen at any level of an application stack:
- Network services - Unnecessary ports open, weak firewall rules
- Platform/Framework - Default configurations, unnecessary features enabled
- Application server - Verbose error messages, directory listing
- Database - Default credentials, excessive permissions
- Cloud services - Public S3 buckets, overly permissive IAM roles
The challenge is that modern applications have many configuration points, and each one is a potential vulnerability.
Common Misconfiguration Patterns
1. Default Credentials
The Problem:
Many systems ship with default usernames and passwords that are publicly documented.
# VULNERABLE: Default credentials in docker-compose
services:
database:
image: postgres:14
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres # Default!
Secure Configuration:
# SECURE: Use secrets management
services:
database:
image: postgres:14
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
external: true # Managed outside docker-compose
Always change default credentials for:
- Databases (PostgreSQL, MySQL, MongoDB)
- Message brokers (RabbitMQ, Redis)
- Admin panels (WordPress, phpMyAdmin)
- Network devices (routers, switches)
- Cloud services (AWS root account)
2. Verbose Error Messages
The Problem:
Detailed error messages help attackers understand your system.
// VULNERABLE: Stack trace exposed to users
app.use((err, req, res, next) => {
res.status(500).json({
error: err.message,
stack: err.stack, // Reveals internal paths and code structure!
query: req.query // Might contain sensitive data
});
});
Attackers learn:
- Framework and library versions
- File paths and directory structure
- Database schema from SQL errors
- Internal API endpoints
Secure Configuration:
// SECURE: Generic errors in production, detailed in development
app.use((err, req, res, next) => {
// Log full error for debugging
logger.error('Unhandled error', {
error: err.message,
stack: err.stack,
requestId: req.id,
path: req.path,
method: req.method
});
// Return generic message in production
if (process.env.NODE_ENV === 'production') {
res.status(500).json({
error: 'An unexpected error occurred',
requestId: req.id // For support reference
});
} else {
// Detailed errors in development
res.status(500).json({
error: err.message,
stack: err.stack
});
}
});
3. Unnecessary Services and Features
The Problem:
Every enabled feature is a potential attack surface.
# VULNERABLE: Unnecessary features enabled
server {
listen 80;
# Directory listing enabled
autoindex on;
# Server version exposed
server_tokens on;
location /admin {
# Admin panel accessible from internet
}
}
Secure Configuration:
# SECURE: Minimal, hardened configuration
server {
listen 443 ssl http2;
# Hide server version
server_tokens off;
# Disable directory listing
autoindex off;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Admin panel - internal only
location /admin {
allow 10.0.0.0/8; # Internal network only
deny all;
}
}
4. Cloud Misconfigurations
The Problem:
Cloud services default to convenience, not security.
# VULNERABLE: Public S3 bucket
resource "aws_s3_bucket" "data" {
bucket = "company-data"
}
resource "aws_s3_bucket_acl" "data" {
bucket = aws_s3_bucket.data.id
acl = "public-read" # Anyone can read!
}
Secure Configuration:
# SECURE: Private bucket with encryption
resource "aws_s3_bucket" "data" {
bucket = "company-data"
}
resource "aws_s3_bucket_public_access_block" "data" {
bucket = aws_s3_bucket.data.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
bucket = aws_s3_bucket.data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}
resource "aws_s3_bucket_versioning" "data" {
bucket = aws_s3_bucket.data.id
versioning_configuration {
status = "Enabled"
}
}
5. Missing Security Headers
The Problem:
Browsers have security features that must be explicitly enabled.
// VULNERABLE: No security headers
app.get('/', (req, res) => {
res.send('<html>...</html>');
});
Secure Configuration:
const helmet = require('helmet');
// Helmet sets many security headers automatically
app.use(helmet());
// Customize as needed
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
upgradeInsecureRequests: [],
},
}));
Configuration Management Best Practices
1. Infrastructure as Code
Define configurations in version-controlled code, not manual settings:
# Kubernetes deployment with security context
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
template:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containers:
- name: api
image: api:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
limits:
cpu: "500m"
memory: "256Mi"
requests:
cpu: "100m"
memory: "128Mi"
2. Configuration Scanning
Use tools to detect misconfigurations before deployment:
# Scan Terraform for security issues
checkov -d ./terraform/
# Scan Kubernetes manifests
kubesec scan deployment.yaml
# Scan Docker images
trivy image myapp:latest
# Scan CloudFormation
cfn-lint template.yaml
cfn_nag_scan --input-path template.yaml
3. Environment Separation
Development and production should have different configurations:
// config/index.js
const configs = {
development: {
debug: true,
logLevel: 'debug',
cors: { origin: '*' },
rateLimit: { max: 1000 }
},
production: {
debug: false,
logLevel: 'warn',
cors: { origin: 'https://myapp.com' },
rateLimit: { max: 100 }
}
};
module.exports = configs[process.env.NODE_ENV || 'development'];
Key Takeaways
- Change all default credentials immediately after installation
- Disable unnecessary features - every feature is attack surface
- Use generic error messages in production
- Implement security headers using tools like Helmet
- Scan configurations with automated tools (Checkov, tfsec)
- Use Infrastructure as Code for consistent, reviewable configurations
- Separate environments - production should be more restrictive
- Review regularly - configurations drift over time
Security misconfiguration is preventable with proper processes and automation. The key is treating configuration as code and subjecting it to the same rigor as application code.
Found an issue?