The ability to save (serialize) and load (deserialize) trained models is fundamental to machine learning frameworks. Training a neural network can take hours, days or even weeks on expensive hardware, so developers need to save their work and share with others. Every major framework (PyTorch, Tensorflow, scikit-learn) implements these features because without it, machine learning would be impractical at scale.
In this blog post, we will explore how Keras handles model serialization and deserialization. We will revisit the infamous Lambda layer exploit and recent security improvements, and reveal why significant vulnerabilities might still exist in Keras’s deserialization pipeline. Finally, we will also provide some practical techniques for you to do your own exploration in uncovering MFV (Model File Vulnerabilities) in Keras models.
Keras has undergone significant evolution in its model serialization approach over the years. Originally developed as an independent library by François Chollet, Keras was integrated into Tensorflow as tf.keras becoming Tensorflow’s official high level API. However, with the release of Keras 3.0 in late 2023, the framework became standalone again, supporting multiple backends (JAX, Tensorflow, PyTorch). The Keras 3.0 announcement provides more details on what changed.
Version 3.0 also introduced a new native .keras format, which is more secure than the legacy HDF5 format but still presents interesting attack surfaces for security researchers.
Before diving into the Lambda layer exploit, let's first understand how Keras saves models and what files are generated. This will help us grasp the attack surface better.
Let's start with a basic Sequential model:
The .keras file is actually a ZIP archive containing multiple files.
The three files serve different purposes:
This separation is typical across many ML frameworks—model architecture stored separately from model weights as binary data. Machine learning models typically consist of two main components: the architecture (defining the computational structure) and the weights (containing learned parameters). The architecture includes layer definitions, activation functions, and optimization settings, while weights are the numerical values learned during training. This separation enables model sharing and deployment flexibility, but also creates distinct attack surfaces - particularly in the architecture definitions where arbitrary code can be embedded.
In the context of Keras models, the most interesting file is config.json, which contains model architecture definitions that get loaded directly as executable code.
Here’s what a typical config.json, looks like for a simple Dense layer:
Notice how the config.json contains:
This structure creates multiple attack vectors that we'll explore next.
The Lambda layer exploit was a critical vulnerability that allowed arbitrary code execution during model loading. Let's examine how it worked.
Inspecting the config.json file shows the definition for the Lambda layer:
Keras recursively deserializes layers. For built-in layers defined in the keras.layers package, the layer definition contains a from_config function that gets called every time a model layer type is loaded. Here is the vulnerable code snippet from the Lambda layer:
The vulnerability occurs when python_utils.func_load() is called with the base64-encoded bytecode. This function decodes the bytecode and calls marshal.loads(), which can execute arbitrary code during the unmarshaling process. The base64-encoded payload in our example decodes to:
Keras was loading user-provided code directly. However, it now enforces a default safety mode that blocks Lambda layer deserialization unless the user explicitly passes safe_mode=False.
This is a good transition to go a level above and inspect what goes on under the hood of deserializing Keras models. The main entry point is:
The security-critical function is _retrieve_class_or_fn, which underwent significant changes in recent versions.
Our Huntr researchers discovered a critical vulnerability in Keras versions prior to 3.9, you can read details on these reports here and here. This was later independently reported as CVE-2025-1550. The critical vulnerability allowed arbitrary python modules to be loaded and executed when the model is loaded. The key issue was unrestricted use of importlib.import_module(..). Here is the vulnerable code from Keras for version 3.8.0.
Keras maintainers took concrete steps to fix many weaknesses in the deserialization libraries. Following the security principle of never trusting user input, they implemented several defenses
These improvements significantly reduced the attack surface, but didn't eliminate it entirely.
Despite these fixes, the attack surface of Keras remains substantial.
The allowed list of modules is still very permissive. For example, get_file in keras.utils downloads a remote file to a specified directory. This function can potentially be used in a model configuration to download arbitrary files to the victim's machine.
Here's a proof-of-concept that demonstrates this:
This code injects a Lambda layer that doesn't use serialized Python lambda functions, thus bypassing the safe mode check. This provides a good starting point for exploring weaknesses in Keras's deserialization library.
Important Limitation: The current limitation of this approach is that the call method in Lambda layer passes all arguments from the layer config but always passes the input as the first argument:
This might limit the actual exploitation potential, but it demonstrates that calling arbitrary Python code is still possible in Keras.
Here are some practical techniques to help with your own exploration:
Systematically explore functions across Keras's allowed modules (keras, keras_nlp, keras_cv, keras_hub) that might serve as good exploitation gadgets especially when combined with Lambda layer:
Experiment by directly deserializing layer configs to see what parameters they accept:
Keras maintains backward compatibility and supports legacy file formats. These formats might not have the same robust checks against deserialization exploits. Additionally, there are at least three versions of Keras code:
Each version might have different security implementations and attack surfaces.
For bug bounty hunters, this isn't just an academic exercise—it's a prime opportunity to discover valuable vulnerabilities in AI/ML tools. Here's why:
Launchpad for Discovery: Use the examples in this blog as a springboard. Explore Keras model formats in depth, and you're likely to find more flaws that haven't been discovered yet.
Lucrative Bounties: Each validated Model File Vulnerability (MFV) can earn you up to $3,000+ on Huntr, boosting both your reputation and your income.
Under-explored Territory: While web application security is heavily researched, ML framework security remains relatively unexplored, giving you a competitive advantage.
Real-world Impact: These vulnerabilities affect production systems at major companies using Keras for ML workloads, making your discoveries highly valuable.
Keras has made significant strides in securing model deserialization since the Lambda layer exploit, but the fundamental challenge remains: balancing functionality with security in a framework designed for flexibility. The current allowlist approach, while better than unrestricted imports, still provides a substantial attack surface for creative researchers.
The key takeaway for security researchers is that Model File Vulnerabilities represent a rich, under-explored area with real-world impact. As ML adoption continues to accelerate and model sharing becomes more common, these attack vectors will only become more valuable to understand and exploit responsibly.
Whether you're a seasoned bug bounty hunter or new to ML security, now is the perfect time to dive into this space. The techniques and examples in this post should give you a solid foundation to start your own exploration of Keras MFVs and potentially discover the next critical vulnerability in one of the world's most popular ML frameworks.