A note before we start: every vulnerability in this post is historical, publicly documented for more than two decades, and affects systems Microsoft stopped supporting long ago (Windows 95 in 2001, Windows 98 in 2006). None of this is exploitable on a current system. The goal here is educational: understanding how these flaws worked under the hood helps explain why security practices we now take for granted, privilege separation, input validation, protocol review, didn't exist yet, and how much it cost the industry to make them standard.
Windows 95 turns 31 in August 2026. It was the release that brought Plug and Play, the taskbar, the Start menu, and a native TCP/IP stack into millions of living rooms, but it shipped at a moment when security wasn't yet a recognizable engineering discipline. There was no formal vulnerability response process, no CVSS, and the CVE numbering system itself wouldn't exist until 1999. The result was an entire operating system running without most of what we'd now consider baseline defenses. Seven flaws, including a virus still cited as a landmark case in security courses, show exactly what that looked like in practice.

1. No real separation between ring 3 and ring 0
Windows 95's worst flaw doesn't have a CVE number, because it predates the numbering system itself, and because it isn't a single bug, it's an architectural decision. The Windows 9x kernel didn't properly isolate user mode from kernel mode: for compatibility with 16-bit drivers and MS-DOS software, the system let VxD drivers, and in practice a lot of running code, reach ring 0, the processor's highest privilege level, with no real barrier in the way. There was no single patch that could fix this, it was the system's own design.

