H2O-3 Pre-Auth Logic Flaw in Rapids setproperty Lets Unauthenticated Attackers Enable POJO Import and Execute Arbitrary POJO Code

Project: H2O-3 Repository: https://github.com/h2oai/h2o-3 Vulnerability ID: VPLUS-2026-20803

Title: Pre-Authentication Logic Flaw in H2O-3 Allows Unauthenticated Enabling of Disabled POJO Import and Arbitrary POJO Code Execution

Description: A critical pre-authentication logic flaw exists in H2O-3 because the publicly exposed POST /99/Rapids endpoint allows unauthenticated users to invoke the internal setproperty primitive, which is explicitly intended for debugging and not for direct user exposure.

The setproperty primitive writes attacker-controlled system properties across all cluster nodes. At the same time, the uncompressed model import branch in hex.generic.Generic is designed to block POJO import by default unless the system property pojo.import.enabled is set to true. When this property is false, the code throws a SecurityException stating that POJO import is disabled due to security risk.

In practice, this protection can be bypassed by the same unauthenticated attacker:

  1. Upload a malicious but valid POJO model source file.
  2. Attempt to import it through POST /3/ModelBuilders/generic; the job correctly fails because POJO import is disabled by default.
  3. Call POST /99/Rapids with:

    ast=(setproperty 'sys.ai.h2o.pojo.import.enabled' 'true')
    
  4. Retry the same POJO import; the job succeeds.
  5. The imported POJO is compiled and instantiated, and its constructor logic executes on the server.

This was confirmed by a proof-of-concept POJO whose constructor sets:

  • sys.ai.h2o.session.allow_properties=false

Before exploitation, GET /3/InitID returned:

  • session_properties_allowed=true

After the unauthenticated property toggle and POJO import, GET /3/InitID returned:

  • session_properties_allowed=false

This proves that the attacker not only enabled a default-disabled security gate but also achieved execution of attacker-controlled POJO code and modified cluster-wide runtime state.

This is not merely a missing-authentication issue on a dangerous endpoint. It is a security control bypass: the product intends to mitigate POJO import risk by disabling it by default, but the same unauthenticated user can first re-enable the feature through another exposed debug mechanism and then reach the protected code path.

Affected Component: Rapids debug primitive exposure / runtime security property manipulation / POJO import gate

Affected File: h2o-core/src/main/java/water/rapids/ast/prims/misc/AstSetProperty.java

Affected Function: exec

Affected Line: 33

Technical Root Cause: The application exposes an internal debugging primitive that allows arbitrary runtime system property modification without authentication. Security-sensitive behavior, including POJO import restrictions, relies on mutable runtime properties rather than immutable configuration or privileged authorization checks. As a result, an attacker can disable or bypass built-in protections and then invoke a dangerous feature that executes attacker-supplied Java code.

Attack Vector:

POST /99/Rapids ast=(setproperty 'sys.ai.h2o.pojo.import.enabled' 'true')
-> POST /3/PostFile.bin?destination_frame=<evil>.java
-> POST /3/ModelBuilders/generic model_key=<evil>.java

Proof of Concept: The provided PoC:

  1. uploads a malicious Java POJO source file,
  2. verifies that POJO import is initially blocked by default,
  3. anonymously toggles sys.ai.h2o.pojo.import.enabled to true through Rapids setproperty,
  4. re-imports the same POJO successfully,
  5. confirms code execution by observing a server-side property change via GET /3/InitID.

Observed Behavior:

  • Initial state:
    • GET /3/InitIDsession_properties_allowed=true
  • Before toggling:
    • POST /3/ModelBuilders/generic fails with:
      • java.lang.SecurityException: POJO import is disabled since it brings a security risk
  • Anonymous toggle:
    • POST /99/Rapids
    • response shows:
      • Old values of sys.ai.h2o.pojo.import.enabled (per node): false,false
  • After toggling:
    • POST /3/ModelBuilders/generic succeeds with job status DONE
  • Post-import verification:
    • GET /3/InitIDsession_properties_allowed=false

This confirms that the unauthenticated attacker enabled the security gate and triggered execution of the malicious POJO constructor.

Impact: An unauthenticated attacker can bypass the default-disabled POJO import protection, compile and instantiate attacker-controlled Java source code, and execute arbitrary logic in the H2O-3 server environment. This can lead to full remote code execution, security setting tampering, data compromise, service disruption, and further cluster-wide abuse.

Severity: Critical

CVSS: 9.8

Remediation Recommendations:

  1. Remove or strictly restrict the public exposure of debugging primitives such as setproperty through POST /99/Rapids.
  2. Do not allow unauthenticated or low-privilege users to modify any sys.ai.h2o.* security-related properties.
  3. Make high-risk security switches such as sys.ai.h2o.pojo.import.enabled immutable at runtime, or configurable only at startup by administrators.
  4. Add independent authentication and authorization checks to the POJO import path in POST /3/ModelBuilders/generic instead of relying solely on mutable global properties.
  5. Even when POJO import is intentionally enabled, require trusted signatures, strict source allowlists, or offline prebuilt artifacts instead of compiling and instantiating arbitrary uploaded Java source.