Insecure Defaults
Insecure Defaults at a glance
Overview
Many frameworks and platforms ship with defaults that favor ease of use. In production these defaults can increase impact or leak information. Examples include running containers as root, permissive CORS libraries enabled with a one-liner, cookies set without Secure when TLS is terminated upstream, default tokens mounted into Kubernetes pods, and servers that reveal their exact versions.
Where it occurs
It occurs when development or platform defaults are used in production, such as permissive middleware, weak identity settings, or insecure HTTPS and capability configurations.
Impact
The impact ranges from session theft and CSRF bypass to cluster-wide compromise after a container breakout. Even when no direct exploit is present, information disclosure accelerates targeted attacks.
Prevention
Prevent insecure defaults by applying platform-specific hardening baselines, enforcing safe cookie and CORS settings, running containers as non-root, limiting Kubernetes RBAC and tokens, hiding server versions, and automating CI checks for weak configs.
Examples
Switch tabs to view language/framework variants.
Docker image runs as root by default, enabling high-impact escapes
If the image does not set a non-root user, the app runs as root inside the container. Breakouts or volume writes can be far worse.
FROM node:20
WORKDIR /app
COPY . /app
RUN npm ci --only=production
CMD ["node","server.js"]- Line 1: No USER set, process runs as root
Containers default to root if a USER is not set. Root inside the container increases impact of any RCE and interacts badly with mounted volumes.
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# create non-root user and drop caps
RUN useradd -m appuser && mkdir -p /app && chown -R appuser:appuser /app
USER appuser
# optionally drop capabilities at runtime
# docker run --cap-drop=ALL --read-only -v /app/tmp -p 3000:3000 app:latest
CMD ["node","server.js"]- Line 6: Create and switch to a non-root user and drop capabilities
Run as a dedicated unprivileged user, drop capabilities, use read-only filesystems and minimal images.
Engineer Checklist
-
Run containers as non-root and drop Linux capabilities
-
Disable automount of Kubernetes service account tokens unless required
-
Configure reverse proxy awareness and set cookie Secure, HttpOnly, and SameSite
-
Restrict CORS to known origins and avoid credentials unless necessary
-
Disable server version banners and review response headers
-
Add CI checks to detect insecure defaults before deploy
End-to-End Example
A Node app is packaged in a Docker image without setting a USER, then deployed to Kubernetes. The pod auto-mounts a service account token. An attacker finds a trivial RCE in an endpoint. Because the app runs as root, the attacker writes a cron-like script into a host-mounted volume and uses the mounted token to list secrets.
# VULNERABLE: Dockerfile with insecure defaults
# Dockerfile
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
# VULNERABLE: No USER directive!
# Container runs as root (uid 0) by default
# If compromised, attacker has root access inside container
EXPOSE 3000
# VULNERABLE: Running as root user
CMD ["node", "server.js"]
# VULNERABLE: Kubernetes deployment with insecure defaults
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
# VULNERABLE: No automountServiceAccountToken: false
# Service account token auto-mounted at /var/run/secrets/kubernetes.io/serviceaccount
# Allows API access if container compromised!
# VULNERABLE: No securityContext restrictions
# Allows privileged escalation, running as root, etc.
containers:
- name: app
image: myapp:latest
ports:
- containerPort: 3000
# VULNERABLE: No securityContext on container level
# No runAsNonRoot, no readOnlyRootFilesystem
# No capability drops
# VULNERABLE: Node.js/Express with insecure cookie defaults
const session = require('express-session');
app.use(session({
secret: 'my-secret',
resave: false,
saveUninitialized: false,
cookie: {
// VULNERABLE: No 'secure' flag!
// Cookie sent over HTTP, can be intercepted
// secure: true, // MISSING!
// VULNERABLE: No httpOnly!
// httpOnly: true, // MISSING! XSS can steal cookie
// VULNERABLE: No sameSite!
// sameSite: 'strict', // MISSING! CSRF possible
maxAge: 24 * 60 * 60 * 1000
}
}));
// VULNERABLE: Permissive CORS with default settings
const cors = require('cors');
// VULNERABLE: Allows ALL origins!
app.use(cors({
origin: true, // Reflects any origin!
credentials: true // Allows authenticated requests from ANY origin!
}));
// This enables CSRF from any malicious site
// VULNERABLE: Nginx with version disclosure
# nginx.conf
http {
# VULNERABLE: server_tokens defaults to 'on'
# Exposes exact nginx version in error pages and headers
# Server: nginx/1.18.0
server {
listen 80;
# VULNERABLE: No HTTPS enforcement
# Allows plaintext traffic, cookies sent over HTTP
location / {
proxy_pass http://backend:3000;
# VULNERABLE: No security headers
# Missing X-Content-Type-Options, X-Frame-Options, etc.
}
}
}
// VULNERABLE: ASP.NET Core cookie defaults
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options => {
// VULNERABLE: Cookie.SecurePolicy defaults to None
// Cookies sent over HTTP!
// options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // MISSING!
// VULNERABLE: No SameSite protection
// options.Cookie.SameSite = SameSiteMode.Strict; // MISSING!
options.ExpireTimeSpan = TimeSpan.FromHours(24);
});
// VULNERABLE: Default database connection allows any IP
# PostgreSQL pg_hba.conf
# VULNERABLE: Allows connections from anywhere!
host all all 0.0.0.0/0 md5
# Should restrict to specific IPs or use stronger auth
// VULNERABLE: Express trust proxy not configured
// When behind a reverse proxy (common in production)
// VULNERABLE: Not trusting proxy
// app.set('trust proxy', true); // MISSING!
// Results in req.ip showing proxy IP, not client IP
// req.protocol shows 'http' even when proxy uses HTTPS
// Cookie secure flag won't work correctly!# SECURE: Dockerfile with hardened defaults
# Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# Multi-stage build - smaller, more secure image
FROM node:18-alpine
WORKDIR /app
# SECURE: Create non-root user
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
# Copy only production dependencies and code
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --chown=appuser:appgroup . .
# SECURE: Switch to non-root user
USER appuser
EXPOSE 3000
# Run as non-root
CMD ["node", "server.js"]
# SECURE: Kubernetes deployment with hardened defaults
# deployment.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: web-app-sa
automountServiceAccountToken: false # SECURE: Disable auto-mount
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
# SECURE: Use dedicated service account with minimal permissions
serviceAccountName: web-app-sa
# SECURE: Disable token auto-mount at pod level
automountServiceAccountToken: false
# SECURE: Pod-level security context
securityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 1001
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:latest
ports:
- containerPort: 3000
# SECURE: Container-level security context
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 1001
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
# SECURE: Resource limits
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
# SECURE: Writable volume for temp files
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
# SECURE: Node.js/Express with secure cookie settings
const session = require('express-session');
// SECURE: Configure trust proxy
app.set('trust proxy', 1); // Trust first proxy
app.use(session({
secret: process.env.SESSION_SECRET, // From env, not hardcoded
resave: false,
saveUninitialized: false,
name: 'sessionId', // Don't use default 'connect.sid'
cookie: {
secure: true, // SECURE: Only over HTTPS
httpOnly: true, // SECURE: No JavaScript access
sameSite: 'strict', // SECURE: CSRF protection
maxAge: 3600000, // 1 hour
domain: '.example.com'
}
}));
// SECURE: Restrictive CORS configuration
const cors = require('cors');
const ALLOWED_ORIGINS = [
'https://app.example.com',
'https://www.example.com'
];
app.use(cors({
origin: (origin, callback) => {
// Allow requests with no origin (mobile apps, Postman, etc.)
if (!origin) return callback(null, true);
if (ALLOWED_ORIGINS.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true, // Only with specific origins
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
maxAge: 86400 // 24 hours
}));
// SECURE: Security headers middleware
const helmet = require('helmet');
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
frameguard: { action: 'deny' },
noSniff: true,
xssFilter: true
}));
// Hide X-Powered-By header
app.disable('x-powered-by');
// SECURE: Nginx with hardened defaults
# nginx.conf
http {
# SECURE: Disable version disclosure
server_tokens off;
# Security headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Rate limiting
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
limit_req_status 429;
# Redirect HTTP to HTTPS
server {
listen 80;
server_name app.example.com;
return 301 https://$server_name$request_uri;
}
# HTTPS server
server {
listen 443 ssl http2;
server_name app.example.com;
# TLS configuration
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
limit_req zone=general burst=20 nodelay;
proxy_pass http://backend:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
// SECURE: ASP.NET Core with secure cookie policy
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options => {
options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // HTTPS only
options.Cookie.SameSite = SameSiteMode.Strict; // CSRF protection
options.Cookie.HttpOnly = true; // No JS access
options.Cookie.Name = "AppAuth"; // Custom name
options.ExpireTimeSpan = TimeSpan.FromHours(1);
options.SlidingExpiration = true;
});
// SECURE: Database connection restrictions
# PostgreSQL pg_hba.conf
# Only allow specific IPs
host all all 10.0.1.0/24 scram-sha-256
host all all 192.168.1.100/32 scram-sha-256
# Require SSL
hostssl all all 0.0.0.0/0 scram-sha-256Discovery
This vulnerability is discovered by identifying default credentials, configurations, or settings that remain unchanged in production, such as default admin passwords, enabled debug modes, or permissive security settings.
-
1. Check container user privileges
httpAction
Inspect container runtime configuration to identify if process runs as root
Request
CLI N/A - Analysis or internal stepResponse
Status: 200Body:{ "note": "No runAsNonRoot or runAsUser specified, defaulting to root (uid 0)" }Artifacts
pod_security_context -
2. Verify service account token automount
httpAction
Check if Kubernetes service account tokens are automatically mounted into pods
Request
CLI N/A - Analysis or internal stepResponse
Status: 200Body:{ "note": "Service account token, ca.crt, and namespace files present and readable" }Artifacts
mounted_secrets token_content -
3. Test cookie security flags
httpAction
Inspect session cookies to verify Secure, HttpOnly, and SameSite attributes
Request
GET https://app.example.com/loginResponse
Status: 200Body:{ "note": "Set-Cookie header missing Secure flag despite HTTPS, enabling MITM attacks" }Artifacts
http_response_headers cookie_attributes -
4. Check CORS policy permissiveness
httpAction
Send cross-origin request to test if CORS allows any origin with credentials
Request
OPTIONS https://api.example.com/dataHeaders:Origin: https://evil.example.comResponse
Status: 200Body:{ "note": "Response includes Access-Control-Allow-Origin: * with Allow-Credentials: true" }Artifacts
cors_headers
Exploit steps
An attacker exploits this by using well-known default credentials or leveraging insecure default configurations to gain unauthorized access, escalate privileges, or bypass security controls without sophisticated exploitation techniques.
-
1. Escape container using root privileges
Mount host filesystem from root container
httpAction
Exploit root permissions to mount host filesystem and write to persistent storage
Request
CLI N/A - Analysis or internal stepResponse
Status: 200Body:{ "note": "Host filesystem mounted, SSH backdoor installed on underlying node" }Artifacts
mount_output file_write_confirmation -
2. Enumerate cluster resources with service account
Query Kubernetes API for secrets
httpAction
Use auto-mounted service account token to list secrets and configmaps across namespaces
Request
CLI N/A - Analysis or internal stepResponse
Status: 200Body:{ "note": "Cluster secrets enumerated including DB passwords and API keys" }Artifacts
kubernetes_secrets api_response -
3. Steal session cookies via missing Secure flag
MITM attack to capture cookies
browserAction
Downgrade HTTPS to HTTP to capture session cookies lacking Secure attribute
Request
GET http://app.example.com/dashboardResponse
Status: 200Body:{ "note": "Session cookie transmitted over HTTP, captured by network attacker" }Artifacts
captured_cookies session_token -
4. Cross-site request forgery via permissive CORS
Execute authenticated requests from malicious origin
browserAction
Leverage wildcard CORS to make authenticated API calls from attacker-controlled site
Request
POST https://api.example.com/transferHeaders:Origin: https://evil.example.comContent-Type: application/jsonBody:{ "amount": 1000, "to": "attacker" }Response
Status: 200Body:{ "note": "Cross-origin request succeeds with user credentials, money transferred" }Artifacts
transaction_record cors_response_headers
Specific Impact
Running as root turns a minor exploit into a high impact incident. The attacker persists on a shared volume and enumerates the cluster using the auto-mounted token. If the token has broad RBAC, they can create pods, read secrets, or move laterally.
Fixing only the application bug would not remove the backdoor or revoke the stolen cluster token, so recovery is longer and costlier.
Fix
Harden platform defaults. Run as an unprivileged user and drop capabilities. Disable token auto-mount and use least privilege RBAC or workload identity. Configure proxy awareness and set cookie flags explicitly. Restrict CORS to trusted origins. Add CI checks that fail builds if these defaults are missing.
Detect This Vulnerability in Your Code
Sourcery automatically identifies insecure defaults vulnerabilities and many other security issues in your codebase.
Scan Your Code for Free