Classroom Glossary Public page

Week 1: Wireshark / tshark RCE Quartet — Session Lecture Notes

4,065 words

Reading before Session 1: companion handout (cve-lab-wireshark-rce-quartet-2026-05.md) §0.5 and §5. Read the lab-scope section to understand what this module does and does not cover, then read the cross-CVE shape comparison table to arrive with the right frame. The individual CVE sections (§1-§4) are the reference text you will use during and after each session; do not try to read all four before Session 1.

Four sessions; each anchors one CVE. After session 4, a synthesis block ties the shapes together.


Session 1 (60-90 min): TLS ECH integer-truncation heap overflow — CVE-2026-5402

Context: the TLS dissector

Wireshark's TLS dissector parses every TLS record that crosses the wire: ClientHello, ServerHello, Certificate, Finished, and the application-data records that follow. Every capture with HTTPS, DNS-over-TLS, SMTP STARTTLS, or any other application-layer protocol tunneled through TLS drives this code path on every packet. It is one of the most exercised parsers in the entire Wireshark codebase.

The dissector also handles the TLS 1.3 privacy-preserving extensions, including Encrypted Client Hello (ECH). ECH wraps the ClientHello's sensitive fields, most importantly the Server Name Indication (SNI), inside an encrypted envelope that only the destination server can unwrap. Support for ECH landed in Wireshark 4.6.x so analysts could inspect ECH-enabled captures.

The ECH code path is younger than the rest of the TLS dissector. Newer code, under less cumulative review pressure, is where integer-arithmetic errors tend to survive.

The vulnerability

Three defects compound. None of the three would be exploitable alone; together they give the attacker bytes-past-buffer write into the heap.

