Browse CTFs New CTF Sign in

Exploiting Insecure Key Exchange Protocols via Man-in-the-Middle Parameter Manipulation

crypto_symmetric_kdf Difficulty 1–5 30 min certifiable

Theory

Why This Matters

Unauthenticated Diffie-Hellman key exchange has been a documented vulnerability since Whitfield Diffie and Martin Hellman first proposed it in 1976 — the protocol paper itself acknowledged that authentication was not provided. The practical consequences were demonstrated repeatedly: the Logjam attack (CVE-2015-4000) showed that TLS servers accepting 512-bit "export" Diffie-Hellman parameters could be downgraded to deliberately weak groups, enabling passive decryption by nation-state adversaries who had precomputed discrete logarithms for the small set of commonly used 512-bit primes. More broadly, any implementation of "plain DH" (unauthenticated) is susceptible to active man-in-the-middle attack — an attacker who can interpose between two parties can establish independent shared secrets with each, decrypting and re-encrypting all traffic in real time while both endpoints believe they are communicating securely. This vulnerability class motivates the certificate infrastructure of TLS 1.3 and the signed key exchange of SSH.

Core Concept

Diffie-Hellman key exchange (RFC 3526, RFC 7919) operates over a multiplicative group modulo a large prime p with a generator g. Alice sends g^a mod p; Bob sends g^b mod p. Both compute the shared secret g^(ab) mod p = (g^b)^a = (g^a)^b. The security of this computation rests on the Discrete Logarithm Problem (DLP): recovering a from g^a mod p is computationally infeasible for large p.

The critical omission is authentication. Neither party authenticates their DH public value to the other. An attacker Eve interposing between Alice and Bob can perform a man-in-the-middle (MitM) attack:

  1. Eve intercepts Alice's public value g^a and Bob's public value g^b.
  2. Eve sends her own value g^x to Alice (claiming to be Bob) and g^y to Bob (claiming to be Alice).
  3. Alice computes shared secret K_AE = g^(ax) mod p (shared with Eve, not Bob).
  4. Bob computes shared secret K_BE = g^(by) mod p (shared with Eve, not Alice).
  5. Eve decrypts Alice's messages with K_AE, reads them, re-encrypts with K_BE, and forwards to Bob — and vice versa. Both endpoints observe a functioning encrypted channel; neither detects the interposition.

This is a fully active MitM: Eve can read, modify, inject, and drop messages at will. A passive eavesdropper who only records traffic cannot exploit unauthenticated DH (the DLP prevents recovering the shared secret from observed public values). The distinction between passive and active attack is critical for understanding the threat model.

Downgrade attack (Logjam): a TLS server that advertises support for 512-bit "DHE-EXPORT" cipher suites can be manipulated by an active MitM to force the client to negotiate an export-grade DH group even if both client and server prefer stronger groups. With a 512-bit prime, the discrete log computation is feasible using the Number Field Sieve on precomputed data, enabling the attacker to recover the shared secret in real time. Logjam was directly exploitable against a significant fraction of HTTPS servers in 2015.

TLS 1.3 mitigation: TLS 1.3 (RFC 8446) eliminates unauthenticated key exchange by binding the DH exchange to a certificate signature (for server authentication) and a Finished MAC (for channel binding). Forward secrecy is achieved via ephemeral Diffie-Hellman (DHE or ECDHE): the DH key pair is generated fresh for each session and discarded after use, so compromise of the long-term private key does not retroactively decrypt recorded sessions.

Technical Deep-Dive

# Unauthenticated DH MitM attack simulation
# Prerequisites: pip install cryptography

from cryptography.hazmat.primitives.asymmetric.dh import (
    DHParameterNumbers, DHPublicNumbers, DHPrivateNumbers, generate_parameters
)
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes, serialization
import os

