Mid-level 8 min · March 06, 2026

Network Security Basics — Disable Certificate Verification

SSL certificate errors in a payment API led developers to add verify=False, enabling a MITM attack.

N
Naren Founder & Principal Engineer

20+ years shipping production systems from the metal up. Notes here come from systems that actually shipped.

Follow
Production
production tested
May 23, 2026
last updated
1,554
articles · all by Naren
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Network security protects data and services over untrusted networks using four pillars: confidentiality, integrity, availability, and authentication
  • Firewalls filter traffic by IP/port; stateful firewalls track connection state
  • TLS uses asymmetric crypto for handshake, symmetric for bulk data
  • A single misconfigured port or disabled cert verification can expose your entire system
  • Biggest mistake: treating encryption as equal to integrity — use authenticated encryption (AES-GCM)
✦ Definition~90s read
What is Network Security Basics?

Disabling certificate verification is the network security equivalent of disabling your front door lock because keys are inconvenient. When you skip certificate validation in TLS/HTTPS connections—often done with verify=False in Python's requests library, NODE_TLS_REJECT_UNAUTHORIZED=0 in Node.js, or -k in curl—you're telling your application to accept any server's identity without checking its cryptographic credentials.

Imagine your home has a front door, windows, a mailbox, and a safe inside.

This bypasses the entire Public Key Infrastructure (PKI) that makes HTTPS trustworthy, leaving you vulnerable to man-in-the-middle attacks where an attacker can intercept, read, and modify traffic as if they were the legitimate server. The handshake still happens, encryption still gets applied, but you've removed the authentication pillar—the 'who am I actually talking to?' check—from the security model.

This practice directly undermines the CIA triad (Confidentiality, Integrity, Availability) plus Authentication, which together form the bedrock of network security. Without certificate validation, confidentiality is compromised because an attacker with network access can decrypt your traffic; integrity fails because they can modify data in transit; and authentication is completely absent since you're trusting any certificate presented.

The attack surface expands dramatically: every network hop between your client and the server becomes a potential interception point, including compromised routers, rogue Wi-Fi access points, or malicious proxies on the same subnet. In production environments, this is how credential theft, API key leakage, and data exfiltration happen—not through breaking AES-256, but through disabling the identity check that makes encryption meaningful.

Real defenses against this include proper certificate pinning, using certificate transparency logs to detect misissued certs, and enforcing strict TLS validation in all environments including development (where self-signed certs should be added to a trust store, not globally disabled). Network segmentation and defense-in-depth strategies—like placing internal services behind VPNs or mTLS—reduce the blast radius if a certificate check is accidentally skipped.

The HTTPS handshake itself (ClientHello, ServerHello, certificate exchange, key agreement via Diffie-Hellman) is robust, but only if you verify that the certificate chains to a trusted root and hasn't expired or been revoked. Disabling that verification is a debugging shortcut that has no place in any system handling sensitive data.

Plain-English First

Imagine your home has a front door, windows, a mailbox, and a safe inside. Network security is the job of deciding who gets a key, which windows need bars, what mail you accept, and how strong your safe is. A hacker is someone trying every door handle, looking for an unlocked window, or slipping a fake letter in your mailbox. Every security tool you'll read about — firewalls, encryption, authentication — maps directly to one of those real-world jobs.

Every app you build eventually talks to a network. The moment it does, it inherits every threat that network carries — eavesdroppers, impersonators, denial-of-service floods, and data thieves. High-profile breaches at companies like Equifax and LastPass didn't happen because developers were careless people; they happened because developers didn't understand which layer of the stack was the weak link. Network security isn't a specialisation reserved for security teams — it's foundational knowledge every engineer needs.

The core problem network security solves is trust over an untrusted medium. The internet was designed in the 1970s for cooperative researchers, not adversarial strangers. Data hops through routers owned by companies you've never heard of, and any one of those hops is a potential interception point. Security protocols exist to answer four questions at every hop: Is this data intact? Is it private? Is the sender who they claim to be? Can I keep serving requests without being overwhelmed?

By the end of this article you'll be able to name and explain the four core security properties (CIA + Authentication), understand exactly what a firewall, TLS handshake, and a SYN flood attack do under the hood, read a basic certificate chain with confidence, and spot the two most common security mistakes in code reviews. You'll also have a working Python demonstration of symmetric vs. asymmetric encryption and a TLS socket connection you can actually run.

Why Disabling Certificate Verification Undermines Network Security

Network security basics start with trust: when a client connects to a server over TLS, it must verify the server's certificate against a trusted Certificate Authority (CA). Disabling certificate verification — often via a trust-all or no-op hostname verifier — breaks that trust chain entirely. The client will accept any certificate, including self-signed or malicious ones, making the connection vulnerable to man-in-the-middle (MITM) attacks.

In practice, verification involves two checks: the certificate must be signed by a trusted CA, and the hostname in the certificate must match the server's domain. Disabling either check reduces security to the level of plain HTTP. Libraries like OkHttp, Apache HttpClient, or Java's HttpsURLConnection expose methods to bypass these checks, often for local development or testing against self-signed certificates.

Production systems must never disable certificate verification. The only acceptable use is in isolated test environments where you control both client and server, and even then, prefer importing the test CA into a custom truststore. In real systems, a single disabled verification in a microservice can expose internal APIs to interception, leading to data leaks or credential theft.

Trust-all is not a shortcut
Disabling verification for 'just this one call' often spreads via copy-paste into production code, creating a permanent security hole.
Production Insight
Teams debugging a staging environment disable verification to bypass a misconfigured self-signed cert, then forget to revert before deploying to production.
The symptom: no TLS errors in logs, but a MITM proxy (e.g., a rogue employee on the same network) can intercept all traffic silently.
Rule of thumb: never disable verification — instead, add the self-signed cert to a custom truststore and configure the client to use it.
Key Takeaway
Certificate verification is the only barrier against MITM attacks in TLS.
Disabling it is equivalent to sending data in plaintext over the wire.
Always use a custom truststore for non-standard certs, never a trust-all approach.
Disabling Certificate Verification Risks THECODEFORGE.IO Disabling Certificate Verification Risks How skipping TLS validation breaks security Disable Verification Bypass TLS certificate checks Broken Authentication No server identity validation Exposed CIA Triad Confidentiality, integrity, availability lost Attack Surface Opened MITM, spoofing, data theft possible Defense in Depth Nullified Firewalls, segmentation, monitoring bypassed Secure PKI Restored Re-enable verification to protect ⚠ Never disable cert verification in production Always validate certificates; use proper PKI THECODEFORGE.IO
thecodeforge.io
Disabling Certificate Verification Risks
Network Security Basics

The Four Pillars: CIA Triad + Authentication

Every network security decision traces back to four properties. Miss one and you have a vulnerability.

Confidentiality means only intended recipients can read the data. TLS encryption on your HTTPS connection is confidentiality in action.

Integrity means the data wasn't tampered with in transit. A message authentication code (MAC) or a digital signature gives you this. Without integrity, a man-in-the-middle could flip a single bit in a bank transfer and you'd never know.

Availability means the service stays up for legitimate users. DDoS mitigation, rate limiting, and load balancing all protect availability. The CIA Triad is the classic model — but it's incomplete without the fourth pillar.

Authentication answers 'who are you, actually?' You can have a perfectly encrypted channel (confidentiality) straight to the wrong server. Authentication — via certificates, mutual TLS, or signed tokens — verifies identity before trust is granted.

Think of these four as a lock (confidentiality), a tamper-evident seal (integrity), a backup generator (availability), and a passport check (authentication). A secure system needs all four.

cia_triad_demo.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import hmac
import hashlib
import os

# ── INTEGRITY DEMO ──────────────────────────────────────────────────────────
# We use HMAC-SHA256 to create a Message Authentication Code (MAC).
# Both sender and receiver share a secret key.
# If even one byte of the message changes, the MAC will not match.

def create_mac(secret_key: bytes, message: bytes) -> str:
    """Produce a hex MAC for a message using a shared secret key."""
    mac = hmac.new(secret_key, message, hashlib.sha256)
    return mac.hexdigest()   # hex string is safe to transmit alongside the message

def verify_mac(secret_key: bytes, message: bytes, received_mac: str) -> bool:
    """Recompute the MAC and compare using constant-time comparison.
    
    hmac.compare_digest prevents timing attacks — an attacker can't
    tell 'how close' a forged MAC is by measuring response time.
    """
    expected_mac = create_mac(secret_key, message)
    return hmac.compare_digest(expected_mac, received_mac)


if __name__ == "__main__":
    shared_secret = os.urandom(32)   # 256-bit random key, never hardcoded in real code

    original_message = b"Transfer $500 to account 9982"
    mac_tag = create_mac(shared_secret, original_message)

    print("=== INTEGRITY CHECK DEMO ===")
    print(f"Original message : {original_message.decode()}")
    print(f"MAC tag          : {mac_tag[:16]}...  (truncated for display)")

    # Scenario 1: Message arrives unchanged
    is_valid = verify_mac(shared_secret, original_message, mac_tag)
    print(f"\nUnmodified message valid? {is_valid}")   # True

    # Scenario 2: Man-in-the-middle flips one digit in the amount
    tampered_message = b"Transfer $900 to account 9982"
    is_valid_after_tamper = verify_mac(shared_secret, tampered_message, mac_tag)
    print(f"Tampered  message valid? {is_valid_after_tamper}")   # False

    print("\n--- Result ---")
    if not is_valid_after_tamper:
        print("ALERT: Message integrity violated. Reject and log this event.")
Output
=== INTEGRITY CHECK DEMO ===
Original message : Transfer $500 to account 9982
MAC tag : 3f8a19c4e7b20d91... (truncated for display)
Unmodified message valid? True
Tampered message valid? False
--- Result ---
ALERT: Message integrity violated. Reject and log this event.
Watch Out: Encryption ≠ Integrity
Encrypting a message hides its contents but does NOT stop a blind bit-flip attack. An attacker who can't read your ciphertext can still alter it. Always combine encryption with a MAC or use an authenticated encryption mode like AES-GCM, which provides both in one operation.
Production Insight
A common production failure: using AES-CBC without a MAC left a payment system vulnerable to padding oracle attacks.
Attackers could decrypt ciphertext character by character by observing error messages.
Rule: always use authenticated encryption like AES-GCM or ChaCha20-Poly1305, never raw CBC.
Key Takeaway
CIA + Authentication = the four properties every control maps to.
If you can't name which property a tool protects, you don't know when you need it.
Encryption without integrity is like locking a door with a flimsy lock that anyone can pick.
Choosing a Security Property to Protect
IfNeed to prevent eavesdropping
UseUse encryption (confidentiality) with TLS or AES
IfNeed to detect tampering
UseUse HMAC or digital signature (integrity)
IfNeed to verify sender identity
UseUse digital certificates or mTLS (authentication)

Firewalls, Ports and the Attack Surface You're Actually Exposing

A firewall is a gatekeeper that inspects traffic and decides — based on rules — whether to allow or drop each packet. Understanding what it's actually filtering helps you write better network code.

