H2O-3 Pre-Auth Insecure Deserialization in Binary Model Import Allows Execution of Attacker-Controlled JAR via Restored custom_metric_func
Project: H2O-3
Repository: https://github.com/h2oai/h2o-3
Vulnerability ID: VPLUS-2026-20800
Title: Pre-Authentication Insecure Deserialization in H2O-3 Binary Model Import Leads to Remote Code Execution via custom_metric_func
Description: A critical pre-authentication deserialization vulnerability exists in H2O-3 that allows an unauthenticated attacker to restore a malicious custom_metric_func reference from an imported binary model and trigger execution of attacker-controlled code during scoring.
The attack chain is as follows:
- The attacker uploads a malicious JAR into DKV using:
POST /3/PutKey.bin
- The attacker trains a model with:
custom_metric_func=java:<jarKey>=audit.MetricPoc
- The model is exported as a binary file using:
GET /99/Models.bin/<model>?dir=/tmp/<model>.bin&force=true
- The original model is removed.
- The binary model is re-imported using:
POST /99/Models.bin/
During import, hex.Model.importBinaryModel() performs Keyed.readAll(ab) on attacker-controlled model bytes. This restores the serialized custom_metric_func reference from the binary model.
Later, when the imported model is used for scoring, water.udf.DkvClassLoader resolves the restored java:<jarKey>=<class> reference, loads the attacker-controlled JAR from DKV, and instantiates the specified class. This leads to attacker-controlled code execution inside the server JVM.
The issue was confirmed dynamically. After re-importing the model, scoring was performed without passing custom_metric_func again, yet the response still returned:
custom_metric_value=2.0
This value was produced by the malicious class reading the server-side JVM property sys.ai.h2o.audit.stage=2, proving that:
- the malicious
custom_metric_funcreference survived binary export/import, and - the attacker-controlled class was loaded and executed during scoring.
Affected Component: Binary model import / model deserialization / dynamic custom metric loading
Affected File: h2o-core/src/main/java/hex/Model.java
Affected Function: importBinaryModel
Affected Line: 3350
Technical Root Cause: The vulnerability is caused by insecure deserialization of untrusted model bytes during binary model import. The imported object graph can contain dynamic executable references such as custom_metric_func, which are later honored by the scoring pipeline. Because the system also supports loading classes from DKV-backed JARs, the deserialization step effectively revives attacker-controlled code references and enables code execution.
Attack Vector:
POST /3/PutKey.bin -> upload malicious JAR
POST /3/ModelBuilders/gbm -> train model with custom_metric_func=java:<jarKey>=audit.MetricPoc
GET /99/Models.bin/<model> -> export binary model
DELETE /3/Models/<model> -> delete original model
POST /99/Models.bin/ -> import binary model
POST /3/Predictions/models/<model>/frames/<frame> -> trigger malicious code during scoring
Proof of Concept: The issue was reproduced with the supplied Python script, which:
- builds a malicious JAR containing
audit.MetricPoc - uploads it to DKV
- trains a GBM model with a malicious
custom_metric_func - exports the model to binary format
- deletes the original model
- re-imports the binary model
- changes a JVM property to a known value
- performs scoring to verify code execution
Observed Behavior: The reproduced output showed:
- successful malicious JAR upload
- successful model training
- successful binary export and re-import
- imported model still containing:
java:v07_metric_1773964714.jar=audit.MetricPoc
- scoring result:
custom_metric_name = v07_metric_1773964714.jarcustom_metric_value = 2.0
This proves that the malicious class reference was restored from the imported model binary and executed during scoring.
Example Verification Output:
IMPORT={
"model_id": "v07_model_1773964714",
"imported_custom_metric_func": "java:v07_metric_1773964714.jar=audit.MetricPoc"
}
SCORE={
"model_id": "v07_model_1773964714",
"custom_metric_name": "v07_metric_1773964714.jar",
"custom_metric_value": 2.0,
"scoring_time": 1773964716977
}
Impact: This vulnerability allows unauthenticated attackers to achieve remote code execution in the H2O-3 server JVM by combining binary model import with DKV-based JAR loading and restored custom_metric_func references. Successful exploitation may lead to full compromise of the application process, access to sensitive data, arbitrary code execution, and potential lateral movement within the environment.
Severity: Critical
CVSS: 9.8
Remediation Recommendations:
- Immediately require authentication and strong authorization for high-risk endpoints including
/3/PutKey.bin,/99/Models.bin/, model export, import, and scoring-related administrative features. - Do not deserialize untrusted model files using generic
Keyed.readAll(...). Replace binary import with a restricted safe format. - Strip, reject, or rebuild executable references such as
custom_metric_funcduring model import. - Enforce strict allowlists or signature verification for JAR keys and classes referenced by
custom_metric_func. - Revalidate all imported model parameters after deserialization and reject any dynamic class-loading references.
- Consider disabling DKV-backed user-provided code loading by default unless explicitly required in trusted deployments.