Those Who Do Not Learn from Advisories Are Doomed to Repeat Them

Louis Nyffenegger (Founder · PentesterLab)

Kawaiicon 2025 (Kawaiicon III) · Day 2 · Main Stage

Overview

In his Kawaiicon talk, "Those Who Do Not Learn from Advisories Are Doomed to Repeat Them," Louis Nyffenegger, founder of PentesterLab, passionately advocates for the proactive study of past Common Vulnerabilities and Exposures (CVEs) as a cornerstone of effective cybersecurity. Nyffenegger, who describes his deep dive into old CVEs as a personal hobby, demonstrates how understanding historical vulnerabilities can dramatically improve a security professional's "spidey sense" for identifying and mitigating current and future threats. The talk challenges the common industry approach of only reacting to new vulnerabilities and instead champions a methodology of learning from the mistakes of the past.

Watch on YouTube

Visual summary for Those Who Do Not Learn from Advisories Are Doomed to Repeat Them by Louis Nyffenegger
Visual summary for Those Who Do Not Learn from Advisories Are Doomed to Repeat Them by Louis Nyffenegger

Key moments

  1. 0:00 Introduction and AES-GCM authentication bypass
  2. 6:10 Understanding recurring vulnerability cycles
  3. 9:00 Practical methodology: using GitHub Advisory Database
  4. 12:00 Learning common developer traps and security patterns
  5. 18:00 Golang hashing misuse leading to private key exposure
  6. 21:00 Classic WordPress cookie parsing vulnerability (no separator)
  7. 27:00 PHP arraysearch zero-is-false vulnerability

Those Who Do Not Learn from Advisories Are Doomed to Repeat Them

Speakers: Louis Nyffenegger, Founder, PentesterLab

Conference: Kawaiicon

YouTube: https://www.youtube.com/watch?v=gL1xIK81HqY

Overview

In his Kawaiicon talk, "Those Who Do Not Learn from Advisories Are Doomed to Repeat Them," Louis Nyffenegger, founder of PentesterLab, passionately advocates for the proactive study of past Common Vulnerabilities and Exposures (CVEs) as a cornerstone of effective cybersecurity. Nyffenegger, who describes his deep dive into old CVEs as a personal hobby, demonstrates how understanding historical vulnerabilities can dramatically improve a security professional's "spidey sense" for identifying and mitigating current and future threats. The talk challenges the common industry approach of only reacting to new vulnerabilities and instead champions a methodology of learning from the mistakes of the past.

Nyffenegger argues that the cybersecurity landscape is plagued by recurring vulnerability cycles, where critical flaws are discovered, patched, forgotten, and then rediscovered years later in different contexts or codebases. By dissecting specific examples ranging from cryptographic misconfigurations to subtle coding errors, he illustrates how these vulnerabilities resurface across various programming languages and frameworks. This talk is crucial for developers, security engineers, and anyone involved in code review or architectural design, offering a practical framework for leveraging existing security intelligence to build more resilient and secure systems.

The core message is a call to action: instead of viewing CVEs merely as compliance checkboxes or exploit targets, they should be embraced as invaluable educational resources. Nyffenegger provides a structured approach for extracting lessons from advisories, identifying common traps and patterns, and ultimately fostering a proactive security mindset that can break the cycle of repeated vulnerabilities.

Background

▶ Watch: Introduction and AES-GCM authentication bypass (0:00)

Nyffenegger's motivation stems from his work at PentesterLab, where providing high-quality training content is paramount. He found that analyzing old CVEs not only serves as an endless source of real-world examples but also reveals fundamental security principles and common developer pitfalls. This "hobby" of dissecting past vulnerabilities led him to observe a disturbing trend: many security flaws are not new discoveries but rather rediscoveries of problems that have been solved—and subsequently forgotten—years or even decades prior.