Every server process binds to a port (a numbered door). Port 443 is HTTPS, 22 is SSH, 5432 is PostgreSQL. When your cloud VM starts, every open port is a potential entry point. A firewall's rule table says things like: 'allow TCP on port 443 from anywhere, allow TCP on port 22 from my IP only, drop everything else'.

There are two generations of firewalls worth knowing. A packet filter (Layer 3/4) looks only at IP addresses, port numbers, and protocol flags. It's fast but blind to application content. A stateful firewall (also Layer 4) tracks connection state — it knows the difference between a reply to a request you made vs. an unsolicited inbound packet. Most production firewalls are stateful.

Your real attack surface is the combination of open ports, the software version running on each, and the privileges that software holds. A firewall reduces the surface but the surviving entry points must be hardened independently. Closing a port is always safer than patching the service behind it.

port_scanner_basic.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import socket
import concurrent.futures
from typing import List, Tuple

# A minimal TCP port scanner — the same core logic used by tools like nmap.
# Understanding this helps you see EXACTLY what an attacker sees when they
# probe your server. Only scan hosts you own or have explicit permission to scan.

TARGET_HOST = "127.0.0.1"        # Scanning localhost — safe to run anywhere
PORTS_TO_CHECK = range(1, 1025)  # Well-known ports (IANA-reserved range)
CONNECT_TIMEOUT_SECONDS = 0.5    # Short timeout keeps scan fast

def probe_port(host: str, port: int) -> Tuple[int, bool, str]:
    """Attempt a TCP handshake. If it succeeds, the port is open.
    
    A completed TCP SYN-ACK exchange means a process is listening.
    A RST or timeout means the port is closed or filtered.
    """
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as tcp_socket:
            tcp_socket.settimeout(CONNECT_TIMEOUT_SECONDS)
            result_code = tcp_socket.connect_ex((host, port))  # 0 = success
            is_open = (result_code == 0)

            # Try to resolve the service name (e.g. 80 -> 'http')
            try:
                service_name = socket.getservbyport(port, "tcp")
            except OSError:
                service_name = "unknown"

            return port, is_open, service_name
    except (socket.timeout, ConnectionRefusedError, OSError):
        return port, False, "unreachable"

def scan_host(host: str, ports) -> List[Tuple[int, str]]:
    """Scan multiple ports in parallel using a thread pool."""
    open_ports = []
    # ThreadPoolExecutor lets us fire many simultaneous connection attempts
    with concurrent.futures.ThreadPoolExecutor(max_workers=100) as executor:
        futures = {executor.submit(probe_port, host, port): port for port in ports}
        for future in concurrent.futures.as_completed(futures):
            port, is_open, service = future.result()
            if is_open:
                open_ports.append((port, service))
    return sorted(open_ports)  # sort by port number for readability


if __name__ == "__main__":
    print(f"Scanning {TARGET_HOST} — ports 1-1024 ...\n")
    discovered = scan_host(TARGET_HOST, PORTS_TO_CHECK)

    if discovered:
        print(f"{'PORT':<8} {'SERVICE':<16} STATUS")
        print("-" * 35)
        for port_number, service_name in discovered:
            print(f"{port_number:<8} {service_name:<16} OPEN")
    else:
        print("No open ports found in range 1-1024.")

    print(f"\nTotal open ports: {len(discovered)}")
    print("Each open port is a potential entry point — close what you don't need.")
Output
Scanning 127.0.0.1 — ports 1-1024 ...
PORT SERVICE STATUS
-----------------------------------
22 ssh OPEN
80 http OPEN
443 https OPEN
5432 postgresql OPEN
Total open ports: 4
Each open port is a potential entry point — close what you don't need.
Pro Tip: Run This Against Your Own Staging Server
Most developers have no idea how many ports their cloud VM exposes. Run this scan against your own server before an attacker does. A freshly provisioned Ubuntu VM on AWS commonly has ports 22, 8080, and several others open by default — not because you opened them, but because default installs do.
Production Insight
Default cloud images often leave debug ports like 9100 (node exporter) or 3000 (Grafana) open.
Attackers scan entire AWS IP ranges for these default ports to find easy targets.
Rule: always run a port scan against a fresh VM and close every port you don't explicitly need.
Key Takeaway
Open ports = attack surface.
A firewall reduces the surface but doesn't eliminate it.
Closing a port is always safer than patching the service behind it.
Firewall Rule Decision Tree
IfService must be accessible from internet
UseAllow inbound on specific port from 0.0.0.0/0, but only if absolutely necessary
IfService only needed by internal team
UseRestrict access to VPN IP range or corporate IPs
IfService no longer needed
UseStop the service and close the port entirely — don't just firewall it

TLS and Encryption: What Actually Happens in That HTTPS Handshake

HTTPS is HTTP wrapped in TLS (Transport Layer Security). Developers use it daily but very few can describe what actually happens between 'you type a URL' and 'the page loads'. That gap bites you during debugging and in interviews.

The TLS 1.3 handshake has three jobs: agree on cipher algorithms, authenticate the server (and optionally the client), and derive shared symmetric keys. It completes in one round trip.

Here's the sequence: Your browser sends a ClientHello with supported cipher suites and a random value. The server replies with a ServerHello, picks the cipher suite, sends its certificate (which contains its public key and is signed by a Certificate Authority), and already sends its key share for key exchange. Your browser verifies the certificate chain up to a trusted root CA, computes the shared session key using Diffie-Hellman, and from this point all data flows encrypted with a fast symmetric cipher (AES-GCM or ChaCha20-Poly1305).