- Scanned PE executables for unused gaps between code sections
- Split its own body into pieces that fit those gaps, without increasing the infected file's size
- Used a VxD driver as a jump point from ring 3 to ring 0
- From the kernel, hooked file system calls to reinfect any executable that got opened
; Win9x: ring 3 -> ring 0 escalation (technique used by CIH)
SIDT [mem] ; leaks the IDT base (SIDT is NOT privileged)
; locate an interrupt descriptor in the IDT (e.g. INT 3)
; save the original gate, then overwrite it to point at attacker code
INT 3 ; firing the interrupt lands execution in ring 0
; from here the code runs with kernel privilegeThat's the gap CIH, also known as Chernobyl, exploited at global scale. Discovered in June 1998 and attributed to Taiwanese student Chen Ing-hau, the virus spread through pirated software channels and even ended up, unintentionally, on batches of commercial computers. Its infection technique, described above, made it hard to detect by checking file size. The payload triggered on specific dates, most notably April 26, the anniversary of the Chernobyl disaster, which is where the nickname came from; the worst real-world outbreak hit on April 26, 1999. It had two stages: it overwrote the first megabyte of the boot drive with garbage, destroying the partition table and making the data inaccessible, and on motherboards with certain software-flashable Flash BIOS chips, it tried to overwrite the BIOS itself, bricking the machine at the hardware level, one of the first pieces of malware capable of that. Estimates from the time put the damage at around 60 million infected machines and $40 million in losses. One detail reinforces the architectural diagnosis: Windows NT, with a real kernel and real privilege separation, was immune.
2. A 1-byte password was enough (CVE-2000-0979)
File and Print Sharing on Windows 95, 98, and Me protected shares with share-level passwords, the simplest possible model, no user accounts, just one password for the entire resource. The problem was in the validation: the server compared the password the client sent using the length the client itself declared in the packet, not the actual configured password's length. An attacker could send an access request with a 1-byte password field, and if that single byte matched the real password's first character, that part of the check would pass. In practice, that turned a brute force that should have grown exponentially with password length into a sequence of short attempts, one byte at a time, reconstructing the password incrementally instead of guessing it all at once.
# File & Print Sharing - share-level authentication
# SMB request carrying a 1-byte password field:
PasswordLength = 1
Password = "A" # one guessed byte at a time
# the server compares only PasswordLength bytes against the real password
# if "A" == first character, this part of the check passes
# -> recover the password byte by byte instead of full brute forceThe National Vulnerability Database tracks this as CVE-2000-0979, CVSS 2.0 score of 6.4 (AV:N/AC:L/Au:N/C:P/I:P/A:N; there's no CVSS v3 score because the flaw predates that standard). Microsoft didn't fix it until October 10, 2000, in bulletin MS00-072, five years after Windows 95 shipped.
3. 32-bit RC4 on .PWL files
PWL files cached the credentials a user had already typed in (network password, dial-up, file share, printer) so the system wouldn't have to ask again every session. Windows 95 encrypted that cache with RC4, but limited the effective key space to just 32 bits, far too little to resist brute force. In December 1995, a few months after launch and four years before the CVE system even existed, New Zealand researcher Peter Gutmann published a description of how Windows generated those 32-bit RC4 keys. Days later, Norwegian programmer Frank Andrew Stevenson went further and demonstrated a practical attack worse than the key size alone: the scheme reused RC4 keystream across parts of the same file, the classic mistake in any stream cipher, XORing two ciphertexts encrypted with the same keystream reveals the XOR of the original plaintexts. Since the first 20 bytes of every .PWL file were predictable (derived from the uppercase username padded with nulls), an attacker could recover part of the keystream through a known-plaintext attack and use it to decrypt the rest of the file. Tools like Glide, from that era, automated the crack.
# .PWL: the same RC4 keystream is reused across parts of the file
C1 = P1 XOR KS
C2 = P2 XOR KS
C1 XOR C2 = P1 XOR P2 # the keystream cancels out
# the first ~20 bytes of P1 are predictable (uppercase username + nulls)
# -> recover KS via known-plaintext, then decrypt the restMicrosoft's response extended the key to 128 bits, but the patch (documented in knowledge base article Q132807) didn't fix the underlying problem, the keystream reuse was still there, so Stevenson's attack kept working regardless of key size. It's the same lesson that keeps coming back in any homegrown crypto review today: making the key bigger doesn't fix a stream cipher design that reuses keystream.
4. One packet, one crash: WinNuke (CVE-1999-0153)
WinNuke exploited how Windows handled TCP out-of-band (OOB) data on port 139, used by NetBIOS. The attack crafted a TCP segment with the URG flag set and the urgent pointer pointing past the actual data sent. The OOB parser in the Windows kernel didn't validate that condition properly and crashed the system, usually into a blue screen.
- TCP segment targeting port 139 (NetBIOS Session Service)
- URG flag set in the TCP header
- Urgent pointer value beyond the actual data sent
- Minimal payload, just enough to trigger the kernel's OOB handling
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, 139)) # NetBIOS Session Service
s.send(b"Bye", socket.MSG_OOB) # out-of-band data, sets the URG flag
s.close()The code was published on May 9, 1997 by someone using the handle '_eci', and spread fast, variants like fedup, killwin, liquidnuke, and winnuker02 followed, and the verb 'to nuke' became common vocabulary in IRC channels of the time. NVD tracks it as CVE-1999-0153, CVSS 2.0 score of 5.0 (AV:N/AC:L/Au:N/C:N/I:N/A:P). The fix came through the Winsock 2 update for Windows 95 and Service Pack 3 for Windows NT 4.0; Windows 98, from RC0 onward, shipped immune to this specific variant.
5. Ping of Death (CVE-1999-0128)
The IP protocol caps a packet at 65,535 bytes. Ping of Death sent an ICMP packet (the same protocol behind the ping command) fragmented in a way that, once reassembled, the calculated total size went past that limit. Most TCP/IP stack implementations of the time didn't validate that limit during fragment reassembly, which caused a buffer overflow, a crash, or a reboot. It required no session and no authentication, a single malformed packet was enough, and the problem wasn't Windows-specific, it hit nearly every TCP/IP stack of the era, from routers to Unix workstations. NVD tracks this as CVE-1999-0128, CVSS 2.0 score of 5.0 (AV:N/AC:L/Au:N/C:N/I:N/A:P).
# Windows: oversized ICMP echo, overflows on reassembly
ping -l 65510 target
# Unix equivalent
ping -s 65510 target6. The fragmentation family: Teardrop, LAND, and Bonk
Ping of Death wasn't an isolated case, it was a symptom of a broader problem: TCP/IP stacks of the era trusted the offset and length fields the sender itself supplied, without checking whether they made sense. Teardrop (CVE-1999-0015) exploited exactly that: it sent IP fragments with deliberately overlapping offset fields, in a way that the reassembly algorithm couldn't correctly calculate where each piece belonged, which crashed the network stack. A later variant, named Teardrop-2 (CVE-1999-0104), repeated the same principle against implementations that had already received a partial patch against the original Teardrop.
# Teardrop: overlapping IP fragment offsets
frag 1: MF=1 offset=0 len=36
frag 2: MF=0 offset=24 len=4 # falls INSIDE fragment 1
# a vulnerable stack computes (end - offset) and underflows the length
# LAND: TCP SYN with source == destination
source = 10.0.0.5:139
destination = 10.0.0.5:139 # the host answers itself, in a loopTwo other variants followed the same pattern. LAND (CVE-1999-0016) used a TCP SYN packet with the source IP and port set equal to the destination, making the target machine reply to itself and fall into a loop that ate resources until it crashed, the same underlying flaw (missing header validation), just targeting the source address instead of the fragment offset. Bonk (CVE-1999-0258) was a Teardrop variant targeting UDP fragments with conflicting offset and length fields, same class of bug, different protocol target. All received a CVSS 2.0 score of 5.0 in NVD, the same range as WinNuke and Ping of Death.
The common pattern across WinNuke, Ping of Death, Teardrop, LAND, and Bonk is the same: none of these network stacks properly validated the metadata the sender controlled (offset, size, address, urgent pointer) before trusting it to reassemble or process a packet. It wasn't Windows-specific either, the same fragment-reassembly flaw crashed Linux kernel versions before 2.0.32 and 2.1.63, and other TCP/IP stacks of the era.
7. DOS device names in a path: \con\con (CVE-2000-0168)
MS-DOS reserved a handful of special device names, CON (console), PRN (printer), AUX, NUL, and the COM1 through COM9 and LPT1 through LPT9 series, that couldn't become file names because the operating system routed them straight to the matching hardware or peripheral instead of the file system. Windows 9x inherited that reserved list and correctly blocked creating a file or folder literally named CON. What it didn't check was a path containing more than one reserved device name at once, like c:\con\con. When Windows tried to resolve that path, it attempted an illegal resource access, treating it as two nested physical devices, and crashed.
<!-- CVE-2000-0168: the victim only has to open the page -->
<img src="c:\con\con">The National Vulnerability Database tracks this as CVE-2000-0168, 'DOS Device in Path Name,' CVSS 2.0 score of 5.0 (AV:N/AC:L/Au:N/C:N/I:N/A:P), published in March 2000. Microsoft's bulletin MS00-017, from March 16, 2000, describes what made this bug more dangerous than a typo at a command prompt: because the era's browser processed file paths in any attribute pointing to a resource, a single web page with an <img src="c:\con\con"> tag was enough to crash any visitor's machine running Windows 95 or 98, no click, no download, no privilege required. The victim just had to open the page.
The underlying pattern didn't go away when support for Windows 9x ended. DOS device names are still reserved today, trying to create a file named CON.txt or NUL on a modern Windows machine still fails, and for decades third-party tools have tripped over that special handling in unexpected ways. More broadly, it's the same class of bug, a path parser that treats part of the input as a special case instead of validating the entire input, that shows up today as path traversal, abuse of NTFS Alternate Data Streams, or short-name (8.3) normalization flaws used to escape a sandbox or an upload directory.
The pattern that survives to this day
None of these seven flaws is exploitable on a supported system today, but the pattern behind each one keeps showing up, just in newer code. Missing privilege separation became container escapes and signed kernel driver vulnerabilities. Input validation that trusts what the sender claims about its own packet is still the root cause of a large share of the RCEs and DoS bugs that show up in advisories every month, including in critical modern infrastructure software. A path parser that treats a reserved name as a one-off exception instead of validating the entire input still turns into path traversal and special-filename abuse in current applications. Homegrown cryptography that reuses keystream or artificially shrinks the key space still turns up in pentest engagements, usually in legacy systems nobody has reviewed since they were written. And an authentication check that only compares part of what it should still qualifies as the kind of finding that separates an automated scanner from an actual manual review.
Testing today isn't about looking for CIH or WinNuke, it's about looking for the next version of the same mistake: a route that trusts the client too much, a parser that doesn't validate a limit before allocating memory, a cryptography implementation that looks solid until someone actually reviews the design. That's exactly the kind of bug, silent until someone sends the wrong input, that network pentesting and protocol fuzzing exist to find before an attacker does. Thirty-one years later, the lesson from Windows 95 is still the same: security isn't a feature you bolt on later, it's an architectural decision you pay for, one way or another, much later down the line.