He introduces this concept by highlighting CVE-2025-54887, a vulnerability involving missing GCM authentication tag validation in encrypted JWTs (JSON Web Tokens) within a Ruby library. This particular CVE serves as a prime example of a recurring issue. Authenticated Encryption with Associated Data (AEAD) modes like AES-GCM (Galois/Counter Mode) are crucial because they provide both confidentiality (encryption) and integrity/authenticity (authentication). The authentication tag ensures that the ciphertext has not been tampered with. If an attacker modifies the ciphertext or the initialization vector (IV), the authentication tag should fail validation, preventing decryption and data corruption. However, in the case of CVE-2025-54887, the implementation only checked the first byte of the authentication tag, allowing an attacker to brute-force the remaining bytes (which is quick for a single byte, though the talk later clarifies it's a full brute-force of the tag that becomes feasible with a partial check). This specific flaw was not unique to Ruby; Nyffenegger notes similar issues in PHP's openSSL_decrypt (also checking only the first byte), Node.js's crypto module (checking four bytes, making brute-forcing harder but still feasible), Erlang/Elixir, and Lua. Ruby on Rails, thankfully, had a specific protection in place that checked the full tag length.

Nyffenegger then expands on this observation, illustrating the "cycle of vulnerability" with several historical examples:

  • HTTP Proxy vulnerabilities: Big in 2001, forgotten, then resurfaced in 2015-2016.
  • Zip Slip (directory traversal in zip files): Known in 2001, "rediscovered" by the Snyk team in 2018, and still prevalent in Golang codebases.
  • Unicode shenanigans: A bug in GitHub (November 2019) allowing logins with a dotless Turkish 'i' due to case-insensitive comparison, then found in Django (December 2019), and recently reappearing in npm libraries.
  • SAML (Security Assertion Markup Language) signature wrapping: Prominent in 2005, then nothing for years, only to become a major concern again more recently.
  • Request Smuggling: Nyffenegger cites James Kettle's anecdote of owning Akamai by simply copy-pasting a payload from a 2004 PDF—a testament to the enduring relevance of old vulnerabilities.

This consistent pattern of rediscovery underscores Nyffenegger's central premise: the security community frequently fails to learn from its past, leading to a repetitive cycle of vulnerabilities.

Key Findings

▶ Watch: Practical methodology: using GitHub Advisory Database (9:00)

The central finding of Nyffenegger's talk is that the vast majority of "new" vulnerabilities are, in essence, old problems resurfacing due to a lack of institutional memory and proactive learning from past security advisories. He argues that by systematically studying historical CVEs, security professionals can develop a profound understanding of common pitfalls, recurring patterns, and subtle implementation errors that lead to exploitable weaknesses. This approach not only aids in identifying existing bugs but also in preventing future ones.

Nyffenegger outlines a practical methodology for leveraging the wealth of information contained within security advisories:

  1. Utilize the GitHub Advisory Database: This platform is highlighted as the single best resource for finding CVEs, offering robust filtering capabilities. Users can filter by programming language (e.g., Python, C, Go), severity (e.g., high impact), and CWE (Common Weakness Enumeration), allowing for highly targeted research. For instance, one could search for "high impact directory traversal in Golang."
  2. Analyze the Fix: Once an advisory is selected, the process involves cloning the vulnerable repository, identifying the vulnerable and fixed versions (often via Git tags), and then performing a git diff to understand the exact code changes that remediated the vulnerability. Crucially, many GitHub advisories provide direct links to the commit diff, simplifying this step.
  3. Extract Lessons: From this analysis, Nyffenegger identifies three key types of learning:
  • Traps: Specific functions, APIs, or common developer assumptions that lead to security vulnerabilities. An example is Golang's path.Clean, which developers often mistakenly believe prevents directory traversal but only cleans the path format. Another is the distinction between open (kernel open, potentially dangerous) and File.open in Ruby, where open combined with user-controlled input and a pipe character (|) can lead to command execution.
  • Patterns: Recurring architectural or logical flaws that appear across different languages and codebases. The "filter-then-modify" pattern is a prime example: code that attempts to filter malicious input (e.g., preventing .jsp file uploads) but then modifies the input after the filtering step, allowing an attacker to reintroduce the malicious content.
  • Improving "Spidey Sense": The cumulative effect of studying traps and patterns is an enhanced intuition for spotting potential vulnerabilities during code review or architectural design. This "spidey sense" allows security professionals to quickly identify suspicious code constructs or design choices.

Beyond individual vulnerabilities, Nyffenegger also emphasizes the importance of analyzing trends in CVE data. By plotting vulnerability types over time or comparing them across languages, one can gain insights into emerging threats or common weaknesses in specific ecosystems. For instance, he notes a recent surge in command injection vulnerabilities in Golang, potentially linked to the rise of AI-powered Multi-Cloud Platform (MCP) servers. Similarly, a resurgence of serialization vulnerabilities in Python could be attributed to the widespread use of serialization by LLMs (Large Language Models). Understanding these trends helps blue teams prioritize security education for developers and focus their defensive strategies.

Technical Deep Dive

▶ Watch: Learning common developer traps and security patterns (12:00)

Nyffenegger’s talk is rich with specific technical examples, demonstrating how seemingly minor coding errors or misunderstandings of API behavior can lead to significant security vulnerabilities.

GCM Authentication Tag Bypass (CVE-2025-54887)

The talk begins with CVE-2025-54887, detailing a critical flaw in AES-GCM implementations. GCM provides both encryption (confidentiality) and authentication (integrity) via an authentication tag. The vulnerability arises when the verification process for this tag is incomplete. Instead of validating the entire tag, some implementations only check a partial segment, such as the first byte (Ruby, PHP, Erlang/Elixir, Lua) or four bytes (Node.js).

  • Mechanism: If only the first byte is checked, an attacker can modify the ciphertext or associated authenticated data and then brute-force the single byte of the authentication tag until it matches the expected value. Since there are only 256 possible values for a single byte, this is trivial. If four bytes are checked, the brute-force space is larger (2^32), but still feasible for targeted attacks, especially given the speed of modern hardware.
  • Impact: This bypass allows an attacker to decrypt and/or modify encrypted data, compromising both confidentiality and integrity. In the context of JWTs, this could lead to token manipulation, privilege escalation, or other unauthorized actions.
  • Mitigation: Ruby on Rails, for example, had a specific protection that explicitly checked the length of the authentication tag, preventing this attack. The underlying Ruby OpenSSL issue (opened in 2016) noted the need to check the tag length but expressed concerns about backward compatibility, highlighting the challenge of fixing deep-seated crypto library issues.

CORS Bypass via Subdomain Wildcard Misinterpretation (GoZero Golang CVE)

Nyffenegger presents a Cross-Origin Resource Sharing (CORS) vulnerability found in the GoZero framework (Golang).

  • Intention: The developers aimed to allow requests from a specific domain and all its subdomains (e.g., pentestab.com and *.pentestab.com).
  • Implementation Flaw: Instead of correctly parsing and validating subdomains, the code checked if the requesting origin ends with the allowed domain.
  • Attack: If pentestab.com was allowed, an attacker could register hackedbypentestab.com and make requests from this malicious domain, which would pass the ends with check, effectively bypassing the CORS policy.

File Open Trap: open vs File.open (Ruby/Perl)

This example illustrates a common trap related to function overloading and dangerous defaults.

  • Scenario: A Ruby developer wants to open a file using File.open('path/to/file'). In an effort to be "faster," they might shorten it to just open('path/to/file').
  • The Trap: open is not an alias for File.open. In Ruby (and Perl), open is a kernel-level function that, if its argument starts with a pipe character (|), will execute the remainder of the string as a shell command.
  • Attack: If an application uses open with a user-controlled path, an attacker can provide input like | rm -rf / to achieve code execution, escalating a potential directory traversal to a critical compromise.

Filter-Then-Modify Pattern (Java Example)

This pattern is a recurring logical flaw:

  • Scenario: A Java application filters uploaded files to prevent dangerous extensions (e.g., .jsp, .php). The filtering logic is robust.
  • The Flaw: For "whatever reason" (e.g., production hotfix, feature implementation), the application then modifies the file name after the filtering step. For example, it might normalize characters, add a prefix, or change the extension based on internal logic.
  • Attack: An attacker can craft an input that, while initially passing the filter, gets transformed into a malicious file name by the post-filtering modification. For instance, uploading file.jsp.safe might be filtered, but if the system later removes .safe, it becomes file.jsp. This pattern highlights the importance of validating input at the correct stage and ensuring that transformations do not reintroduce filtered content.

SHA-384 Hash Misuse in Golang (Goju CVE)

This example demonstrates a subtle cryptographic misuse in a Canonical Goju project.

  • The Trap: Golang's sum function (e.g., sha512.Sum384) has a peculiar behavior: if data is passed directly to sum, it prepends that data to the hash of an empty string, rather than hashing the data itself. A correct hash requires h := sha512.New384(); h.Write(data); h.Sum(nil).
  • Real-world CVE: A recent vulnerability involved generating ED25519 private and public keys for SSH certificates. The code used generateKey() and assigned the result to a variable key. During a migration from RSA to ED25519, the developer mistakenly used key (which was the private key) directly when computing the SubjectKeyID for the certificate, instead of key.Public().
  • Combined Flaw: The SubjectKeyID was computed using the incorrect sum function, effectively prepending the cleartext private key to an empty hash and embedding it directly into the certificate.
  • Additional Irony: This SubjectKeyID computation was entirely unnecessary for Golang 1.15+ (since 2020), meaning the vulnerability was introduced for a non-existent requirement. This highlights the importance of asking, "Is this code still needed?"

WordPress Cookie Authentication Bypass (2008)

This classic vulnerability illustrates the dangers of improper string concatenation for signed data.

  • Mechanism: WordPress used signed cookies for authentication. The signature (HMAC) was computed over a concatenated string of the username and expiration timestamp.
  • Flaw: No separator was used between the username and expiration in the string that was hashed.
  • Attack: If admin1 (username) and long_expiration (timestamp) were concatenated, the HMAC would be HMAC(admin1long_expiration). An attacker logged in as admin1 could modify their cookie to admin (username) and 1long_expiration (timestamp). The concatenated string admin1long_expiration would be identical, resulting in the same valid HMAC. The application would then parse this as admin with a modified expiration, potentially leading to privilege escalation if admin was a higher-privileged user.
  • Fix: The fix involved introducing a separator (e.g., a pipe |) between the username and expiration before computing the HMAC, ensuring that HMAC(admin1|long_expiration) is distinct from HMAC(admin|1long_expiration).

Play Framework Session Cookie Deserialization (2013)

This bug is a direct consequence of "reinventing the wheel" for serialization.

  • Mechanism: The Play framework attempted to implement its own session cookie serialization, resulting in a format like key1=value1\x00key2=value2. The session.put(key, value) function was used to store session data. It filtered keys to prevent colons but did not filter values.
  • Flaw: The parsing logic split the cookie string by \x00 (null byte) and then by equals (=) to extract key-value pairs. If an attacker could inject \x00 into a value, they could introduce new key-value pairs that would be processed later.
  • Attack: An attacker could craft a username like Louie\x00username=admin. When this username was stored, the cookie would contain username=Louie\x00username=admin and a valid signature. When the application reloaded the page, its parsing logic would first process username=Louie and then, because of the injected \x00, it would process username=admin. The second session.put("username", "admin") would overwrite the first, effectively logging the user in as admin despite the signed cookie.

Recent PHP array_search Type Juggling

This example highlights the dangers of implicit type conversion in dynamically typed languages.

  • Scenario: A PHP application uses a deny list and array_search to check if an item is forbidden. If array_search finds the item, it throws an exception.
  • The Trap: array_search returns false if the item is not found, or the integer index if it is found. In PHP, the integer 0 (the index of the first item in an array) is type-juggled to false in a boolean context.
  • Attack: If the malicious item is the first element in the deny list (index 0), array_search will return 0. However, in a conditional statement like if (array_search(...) == true), 0 will evaluate to false, allowing the forbidden item to bypass the filter.
  • Contrast: Nyffenegger contrasts this with Ruby, where 0 is treated as a truthy object, preventing this specific type of bug.

Nyffenegger also briefly mentions other recent findings: an online store login bypass where purchasing something with an [email protected] email logged the user in as admin, and using eval() instead of JSON.parse() for JSON data, leading to code execution as a service. He also learned "yesterday on the plane" that git clone with an extension can be used to run commands, demonstrating the continuous discovery of new (or rediscovered) traps.

Demo / Proof of Concept

▶ Watch: Classic WordPress cookie parsing vulnerability (no separator) (21:00)

While Louis Nyffenegger's talk is highly technical and provides numerous detailed examples of vulnerabilities, it does not feature any live demonstrations or proof-of-concept code execution during the presentation. Instead, he meticulously walks the audience through the vulnerable code snippets, explains the underlying logic flaws, illustrates the attack vectors with clear diagrams and hypothetical scenarios, and shows how the issues were ultimately fixed. The emphasis is on understanding the conceptual and technical specifics of each vulnerability rather than showcasing a live exploit.

Defensive Implications

▶ Watch: PHP array_search zero-is-false vulnerability (27:00)

Nyffenegger's talk provides a compelling blueprint for how defenders can proactively strengthen their security posture and break the cycle of recurring vulnerabilities. The core defensive strategy revolves around learning from the past to prevent future mistakes.

  1. Embrace Historical CVEs as Educational Resources: The most fundamental implication is to shift the perception of CVEs from mere compliance items or exploit targets to invaluable learning opportunities. Security teams and developers should regularly study old advisories, not just new ones, to understand the root causes and common patterns of vulnerabilities. This proactive learning builds a stronger foundational understanding of security principles.
  1. Systematically Utilize the GitHub Advisory Database: This resource is highlighted as a powerful tool for targeted learning. Defenders can use its filtering capabilities (by language, severity, CWE) to:
  • Educate Developers: If a team primarily works in Golang, they can filter for Golang CVEs to understand common traps and patterns specific to that ecosystem (e.g., path.Clean misuse, command injection trends).
  • Prioritize Security Efforts: By observing trends (e.g., serialization issues in Python due to LLMs, command injection in Golang), security teams can identify areas of high risk and prioritize security training, code review focus, and tooling investments.
  • Inform Threat Modeling: Understanding common attack patterns and vulnerability types helps in constructing more accurate threat models for new and existing applications.
  1. Develop a "Spidey Sense" for Vulnerable Code: Through consistent review of past vulnerabilities, developers and security engineers can cultivate an intuitive ability to spot potential weaknesses. This "spidey sense" is invaluable during code reviews, architectural design discussions, and penetration testing. It allows for the identification of subtle flaws that might otherwise be overlooked.
  1. Implement Robust Input Validation and Sanitization:
  • Validate Early and Often: As seen in the "filter-then-modify" pattern, validation must occur at the appropriate stage and be robust enough to withstand subsequent transformations. Input should be validated as close to the point of entry as possible.
  • Validate All Components: For data structures or concatenated strings, ensure that all individual components (keys and values) are properly validated and sanitized, not just the keys (as seen in the Play Framework example).
  • Use Separators for Signed Data: When concatenating multiple pieces of data for cryptographic signing (like HMACs), always use clear, unambiguous separators to prevent ambiguity and signature bypasses (WordPress example).
  1. Avoid Reinventing Security Primitives: The Play Framework's custom serialization highlights the danger of "rolling your own" security mechanisms. For cryptography, serialization, and other security-sensitive operations, always use well-vetted, standard libraries and established best practices.
  1. Understand API Behavior and Return Values: Developers must have a deep understanding of the functions and APIs they use, especially their edge cases and return values. The Golang sum function's behavior (prepending data) and PHP's array_search type juggling (0 evaluating to false) are stark reminders that assumptions about library behavior can lead to critical vulnerabilities. Always check return values explicitly.
  1. Regularly Review Code for Unnecessary Functionality: The Golang SubjectKeyID example where the vulnerable code was no longer needed since Go 1.15 emphasizes the importance of code hygiene. Removing dead or obsolete code reduces the attack surface and eliminates potential sources of new vulnerabilities.
  1. Promote a Culture of Learning and Transparency: Nyffenegger concludes by advocating for celebrating bugs as learning lessons rather than shaming their discovery. Encouraging open discussion and analysis of vulnerabilities within teams fosters a continuous learning environment, ultimately leading to more secure development practices. For individuals seeking roles in security, studying and writing about CVEs can demonstrate understanding and analytical skills, removing the "luck" element of finding a new bug.

By integrating these defensive implications into daily practices, organizations can proactively address common vulnerability patterns, reduce their exposure to risk, and develop a more resilient and security-aware engineering culture.

Key Takeaways

  • Vulnerability Cycles Are Real: The cybersecurity industry repeatedly falls victim to the same types of vulnerabilities due to a lack of historical learning. Old CVEs resurface in new contexts, languages, and frameworks.
  • Old CVEs are a Goldmine for Learning: Studying past security advisories is an incredibly effective way to improve security skills, understand common pitfalls ("traps"), recognize recurring design flaws ("patterns"), and develop an intuitive "spidey sense" for vulnerable code.
  • Leverage the GitHub Advisory Database: This platform is an invaluable, underutilized resource for targeted learning, allowing filtering by language, severity, and CWE to focus on relevant vulnerabilities.
  • Deeply Understand API Behavior: Subtle misunderstandings of function behavior (e.g., Golang's sum, PHP's array_search type juggling) or dangerous defaults (e.g., Ruby's open vs File.open) are frequent causes of critical bugs. Always verify return values and explicit behaviors.
  • Defensive Coding Principles Matter: Robust input validation (at the right stage, for all components), using clear separators for signed data, and avoiding custom implementations of security primitives (like serialization or crypto) are non-negotiable for building secure applications.
  • Foster a Learning Culture: Bugs should be celebrated as learning opportunities, not sources of shame. Encouraging the study and discussion of vulnerabilities within teams can significantly elevate collective security knowledge and practice. (And, as a running gag from the speaker, Ruby is indeed the best programming language).

About the Speaker(s)

Louis Nyffenegger is the founder of PentesterLab, an online platform dedicated to helping individuals learn penetration testing through hands-on exercises. He is known for his deep expertise in web security and his passion for dissecting and understanding vulnerabilities. Nyffenegger describes his interest in looking at old CVEs as a personal hobby, which he leverages to create high-quality, practical training content for PentesterLab. His work focuses on making complex security concepts accessible and actionable for aspiring and experienced security professionals alike.

Reviews

Dr. Zero (Offensive Security Researcher) — STRONG ACCEPT

Louis Nyffenegger delivers exactly what the title promises: a practical methodology for mining old CVEs to build pattern recognition. The talk is dense with real examples, names specific traps across languages, and makes a compelling case that most 'new' vulns are just forgotten old ones. Not novel research per se, but excellent craft and genuinely useful pedagogy.

Heather Calloway (CISO) — STRONG ACCEPT

This is exactly the kind of talk I want my security engineering leads watching. Nyffenegger delivers a practical methodology for turning CVE analysis into institutional knowledge — the kind that actually prevents bugs rather than just finding them. Worth the time for anyone running appsec or developer security programs.

→ Top-rated talks at Kawaiicon 2025 (Kawaiicon III)

All talks from Kawaiicon 2025 (Kawaiicon III)