The asymmetric crypto (slow, public-key) is only used for the handshake. The actual data uses symmetric keys (fast, shared secret). This hybrid approach is why TLS can protect gigabytes of data efficiently.

tls_connection_demo.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import ssl
import socket
import json

# This script makes a real TLS connection and inspects the certificate chain.
# It shows you exactly what your browser validates silently on every HTTPS request.

TARGET_HOST = "httpbin.org"   # A public test server — fine to query
HTTPS_PORT  = 443
HTTP_TIMEOUT_SECONDS = 5

def inspect_tls_connection(host: str, port: int) -> dict:
    """Open a verified TLS connection and extract security metadata."""

    # ssl.create_default_context() loads the OS certificate store.
    # It enforces hostname verification and certificate chain validation
    # automatically — this is the safe default, not the permissive one.
    tls_context = ssl.create_default_context()

    raw_socket = socket.create_connection((host, port), timeout=HTTP_TIMEOUT_SECONDS)

    # wrap_socket upgrades the plain TCP socket to TLS.
    # server_hostname is needed for SNI (Server Name Indication) so the
    # server knows which certificate to present when hosting multiple domains.
    tls_socket = tls_context.wrap_socket(raw_socket, server_hostname=host)

    # --- Extract metadata AFTER the handshake completes ---
    cipher_name, tls_version, key_bits = tls_socket.cipher()
    peer_cert = tls_socket.getpeercert()  # parsed certificate dict

    # Pull the Subject Common Name and issuer from the cert
    subject_fields  = dict(field for entry in peer_cert["subject"]  for field in entry)
    issuer_fields   = dict(field for entry in peer_cert["issuer"]   for field in entry)

    security_info = {
        "tls_version"       : tls_version,
        "cipher_suite"      : cipher_name,
        "cipher_key_bits"   : key_bits,
        "cert_common_name"  : subject_fields.get("commonName", "N/A"),
        "cert_issuer_org"   : issuer_fields.get("organizationName", "N/A"),
        "cert_valid_from"   : peer_cert.get("notBefore", "N/A"),
        "cert_valid_until"  : peer_cert.get("notAfter",  "N/A"),
        "hostname_verified" : True   # wrap_socket raised if verification failed
    }

    tls_socket.close()
    raw_socket.close()
    return security_info


if __name__ == "__main__":
    print(f"Establishing TLS connection to {TARGET_HOST}:{HTTPS_PORT}...\n")

    try:
        info = inspect_tls_connection(TARGET_HOST, HTTPS_PORT)
        print("=== TLS Handshake Security Summary ===")
        for label, value in info.items():
            print(f"  {label:<22}: {value}")

        # Warn if the server is using an outdated TLS version
        if info["tls_version"] in ("TLSv1", "TLSv1.1"):
            print("\n  WARNING: Server supports deprecated TLS version — upgrade required.")
        else:
            print("\n  TLS version is current and secure.")

    except ssl.SSLCertVerificationError as cert_error:
        # This fires if the cert is self-signed, expired, or hostname mismatches
        print(f"Certificate verification FAILED: {cert_error}")
        print("Do NOT ignore this error in production — it means you may be talking to an impersonator.")
Output
Establishing TLS connection to httpbin.org:443...
=== TLS Handshake Security Summary ===
tls_version : TLSv1.3
cipher_suite : TLS_AES_256_GCM_SHA384
cipher_key_bits : 256
cert_common_name : httpbin.org
cert_issuer_org : Let's Encrypt
cert_valid_from : Mar 15 00:00:00 2024 GMT
cert_valid_until : Jun 13 23:59:59 2024 GMT
hostname_verified : True
TLS version is current and secure.
Interview Gold: Why TLS Uses Both Asymmetric and Symmetric Crypto
Asymmetric (RSA/ECDH) crypto is mathematically expensive — encrypting a 10 MB file with RSA would take seconds. Symmetric (AES) crypto is blindingly fast but requires both parties to share a secret key first. TLS solves this elegantly: use asymmetric crypto once during the handshake to securely exchange a symmetric key, then switch to symmetric for all data. This is called a hybrid cryptosystem and it's why HTTPS is both secure AND fast.
Production Insight
A production incident where a third-party API used an expired certificate caused all integrators to fail silently.
The error was swallowed by a generic catch-all, and data wasn't sent for 3 days.
Rule: monitor certificate expiry with automated checks and alert at least 30 days before expiration.
Key Takeaway
TLS is a hybrid cryptosystem: asymmetric for key exchange, symmetric for data.
Always validate certificates — a missing check can cost you millions.
The handshake is one round trip in TLS 1.3 — not the three it used to be.
TLS Configuration Decisions
IfClient must verify server identity
UseEnable certificate validation — never disable it even for internal services
IfServer must verify client identity
UseUse mutual TLS (mTLS) with client certificates
IfNeed to support old clients
UseAllow TLS 1.2, disable TLS 1.0/1.1 and SSLv3

Common Attacks and the Defenses That Beat Them

Knowing attack patterns is what separates a developer who 'uses HTTPS' from one who can actually reason about their system's threat model. Here are the four attacks you'll encounter most in real systems and interviews.

Man-in-the-Middle (MitM): An attacker positions themselves between client and server, relaying — and potentially altering — traffic. Defense: TLS with proper certificate verification. The moment you disable cert validation in code, you open a MitM window.

SYN Flood (DDoS): An attacker sends millions of TCP SYN packets from spoofed IPs but never completes the handshake. The server allocates memory for each half-open connection until it runs out. Defense: SYN cookies — the server doesn't allocate state until the handshake completes; it encodes connection state inside the SYN-ACK sequence number.

