QUACK: Hindering Deserialization Attacks via Static Duck Typing
Yaniv David
Network and Distributed System Security (NDSS) Symposium 2024 · Day 1 · Android & IoT Security · Android & IoT Security
Overview
Deserialization vulnerabilities represent a pervasive and critical threat in modern software development, consistently ranking among the OWASP Top 10 for web application security risks. Attackers exploit these flaws by manipulating serialized objects, triggering arbitrary code execution through existing code segments known as gadgets. Despite the severity, current defensive mechanisms, primarily manual allow or deny lists for deserialized classes, are notoriously cumbersome and error-prone. This leads to widespread neglect by developers, leaving countless applications vulnerable. This talk introduces QUACK, a novel framework designed to automatically mitigate deserialization attacks in PHP applications by leveraging a sophisticated static duck typing inference technique.

Key moments
- 0:00 Introduction to deserialization attacks and QUACK's purpose
- 0:30 PHP motivating example: arbitrary file deletion via wakeup
- 1:00 Scale of deserialization problem and why current defenses fail
- 1:45 QUACK's core insight: inferring allowed classes from usage
- 2:15 PHP serialization, deserialization, and crucial magic methods
- 3:15 Categories of deserialization exploits, focus on type-confusion
- 4:15 Limitations of traditional type inference for deserialization protection
- 4:45 QUACK's core technical approach: static duck typing inference
QUACK: Hindering Deserialization Attacks via Static Duck Typing
Speakers: Yaniv David
Conference: NDSS Symposium
YouTube: https://www.youtube.com/watch?v=RKiFQwH7Pik
Overview
Deserialization vulnerabilities represent a pervasive and critical threat in modern software development, consistently ranking among the OWASP Top 10 for web application security risks. Attackers exploit these flaws by manipulating serialized objects, triggering arbitrary code execution through existing code segments known as gadgets. Despite the severity, current defensive mechanisms, primarily manual allow or deny lists for deserialized classes, are notoriously cumbersome and error-prone. This leads to widespread neglect by developers, leaving countless applications vulnerable. This talk introduces QUACK, a novel framework designed to automatically mitigate deserialization attacks in PHP applications by leveraging a sophisticated static duck typing inference technique.
Presented by Yaniv David at the NDSS Symposium, QUACK addresses the fundamental challenge of precisely identifying the legitimate classes an application expects during deserialization. Its core innovation lies in statically analyzing how a deserialized object is used within the program to automatically infer a comprehensive and minimal set of allowed classes. This inferred set is then used to instrument deserialization API calls, drastically limiting the attack surface available to malicious actors.
QUACK's significance is underscored by its practical efficacy and developer-friendly approach. It successfully prevents state-of-the-art exploit generation tools from crafting viable attacks, blocking, on average, 97% of an application's code that could be abused as gadgets. The framework's precision ensures no legitimate functionality is disrupted, and its efficiency allows for seamless integration into modern CI/CD pipelines. By automating a previously tedious and neglected security task, QUACK offers a robust and practical solution to a long-standing security problem, enhancing the resilience of PHP applications against a prevalent class of attacks.
Background
▶ Watch: Introduction to deserialization attacks and QUACK's purpose (0:00)
The necessity for programs to save and restore object states is fundamental to software engineering, facilitating tasks from database persistence and distributed system communication to sharing complex data structures like machine learning models. Managed programming languages simplify this process through native serialization and deserialization APIs, abstracting the underlying data representation. Serialization converts an object into a storable or transmissible format, while deserialization reconstructs the object from this format.
PHP's serialization API, particularly the unserialize() function, is powerful but also a frequent source of vulnerabilities. A critical aspect of PHP deserialization is its magic methods, such as _wakeup, _sleep, and _destruct, which are automatically invoked at specific points in an object's lifecycle. The _wakeup method, called immediately after an object is reconstructed, is a prime target for attackers, enabling code execution upon object creation. Historically, PHP versions prior to v8 also had an implicit deserialization vulnerability related to PHAR (PHP Archive) files, where filesystem operations on a PHAR file could trigger metadata deserialization. PHP v8+ now requires explicit calls to Phar::getMetadata() to mitigate this. Unlike Java, which often requires classes to implement java.io.Serializable, PHP implicitly requires classes to be loaded into the interpreter at deserialization time for them to be instantiated.
Despite frequent warnings against passing untrusted input to deserialization functions, these guidelines are often overlooked, leading to arbitrary object creation. This forms the basis for deserialization attacks, which generally fall into three categories:
- Data-injection attacks: The deserialized object is of the intended type, but its data attributes are manipulated to malicious values (e.g., changing a log file path to
.htaccess). While possible, QUACK's analysis found these to be infrequent in PHP CVEs. - Type-confusion attacks: The deserialized object is of an unintended type. This category is most prevalent in PHP and is the primary focus of QUACK. A specialization of this is Property-Oriented Programming (POP) attacks, where attackers chain methods from different objects (dubbed gadgets) to form an exploit chain. Tools like FUGIO exist to automatically identify and build these chains. All PHP deserialization attacks observed by QUACK's researchers fall under this category.
- Arbitrary-command-evaluation attacks: The deserialization routine directly interprets attacker-provided data as executable code. Python's
picklemodule is a prime example of this, allowing direct code execution. These attacks are outside QUACK's current scope.
Mitigating type-confusion deserialization attacks is notoriously difficult. PHP v7 introduced an optional allowed_classes parameter to unserialize(), which offers an effective defense by restricting the types that can be instantiated. However, its adoption is remarkably low; QUACK's analysis of 5.4 million PHP deserialization API invocations on GitHub revealed that only about 0.1% (4.6K) actually specify an allow or deny list. This neglect stems from the immense complexity of maintaining these lists, which require developers to specify not only the intended class but also all its transitive fields, handle collections, and account for classes defined in different modules, a constant burden with code updates. Traditional type inference algorithms, designed for precise type assignments to individual variables, are ill-suited for this task, especially in gradually typed languages like PHP where unserialize() often returns a mixed type. This highlights the critical need for an automated, context-aware approach like QUACK's static duck typing.
Key Findings
▶ Watch: Scale of deserialization problem and why current defenses fail (1:00)
The evaluation of QUACK across various PHP applications and vulnerabilities demonstrated its profound effectiveness and practical utility, leading to several significant contributions:
- Automated Deserialization Attack Mitigation: QUACK provides an automatic and robust framework for protecting PHP applications by instrumenting deserialization API calls. This eliminates the need for developers to manually manage complex
allowed_classeslists, which are error-prone and rarely implemented correctly. - Novel Static Duck Typing Inference: A groundbreaking contribution is QUACK's new static analysis technique. It precisely infers the intended set of allowed classes for a deserialization operation by observing how the deserialized object is subsequently used within the program. This addresses a critical gap where existing type inference tools fail to provide sufficient precision for dynamically typed languages like PHP.
- Significant Attack Surface Reduction: QUACK drastically curtails the potential for exploitation by blocking, on average, an impressive 97% of an application's code that could be leveraged as gadgets in an exploit chain. In 80% of the evaluated cases, QUACK achieved a 100% gadget blocking rate, severely limiting attacker capabilities.
- Complete Exploit Prevention: Rigorous testing using FUGIO, a state-of-the-art exploit generation tool, confirmed QUACK's efficacy. For all protected applications in the FUGIO dataset, QUACK's fixes successfully prevented FUGIO from constructing any viable deserialization exploits, effectively neutralizing type-confusion attacks.
- Precision and Safety: A crucial aspect of QUACK's design is its precision. The evaluation demonstrated that QUACK's inferred
allowed_classessets did not wrongfully exclude any legitimate, benign classes. This ensures that the implemented fixes do not introduce application crashes or break intended functionality. - Practicality for DevOps: With an average analysis runtime of 193 seconds and a maximum of 362 seconds, QUACK is highly efficient. This speed makes it perfectly suitable for integration into rapid continuous integration/continuous deployment (CI/CD) pipelines and daily developer workflows, enabling proactive security hardening.
- Real-World Validation: The practical utility of QUACK was further validated by the successful submission of anonymized pull requests containing QUACK's suggested fixes to real-world open-source projects from the CRAWLED dataset. All three submitted pull requests were merged by their respective maintainers, often within 12 hours, confirming developer acceptance and the ease of integrating QUACK's output. An example of such a merged PR can be found at https://github.com/cakephp/cakephp/pull/17162.
Technical Deep Dive
▶ Watch: PHP serialization, deserialization, and crucial magic methods (2:15)
QUACK's fundamental technical contribution is its static duck typing inference technique, which derives the intended type of a deserialized object by observing its usage patterns within the application code. This innovative approach forms the bedrock of its automated deserialization protection.
Threat Model and Limitations
QUACK operates under a specific threat model:
- Adversarial Capabilities: The attacker is assumed to have full control over the serialized object input provided to the deserialization API. This includes crafting malicious HTTP requests or uploading manipulated data. The attacker is also presumed to have precise knowledge of the application and library code to construct sophisticated exploits.
- Hardening Assumptions: QUACK is specifically designed to prevent type-confusion attacks that rely on deserializing an object into an unintended class. It does not aim to mitigate data-injection-only attacks (where the object type is correct but its data is malicious) or arbitrary-command-evaluation attacks (where attacker data is directly executed as code, typical of Python's
pickle). QUACK requires comprehensive and precise access to the application and library code for its analysis; it cannot provide generic protection for a library without the full context of its usage. Crucially, it does not rely on the PHP runtime to implicitly block gadgets but rather explicitly restricts the minimal set of allowed classes.
Key limitations of QUACK's current implementation include:
- PHAR (pre-v8): Implicit deserialization of PHAR metadata in PHP versions prior to 8.0 is outside its current scope, though it could be extended to support
Phar::getMetadata(). - Unresolved Dynamic Behavior: While QUACK provides partial support for dynamic features like magic methods and autoloaders, it issues alerts for unsupported dynamic invocations (e.g.,
call_user_func_arrayornew $class(...)where the class name is a dynamically determined string). In such cases, it falls back to a sound but potentially imprecise result, allowing a broader set of classes. - Partial Object Updates: QUACK relies on the assumption that the use patterns of the deserialized object are available for analysis. This assumption is broken if an object is only partially updated after deserialization, which limits the available evidence for inference.
Motivating Example: Open Web Analytics (OWA) CVE-2014-2294
To illustrate QUACK's methodology, consider the vulnerability in Open Web Analytics v1.5.6 (CVE-2014-2294). A user-provided input, $raw_event, is passed to unserialize(). Without QUACK, a tool like FUGIO can synthesize 14 unique exploits against this entry point. The core problem is the absence of explicit type information at the deserialization call site to guide any protection mechanism.
QUACK observes how the $event object, the result of unserialize(), is subsequently used. It notes that $event is passed as an argument to the notify method of the $dispatch object. By analyzing the code, QUACK determines that $dispatch is an instance of owa_eventDispatch. The owa_eventDispatch::notify() method, in turn, calls $event->getEventType(). QUACK then performs a static analysis of all available classes and identifies that owa_event is the only class containing a getEventType() method. Based on this evidence, QUACK infers that $event is intended to be of type owa_event.
Consequently, QUACK rewrites the vulnerable unserialize call to incorporate the allowed_classes parameter: $event = unserialize(base64_decode($raw_event), ['allowed_classes' => ['owa_event']]);. This simple, automatically generated fix completely mitigates all 14 FUGIO exploits, as their attack chains invariably require classes other than owa_event.
System Design and Architecture
QUACK's system design is structured into two main phases: Input Package Analysis and Deserialization Protection, as depicted in Figure 4 of the original paper.
Step I: Input Package Analysis
- Deserialization Detection: QUACK begins by parsing all PHP application files into their Abstract Syntax Trees (ASTs). It then systematically identifies all call statements that target deserialization APIs, referred to as
DSCStmts. - Compute Available Classes: For each identified
DSCStmt, QUACK computes a sound overapproximation of all classes that could potentially be loaded and available at that specific call site. This involves a sophisticated analysis of PHP's dynamic class loading mechanisms, including both explicitincludeandrequiredirectives and implicit autoloaders. This comprehensive set of potentially loadable classes is designated asAvailableClasses.
Step II: Deserialization Protection
- Static Duck Typing: This is QUACK's core innovation. For each deserialization call, QUACK employs a novel static analysis to infer the intended set of allowed classes. This technique meticulously observes how the deserialized object is used after its creation, collecting evidence to narrow down the possibilities. QUACK maintains a mapping:
Deserializations: SerObjs -> DSCStmt -> AllowedClassesto track objects deserialized in multiple locations, each potentially with different usage patterns. - Safe API Call Generator: Once the
AllowedClassesset is inferred for a givenDSCStmt, QUACK automatically rewrites the vulnerableunserializecall to include theallowed_classesparameter, effectively restricting the types that can be instantiated during deserialization.
The core inference logic is encapsulated in Algorithm 1: GetAllowedClasses.
- Input: A
DSCStmt(deserialization call statement). - Output:
AllowedClasses(the set of inferred allowed classes). - Initialization: Three sets—
AllTracked,CurTracked, andAllowedClasses—are initialized as empty.CurTrackedis populated with the definition of theDSCStmt's result (Def(DSCStmt)).AvailableClassesis computed usingAvailableClassesAtStmt(DSCStmt). - Iterative Analysis: The algorithm enters a
whileloop that continues as long asCurTrackedis not empty. In each iteration: - An object is popped from
CurTrackedtoTracked. - If
Trackedhas already been processed (Tracked∈AllTracked), the algorithm skips to the next iteration to ensure termination. - Otherwise,
Trackedis added toAllTracked. - For every statement
CUStmtthatUses(Tracked): AllowedClassesis updated byEvidenceFromStmt(CUStmt, Tracked, AvailableClasses). This is the crucial step where duck typing rules are applied to collect evidence.CurTrackedis updated with new definitions derived fromCUStmt(Def(CUStmt)), propagating the analysis to transitively used objects.
The EvidenceFromStmt function is critical, collecting class evidence using pre-defined rules detailed in Table I of the paper. These rules apply either exact or duck-typing matching logic:
- Exact Matching: Returns a specific type when explicit type information is available, such as a function argument type hint or an explicit cast.
- Duck-Typing Matching: Filters the
AvailableClassesbased on observed usage patterns. For example, ift->MethodX()is called on the deserialized objectt, QUACK infers thattmust be an instance of a class that defines a method namedMethodX. Other rules include field access (t->FieldX), binary operations (t BinaryOp a), array access ($t[offset]), assignments (a = t), and equality checks (switch (t): case (a)).
QUACK intelligently handles wrapped objects (objects containing references to other objects) by collecting evidence from all uses of a specific object together. For instance, if $z->zoo(); $z->bar() is called, QUACK requires that classes added to AllowedClasses must possess both zoo and bar methods, ensuring high precision. It also accommodates PHP's magic methods like _get by over-approximating their behavior: if a class implements _get (allowing access to non-existent properties), any field-matching duck-typing rule will consider this class, even if the property is not explicitly defined.
Implementation Details
QUACK is implemented as an extension of existing PHP static analysis frameworks. The primary tool leveraged is Psalm (v5.7.0), a widely used PHP linter, to which approximately 3000 lines of Scala code were added to implement QUACK's static duck typing algorithm. To augment Psalm's capabilities, QUACK integrates Joern (v2.0.140), a static analysis framework, with custom PHP type-inference passes that have since been contributed back to the upstream Joern project. The duck typing algorithm itself is a Scala program interacting with Joern's API. For analyzing WordPress plugins, the wordpress-stubs PHP package is used. Elements of Saphire 8 were also utilized for Class-Def-Graph (CDG) computation, although its PHP 8.0 limitation posed some constraints.
A crucial component of QUACK's analysis is its understanding of PHP's class loading mechanism, which is essential for determining the AvailableClasses set. This involves:
- Explicit Loading: QUACK analyzes
includeorrequiredirectives, resolving file paths (handling static string literals, compositions, and calls to known PHP string manipulation APIs), using wildcards for unresolved parts. - Implicit Loading (Autoloaders): PHP allows dynamic autoloaders. QUACK specifically supports Composer-generated PSR-4 autoloaders, which map namespaces to subdirectories. It parses these mappings to detect all possible autoloaded files. Other autoloader schemes are currently not supported, resulting in an error alert.
The Class-Def-Graph (CDG) Construction is central to this. QUACK builds a directed graph where:
- Nodes: Represent PHP files, annotated with the classes defined within them.
- Edges: Represent dependencies between files, either explicit (
include) or implicit (via autoloaders). - Traversal: For a deserialization call (
DSCStmt) located in fileF, QUACK traverses the CDG. It performs both a forward pass (following outgoing edges) and a backward pass (following incoming edges) fromF. All classes defined in the files traversed during these passes are collected to form the finalAvailableClassesset for that specific deserialization site.
QUACK is provided as a Python wrapper script that orchestrates the analysis engine, running within a Docker container. The engine contains native x86 binaries and was tested on Ubuntu 20.04.6 LTS with Python 3.9.4. The artifact includes detailed instructions, QUACK's source code, evaluated applications, a modified FUGIO tool, and QUACK's Docker image, requiring an x86 machine, at least 6GB of disk space, Docker, and Python.
Demo / Proof of Concept
▶ Watch: Categories of deserialization exploits, focus on type-confusion (3:15)
While the talk did not feature a live, interactive demonstration, the robust evaluation section serves as a comprehensive proof of concept for QUACK's capabilities. The motivating example involving Open Web Analytics v1.5.6 (CVE-2014-2294) conceptually illustrates how QUACK automatically infers the owa_event class and subsequently hardens the unserialize call, effectively preventing all 14 exploits generated by FUGIO.
The formal evaluation, conducted across three PHP datasets (FUGIO, VULN202X, and CRAWLED), rigorously measured QUACK's effectiveness. On average, QUACK's fixes blocked 97% of potential gadgets, achieving 100% blocking in 80% of cases. Crucially, when tested against the FUGIO exploit generation tool, QUACK-protected applications were immune to any generated exploits. This empirical evidence, coupled with the successful integration of QUACK's suggested fixes into open-source projects via merged pull requests (such as the one for CakePHP, https://github.com/cakephp/cakephp/pull/17162), unequivocally demonstrates QUACK's practical utility and its ability to prevent real-world deserialization attacks.
Defensive Implications
▶ Watch: QUACK's core technical approach: static duck typing inference (4:45)
QUACK offers significant defensive implications for developers, organizations, and the broader software security landscape:
- For Developers:
- Automate Security Hardening: Developers should cease relying on manual
allowed_classeslists, which are proven to be error-prone, time-consuming, and consequently, rarely implemented or maintained. QUACK provides a robust, automated alternative. - Integrate into CI/CD: QUACK's efficiency (average runtime of 193 seconds, max 362 seconds) makes it ideal for integration into Continuous Integration/Continuous Deployment (CI/CD) pipelines. This enables proactive security hardening early in the development lifecycle, preventing vulnerabilities from reaching production.
- Embrace Static Analysis: The success of QUACK underscores the value of sophisticated static analysis tools for automatically identifying and mitigating complex vulnerabilities that are difficult for humans to consistently manage.
- Understand Scope and Limitations: While powerful, QUACK specifically targets type-confusion attacks. Developers must remain aware that other deserialization attack vectors (data-injection, arbitrary-command-evaluation) may still require different defensive strategies. Furthermore, QUACK's alerts regarding unresolved dynamic behavior should be treated as indicators for potential areas requiring manual review or more advanced dynamic analysis.
- For Organizations:
- Prioritize Deserialization Vulnerabilities: Given their consistent presence in the OWASP Top 10 and the high volume of CWE-502 vulnerabilities (852 on GitHub), organizations should prioritize addressing deserialization flaws. Tools like QUACK provide a scalable solution for this.
- Invest in Automated Security Tools: Investing in and deploying automated static analysis tools capable of identifying and remediating deserialization vulnerabilities, particularly in widely used languages like PHP, is a strategic imperative.
- Reduce Attack Surface: By automatically limiting the classes that can be instantiated during deserialization, QUACK drastically reduces the attack surface, making it significantly harder for attackers to chain gadgets and achieve arbitrary code execution. This proactive reduction strengthens the overall security posture.
- General Security Posture:
- Shift-Left Security: QUACK embodies the "shift-left" principle by enabling security analysis and remediation much earlier in the development process, reducing the cost and complexity of fixing vulnerabilities downstream.
- Bridging the Usability Gap: The project highlights a critical issue in security: even effective defensive mechanisms (like PHP's
allowed_classesparameter) are useless if they are too difficult for developers to implement correctly. Automation, as demonstrated by QUACK, is key to bridging this usability gap and improving real-world security.
Key Takeaways
- Automated Mitigation: QUACK provides an automated framework to protect PHP applications against deserialization attacks, eliminating the need for manual, error-prone
allowed_classesmanagement. - Novel Static Duck Typing: Its core innovation is a static duck typing inference technique that automatically determines the legitimate set of allowed classes by observing how deserialized objects are used in the program.
- Significant Attack Surface Reduction: QUACK drastically reduces the attack surface, blocking an average of 97% of potential gadgets and achieving 100% blocking in 80% of cases, effectively hindering exploit chain construction.
- Complete Exploit Prevention: The framework successfully prevents state-of-the-art exploit generation tools like FUGIO from creating viable exploits against protected applications, proving its efficacy against type-confusion attacks.
- Practical and Precise: QUACK is efficient enough for CI/CD integration (average 193s runtime), highly precise (no legitimate classes wrongfully blocked), and has been validated by real-world open-source project maintainers.
- Extensible Principles: The fundamental insight of inferring allowed classes from object usage patterns is broadly applicable and holds promise for extending similar automated protection to other managed programming languages beyond PHP.
About the Speaker(s)
The transcript and metadata for this talk do not provide specific biographical details for the speaker, Yaniv David.
All talks from Network and Distributed System Security (NDSS) Symposium 2024