Skip to content

AutoAgent publishes an unauthenticated command-execution TCP server (tcp_server.py) on host 0.0.0.0, running attacker-supplied commands as root inside a container that mounts the host workspace #96

Description

@geo-chen

Summary

When AutoAgent runs an agent it starts a Docker sandbox and, inside it, a small TCP server (autoagent/environment/tcp_server.py) that reads a command from any connecting client and executes it through /bin/bash -c via subprocess.Popen(..., shell=True). There is no authentication. The server binds 0.0.0.0 inside the container, and the container is created with docker run -p {communication_port}:{communication_port}, which Docker publishes on the host's 0.0.0.0, so the command server is reachable from the network. The container runs --user root and bind-mounts the host workspace directory (-v {local_workplace}:{docker_workplace}). Consequently any unauthenticated client that can reach the published port (default 12347/12346) executes arbitrary commands as root inside the container and can read and write the mounted host workspace.

Details

Container creation (autoagent/environment/docker_env.py):

docker_command = [
    "docker", "run", "-d", "--name", self.container_name, "--user", "root",
    "-v", f"{self.local_workplace}:{self.docker_workplace}",          # host dir mounted into container
    "-w", f"{self.docker_workplace}", "-p", f"{self.communication_port}:{self.communication_port}", BASE_IMAGES,
    "/bin/bash", "-c",
    f"python3 {self.docker_workplace}/tcp_server.py --workplace {self.workplace_name} --conda_path {self.conda_path} --port {self.communication_port}"

-p {port}:{port} with no host IP publishes the port on 0.0.0.0 of the host (the project's own check_container_ports documents the resulting mapping as 0.0.0.0:12345->12345/tcp). The command server (autoagent/environment/tcp_server.py) reads a command and runs it with a shell, with no authentication and no allowlist:

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("0.0.0.0", args.port))
server.listen(1)
...
while True:
    conn, addr = server.accept()
    while True:
        command = receive_all(conn)            # raw bytes from the client
        if not command:
            break
        modified_command = f"/bin/bash -c 'source {args.conda_path}/etc/profile.d/conda.sh && conda activate autogpt && cd /{args.workplace} && {command}'"
        process = subprocess.Popen(modified_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)

receive_all simply reads until a recv returns fewer than 4096 bytes, so one send of the command string is executed. The default --port is 12347 (autoagent/cli.py). There is no token, signature, or origin check; the protocol is "connect, send command, receive output." Because the container is --user root and mounts the host workspace, the executed command runs as root and can read/write the host-side workspace files (and pivot from there).

(There is a second, related exposure: autoagent/server.py runs a FastAPI "MetaChain API" with uvicorn.run(app, host="0.0.0.0", port=8000) and no authentication, dynamically exposing every tool and agent as an endpoint that runs the agent; if started, it is likewise an unauthenticated, network-exposed agent-execution API.)

POC: available upon request

Impact

Any unauthenticated attacker who can reach the published communication port (same LAN, shared/cloud network, or a co-located host) executes arbitrary commands as root inside the AutoAgent sandbox container and reads/writes the bind-mounted host workspace directory, with no credentials and no user interaction. This is remote code execution with a path to host data (the mounted workspace) and lateral movement. Fix: do not publish the command port on 0.0.0.0 (bind the container port to 127.0.0.1 only, e.g. -p 127.0.0.1:{port}:{port}, and have tcp_server.py bind 127.0.0.1); require an authentication token on the command channel; avoid running the container as root; and avoid shell=True. Apply the same (auth + loopback bind) to autoagent/server.py.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions