Linux infrastructure is a surprisingly flexible attack surface, often because we assume security is a solved problem once the OS is installed.

Let’s look at a common scenario: a web server serving dynamic content, talking to a database.

# Imagine this is a simplified flow:
# User Request -> Web Server (nginx/apache) -> Application (Python/Node/PHP) -> Database (PostgreSQL/MySQL)

This interaction, while standard, creates multiple points where an attacker could gain a foothold. The threat model isn’t just about preventing attacks, but about understanding how they could happen and what the impact would be.

The core problem we’re solving with threat modeling is identifying weaknesses before attackers do. It’s about systematically asking "what if" for every component and every interaction. We’re not just looking for known vulnerabilities; we’re looking for logical flaws in how systems are designed to talk to each other, or how they expose information.

Here’s a typical setup we might threat model:

  • Web Server: nginx serving static assets and proxying to an application server.
  • Application Server: A Python Flask app running under uwsgi.
  • Database: PostgreSQL on a separate host, accessible only from the app server.
  • Firewall: iptables or ufw on each host.

Let’s walk through a simplified threat model for this.

1. Identify Assets: What are we protecting? * User data (personal info, credentials, payment details) * Application code * Database integrity and availability * Server availability

2. Identify Entry Points: How could an attacker get in? * Web Server: * Exposed HTTP/S ports (80, 443). * Vulnerabilities in nginx itself (rare but possible). * Vulnerabilities in the application code it proxies to. * Misconfigured TLS/SSL. * Application Server: * Input validation flaws (SQL injection, XSS, command injection). * Deserialization vulnerabilities. * Authentication/authorization bypasses. * Exposed internal APIs. * Database: * Direct access if exposed externally (shouldn’t be). * Compromise of the application server allows lateral movement. * Weak database credentials. * Vulnerabilities in PostgreSQL itself. * Network: * Unrestricted firewall rules. * Compromised network devices. * SSH access with weak keys or passwords.

3. Decompose the System: Break it down into components and trust boundaries.

  • External User -> (Network) -> Web Server (Trust Boundary 1) -> Application Server (Trust Boundary 2) -> Database Server (Trust Boundary 3)

4. Analyze Threats (STRIDE Method is useful here):

  • Spoofing: An attacker pretends to be someone or something else.
    • Threat: Attacker spoofs a valid user to access restricted data.
    • Mitigation: Strong authentication (MFA, robust password policies), session management, rate limiting on login attempts.
  • Tampering: Modifying data or code.
    • Threat: Attacker injects malicious SQL into a web form, altering database records.
    • Mitigation: Parameterized queries (prepared statements) in the application code. For example, in Python with psycopg2:
      # BAD: String formatting
      # cursor.execute("SELECT * FROM users WHERE username = '%s'" % user_input)
      
      # GOOD: Parameterized query
      cursor.execute("SELECT * FROM users WHERE username = %s", (user_input,))
      
      This ensures user input is treated as data, not executable SQL.
  • Repudiation: Denying having performed an action.
    • Threat: An administrator performs a malicious action and denies it.
    • Mitigation: Comprehensive audit logging. For sudo commands:
      # Ensure this is in /etc/sudoers
      Defaults logfile=/var/log/sudo.log
      # Check logs with:
      sudo grep 'Apr 10 10:30:00 myhost sudo:    root :' /var/log/sudo.log
      
  • Information Disclosure: Revealing sensitive information.
    • Threat: Sensitive database connection strings or API keys are leaked from application configuration files.
    • Mitigation: Store secrets outside the application code. Use environment variables or a dedicated secrets management tool (like HashiCorp Vault or AWS Secrets Manager). For example, if using environment variables:
      # In your application startup script:
      export DB_HOST="db.internal.example.com"
      export DB_USER="app_user"
      export DB_PASSWORD=$(cat /run/secrets/db_password) # Example: using a mounted secret file
      
      And ensure the secrets file has restricted permissions: chmod 600 /run/secrets/db_password.
  • Denial of Service: Making a system unavailable.
    • Threat: A flood of requests to the web server exhausts its resources.
    • Mitigation: Rate limiting at the web server (nginx limit_req_zone) or firewall level, robust load balancing, and autoscaling.
      # nginx.conf snippet
      http {
          limit_req_zone $binary_remote_addr zone=mylimit:10m rate=5r/s;
          server {
              location / {
                  limit_req zone=mylimit burst=20 nodelay;
                  proxy_pass http://app_server;
              }
          }
      }
      
      This limits requests per second per client IP.
  • Elevation of Privilege: Gaining higher-level access.
    • Threat: A low-privileged web application process can write to sensitive system files.
    • Mitigation: Principle of Least Privilege. Ensure application processes run as dedicated, unprivileged users. For uwsgi with Flask:
      # uwsgi.ini
      [uwsgi]
      uid = appuser
      gid = appgroup
      # ... other settings
      
      Then, ensure appuser and appgroup have only read access to necessary files and write access only to designated directories (e.g., logs, uploads).

The most surprising thing about threat modeling is how often the most critical vulnerabilities aren’t in the complex application logic, but in the simple, overlooked configurations of fundamental system components like file permissions or network access rules.

Consider the implicit trust established by network segmentation. We often assume that if ServerA cannot directly ping ServerB on port 5432 (PostgreSQL), then ServerA cannot affect ServerB’s database. However, if ServerA is compromised and running an application that does have a legitimate connection to ServerB’s database, then ServerA effectively becomes a pivot point. The threat model needs to account for what a compromised internal host can do, not just external threats. This means thinking about the permissions and network access between your internal services. For instance, is the database user app_user allowed to execute COPY FROM PROGRAM or \gexec in PostgreSQL? If so, a SQL injection attack could lead to command execution on the database server itself, even if the database port is firewalled off from direct external access.

The next logical step after threat modeling is often implementing a robust incident response plan to handle the threats you’ve identified.

Want structured learning?

Take the full Cdk course →