SQL Injection via Network Layer: Not purely a network attack, but often delivered over HTTP. Raw user input concatenated into queries lets attackers exfiltrate your entire database. Defense: parameterised queries, always. Never string-format SQL.

Credential Stuffing: Attackers take leaked username/password pairs from one breach and try them on other services. Defense: rate limiting, multi-factor authentication, and breach-detection checks against databases like HaveIBeenPwned.

rate_limiter_defense.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import time
from collections import defaultdict, deque
from typing import Tuple

# A sliding-window rate limiter — the first line of defense against
# credential stuffing, brute-force login, and DDoS at the application layer.
#
# A fixed-window counter (e.g. '100 requests per minute') has a known flaw:
# an attacker can send 100 at 00:59 and 100 at 01:00 — 200 requests in 2 seconds.
# A sliding window fixes this by tracking actual request timestamps.

class SlidingWindowRateLimiter:
    def __init__(self, max_requests: int, window_seconds: int):
        """
        max_requests  : how many requests are allowed per window
        window_seconds: the rolling time window in seconds
        """
        self.max_requests     = max_requests
        self.window_seconds   = window_seconds
        # Maps client_id -> deque of timestamps for requests in the current window
        self.request_history  = defaultdict(deque)

    def is_allowed(self, client_identifier: str) -> Tuple[bool, int]:
        """Check if a client is within their rate limit.
        
        Returns (allowed: bool, requests_remaining: int).
        The client_identifier could be an IP address, user ID, or API key.
        """
        current_time    = time.monotonic()            # monotonic avoids clock-skew bugs
        window_start    = current_time - self.window_seconds
        client_requests = self.request_history[client_identifier]

        # Evict timestamps older than the window — this is what makes it 'sliding'
        while client_requests and client_requests[0] < window_start:
            client_requests.popleft()

        requests_in_window = len(client_requests)

        if requests_in_window >= self.max_requests:
            # Deny — client has exhausted their quota
            return False, 0

        # Allow — record this request
        client_requests.append(current_time)
        remaining = self.max_requests - len(client_requests)
        return True, remaining


if __name__ == "__main__":
    # Simulate a login endpoint: max 5 attempts per 10-second window
    login_limiter = SlidingWindowRateLimiter(max_requests=5, window_seconds=10)

    attacker_ip = "192.168.1.105"  # Simulated credential-stuffing attacker
    legit_user  = "10.0.0.42"

    print("=== Credential Stuffing Defense Simulation ===")
    print(f"Limit: {login_limiter.max_requests} requests per {login_limiter.window_seconds}s\n")

    # Attacker fires 8 rapid login attempts
    for attempt_number in range(1, 9):
        allowed, remaining = login_limiter.is_allowed(attacker_ip)
        status = "ALLOWED  " if allowed else "BLOCKED  "
        print(f"Attacker attempt #{attempt_number}: {status} | Remaining quota: {remaining}")

    print()
    # Legitimate user makes one request — their window is clean
    allowed, remaining = login_limiter.is_allowed(legit_user)
    print(f"Legit user request:  {'ALLOWED' if allowed else 'BLOCKED'} | Remaining quota: {remaining}")
Output
=== Credential Stuffing Defense Simulation ===
Limit: 5 requests per 10s
Attacker attempt #1: ALLOWED | Remaining quota: 4
Attacker attempt #2: ALLOWED | Remaining quota: 3
Attacker attempt #3: ALLOWED | Remaining quota: 2
Attacker attempt #4: ALLOWED | Remaining quota: 1
Attacker attempt #5: ALLOWED | Remaining quota: 0
Attacker attempt #6: BLOCKED | Remaining quota: 0
Attacker attempt #7: BLOCKED | Remaining quota: 0
Attacker attempt #8: BLOCKED | Remaining quota: 0
Legit user request: ALLOWED | Remaining quota: 4
Watch Out: Rate Limiting on User ID, Not Just IP
Sophisticated credential stuffing attacks rotate through thousands of residential IPs — one attempt per IP. Rate limiting on IP alone won't stop them. Rate limit on both IP AND the target account identifier (username or email). Five failed logins for account 'alice@example.com' should trigger a lockout regardless of how many different IPs tried them.
Production Insight
A major e-commerce site was taken down by a SYN flood despite having a hardware firewall.
The issue: the firewall's connection table filled up because SYN cookies weren't enabled.
Rule: ensure your load balancer or OS has SYN cookies enabled (net.ipv4.tcp_syncookies=1).
Key Takeaway
TLS stops MITM, SYN cookies stop SYN floods, rate limiting stops credential stuffing.
The right defense depends on the attack vector — not all DDoS is the same.
Always combine defenses: no single control covers all threats.
Choosing the Right Defense for Common Attacks
IfAttack pattern: MITM between client and server
UseDefend with TLS + strict certificate validation
IfAttack pattern: SYN flood DDoS
UseEnable SYN cookies on the server-side OS or load balancer
IfAttack pattern: credential stuffing via HTTP API
UseImplement rate limiting on both IP and account ID with sliding window

Network Segmentation and Defense in Depth

A single firewall around your whole infrastructure is a single point of failure. If an attacker breaches it, they have full access to everything behind it. Network segmentation divides your network into smaller, isolated zones so that a compromise in one zone doesn't automatically spread to others.

Defense in depth means layering multiple independent security controls. If one fails, another still blocks the attack. For example: a firewall at the perimeter, another between internal zones, authentication on every service, encryption in transit, and monitoring to detect anomalies.

