WAF++ WAF++
Back to WAF++ Homepage

Best Practice: Vulnerability & Patch Management

Context

Known vulnerabilities (CVEs) are the most easily exploitable attack vector – because the attack methods are publicly documented and automated exploits exist. At the same time, patch management is often the most neglected security area, because it delivers no new features and creates effort.

The consequence: Log4Shell (CVE-2021-44228) was in widespread exploitation 12 days after public disclosure. Organizations without structured patch management could not even identify affected systems – let alone patch them.

Typical oversights:

  • Container images are based on Ubuntu 18.04 LTS – end-of-life for years

  • ECR image scanning disabled – known CVEs in production undetected

  • No SBOM process – when new CVEs emerge, it is unclear which systems are affected

  • Dependabot PRs are ignored – 200 open security updates

  • No SLA for patches – prioritization is done by gut feeling, not severity

Container Security

ECR Image Scanning

Amazon ECR offers two scanning modes:

Mode Technology Recommendation

Basic Scanning

Clair; open-source CVE database

Minimum requirement; only for low-risk workloads

Enhanced Scanning

Amazon Inspector; AWS Security Hub integration; continuous scanning even after push

Required for production workloads; CVE findings visible in Security Hub

resource "aws_ecr_repository" "payment_service" {
  name                 = "payment-service"
  image_tag_mutability = "IMMUTABLE"  # Tags cannot be overwritten

  image_scanning_configuration {
    scan_on_push = true  # Automatic scanning on every push
  }

  encryption_configuration {
    encryption_type = "KMS"
    kms_key         = aws_kms_key.ecr_cmk.arn
  }

  tags = {
    owner       = "payment-team"
    environment = "production"
    workload    = "payment-service"
  }
}

# Lifecycle policy: automatically delete old images
resource "aws_ecr_lifecycle_policy" "payment_service" {
  repository = aws_ecr_repository.payment_service.name

  policy = jsonencode({
    rules = [
      {
        rulePriority = 1
        description  = "Keep only the last 10 images per tag"
        selection = {
          tagStatus   = "tagged"
          countType   = "imageCountMoreThan"
          countNumber = 10
        }
        action = { type = "expire" }
      },
      {
        rulePriority = 2
        description  = "Delete untagged images after 7 days"
        selection = {
          tagStatus     = "untagged"
          tagPrefixList = []
          countType     = "sinceImagePushed"
          countUnit     = "days"
          countNumber   = 7
        }
        action = { type = "expire" }
      }
    ]
  })
}

Trivy and Grype in the CI Pipeline

Local scanning before pushing prevents CVE-affected images from ever reaching the registry:

# GitHub Actions workflow – container security scanning
name: Container Build & Scan

on:
  push:
    paths: ['Dockerfile', 'src/**']

jobs:
  build-and-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build Docker Image
        run: docker build -t payment-service:${{ github.sha }} .

      - name: Scan with Trivy
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: payment-service:${{ github.sha }}
          format: sarif
          output: trivy-results.sarif
          severity: CRITICAL,HIGH
          exit-code: 1  # Pipeline fails on CRITICAL/HIGH

      - name: Upload Trivy Results to Security Hub
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: trivy-results.sarif

      - name: Scan with Grype (second opinion)
        run: |
          grype payment-service:${{ github.sha }} \
            --fail-on critical \
            --output json \
            > grype-results.json

Base Image Strategy

  • Use distroless images – no shell, no package manager, minimal attack surface

  • Pin specific tags – never FROM ubuntu:latest; always FROM ubuntu:22.04@sha256:abc123

  • Automate base image updates – configure Renovate or Dependabot for Dockerfiles

# Good example: distroless, specific digest
FROM gcr.io/distroless/java21-debian12:nonroot@sha256:a1b2c3d4e5f6...

# Bad example: mutable tag, many packages
# FROM openjdk:latest

AMI Patching Strategy

Bake vs. In-Place

Strategy Approach Recommendation

Bake (Immutable)

