Unauthenticated ECS host bind-mount to Docker-host RCE / container escape in floci-io/floci
An unauthenticated ECS RegisterTaskDefinition plus RunTask bind-mounts the Docker host filesystem read-write into an attacker container and runs commands as root on the host.
Summary
Floci's ECS JSON-1.1 API is reachable unauthenticated on the default
0.0.0.0:4566 bind, and its RegisterTaskDefinition handler accepts a host
volume host.sourcePath verbatim: no enabled-flag, no allowlist, no
canonicalization, and no rejection of the Docker socket or sensitive paths.
When Floci is given the Docker socket (the standard configuration for
ECS/Lambda/CodeBuild, where docker-compose.yml mounts /var/run/docker.sock
by default) an unauthenticated attacker can call RegisterTaskDefinition with
sourcePath="/" or /var/run/docker.sock and then RunTask to bind-mount the
Docker host filesystem read-write into an attacker-controlled container and run
an arbitrary command as root on the host. This is a full container escape and
host takeover from a single unauthenticated network caller, and it works on the
published native image.
Notably, Floci already gates the equivalent Lambda hot-reload host bind-mount with a default-off flag, an absolute-path check, and an allowlist, so host mounts are knowingly guarded elsewhere. The ECS path simply omits the same guards.
Severity
CVSS v3.1 base score 10.0, Critical: AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H.
The scope change (S:C) is what drives the score: the vulnerable component (the
Floci container) is used to compromise a different security scope, the Docker
host and every other container on it. The single environmental precondition,
that Floci can reach the Docker socket, is the documented default-shipped
configuration, so exploitation is the expected-deployment case rather than an
edge case.
Root cause
An unauthenticated API entry point feeds an attacker-controlled host path straight into a Docker bind-mount with no guard:
- The ECS JSON-1.1 dispatcher routes on
X-Amz-Targetwith no signature verification and no IAM check. Signature validation and IAM enforcement are both off by default. RegisterTaskDefinitionstores each volume'shost.sourcePathverbatim.- At launch, the container manager maps each mount point to its host source path
and calls
withBind/withReadOnlyBinddirectly, with no canonicalization, no allowlist, no enabled-flag, and no rejection of/or the Docker socket. - Real Docker mode is the default (
ecs.mock = false), and the socket is mounted by the defaultdocker-compose.yml.
Enabling the strongest available hardening does not stop it: the lone
authorization filter is fail-open on a missing Authorization header,
signature validation only guards S3 presigned URLs, and the TLS toggle does not
force HTTPS. The only configurations that stop it both disable ECS entirely
(ecs.mock=true or ecs.enabled=false), neither of which is an auth control.
Impact
- Full Docker-host compromise from the network, unauthenticated: arbitrary read/write of every file on the host and command execution as root.
- Container escape and lateral movement by mounting
/var/run/docker.sockand driving the host's Docker daemon directly. - CI/CD and cloud blast radius, since the socket is exactly the configuration used when Floci backs ECS/Lambda/CodeBuild in pipelines and shared hosts.
Remediation
Gate ECS host volumes the way the Lambda hot-reload bind already is:
- Add a default-off
allowHostVolumesflag and reject task definitions that specifyhost.sourcePathunless explicitly enabled. - Canonicalize and validate the supplied path (resolve symlinks and
..) and require an absolute path. - Allowlist the resolved path against operator-provided prefixes.
- Unconditionally deny sensitive targets:
/,/var/run/docker.sock, the containerd socket,/proc,/sys,/etc,/root, and the host device tree.
Fixed in io.github.hectorvent:floci 2.1.0.
Disclosure
Reported privately via the floci-io/floci GitHub Security Advisory channel under coordinated disclosure. The proof-of-concept exploit is withheld from public disclosure until a fix ships.