Defect 1: uint16_t truncation in extensions_end. The dissector computes the end-of-extensions offset inside the outer ClientHello and stores it in a 16-bit local variable. A TVB (Wireshark's opaque buffer abstraction) can hold more than 65 535 bytes. A computed offset above that wraps back to a value smaller than the actual end, producing an end pointer that points before the true end of the extension region.

Defect 2: uint16_t truncation in outer_offset. Same shape, applied to the offset into the outer ClientHello from which each referenced extension's bytes are copied. A wrapped offset can index backwards into adjacent heap memory rather than into the extension bytes the code intended to read.

Defect 3: unsigned underflow in hello_length comparison. There is a bounds check that should refuse to write beyond the transcript buffer. The check computes a difference in unsigned arithmetic. When that difference would have been negative under signed arithmetic (meaning the copy would already have exceeded the buffer), unsigned arithmetic wraps the result to UINT_MAX instead. A comparison like remaining >= copy_length then reads as "I have 4 294 967 295 bytes left" rather than "I have exceeded the buffer." The check is defeated.

What the fix does

The patch replaces narrow integer types with unsigned int or size_t throughout the affected arithmetic, and rewrites the hello_length comparison to compute the difference in a width that cannot underflow. After the patch, the dissector refuses to copy any byte unless the destination offset plus the copy length is strictly less than the actual transcript-buffer size, computed in untruncated arithmetic.

The patch is in the Wireshark git history under issue #21090.

What to look for in the pcap

Open cve-quartet-2026-05/cve-2026-5402-trigger-tls-ech-overflow.pcapng in patched Wireshark 4.6.5. Navigate to the TLS ClientHello record. In the TLS dissector tree, expand the Handshake layer to the ClientHello, then expand Extensions, then find the encrypted_client_hello extension. The extension's internal length fields, when decoded by the vulnerable 4.6.4 dissector, produce offset values that wrap. Patched 4.6.5 raises an expert-info warning on this record rather than following the wrapped pointer; you will see it as a yellow expert-info line in the packet details.

Lab Part 1 in labs/lab-1-pcap-analysis.md walks this inspection step by step.

Defensive synthesis

Upgrade first. Wireshark 4.6.5 patches this directly. No other mitigation closes the root cause.

Sandboxing. Open .pcapng files of unknown provenance inside a disposable VM or container. The attack vector here is not live capture; it is an analyst opening a crafted file on their workstation. Sandboxing the open operation removes the exposure.

ECH-specific preference toggle. For analysts who do not routinely inspect ECH-using traffic: Edit -> Preferences -> Protocols -> TLS -> disable "Decode encrypted client hello." This closes the specific ECH code path without affecting the rest of the TLS dissector.

Pedagogical takeaway

The defensive principle that this CVE demonstrates: size arithmetic on attacker-controlled data must be performed in a width that cannot wrap, and the result must be checked against the actual buffer bounds before any copy or pointer dereference. This generalizes far beyond TLS. CSA-101 students who have walked Petzold's multibyte-arithmetic chapter should recognize this as the same integer-arithmetic failure mode applied at a higher level of abstraction.

The shape name to take away: integer-truncation heap overflow. When you read a future CVE advisory that mentions uint16_t or similar narrow-type arithmetic on wire-format length fields in a parser, this is the shape to check for.

Session 1 discussion prompts

  1. CVE-2026-5402 is reachable via a crafted .pcapng file an analyst opens for offline review, not only via live captured malicious traffic. How does this change your threat model for the capture-analysis step of a typical incident-response workflow?

  2. The fix replaces narrow integer types with wider ones. Why did the original code use uint16_t in the first place? What performance or compatibility motivation might a developer have had, and why is that motivation insufficient given the attack surface?

  3. Edit -> Preferences -> TLS -> disable ECH decoding is a workstation-hardening step. For a SOC that ingests hundreds of captures per day, what would a deployment process for this preference change look like across all analyst workstations?


Session 2 (60-90 min): SBC codec loop-accounting-failure heap overflow — CVE-2026-5403

Context: the SBC audio dissector

Wireshark's SBC (Subband Codec) plugin decodes Bluetooth's mandatory low-complexity audio codec. Bluetooth audio devices (wireless headphones, hands-free car kits, LE Audio hearing aids) fall back to SBC when no higher-quality codec is available. Wireshark's codec plugin under plugins/codecs/sbc/sbc.c decodes captured SBC payloads from RTP-over-Bluetooth captures into PCM audio, so analysts can play back Bluetooth-audio sessions.

Audio-codec plugins receive comparatively less review attention than application-protocol dissectors. This is a recurring pattern: code paths that feel less central to the core analysis workflow accumulate subtle bugs that more-scrutinized paths would have caught earlier.

The vulnerability

The vulnerable function is codec_sbc_decode() in plugins/codecs/sbc/sbc.c.

The function receives three things: an input buffer of encoded SBC bytes, an output buffer of fixed 8 192-byte capacity, and a variable size_out initialized to that capacity. The function enters a while loop that calls into the underlying SBC decoding library on each iteration, advancing input and output pointers as each SBC frame consumes some bytes and produces some PCM samples.

The bug is in what the loop does not do. After each iteration writes decoded PCM into the output buffer, the loop advances the output pointer but never subtracts the bytes just written from size_out. On the next iteration, size_out still reads as the original 8 192 bytes. The next library call writes more PCM without any knowledge that the buffer is filling up. Once the cumulative decoded PCM exceeds 8 192 bytes, the next library-internal write lands past the end of the heap-allocated output buffer.

A secondary defect at the call site in ui/rtp_media.c always allocates exactly 8 192 bytes of output space regardless of how many SBC frames the input stream encodes. Even a properly-accounting loop would have truncated audio for longer streams; the absent accounting turns truncation into heap corruption.

The structural contrast with CVE-2026-5402

Both CVEs produce a heap overflow. The mechanism differs:

CVE-2026-5402 has one-indirection between the integer arithmetic defect and the out-of-bounds write. The truncated offset enables an attacker-controlled destination pointer.

CVE-2026-5403 has loop-level indirection. No individual arithmetic operation is wrong. The loop correctly advances its pointers each iteration. The mistake is at the level of the loop's invariant: the postcondition after each iteration should be "remaining output capacity has been decremented by the bytes just written," and that postcondition is simply absent.

What the fix does

The patch tracks remaining input and output sizes inside the loop via subtraction, checks that the current frame's expected output length fits in the remaining buffer space before the decode call, and breaks out of the loop with a clean error when capacity is exhausted. The fix is roughly three additional lines: one subtraction, one comparison, one break.

The patch is in the Wireshark git history under issue #21103.

Defensive synthesis

Upgrade. Wireshark 4.6.5 / 4.4.15 patch this directly. SOCs using Wireshark for VoIP troubleshooting are the most exposed cohort.

Disable SBC if not needed. Edit -> Preferences -> Protocols -> RTP -> uncheck SBC audio playback. Most analysts who do not routinely inspect Bluetooth-audio captures can disable this codec entirely.

Surface-area note. Audio-codec dissectors as a class are a recurring CVE source. The same pattern (fixed-size output buffer, decode loop that never tracks remaining capacity) can appear in G.711, G.722, Opus, and AMR codec plugins. Analysts who track Wireshark advisories should monitor codec-plugin advisories with extra attention.

Pedagogical takeaway

Memory corruption does not require sophisticated attacker control over inputs. The SBC bug required no crafted length field, no integer arithmetic manipulation, no knowledge of heap layout. An attacker's only action was to supply enough encoded SBC frames that the cumulative decoded output exceeded 8 192 bytes. Straightforwardly innocent loop-design failures produce the same class of bug as carefully crafted length-field manipulations.

The shape name: loop-accounting-failure heap overflow. When you review a future CVE in a codec, compression, or streaming parser, ask: does the decode loop track remaining output capacity across iterations?

Session 2 discussion prompts

  1. The fix to CVE-2026-5403 is approximately three lines. Why did the original code not include those three lines? What assumption does the original code make about the relationship between input stream length and output buffer size?

  2. Audio-codec dissectors receive less security review because they "feel less central." How would you structure a code-review checklist that catches loop-accounting failures across all codec plugins in a codebase the size of Wireshark's, without requiring a full audit of every plugin?

  3. Contrast the network-IDS detection approach for CVE-2026-5403 versus CVE-2026-5402. For CVE-2026-5402, a Suricata rule can inspect TLS extension length fields. For CVE-2026-5403, the trigger is the quantity of legitimate-looking SBC frames, not a malformed field. How does this change the detection strategy?


Session 3 (60-90 min): RDP ZGFX uncompressed-path heap overflow — CVE-2026-5405

Context: the RDP dissector and RemoteFX ZGFX

Wireshark's RDP dissector parses Microsoft's Remote Desktop Protocol across its many sub-layers: TPKT framing, T.125 multipoint communication, T.124 generic conference control, and the RDP application data itself, which carries input events, display updates, clipboard, audio redirection, and file transfer.

The dissector also handles RemoteFX Graphics (ZGFX), Microsoft's bandwidth-optimized graphics-update codec. ZGFX segments come in two forms: compressed (the normal path, a Lempel-Ziv-style algorithm) and uncompressed (a fallback for data that would not benefit from compression). Both forms share a 65 536-byte output buffer.

The compressed path and the uncompressed path have different code histories. The compressed path's decoder is complex, and that complexity forced careful bounds-check discipline from the beginning. The uncompressed path was simpler: a single tvb_memcpy() call. That simplicity left room for an implicit assumption that neither the code nor the comments ever state explicitly.

The vulnerability

The vulnerable function is rdp8_decompress_segment() in epan/tvbuff_rdp.c, line 344.

When a segment arrives with the uncompressed flag set, the function dispatches to a direct tvb_memcpy(tvb, outputSegment, offset, payload_length) call. The outputSegment buffer is 65 536 bytes. The payload_length value comes from the attacker's packet. There is no check that payload_length <= 65536 before the copy.

The attacker supplies: uncompressed flag, a payload_length value greater than 65 536, and that many bytes of payload. The dissector copies all of them into the output buffer, writing past the end.

The compressed path does not have this bug because its helper functions (zgfx_write_raw(), zgfx_write_literal(), zgfx_write_from_history()) each enforce bounds checks internally. The compressed decoder was complex enough that its authors had to think carefully about buffer management. The uncompressed decoder was simple enough that its author assumed the calling layer would have already validated the size. That assumption was wrong.

The structural position in the quartet

This is the most direct of the three heap-overflow shapes in this module:

  • CVE-2026-5402: two integer truncations plus an underflow enable an out-of-bounds destination pointer. Three defects compound.
  • CVE-2026-5403: loop iterations accumulate past the buffer boundary. State across iterations is the issue.
  • CVE-2026-5405: one memcpy with no bounds check on an attacker-supplied length. One missing line.

RDP is the clearest illustration of the fundamental pattern that all three CVEs instantiate: a copy of attacker-controlled size into a fixed-size destination buffer without a length check immediately before the copy.

What the fix does

The fix adds one bounds check: refuse to copy if payload_length > 65536, and raise an expert_info warning to the dissector's expert-info channel so the analyst sees that the capture contained an oversized segment. One conditional, one early return, one informational message. The patch is in the Wireshark git history under issue #21105.

Analogous CVEs

FreeRDP CVE-2022-39316 and CVE-2022-39320 are the same ZGFX-uncompressed-path missing-bounds-check pattern in a different RDP implementation. Two different codebases, the same structural mistake in the same sub-protocol, discovered years apart. This recurrence suggests the pattern is endemic to ZGFX implementations across the open-source ecosystem.

Defensive synthesis

Upgrade. Wireshark 4.6.5 / 4.4.15 patch this. RDP-heavy environments (MSPs, SOCs that capture RDP forensically, remote-admin tool vendors) should prioritize this patch.

Suricata rule. A rule that walks the RDP framing to the ZGFX segment header and alerts on segments with the uncompressed flag set and payload_length > 65536 is the canonical detection shape. Suricata's RDP parser supports this traversal, though it may require explicit activation in suricata.yaml.

Disable RDP dissection. Edit -> Preferences -> Protocols -> RDP -> uncheck "Dissect RDP packets." Closes the entire RDP code path for environments that do not routinely analyze RDP captures.

Pedagogical takeaway

Every parser path that copies bytes into a fixed-size buffer must perform its own length check, regardless of what other paths do or what assumptions about upstream validation the path's author held. The RDP ZGFX pattern is a direct illustration. The compressed path was correct because its complexity forced attention to buffer management; the uncompressed path was incorrect because its simplicity made the missing check feel unnecessary.

The generalization: fast paths that bypass the validation logic the slow path performs are candidate vulnerabilities. This shape recurs across image codecs (compressed vs. uncompressed paths), protocol parsers (encrypted vs. plaintext paths), and deserializers (signed vs. unsigned integer paths). Belt-3 graduates reviewing any codebase with a fast/slow path asymmetry should default to checking what validation the fast path skips.

The shape name: asymmetric-fast-path heap overflow.

Session 3 discussion prompts

  1. CVE-2026-5405 and FreeRDP CVE-2022-39316 / CVE-2022-39320 are the same structural mistake in the same sub-protocol in different codebases, discovered years apart. What process failure does this suggest, and what would a better process look like for sharing vulnerability pattern knowledge across open-source implementations of the same protocol?

  2. The fix is one conditional. Why might a code reviewer have approved the original code without noticing the missing check? What code-review heuristic would reliably catch this class of issue?

  3. At the SOC layer: the Suricata rule for CVE-2026-5405 needs to walk multiple protocol layers (TPKT -> T.125 -> T.124 -> ZGFX) to reach the length field. What are the operational trade-offs between enabling this multi-layer rule versus disabling RDP dissection in Wireshark entirely?


Session 4 (60-90 min): Profile import zip-slip-to-RCE — CVE-2026-5656

Context: the profile import mechanism

Wireshark's profile import feature lets analysts share customized configurations as ZIP archives. A profile contains coloring rules, capture filters, display filters, column layouts, and Lua plugins. An analyst imports a profile via the GUI's "Manage Profiles -> Import" dialog, and Wireshark extracts the archive into the user's per-profile configuration directory, typically ~/.config/wireshark/profiles/<name>/ on Linux or %APPDATA%\Wireshark\profiles\<name>\ on Windows.

Wireshark also auto-loads any .lua file placed in the per-profile plugin directory on startup. Lua plugins extend dissectors, add custom expert-info checks, and implement domain-specific protocol decoders. This is a core feature, not a misconfiguration.

The intersection of "ZIP extraction without path validation" and "auto-execution of files dropped at extraction time" is the structural shape CVE-2026-5656 exploits.

The vulnerability

The bug is a zip-slip (CWE-22): archive entry names that contain ../ path-traversal sequences are extracted to locations outside the intended destination directory.

Step 1: zip-slip primitive. The ZIP extraction code in ui/qt/utils/wireshark_zip_helper.cpp reads each entry's name, appends it to the extraction-directory base path, and writes the entry's content to that location. There is no check that the resolved absolute path stays within the extraction directory. An entry named ../../../plugins/evil.lua escapes the profile directory and lands in the global Lua-plugins directory.

Step 2: auto-execute via init_wslua.c. On the next Wireshark startup, lua_load_pers_plugins() in epan/wslua/init_wslua.c auto-loads every .lua file in the per-profile and global plugins directories. The function does not and should not re-validate where each plugin file came from; by the time it runs, an attacker-placed file looks identical to a file the analyst installed manually.

The chain: import a malicious profile zip -> zip-slip places evil.lua in the Lua plugins directory -> next Wireshark startup executes evil.lua -> arbitrary code runs in the analyst's user context.

The structural contrast with the heap-overflow trio

CVE-2026-5656 is not a memory-corruption bug. There is no heap overflow, no integer arithmetic, no buffer. The bug is a logic error in file-system path handling, combined with an architectural decision about auto-execution.

This is why the quartet forms a pedagogically useful set: three bugs with the same result (heap corruption) via different mechanisms, and one bug with a completely different mechanism (path validation failure + trust in the calling layer) that produces the same high-severity outcome. Students who understand all four shapes have a richer vocabulary than students who only know "heap overflow means something went wrong with a buffer."

The zip-slip primitive is not Wireshark-specific. It has appeared in dozens of CVEs across Java web frameworks, Node.js packages, Python build tools, and language-runtime archive utilities. Any code that extracts ZIP archives without checking that extracted paths stay within the intended directory is potentially vulnerable. CWE-22 has been named and documented since 2006; this CVE is a 2026 occurrence.

What the fix does

The fix is in WiresharkZipHelper::unzip(): before any file write, the resolved absolute path is canonicalized and compared against the canonicalized extraction-directory root. If the resolved path does not have the extraction root as a prefix, the entry is rejected and an error is logged. The fix is the standard zip-slip mitigation; the pattern appears in OWASP's guidance and in the security advisories for earlier zip-slip CVEs across other projects.

The patch is in the Wireshark git history under issue #21115.

Discovery: Theori with Xint

CVE-2026-5656 was discovered by a team of five researchers at Theori, working with Xint: Joohyun Park, Hyuk Kwon, Yonghwa Lee, Taisic Yun, and Sangjun Song. Coordinated disclosure followed the standard responsible-disclosure timeline before the 4.6.5 release.

Defensive synthesis

Upgrade first. Wireshark 4.6.5 / 4.4.15 fix the zip-slip primitive directly.

Profile-import discipline. Before importing any profile archive from outside your organization: inspect the entry list first with unzip -l profile.zip. If any entry name contains .. or an absolute path, refuse to import.

File-gateway scanning. Mail gateways and file-sharing platforms can scan .zip attachments for entries with .. in the path. This is a one-line grep equivalent and a near-zero false-positive rule.

EDR rule. A rule that alerts on any process creating a .lua file in the Wireshark plugins directory via a path that did not originate from a known-good installer catches post-exploitation behavior.

Pedagogical takeaway

The zip-slip pattern is not a parser bug in the traditional sense. It is a correctness failure in the extraction logic that makes an architectural assumption the extractor is not in a position to guarantee. The auto-execute step converts a file-write primitive into RCE by trusting that the files it is about to execute arrived through a controlled channel. That trust is defeated by the zip-slip.

For RE-011 students: this CVE is the most accessible patch-diff lab in the quartet because the bug and fix exist entirely at the C++ source-code and file-system-semantics level. No memory-layout knowledge, no binary analysis, no exploit-development background needed. Read WiresharkZipHelper::unzip() in 4.6.4, identify the missing path check, read the same function in 4.6.5 to see the fix, and you have done a complete patch-diff analysis. That exercise is in the RE-011 lab cluster.

The shape name: zip-slip-to-RCE.

Session 4 discussion prompts

  1. The zip-slip primitive (CWE-22) has been documented since 2006 and has appeared in dozens of CVEs. Why does it continue to appear in new codebases? What organizational or developer-education failure does its persistence represent?

  2. The auto-execute architectural decision in init_wslua.c is not a bug; it is a deliberate feature. A more defensive architecture would require users to explicitly authorize new plugins that arrived through profile import before auto-execution. What usability trade-offs would that change create, and how would you frame those trade-offs to a Wireshark maintainer in a responsible-disclosure conversation?

  3. The five researchers who discovered CVE-2026-5656 published coordinated disclosure. What obligations does a coordinated-disclosure process place on the discoverer, and what does that process look like from the vendor's (Wireshark maintainers') side?