Practical segmentation strategies: - Put web servers in a public subnet, application servers in a private subnet, databases in a separate private subnet with stricter rules. - Use VLANs or VPCs to isolate environments (prod, staging, dev). - For microservices, use a service mesh like Istio to enforce mTLS and fine-grained access policies between services.

Why it matters in production: A single compromised web server should not give an attacker direct access to the database. If you have database-only firewall rules that only allow traffic from the app server's IP, the attacker must first pivot to the app server, then from there to the database — much harder. Segmentation forces attackers to chain exploits, giving you time to detect and respond.

network_segmentation_rules.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/bin/bash
# Example iptables rules for a three-tier segmentation on a single host
# This is a simplified demonstration; in production use cloud security groups or nftables.

# ── Variables ────────────────────────────────────────────────────────────
WEB_SUBNET="10.0.1.0/24"
APP_SUBNET="10.0.2.0/24"
DB_SUBNET="10.0.3.0/24"

# ── Flush existing rules (careful: will break existing connections) ──────
iptables -F
iptables -X

# ── Default policies ──────────────────────────────────────────────────────
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# ── Allow established connections ─────────────────────────────────────────
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# ── Web tier: allow HTTP/HTTPS from anywhere ──────────────────────────────
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# ── App tier: allow traffic only from web subnet to app ports ─────────────
iptables -A INPUT -s $WEB_SUBNET -p tcp --dport 8080 -j ACCEPT

# ── Database tier: allow traffic only from app subnet to DB port ───────────
iptables -A INPUT -s $APP_SUBNET -p tcp --dport 5432 -j ACCEPT

# ── SSH only from management IP ───────────────────────────────────────────
iptables -A INPUT -s 203.0.113.0/24 -p tcp --dport 22 -j ACCEPT

# ── Log dropped packets (optional) ───────────────────────────────────────
iptables -A INPUT -j LOG --log-prefix "FW-DROP: "

echo "Rules applied. Use iptables -L -v to see them."
Output
Rules applied. Use iptables -L -v to see them.
Mental Model: The Castle Wall vs. The Cell Block
  • A single wall: one breach = everything lost. That's perimeter-only security.
  • Multiple locked doors: even if the attacker gets through the outer wall, they still need to pick three more locks before reaching the treasure.
  • Each layer buys time. Time means detection, alerting, and response.
  • In network terms: segmentation + encryption + authentication + monitoring.
Production Insight
A startup kept all microservices in the same VPC subnet with a permissive security group. An attacker exploited a vulnerable Node.js service and then used it to SSH into the MongoDB server — all because there was no segmentation between tiers.
Rule: always place databases in a separate subnet with explicit ingress only from the app tier.
Key Takeaway
Segmentation forces attackers to work harder.
Defense in depth means no single point of compromise leads to total system breach.
A flat network is a flat invitation to a hacker.
Segmentation Decisions
IfOnly one tier of servers (e.g., static website)
UseSimple firewall with only HTTP/S ports open
IfMultiple tiers: web, app, database
UseEach tier in its own subnet with strict ingress/egress rules between them
IfMicroservices with frequent inter-service calls
UseUse a service mesh (Istio, Linkerd) to enforce mTLS and access policies

Traffic Analysis & Monitoring: Where Your Defenses Actually Show Up

You can spend a fortune on firewalls and encryption, but if you're not watching the wire, you're flying blind. Traffic analysis is the art of knowing what's normal on your network so you can spot what isn't. Most breaches announce themselves with subtle traffic patterns long before the payload detonates. The problem? Most teams don't baseline their own traffic. They deploy monitoring tools, configure them with defaults, and call it done.

Start with NetFlow or sFlow data. Capture and analyze traffic metadata—source IPs, destination ports, packet sizes, protocol distributions. A sudden spike in outbound DNS queries from a single workstation isn't a coincidence. It's a C2 beacon calling home. If you're not sampling and analyzing your network flows on a regular cadence, you're not defending. You're just hoping.

Set up thresholds. Alert on anomalies like unexpected SSH traffic from a web server or a client workstation hitting 50+ distinct external IPs in five minutes. This is where intrusion detection earns its keep. The signature-based stuff catches yesterday's attacks. The statistical baseline catches what the vendor hasn't signed yet.

TrafficAnomalyDetector.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// io.thecodeforge — cs-fundamentals tutorial

import pyshark
from collections import defaultdict
import time

# Production baseline: internal DNS server IP
INTERNAL_DNS = "10.0.20.53"
DOMAIN_WHITELIST = {"thecodeforge.io", "apt-update.corp", "vpn.corp"}

def monitor_dns_anomalies(interface="eth0", duration_sec=60):
    suspicious_ips = defaultdict(set)
    capture = pyshark.LiveCapture(interface=interface, bpf_filter="udp port 53")

    print(f"[*] Monitoring DNS on {interface} for {duration_sec}s...")
    start = time.time()
    
    for packet in capture.sniff_continuously():
        if time.time() - start > duration_sec:
            break
        try:
            src_ip = packet.ip.src
            qname = packet.dns.qry_name
            if src_ip != INTERNAL_DNS and qname not in DOMAIN_WHITELIST:
                suspicious_ips[src_ip].add(qname)
        except AttributeError:
            continue

    for ip, domains in suspicious_ips.items():
        if len(domains) > 10:
            print(f"[!] ALERT: {ip} resolved {len(domains)} unknown domains")
            for d in list(domains)[:5]:
                print(f"    -> {d}")