# --- Demonstrate unauthenticated DH MitM ---
# Use RFC 3526 Group 14 (2048-bit) — standard safe-prime group
P_RFC3526_G14 = int(
    "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
    "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
    "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
    "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
    "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
    "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
    "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
    "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
    "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
    "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
    "15728E5A8AACAA68FFFFFFFFFFFFFFFF",
    16
)
G = 2

def dh_generate_keypair(p, g):
    """Generate a (private_exp, public_value) pair."""
    private = int.from_bytes(os.urandom(32), 'big') % (p - 2) + 1
    public  = pow(g, private, p)
    return private, public

def dh_compute_shared(their_public, my_private, p):
    """Compute shared secret = their_public^my_private mod p."""
    return pow(their_public, my_private, p)

# Legitimate exchange: Alice and Bob
a, ga = dh_generate_keypair(P_RFC3526_G14, G)   # Alice: a, g^a
b, gb = dh_generate_keypair(P_RFC3526_G14, G)   # Bob:   b, g^b

# MitM: Eve intercepts and replaces both public values
x, gx = dh_generate_keypair(P_RFC3526_G14, G)   # Eve's key toward Alice
y, gy = dh_generate_keypair(P_RFC3526_G14, G)   # Eve's key toward Bob

# Alice receives g^x (Eve's value), thinks it is g^b
K_AE = dh_compute_shared(gx, a, P_RFC3526_G14)
# Bob receives g^y (Eve's value), thinks it is g^a
K_BE = dh_compute_shared(gy, b, P_RFC3526_G14)
# Eve holds both shared secrets:
K_EA = dh_compute_shared(ga, x, P_RFC3526_G14)  # = K_AE
K_EB = dh_compute_shared(gb, y, P_RFC3526_G14)  # = K_BE

assert K_AE == K_EA, "Alice-Eve shared secret mismatch"
assert K_BE == K_EB, "Bob-Eve shared secret mismatch"

print("[+] MitM established:")
print(f"    Alice-Eve shared secret (first 8 bytes): {K_AE.to_bytes(256,'big')[:8].hex()}")
print(f"    Bob-Eve   shared secret (first 8 bytes): {K_BE.to_bytes(256,'big')[:8].hex()}")

# Eve derives symmetric keys from both shared secrets for decryption/re-encryption
def derive_session_key(shared_secret_int: int) -> bytes:
    raw = shared_secret_int.to_bytes(256, 'big')
    return HKDF(
        algorithm=hashes.SHA256(), length=32,
        salt=None, info=b"session-key"
    ).derive(raw)

key_alice = derive_session_key(K_AE)
key_bob   = derive_session_key(K_BE)
# Logjam downgrade test (against TLS server):
# testssl.sh checks for DHE-EXPORT support and small DH primes:
docker run --rm drwetter/testssl.sh --logjam https://target.example.com:443

# Manually check DH group size with openssl:
openssl s_client -connect target.example.com:443 -cipher "DHE-RSA-AES128-SHA" 
    2>&1 | grep "Server Temp Key"
# Output: "Server Temp Key: DH, 2048 bits" = safe
#         "Server Temp Key: DH, 512 bits"  = vulnerable to Logjam

Cryptanalysis Methodology

  1. Intercept the DH public values — In a network or protocol challenge, identify the messages containing g^a and g^b. Parse the DH group parameters (p, g) from the protocol specification or by reading the wire format.
  2. Confirm absence of authentication — Verify that neither g^a nor g^b is signed, bound to a certificate, or accompanied by any authenticating MAC. If a signature is present, check whether the signature verification is actually performed (a common implementation flaw is to sign but not verify).
  3. Execute the MitM — Generate two fresh DH key pairs (x, g^x) and (y, g^y). Return g^x to Alice in place of g^b, and g^y to Bob in place of g^a. Compute K_AE = (g^a)^x mod p and K_BE = (g^b)^y mod p.
  4. Derive session keys — Apply whatever KDF the protocol uses (HKDF, SHA-256, or a custom construction) to each shared secret to obtain the symmetric encryption keys used for each direction of the channel.
  5. Decrypt and re-encrypt traffic — Use K_AE to decrypt messages from Alice; modify if desired; re-encrypt with K_BE before forwarding to Bob. Use a packet proxy or custom socket relay script to implement this in real-time for a network challenge.
  6. Test for downgrade (Logjam context) — If the protocol negotiates DH group strength, attempt to downgrade the negotiation to a weak group (512-bit prime). Use testssl.sh --logjam or manually specify an export cipher suite to verify the server accepts weak parameters.

