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)
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.
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.defcreate_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 messagedefverify_mac(secret_key: bytes, message: bytes, received_mac: str) -> bool:
"""Recompute the MACand compare using constant-time comparison.
hmac.compare_digest prevents timing attacks — an attacker can't
tell 'how close' a forged MACis 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}") # Falseprint("\n--- Result ---")
ifnot 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 importList, 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 fastdefprobe_port(host: str, port: int) -> Tuple[int, bool, str]:
"""Attempt a TCP handshake. If it succeeds, the port is open.
A completed TCPSYN-ACK exchange means a process is listening.
A RSTor 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")
exceptOSError:
service_name = "unknown"return port, is_open, service_name
except (socket.timeout, ConnectionRefusedError, OSError):
return port, False, "unreachable"defscan_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 attemptswith 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 readabilityif __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 = 5definspect_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 versionif 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.SSLCertVerificationErroras cert_error:
# This fires if the cert is self-signed, expired, or hostname mismatchesprint(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 importTuple# 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.classSlidingWindowRateLimiter:
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 windowself.request_history = defaultdict(deque)
defis_allowed(self, client_identifier: str) -> Tuple[bool, int]:
"""Checkif a client is within their rate limit.
Returns (allowed: bool, requests_remaining: int).
The client_identifier could be an IP address, user ID, orAPI 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 quotareturnFalse, 0# Allow — record this request
client_requests.append(current_time)
remaining = self.max_requests - len(client_requests)
returnTrue, 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 attemptsfor attempt_number inrange(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 INPUTDROP
iptables -P FORWARDDROP
iptables -P OUTPUTACCEPT
# ── 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
● 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.
Rate limit on IP + account ID. Use a sliding window. For distributed attacks, enable CAPTCHA or WAF rules.
Symmetric vs. Asymmetric Encryption
Feature / Aspect
Symmetric Encryption (AES)
Asymmetric Encryption (RSA/ECDH)
Key type
Single shared secret key
Public/private key pair
Speed
Very fast — hardware-accelerated
10–100x slower than AES
Key distribution problem
Hard — how do you share the key securely?
Solved — share public key openly
Typical use
Bulk data encryption (TLS data phase)
Key exchange and digital signatures
Key size for 128-bit security
128-bit AES key
3072-bit RSA or 256-bit ECDH key
Vulnerable to quantum computing
Needs 256-bit key to remain safe
RSA/DH broken by Shor's algorithm
Real-world example
AES-256-GCM encrypting your HTTPS body
ECDH key exchange in TLS handshake
Provides authentication?
No — only if both parties already share key
Yes — 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.
Q02 of 04SENIOR
What is a SYN flood attack and how do SYN cookies defend against it without changing the client-side protocol at all?
ANSWER
A SYN flood sends many TCP SYN packets with spoofed source IPs. The server allocates memory (a TCB) for each connection, waiting for the ACK to complete the handshake. Since the ACK never comes, the server exhausts memory. SYN cookies encode connection state in the SYN-ACK sequence number using a hash. The server doesn't allocate a TCB until it receives a valid ACK with that encoded sequence number. The client side works exactly the same — no changes needed. This makes SYN cookies transparent.
Q03 of 04SENIOR
If a service sits behind a firewall and only port 443 is open, is it safe to skip authentication between internal microservices on the same VPC? What's the risk?
ANSWER
No, it is not safe. A firewall at the perimeter does not protect against an attacker who compromises one service inside the VPC. If authentication is skipped, the compromised service can impersonate any other service to talk to the database or payment API (lateral movement). The risk is that a single vulnerability in one service (e.g., RCE via a web endpoint) gives the attacker full access to all internal services. Always use authentication and encryption internally — zero trust is the only safe model.
Q04 of 04SENIOR
What is the difference between a packet-filtering firewall and a stateful firewall? When would you use each?
ANSWER
A packet filter examines only IP headers (source, destination, port, protocol flags). It is stateless and fast. A stateful firewall also tracks connection state — it remembers that a SYN-ACK is a response to a previous SYN, while an unsolicited SYN-ACK can be dropped. Stateful firewalls are now standard in production because they prevent many types of spoofing and injection attacks. Packet filters are sometimes used in embedded systems or high-speed kernel modules where state tracking is too expensive, but for most use cases stateful is superior.
01
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?
SENIOR
02
What is a SYN flood attack and how do SYN cookies defend against it without changing the client-side protocol at all?
SENIOR
03
If a service sits behind a firewall and only port 443 is open, is it safe to skip authentication between internal microservices on the same VPC? What's the risk?
SENIOR
04
What is the difference between a packet-filtering firewall and a stateful firewall? When would you use each?
SENIOR
FAQ · 5 QUESTIONS
Frequently Asked Questions
01
What is the difference between TLS and SSL?
SSL (Secure Sockets Layer) is the predecessor to TLS (Transport Layer Security). SSL 3.0 was deprecated in 2015 and all versions are now considered insecure. When people say 'SSL certificate' today they almost always mean a TLS certificate — the naming just stuck. If you're configuring a server, make sure you're enabling TLS 1.2 at minimum and TLS 1.3 preferably, and explicitly disabling SSL 2.0, SSL 3.0, and TLS 1.0.
Was this helpful?
02
What is the difference between a firewall and a VPN?
A firewall decides what traffic is allowed in and out of a network based on rules. A VPN (Virtual Private Network) creates an encrypted tunnel between two endpoints so traffic passing through untrusted networks (like public Wi-Fi) is protected in transit. They solve different problems and are often used together: a VPN prevents eavesdropping on the path, while a firewall controls what you can reach at the destination.
Was this helpful?
03
Why can't you just encrypt everything with RSA instead of using AES for data?
RSA encryption is orders of magnitude slower than AES because it relies on modular exponentiation with very large numbers. Encrypting a 1 MB file with RSA-2048 takes roughly 100x longer than AES-256. Beyond speed, RSA has a maximum plaintext size tied to the key length (2048-bit RSA can only directly encrypt ~245 bytes), so it's architecturally unsuited for bulk data. The hybrid approach — RSA/ECDH to exchange an AES key, AES for the data — gives you the security of asymmetric crypto with the speed of symmetric crypto.
Was this helpful?
04
What is defense in depth and why is it important?
Defense in depth is the practice of layering multiple independent security controls so that if one fails, another still protects the system. For example: a perimeter firewall + internal firewall + authentication + encryption + monitoring. It's important because no single security measure is perfect. A vulnerability in one layer should not lead to total compromise. Segmentation is a key part of defense in depth.
Was this helpful?
05
How does a SYN cookie work without changing the client?
When the server receives a SYN, it encodes the connection parameters (MSS, etc.) into a hash in the SYN-ACK sequence number, plus a timestamp. It does not allocate a TCB. The client sends an ACK with the sequence number +1. The server verifies the hash and only then allocates a TCB. The client never knows this happened. This defeats SYN floods because the server does not allocate memory until the handshake is fully verified.