Output
[*] Monitoring DNS on eth0 for 60s...
[!] ALERT: 192.168.1.105 resolved 37 unknown domains
-> update-check.evilcdn.net
-> beacon.c2server.io
-> phish-payload-v2.s3.amazonaws.com
Blind Spot:
If your monitoring tools run on the same VLAN as production traffic and you don't segment management interfaces, an attacker who compromises a workstation can pivot directly into your monitoring stack and mute your alarms.
Key Takeaway
You can't defend what you don't measure. Baseline your traffic, alert on outliers, and treat every anomaly as an active compromise until proven otherwise.

Applied Cryptography & PKI: The Grim Reality of Key Management

Cryptography is not magic. It's math with a shelf life and a failure mode called 'who's holding the root CA private key'. Every engineer I've seen burn a production system didn't fail because AES was weak. They failed because they stored the decryption key in a config file committed to GitHub, or they set the certificate expiry to 10 years 'to avoid hassle'.

Public Key Infrastructure (PKI) is the backbone of TLS, code signing, and device authentication. It works beautifully when you chain trust correctly and absolutely falls apart when you don't. The most common mistake? Self-signed certificates everywhere because 'it's just internal'. That's how you train every app to trust anything signed by anyone. Then one rogue cert later, and your internal dashboard is serving malware.

Rotate your keys. Enforce short-lived certificates—90 days max for TLS. Automate renewal with ACME or Vault. If you're manually copying PEM files to servers, you're building technical debt that a breach will cash.

The other silent killer: key escrow with no audit. If five engineers have access to the root CA passphrase and nobody logs who used it, you don't have a PKI. You have a mechanism for plausible deniability.

CertExpiryChecker.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// io.thecodeforge — cs-fundamentals tutorial

import ssl
import socket
import datetime

CRITICAL_SERVICES = [
    ("api.payments.corp", 443),
    ("auth.sso.corp", 443),
    ("mail.route.corp", 25),
]

def check_cert_expiry(hostname, port):
    context = ssl.create_default_context()
    with socket.create_connection((hostname, port), timeout=5) as sock:
        with context.wrap_socket(sock, server_hostname=hostname) as ssock:
            cert = ssock.getpeercert()
            expiry = datetime.datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z')
            remaining = (expiry - datetime.datetime.utcnow()).days
            return hostname, remaining

print("Service                Days to Expiry")
print("-" * 40)
for host, port in CRITICAL_SERVICES:
    try:
        _, days = check_cert_expiry(host, port)
        status = "EXPIRED" if days < 0 else f"{days:3d}"
        print(f"{host:22s} {status}")
    except Exception as e:
        print(f"{host:22s} FAIL/{type(e).__name__}")
Output
Service Days to Expiry
----------------------------------------
api.payments.corp -12 EXPIRED
auth.sso.corp 45
mail.route.corp 312
WARNING: 1 service has an expired certificate.
Senior Shortcut:
Use cert-manager in Kubernetes or HashiCorp Vault's PKI secret engine. They enforce short-lived certs and rotation. Manual cert management is a full-time job you don't have.
Key Takeaway
Cryptography is a trust chain that breaks at the weakest link in your operational practices. Automate key rotation, audit access to CAs, and never let a certificate live past 90 days.
● Production incidentPOST-MORTEMseverity: high

The Case of the Missing Certificate Validation

Symptom
Payment API calls intermittently failed with SSL certificate errors. Developers added verify=False in requests library to suppress the errors in development, and the change shipped to production.
Assumption
The internal network is trusted, so disabling certificate verification is safe. The API endpoint uses a self-signed certificate that 'works anyway'.
Root cause
The payment API was running on a VM whose internal DNS was poisoned by a compromised router. The attacker presented a rogue certificate to intercept traffic. With verification disabled, the client accepted any certificate — including the attacker's.
Fix
1. Replaced the self-signed cert with a certificate from a private CA. 2. Installed the CA cert on all client machines. 3. Removed all verify=False overrides. 4. Added network segmentation: payment services moved to a separate VPC with strict firewall rules.
Key lesson
  • Never disable certificate verification, even in internal networks. Use a private CA instead.
  • A network attacker on the same subnet can intercept traffic if verification is off.
  • Automated compliance checks should scan for verify=False or rejectUnauthorized: false in code.
Production debug guideSymptom → Action for Common Network Security Issues4 entries
Symptom · 01
HTTPS request fails with certificate error
Fix
Use openssl s_client -connect host:443 -servername host -showcerts to inspect the certificate chain. Check expiry, issuer, and hostname match. Verify the CA bundle on the client.
Symptom · 02
Service unreachable on a known port
Fix
Run nmap -p <port> <host> to check if port is open. Then check firewall rules (iptables -L, cloud security groups). Use netstat -tulpn on the server to confirm the service is listening.
Symptom · 03
Suspected man-in-the-middle attack
Fix
Compare the server's SSH host key fingerprint with the known fingerprint. For HTTPS, verify the certificate fingerprint via a trusted out-of-band channel. Check for unexpected DNS responses using dig at different resolvers.
Symptom · 04
Rate limiting is blocking legitimate users
Fix
Inspect logs for the client IP and user-agent. Check if the rate limiter is using only IP (attacker rotates IPs) or also account ID. Temporarily allowlist the affected IPs while adjusting the algorithm to sliding window.
★ Network Security Quick Debug Cheat SheetCommands and fixes for the most common network security issues that bite developers in production.
Certificate validation failed
Immediate action
Do NOT disable verification. Identify the failure reason.
Commands
openssl s_client -connect example.com:443 -showcerts
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -subject -dates
Fix now
If using self-signed cert, add it to the trust store. If expired, renew immediately. Never set verify=False.
Port scan shows unexpected open port+
Immediate action
Identify the process listening on that port and close it if not needed.
Commands
sudo netstat -tulpn | grep :<port>
sudo lsof -i :<port>
Fix now
Stop the service and update firewall rules to block the port. If the service is needed, move it to a different port with firewall restrictions.
DNS resolution fails for internal service+
Immediate action
Check that the service name resolves to the correct IP.
Commands
nslookup <service-name> <dns-server-ip>
dig @<dns-server-ip> <service-name> A +short
Fix now
If using Docker Compose, ensure containers are on the same network. Check /etc/hosts for static entries that may override.
Application-layer DDoS or credential stuffing+
Immediate action
Check rate limiter logs. Identify if attack is from single IP or distributed.
Commands
tail -f /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -nr | head -20
grep '<account-id>' /var/log/app.log | grep 'LOGIN_FAILED' | wc -l
Fix now
Rate limit on IP + account ID. Use a sliding window. For distributed attacks, enable CAPTCHA or WAF rules.
Symmetric vs. Asymmetric Encryption
Feature / AspectSymmetric Encryption (AES)Asymmetric Encryption (RSA/ECDH)
Key typeSingle shared secret keyPublic/private key pair
SpeedVery fast — hardware-accelerated10–100x slower than AES
Key distribution problemHard — how do you share the key securely?Solved — share public key openly
Typical useBulk data encryption (TLS data phase)Key exchange and digital signatures
Key size for 128-bit security128-bit AES key3072-bit RSA or 256-bit ECDH key
Vulnerable to quantum computingNeeds 256-bit key to remain safeRSA/DH broken by Shor's algorithm
Real-world exampleAES-256-GCM encrypting your HTTPS bodyECDH key exchange in TLS handshake
Provides authentication?No — only if both parties already share keyYes — via digital signatures

Key takeaways

1
The CIA Triad plus Authentication are the four properties every security control maps to
if you can't name which property a tool protects, you don't know when you need it.
2
TLS is a hybrid cryptosystem
asymmetric crypto for the handshake (secure key exchange), symmetric crypto for data (performance) — this distinction is a favourite interview question.
3
Encryption and integrity are separate guarantees
AES-CBC without a MAC lets an attacker blindly flip ciphertext bits and corrupt data silently; always use authenticated encryption (AES-GCM or ChaCha20-Poly1305).
4
Rate limiting on IP alone fails against distributed credential stuffing
always rate limit on the target account identifier too, and combine with breach-detection to reject known-leaked passwords at registration.
5
Network segmentation and defense in depth are not optional
a flat network means a single breach compromises everything.
6
Always verify TLS certificates in production
disabling verification is the root cause of many real-world breaches.

Common mistakes to avoid

4 patterns
×

Disabling TLS certificate verification

Symptom
SSL errors during development that developers 'fix' by turning off verification, which ships to production and leaves the connection open to man-in-the-middle attacks.
Fix
Obtain a valid certificate (Let's Encrypt is free). For internal services, set up a private CA and add it to your trust store. Never disable verification.
×

Storing secrets in environment variables that get logged or exposed

Symptom
Secrets appearing in log files, crash reports, or docker inspect output.
Fix
Use a dedicated secrets manager (AWS Secrets Manager, HashiCorp Vault). Never print environment variables wholesale. Add secret patterns to your log scrubbing pipeline.
×

Treating 'behind a firewall' as equivalent to 'secure'

Symptom
Plaintext internal HTTP calls between microservices, assuming the internal network is safe.
Fix
Adopt a zero-trust model: encrypt and authenticate all traffic regardless of whether it's internal. Use mTLS between services.
×

Using encryption without integrity protection

Symptom
Silent data corruption when using AES-CBC without a MAC. Attackers can flip bits even without decryption.
Fix
Always use authenticated encryption modes like AES-GCM or ChaCha20-Poly1305.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Explain what happens step by step during a TLS 1.3 handshake — why does ...
Q02SENIOR
What is a SYN flood attack and how do SYN cookies defend against it with...
Q03SENIOR
If a service sits behind a firewall and only port 443 is open, is it saf...
Q04SENIOR
What is the difference between a packet-filtering firewall and a statefu...
Q01 of 04SENIOR

Explain what happens step by step during a TLS 1.3 handshake — why does it use asymmetric crypto at the start but symmetric crypto for the actual data?

ANSWER
In TLS 1.3, the handshake takes one round trip. The client sends ClientHello (cipher suites, random). Server responds with ServerHello (selected cipher), its certificate (with public key), and key share. Client verifies certificate chain, computes shared symmetric key using the key share (ECDHE), and sends Finished. Then all subsequent data is encrypted with the symmetric key. Asymmetric crypto is slow, but it's only used to securely exchange the symmetric key. Symmetric is fast and used for bulk data — this hybrid approach ensures security without performance penalty.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What is the difference between TLS and SSL?
02
What is the difference between a firewall and a VPN?
03
Why can't you just encrypt everything with RSA instead of using AES for data?
04
What is defense in depth and why is it important?
05
How does a SYN cookie work without changing the client?
N
Naren Founder & Principal Engineer

20+ years shipping production systems from the metal up. Notes here come from systems that actually shipped.

Follow
Verified
production tested
May 23, 2026
last updated
1,554
articles · all by Naren
🔥

That's Computer Networks. Mark it forged?

8 min read · try the examples if you haven't

Previous
REST vs SOAP vs GraphQL
12 / 22 · Computer Networks
Next
Firewalls and Proxies