Secure Implementation Note — All Diffie-Hellman key exchanges must be authenticated. Use TLS 1.3 (RFC 8446) which enforces mutual authentication of the key exchange transcript via certificate signatures and the Finished message MAC, preventing MitM even with ephemeral DH. For SSH: key exchange is authenticated via host key signatures (RFC 4253). Use only standardised DH groups from RFC 7919 (FFDHE groups, safe primes ≥ 2048 bits) or ECDH with NIST P-256 or X25519. Never accept client-negotiated DH parameters — use a server-side hardcoded approved group.

Common Cryptanalysis Errors

  • Confusing passive eavesdropping with active MitM — A passive attacker who only records g^a and g^b cannot compute the shared secret (DLP hardness). The MitM attack requires active interposition and substitution of public values. Always confirm whether the attack model is passive or active.
  • Modifying the public values without adjusting the key derivation — After MitM substitution, the session keys derived by Alice and Bob differ from each other. Correctly relaying traffic requires deriving two separate session keys (one per direction) and re-encrypting at each hop.
  • Ignoring the KDF step — DH produces a group element (large integer), not a symmetric key. The protocol always applies a KDF to the raw shared secret. Skipping the KDF produces a key that does not match the one used by the legitimate party.
  • Assuming authentication is absent just because DH is used — TLS and SSH both use DH but authenticate the exchange. Always verify whether the protocol includes signed transcripts, MACs over the exchange, or certificate binding before assuming MitM is possible.
  • Testing only the happy path — Downgrade attacks require probing whether the server accepts weak parameter sets. Simply observing that a server uses 2048-bit DH normally does not rule out that it also accepts 512-bit DH on legacy cipher suites.
  • Overlooking ephemeral vs static DH — Static DH (same DH key pair reused across sessions) lacks forward secrecy. Ephemeral DH (fresh key pair per session) provides forward secrecy. Even a correctly authenticated DH exchange may lack forward secrecy if static keys are used.

NICE Framework Alignment

Code Knowledge/Skill/Task Statement How This Card Develops It
K0007 Knowledge of authentication, authorisation, and access control methods Demonstrates how the absence of DH public value authentication creates a full MitM channel breach
K0018 Knowledge of encryption algorithms and their weaknesses Explains the mathematical structure of DH key exchange and the Logjam downgrade attack on weak group parameters
K0019 Knowledge of cryptography and cryptographic key management Covers ephemeral vs static DH, forward secrecy, and safe-prime group selection from RFC 7919
K0074 Knowledge of network security protocols Contextualises DH vulnerabilities within TLS 1.3 protocol design and SSH key exchange authentication
S0138 Skill in using public-key infrastructure tools Trains DH key pair generation, shared secret computation, and MitM relay implementation in Python
T0259 Use cryptanalysis tools to recover plaintext from ciphertext Provides complete methodology from unauthenticated DH identification through session key derivation and traffic decryption

Further Reading

  • Diffie, W. and Hellman, M. (1976). New Directions in Cryptography — IEEE Transactions on Information Theory 22(6)
  • Adrian, D. et al. (2015). Imperfect Forward Secrecy: How Diffie-Hellman Fails in Practice (Logjam) — CCS 2015
  • RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3 — IETF (Rescorla)

Challenge Lab

Reinforce your learning with a hands-on generated challenge based on this card's competency.