Back to Cheat Sheets

🔒 Secure By Default Configurations

OWASP Top 10 Proactive Controls (2024) - C5

CORE CONTROL

📋 What Is This Control?

Secure By Default Configurations means shipping and running systems that are safe out of the box. A fresh deployment - one nobody has hardened yet - should already be in a secure state. Security is the starting position, and relaxing it is a deliberate, visible, rare exception. It is the constructive counterpart to Security Misconfiguration: instead of hoping someone remembers to lock things down later, the defaults were never insecure to begin with. The governing principle is deny by default - features, ports, accounts, and permissions are off unless a specific need turns them on.

C5 Proactive Control
Deny by Default Focus Area
OWASP 2024 Proactive Controls

🎯 What It Prevents

  • Exposed No-Auth Datastores: DBs bound to all interfaces with auth off
  • Public Cloud Buckets: Object storage readable by "everyone"
  • Open Admin Dashboards: Consoles reachable from the internet, unauthenticated
  • Default / Sample Credentials: The admin/admin family still active
  • Verbose Errors & Debug Mode: Stack traces and debug pages in production
  • Excess Attack Surface: Sample apps, demo pages, unused ports and services
  • Config Drift: A secure baseline that silently erodes back to insecure

🧭 How It Works

1. Start from deny-by-default - everything off unless needed
↓
2. Codify a known-good baseline (e.g. a CIS Benchmark)
↓
3. Bake it into templates, base images & IaC (a paved road)
↓
4. Remove default creds, sample apps & unused features
↓
5. Scan continuously: drift fails the pipeline or alerts

❌ Without This Control

# nginx: insecure defaults - banner on, listing on, all methods, plaintext server { listen 80; # plaintext, no redirect to HTTPS server_tokens on; # advertises nginx version autoindex on; # browsable directory listings location / { root /var/www/html; } # serves .git, .env, backups too # all HTTP methods accepted; no security headers } # Django settings.py: unsafe out of the box DEBUG = True # tracebacks + debug pages to clients ALLOWED_HOSTS = ['*'] # accepts any Host header SECRET_KEY = 'insecure-default-key' # shipped default secret SESSION_COOKIE_SECURE = False # cookie sent over plain HTTP SECURE_HSTS_SECONDS = 0 # no HSTS # Terraform: bucket that can be made public, stored unencrypted resource "aws_s3_bucket_acl" "data" { acl = "public-read" # world-readable }

✅ Applying the Control

# nginx: safe defaults - HTTPS, no banner, scoped methods, headers preset server { listen 80; return 301 https://$host$request_uri; } # force HTTPS server { listen 443 ssl; server_tokens off; # no version banner ssl_protocols TLSv1.2 TLSv1.3; # modern TLS only add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Content-Type-Options "nosniff" always; add_header Content-Security-Policy "default-src 'none'; frame-ancestors 'none'" always; location / { autoindex off; limit_except GET POST { deny all; } } # deny-by-default location ~ /\.(git|env) { deny all; return 404; } } # Django: secure out of the box DEBUG = False # off by default in production ALLOWED_HOSTS = ['app.example.com'] # explicit host allow-list SECRET_KEY = os.environ['DJANGO_SECRET_KEY'] # injected, never a default SESSION_COOKIE_SECURE = CSRF_COOKIE_SECURE = True SECURE_HSTS_SECONDS = 31536000; SECURE_CONTENT_TYPE_NOSNIFF = True; X_FRAME_OPTIONS = 'DENY' # Terraform: private + encrypted by default resource "aws_s3_bucket_public_access_block" "data" { block_public_acls = block_public_policy = true ignore_public_acls = restrict_public_buckets = true # private by default } # Dockerfile: minimal, pinned, non-root FROM python:3.12-slim@sha256:<digest> # minimal, patched, pinned RUN useradd --uid 10001 appuser USER 10001 # non-root by default

✓ Implementation Checklist

  • A fresh, un-hardened deployment already starts in a safe state
  • Deny by default: features, ports, and services off unless explicitly needed
  • No default or sample credentials - a unique secret is forced at first setup
  • Identities and service accounts start with least privilege
  • Security headers (HSTS, CSP, nosniff, frame denial) preset on every response
  • Debug and verbose errors off in production; detail goes to logs only
  • Cloud buckets private and encrypted by default in your templates
  • A codified secure baseline (e.g. CIS Benchmark) applied identically everywhere
  • Base images and dependencies default to current, patched versions
  • Automated validation and drift detection fail the pipeline on deviation
  • The secure option is the default option developers reach for (paved road)

🔍 Tools & Techniques

Baselines & Hardening:

CIS Benchmarks CIS-CAT OpenSCAP Ansible / Chef helmet.js django-secure

IaC & Drift Detection:

Checkov tfsec / Trivy cfn-nag kube-bench AWS Config securityheaders.com

How to Verify:

  • Deploy a clean instance and confirm it is safe with zero manual hardening
  • Scan for default credentials and open management ports
  • Check response headers and that debug mode is off in production
  • Confirm cloud buckets are private and encrypted by template
  • Verify CI blocks the build when IaC drifts from the baseline

🌍 Why It Matters

  • Exposed no-auth datastores: Databases and search engines that historically shipped listening on all interfaces with auth disabled were deployed straight to the internet and read, tampered with, or wiped. Vendors later changed the default to bind to localhost precisely because the insecure default caused so many incidents.
  • Public cloud storage buckets: Object storage left readable by "everyone" exposed backups and customer records across many organisations. Providers responded by adding block-public-access defaults - moving the safe state into the default.
  • Open admin & orchestration dashboards: Management consoles and cluster dashboards deployed with no authentication and reachable from the internet were hijacked to mine cryptocurrency and pivot inward.
  • Default and sample credentials: Devices and apps shipped with well-known credentials are compromised at scale by automated tools, no exploit required. Shipping with no usable default removes the class entirely.

These are documented classes of misconfiguration, described as patterns rather than tied to any vendor or CVE. Most large misconfiguration breaches trace back to a default that was never changed.

📌 Quick Tips

  • DO NOT plan to "harden it after deployment" - that window is when systems are attacked
  • DO NOT ship or keep default / sample credentials
  • DO NOT assume a vendor default is safe for production
  • DO start from deny-by-default and least privilege
  • DO preset security headers and turn debug off by default
  • DO codify a secure baseline and apply it identically everywhere
  • DO automate drift detection so the baseline cannot silently erode