Create new AMI version with patches; update launch template; rolling update of the auto scaling group; terminate old instances

Recommended for all production workloads; complete reproducibility; no configuration drift

In-Place (SSM Patch Manager)

Patch existing instances via SSM Run Command; configure patch groups; define maintenance windows

Acceptable for workloads that do not support immutability; higher drift risk

EC2 Image Builder for Automated AMI Creation

resource "aws_imagebuilder_image_pipeline" "payment_base" {
  name             = "payment-service-base-ami"
  image_recipe_arn = aws_imagebuilder_image_recipe.payment_base.arn
  infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.main.arn

  schedule {
    schedule_expression                = "cron(0 2 ? * SUN *)"  # Every Sunday at 02:00
    pipeline_execution_start_condition = "EXPRESSION_MATCH_ONLY"
  }

  image_tests_configuration {
    image_tests_enabled = true
    timeout_minutes     = 60
  }
}

Dependency Scanning and SBOM

What Is an SBOM?

A Software Bill of Materials (SBOM) is a complete list of all software components and their versions in an artifact. When a new CVE is reported, an SBOM allows an immediate answer to the question: "Are we affected?"

Without SBOM, answering this question takes days – with SBOM, minutes.

SBOM Generation in the CI Pipeline

- name: Generate SBOM with Syft
  run: |
    syft payment-service:${{ github.sha }} \
      -o spdx-json=sbom.spdx.json \
      -o cyclonedx-json=sbom.cyclonedx.json

- name: Store SBOM as Artifact
  uses: actions/upload-artifact@v4
  with:
    name: sbom-${{ github.sha }}
    path: |
      sbom.spdx.json
      sbom.cyclonedx.json
    retention-days: 365  # Compliance: retain SBOM for 1 year

- name: Attestation with Cosign (SLSA L2)
  run: |
    cosign attest \
      --predicate sbom.spdx.json \
      --type spdxjson \
      payment-service:${{ github.sha }}

Dependabot and Renovate Configuration

# .github/dependabot.yml
version: 2
updates:
  # Python dependencies
  - package-ecosystem: "pip"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10
    groups:
      security-patches:
        patterns: ["*"]
        update-types: ["patch"]

  # Docker base images
  - package-ecosystem: "docker"
    directory: "/"
    schedule:
      interval: "weekly"

  # GitHub Actions
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"

CVE Prioritization

The Prioritization Model

A CVSS score alone is not a sufficient prioritization criterion. The combination of score, exploitability, and context provides the correct picture:

CVE Class CVSS Score Patch SLA Prioritization Criteria

Critical

9.0–10.0

< 24 hours

CVSS >= 9.0 OR actively exploited (CISA KEV) OR RCE on production system

High

7.0–8.9

< 7 days

CVSS 7.0–8.9 OR reachable service exposed OR known PoC exploits

Medium

4.0–6.9

< 30 days

CVSS 4.0–6.9; not directly reachable OR requires local access

Low

0.1–3.9

< 90 days

Low exploitation probability; no known exploit; requires combination

Informational

n/a

Best effort

Configuration recommendations; no directly exploitable bug

CISA Known Exploited Vulnerabilities (KEV)

The CISA KEV list contains CVEs that are actively used in attacks. CVEs on this list should be treated as critical regardless of their CVSS score:

# Check whether current CVEs are on the CISA KEV list
curl -s https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json \
  | jq '.vulnerabilities[] | select(.cveID == "CVE-2021-44228")'

Monitoring and Measurement

  • Amazon Inspector: Continuous scanning of EC2, ECR, and Lambda

  • AWS Security Hub: Aggregates Inspector, GuardDuty, Macie findings

  • GitHub Security Advisories: Dependabot alerts for dependencies

  • Patch Compliance Dashboard: SSM Patch Manager compliance reports

  • Mean Time to Patch (MTTP): Metric per severity level – target: meet SLA

  • wafpass: wafpass check --pillar security --controls WAF-SEC-070,WAF-SEC-110