Cross-CVE synthesis

The progression of indirection

Read the three heap-overflow shapes as a gradient from most-direct to most-indirect:

CVE Shape Mechanism
CVE-2026-5405 (RDP) Asymmetric-fast-path One missing length check before one memcpy. The distance from the attacker's input to the overflow is one call.
CVE-2026-5403 (SBC) Loop-accounting failure The copy is individually correct each iteration; the overflow accumulates across iterations as the loop fails to track remaining capacity.
CVE-2026-5402 (TLS) Integer-truncation chain Two uint16_t truncations plus one unsigned underflow defeat the bounds check. Three defects compound.

CVE-2026-5656 (Profile import) sits outside the gradient. It is not a memory-corruption bug at all; it is a path-validation failure chained to an auto-execute architectural decision. It belongs in this quartet because it produces the same severity outcome via a completely different mechanism, which is why it makes the heap-overflow family more legible by contrast.

Vocabulary: four named shapes

A Belt-3 graduate leaves this module with four named patterns they can apply to future CVEs in any parser codebase:

  1. Integer-truncation heap overflow. Narrow integer arithmetic on attacker-controlled length fields wraps or underflows, defeating bounds checks or producing out-of-range offsets.

  2. Loop-accounting-failure heap overflow. Correct per-iteration copy; missing accumulated-capacity tracking across iterations.

  3. Asymmetric-fast-path heap overflow. The slow path is correctly bounded; the fast path skips the check with an implicit assumption about upstream validation.

  4. Zip-slip-to-RCE. ZIP extraction without destination-path validation; severity escalated by auto-execution of extracted files.

