Contents

BreizhCTF 2026 - Totally Secure

Totally Secure

  • Difficulty: Easy
  • Category: Forensic
  • Author: Zlippy

Description

A friend set up his own “secure” server using copy-paste and ChatGPT prompts. He’s very proud and keeps saying everything is “encrypted” and nobody can see anything on the network.

He shared a network capture and some files recovered from his machine, convinced you won’t understand a thing. Your mission: prove him wrong and recover what was traveling through his supposedly unbreakable communications.

Files:

  • traffic.pcapng — network capture of the client browsing to the server
  • triage_server.tar.gz — forensic dump of the server (configs, processes, logs, filesystem…)

Solve

Step 1 — Map the network traffic

List all TCP conversations from the capture:

tshark -r traffic.pcapng -q -z conv,tcp

One IP stands out: 10.90.35.19, on the same subnet as the client (10.90.35.143). That’s the “secure” server. Filtering on it:

tshark -r traffic.pcapng | grep "10.90.35.19"
 2987 16.625631355 10.90.35.143 → 10.90.35.19  TCP 74 39470 → 443 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 SACK_PERM TSval=697595375 TSecr=0 WS=128
 2988 16.626162727  10.90.35.19 → 10.90.35.143 TCP 74 443 → 39470 [SYN, ACK] Seq=0 Ack=1 Win=65160 Len=0 MSS=1460 SACK_PERM TSval=759725241 TSecr=697595375 WS=128
 2989 16.626210132 10.90.35.143 → 10.90.35.19  TCP 66 39470 → 443 [ACK] Seq=1 Ack=1 Win=64256 Len=0 TSval=697595376 TSecr=759725241
 2990 16.630862909 10.90.35.143 → 10.90.35.19  TLSv1 226 Client Hello (SNI=pleasedontdothat.local)
 2991 16.631297136  10.90.35.19 → 10.90.35.143 TCP 66 443 → 39470 [ACK] Seq=1 Ack=161 Win=65024 Len=0 TSval=759725246 TSecr=697595381
 2992 16.632325670  10.90.35.19 → 10.90.35.143 TLSv1.2 1092 Server Hello, Certificate, Server Hello Done
 2993 16.632349897 10.90.35.143 → 10.90.35.19  TCP 66 39470 → 443 [ACK] Seq=161 Ack=1027 Win=67200 Len=0 TSval=697595382 TSecr=759725247
 2994 16.633977211 10.90.35.143 → 10.90.35.19  TLSv1.2 412 Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message
 2995 16.637547905  10.90.35.19 → 10.90.35.143 TLSv1.2 145 Change Cipher Spec, Encrypted Handshake Message
 2996 16.637802245 10.90.35.143 → 10.90.35.19  TLSv1.2 203 Application Data
 2997 16.638399440  10.90.35.19 → 10.90.35.143 TLSv1.2 3883 Application Data
 2998 16.638489182 10.90.35.143 → 10.90.35.19  TCP 66 39470 → 443 [ACK] Seq=644 Ack=4923 Win=63488 Len=0 TSval=697595388 TSecr=759725253
 2999 16.638914193 10.90.35.143 → 10.90.35.19  TLSv1.2 123 Encrypted Alert
 3000 16.639032578 10.90.35.143 → 10.90.35.19  TCP 66 39470 → 443 [FIN, ACK] Seq=701 Ack=4923 Win=63488 Len=0 TSval=697595389 TSecr=759725253
 3001 16.639297375  10.90.35.19 → 10.90.35.143 TCP 66 443 → 39470 [FIN, ACK] Seq=4923 Ack=702 Win=64640 Len=0 TSval=759725254 TSecr=697595389
 3002 16.639322289 10.90.35.143 → 10.90.35.19  TCP 66 39470 → 443 [ACK] Seq=702 Ack=4924 Win=63488 Len=0 TSval=697595389 TSecr=759725254

The Client Hello reveals the SNI pleasedontdothat.local.

What is SNI? The SNI (Server Name Indication) is a TLS extension that lets the client announce the target domain before the session is encrypted. It is always visible in plaintext in a network capture, even in TLSv1.3. Here, pleasedontdothat.local is readable without decrypting anything.

Step 2 — Identify the negotiated cipher suite

Inspect the Server Hello:

tshark -r traffic.pcapng \
  -Y "ip.addr == 10.90.35.19 && tls.handshake.type == 2" \
  -V | grep "Cipher Suite"
Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x002f)

This is where everything falls apart. TLS_RSA_WITH_AES_128_CBC_SHA uses a static RSA key exchange:

  • The client generates a pre-master secret
  • It encrypts it with the server’s public key and sends it
  • The server decrypts it with its RSA private key
  • Both sides derive session keys from it

Direct consequence: anyone who has the server’s private RSA key can decrypt any past captured session. This is the opposite of Perfect Forward Secrecy (PFS), provided by ECDHE/DHE where session keys are ephemeral and independent of the private key.

Confirmed in the server’s nginx config from the triage:

triage_server/[root]/etc/nginx/sites-available/chall:

ssl_protocols TLSv1.2;
ssl_ciphers "AES128-SHA:AES256-SHA:AES128-SHA256:AES256-SHA256:DES-CBC3-SHA";
ssl_prefer_server_ciphers on;
ssl_session_cache off;
ssl_session_tickets on;

Every cipher listed is an RSA variant with no PFS.

Step 3 — Retrieve the private key from the triage

The triage contains a copy of the server’s filesystem. The nginx SSL files are right there:

ls triage_server/[root]/etc/nginx/ssl/
# server.crt  server.key

The RSA private key is sitting in plain sight in server.key.

Step 4 — Decrypt the TLS traffic

tshark natively supports TLS RSA decryption via tls.keys_list. It uses the private key to decrypt the pre-master secret from the handshake, then reconstructs the session keys to decrypt the application data.

cp "triage_server/[root]/etc/nginx/ssl/server.key" .

tshark -r traffic.pcapng \
  -o "tls.keys_list:10.90.35.19,443,http,server.key" \
  -Y "ip.addr == 10.90.35.19 && http.response" \
  -T fields -e http.file_data \
  | python3 -c "import sys; print(bytes.fromhex(sys.stdin.read().strip()).decode())"
  • -o "tls.keys_list:IP,PORT,PROTO,FILE" — provides the RSA key to tshark for TLS decryption
  • -Y "... && http.response" — filters HTTP packets after decryption (without the key, tshark only sees opaque Application Data)
  • -T fields -e http.file_data — extracts only the HTTP response body
  • The Python pipe converts hex output from tshark into readable text

The decrypted HTTP response is an HTML page containing:

<div class="seccccret-value">BZHCTF{Pl34se_D0nT_D0_Th4t_1n_Pr0d!!!!}</div>

Takeaway: The problem is not TLS itself, but the misconfiguration. Using RSA cipher suites without PFS means the server’s private key is a master key protecting all past and future traffic. A single leak (server compromise, forensic triage) retroactively compromises every captured session. The fix: use TLS 1.3 or enforce ECDHE/DHE-only cipher suites.

Flag : BZHCTF{Pl34se_D0nT_D0_Th4t_1n_Pr0d!!!!}