#!/usr/bin/env bash # Flowise document-store arbitrary file READ (getFileFromStorage) — proposed NEW CVE # (distinct from CVE-2025-71338). Vulnerable lab setup. # # Usage: # bash setup.sh # default: 2.1.0 (clean affected release) # FLOWISE_VERSION=2.2.3 bash setup.sh # any affected release 1.7.1 .. 2.2.3 # FLOWISE_VERSION=2.2.4 bash setup.sh # fixed -> negative control # HOST_PORT=3510 bash setup.sh # # Prereqs: Docker >= 20.10, openssl, curl # Runs the REAL vendor image flowiseai/flowise:. No mock, no modified code. # # THE BUG: getFileFromStorage(file, ...paths) joins the RAW `file` onto the storage root and # fs.readFileSync's it; `file` is attacker-controlled via the FILE-STORAGE:: loader-rehydrate # path, and the required loaders[].files[] entry is forged by updateDocumentStore mass # assignment. Unauthenticated (x-request-from: internal). Affected 1.7.1-2.2.3; fixed 2.2.4. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" FLOWISE_VERSION="${FLOWISE_VERSION:-2.1.0}" IMAGE="lab/vuln/flowise-read:${FLOWISE_VERSION}-lab" BASE_IMAGE="lab/vuln/flowise:${FLOWISE_VERSION}" UPSTREAM_IMAGE="flowiseai/flowise:${FLOWISE_VERSION}" CONTAINER_PREFIX="vuln-lab-flowise-read-${FLOWISE_VERSION}" CONTAINER="${CONTAINER_PREFIX}-$(openssl rand -hex 2)" HOST_PORT="${HOST_PORT:-3510}" LAB_RUN_ID="${LAB_RUN_ID:-$CONTAINER}" HEALTH_URL="http://127.0.0.1:${HOST_PORT}/api/v1/ping" _verlt() { [ "$1" = "$2" ] && return 1; [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -1)" = "$1" ]; } if ! _verlt "$FLOWISE_VERSION" "1.7.1" && _verlt "$FLOWISE_VERSION" "2.2.4"; then EXPECT="AFFECTED (getFileFromStorage file operand unsanitized)" else EXPECT="NOT affected (getFileFromStorage sanitized in 2.2.4)" fi echo "=================================================" echo "Flowise arbitrary file READ (getFileFromStorage) — ${FLOWISE_VERSION}" echo " expectation: ${EXPECT}" echo "=================================================" if ! docker image inspect "$BASE_IMAGE" >/dev/null 2>&1; then echo "[+] Pulling $UPSTREAM_IMAGE..." docker pull "$UPSTREAM_IMAGE" docker tag "$UPSTREAM_IMAGE" "$BASE_IMAGE" docker rmi "$UPSTREAM_IMAGE" fi echo "[+] Building $IMAGE" docker build --build-arg "FLOWISE_VERSION=${FLOWISE_VERSION}" -t "$IMAGE" "$SCRIPT_DIR/docker" OWNED=$(docker ps -aq --filter "label=vuln_run=${LAB_RUN_ID}") if [ -n "$OWNED" ]; then echo "$OWNED" | xargs docker rm -f; fi PORT_HOLDER=$(docker ps --format '{{.Names}}\t{{.Ports}}' | grep ":${HOST_PORT}->" | cut -f1 || true) if [ -n "$PORT_HOLDER" ]; then echo "[-] Host port ${HOST_PORT} already bound by: $PORT_HOLDER — re-run with HOST_PORT=" exit 1 fi echo "[+] Starting $CONTAINER" docker run -d --name "$CONTAINER" \ --label "vuln_run=${LAB_RUN_ID}" \ --label "vuln_class=flowise-docstore-arbitrary-read" \ --label "vuln_version=${FLOWISE_VERSION}" \ -p "127.0.0.1:${HOST_PORT}:3000" \ "$IMAGE" echo "[+] Waiting for Flowise (up to 150s)..." TIMEOUT=150; ELAPSED=0; STATUS="000" while [ $ELAPSED -lt $TIMEOUT ]; do STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_URL" 2>/dev/null) || true [ -z "$STATUS" ] && STATUS="000" [ "$STATUS" = "200" ] && break sleep 5; ELAPSED=$((ELAPSED + 5)) printf " ...%3ds HTTP = %s\n" "$ELAPSED" "$STATUS" done if [ "$STATUS" != "200" ]; then echo "[-] Health check FAILED ($STATUS)"; docker logs "$CONTAINER" 2>&1 | tail -30; exit 1 fi VERSION_JSON=$(curl -s -H 'x-request-from: internal' "http://127.0.0.1:${HOST_PORT}/api/v1/version") echo "[+] GET /api/v1/version -> $VERSION_JSON" case "$VERSION_JSON" in *"\"${FLOWISE_VERSION}\""*) echo "[+] Version confirmed: ${FLOWISE_VERSION} (${EXPECT})" ;; *) echo "[-] VERSION MISMATCH — got: $VERSION_JSON"; exit 1 ;; esac echo "" echo "=================================================" echo "[+] Lab READY — Container: $CONTAINER URL: http://127.0.0.1:${HOST_PORT}" echo "" echo "[+] Run the PoC (self-proving, no listener):" echo " python3 poc.py http://127.0.0.1:${HOST_PORT} \\" echo " --read-path /root/.flowise/encryption.key" echo " # compare to ground truth:" echo " docker exec $CONTAINER cat /root/.flowise/encryption.key" echo "" echo "[+] Teardown: docker rm -f $CONTAINER" echo "================================================="