Applying the shapes forward

When you read a CVE advisory for any parser codebase in the future, the first question is not "what is the score" but "what is the shape." Scoring tells you severity; shape tells you where to look for similar bugs, what detection approach is right for the network layer, and which existing mitigations from your hardening inventory already apply.

Belt-5 work in RE-101, RE-201, and ADV-101 takes shape-recognition into the patch-diff layer: given a CVE advisory, can you identify the vulnerable function, read the patch, and understand why each changed line closes the specific shape? That progression from "I can name it" to "I can find it and trace it to a primitive" is the academy's RE track in miniature.


Vocabulary reference

Term Definition
TVB (tvbuff) Wireshark's opaque buffer abstraction. All dissector byte reads go through TVB functions that range-check against the buffer. A TVB read that passes the TVB layer does not guarantee the result is valid for the dissector's internal purposes.
ECH (Encrypted Client Hello) TLS 1.3 extension that encrypts the ClientHello's SNI field inside an HPKE envelope. Only the destination server can decrypt it. Support added in Wireshark 4.6.x.
ZGFX Microsoft's RemoteFX graphics codec. Segments arrive as either compressed (LZ-style) or uncompressed (raw copy). The asymmetry between these two paths is the locus of CVE-2026-5405.
SBC Subband Codec. Bluetooth's mandatory fallback audio codec. Every Bluetooth audio device supports SBC.
Zip-slip Path traversal via archive-entry filenames containing ../ sequences. Extracting such an archive without destination-path validation drops attacker-controlled files outside the intended directory. First widely named by Snyk's research team in 2018 (CWE-22).
expert_info Wireshark's dissector-side informational channel. Dissectors raise expert-info messages (Info / Warning / Error / Chat) that appear as colored annotations in the packet list. Post-patch Wireshark raises an expert-info warning rather than following wrapped pointers or oversized lengths.
fwlab Academy Docker container image shipping Wireshark 4.6.4 (intentionally vulnerable) for lab-target use. Run under --authorized-by discipline; never install 4.6.4 on a production workstation.