Exposed Docker Socket (Port 2375 / 2376)
detect, harden, and verify closure
An exposed Docker remote API on TCP 2375 with no authentication, a TLS-protected Docker daemon on TCP 2376 with weak or absent client certificate enforcement, or a /var/run/docker.sock mounted into an untrusted container or CI runner is root-equivalent on the host. An attacker who can reach the socket can start any container, mount the host filesystem, read every credential, pivot across the network, and stage cryptojacking, ransomware, or data exfiltration. The mass-exploitation track record is unbroken: Kinsing, TeamTNT, WatchDog, Doki, Hildegard, and Lemon_Duck have each industrialised exposed-Docker-socket discovery and takeover. Learn how to detect the exposure, lock it down, and keep the closure verifiable.
No credit card required. Free plan available forever.
What is an exposed Docker socket?
An exposed Docker socket is the cluster of weaknesses that turn the Docker daemon into a root-equivalent foothold for any caller that can reach it. The most common shape is the Docker remote API bound to TCP 2375 with no authentication (the legacy plaintext default) or TCP 2376 with TLS but no client-certificate enforcement, both reachable from the public internet or from a network segment broader than the operator intended. The second common shape is the Unix domain socket at /var/run/docker.sock bind-mounted into a container, a CI/CD runner, an agent pod, or a development image so the workload inside the container can issue full daemon commands against the host that runs it.
Either shape collapses to the same outcome. A caller with access to the socket can list, create, start, stop, and remove any container; mount the host root filesystem inside a privileged container; read every credential, secret, certificate, and configuration file stored on the host; spawn a new container with the host PID and network namespaces shared in; pivot to peer hosts on the bridge network; and persist by replacing host binaries or scheduling a daemon-managed container. There is no second-line authorisation. The Docker daemon assumes that any caller that reached the socket has already been authorised by the operating system or the network boundary that the operator was supposed to provide. Sister exposures land on the same hosts: an open SSH listener is often what gives the attacker the foothold from which the open Docker socket gets enumerated, an exposed Kubernetes API or kubelet on a neighbouring port is the same root-equivalence pattern at the cluster layer, and a permissive cloud security group that opens 2375 to 0.0.0.0/0 frequently opens MongoDB or Elasticsearch on neighbouring ports at the same time.
The track record is unambiguous. Kinsing, TeamTNT, WatchDog, Doki, Hildegard, and Lemon_Duck have each industrialised exposed-Docker-socket discovery into commodity cryptojacking and lateral-movement campaigns. The pattern is the same across every variant: scan IPv4 for TCP 2375 answering with the Docker engine HTTP banner on /version or /info, post a container creation request that binds the host root filesystem and runs a miner or a reverse shell, escape into the host, and either mine, exfiltrate, or stage onward. Shodan and Censys continuously index tens of thousands of unauthenticated Docker daemons on TCP 2375 at any moment, and the operators of those scans publish public dashboards that script attackers consume daily.
Exposed Docker socket is also a recurring audit, cyber-insurance, and cloud-account isolation finding. The CIS Docker Benchmark controls 2.5 (do not bind Docker to a loopback or external IP without TLS) and 2.6 (enable Docker remote API only with TLS and client certificates) cover the daemon-side bind; PCI DSS Requirement 1.3, 2.2, 7.1, and 7.2 cover boundary protection and least-privilege access to the runtime; ISO 27001 Annex A.5.15, A.8.2, A.8.5, A.8.20, A.8.21, and A.8.24 cover privileged access, network controls, and cryptographic use; NIST SP 800-53 SC-7 (boundary protection), AC-3 (access enforcement), AC-6 (least privilege), and CM-7 (least functionality) all read the same exposure; the CIS Critical Security Controls v8.1 Control 4 (secure configuration), Control 5 (account management), and Control 12 (network infrastructure management) cover the broader posture; and the CISA Secure by Design guidance names default-deny networking and default-enabled authentication as baseline expectations for software shipping with a network listener. Closing or properly hardening the socket is almost always inexpensive compared to the cost of a successful container escape, which is why this class of finding tends to move quickly through remediation tracking once it is surfaced on a controlled record.
How attackers exploit it
Discover open 2375 or 2376
Attackers continuously scan IPv4 (Shodan, Censys, Masscan, ZMap, custom Go and Python tooling) for hosts answering on TCP 2375 and 2376. A simple GET to /version or /info returns the Docker engine version, the API version, the OS and architecture, the kernel version, the operating system, and the container runtime, before any authentication is attempted. For mounted /var/run/docker.sock inside a container, attackers test by sending the same /version request to the Unix socket through curl --unix-socket /var/run/docker.sock.
Probe authentication state
Where the daemon answers /version without a TLS handshake at all, the listener is plaintext and unauthenticated. Where the daemon answers on 2376 with TLS but accepts a connection from a public CA chain, client-certificate enforcement is missing and tlsverify is off. The Docker remote API does not have user-level authentication, so a successful TLS handshake against a daemon configured without --tlsverify is sufficient to issue every privileged command.
Spawn a host-mounted container
The attacker posts a container creation request to /containers/create that binds /:/host (or /:/mnt) with read-write, asks for the privileged flag, requests the host PID and network namespaces, and starts the container. The container now reads and writes every file on the host. From there the attacker reads /etc/shadow, every credential and secret in /root, every cloud-metadata token reachable from the host network, every Kubernetes secret on the node, and every TLS key the daemon trusts.
Persist, pivot, and monetise
The attacker stages a cron job in /etc/cron.d on the host, replaces a binary in /usr/local/bin, adds a public key to /root/.ssh/authorized_keys, pulls a mining image (Kinsing, TeamTNT XMRig variants), runs a reverse shell back to attacker-controlled infrastructure, or spawns a sidecar that watches for new images and infects them. Cluster-attached daemons let the attacker pivot to peer nodes, the kubelet, the cloud metadata service, the cluster API server, and any orchestrator the daemon is registered against.
Common causes
dockerd bound to 0.0.0.0:2375 for convenience
A developer or a legacy runbook asks for remote docker commands from a workstation, a CI runner, or a deployment laptop, and the daemon is started with -H tcp://0.0.0.0:2375 in /etc/docker/daemon.json, /etc/sysconfig/docker, /lib/systemd/system/docker.service, or a custom Compose file. The plaintext bind survives across reboots, image rebuilds, autoscaling events, and account migrations long after the original convenience reason is forgotten.
TLS on 2376 without --tlsverify
The daemon is configured with --tls, --tlscert, and --tlskey but without --tlsverify and --tlscacert. The TLS handshake completes, the operator believes the listener is hardened, and any caller that can reach 2376 can issue full daemon commands. Client-certificate enforcement is the difference between an authenticated remote API and a TLS-wrapped open daemon.
docker.sock mounted into containers
A pipeline stage, a Portainer instance, a Watchtower deployment, a Traefik label, a Jenkins or GitLab Runner pod, a GitHub Actions self-hosted runner, a Kubernetes pod with hostPath /var/run/docker.sock, or a developer-friendly base image binds /var/run/docker.sock into the container. Anything that runs inside the container, including a compromised dependency, a malicious base image layer, or a build step that pulls untrusted code, can now issue privileged commands against the host daemon.
CI/CD runners with root daemon access
Self-hosted GitHub Actions runners, GitLab Runners with the Docker executor, Jenkins agents with the docker-plugin or with docker.sock mounted, Drone CI runners, and TeamCity build agents commonly receive root-equivalent access to the host Docker daemon to support docker-in-docker workflows. A build job that runs untrusted PR code, an unpinned action, a build cache poisoning attack, or a dependency-confusion compromise of the build tooling turns into a host takeover.
Cloud security group rule too broad
An AWS security group, Azure NSG, GCP firewall rule, or DigitalOcean rule opens TCP 2375 (and 2376) to 0.0.0.0/0 during a Terraform run, a debug session, a vendor handoff, or a recovery exercise. The rule is never narrowed, and a public Docker daemon persists across image rebuilds, autoscaling events, and account migrations. Public-cloud attacker tooling continuously sweeps cloud netblocks for the pattern.
Unhardened orchestrator nodes
Swarm manager and worker nodes, standalone Docker hosts behind a managed service, and edge nodes running container workloads inherit the daemon defaults of whoever provisioned them. A Helm chart that mounts hostPath /var/run/docker.sock, a DaemonSet that needs the runtime socket for telemetry, or a node-image template that opens 2375 for orchestration tooling propagates the exposure across every node in the fleet at the same time.
How to detect it
Automated detection
- SecPortal's external scanner port-scan module probes TCP 2375 and 2376 across every verified domain and host in scope, identifies the responding service as the Docker remote API through HTTP banner detection on /version and /info, and raises a critical-severity finding under the rule that the Docker daemon should never be reachable from the public internet. TCP 2375 is tagged as a high-risk port in the scanner alongside 23, 139, 445, 3389, and 5900.
- Continuous monitoring re-runs the external scan on a schedule, so a security-group edit, a Terraform apply, an autoscale event, a CI runner refresh, or a recovered host that reopens 2375 or 2376 reopens the finding rather than leaving the regression invisible until the next audit cycle.
- Bulk import accepts Nessus, Burp, and CSV output from network-tier scanners and from Shodan and Censys exports that already list exposed Docker daemons, so the catalogue stays centralised even when a third-party tool is the original discovery source.
Manual verification
- Use nmap -p 2375,2376 with service detection (-sV) and the http-headers and http-title scripts against the external range to confirm the listener, the engine version, the API version, and whether the response advertises Docker on the HTTP banner.
- Issue curl -sk http://<host>:2375/version and curl -sk https://<host>:2376/version (without a client certificate) against each reachable listener. A populated JSON response with Version, ApiVersion, GoVersion, KernelVersion, Os, and Arch is proof of an unauthenticated or weakly authenticated daemon.
- For inside-the-cluster verification, run docker context ls, docker info, and grep -r docker.sock across Compose files, Helm charts, Kubernetes manifests, and CI runner configuration to enumerate every workload that mounts /var/run/docker.sock or expects the runtime socket as a hostPath volume.
- Cross-reference Shodan and Censys for the organisation's netblocks and known hostnames to see whether external observatories already list exposed Docker daemons, then reconcile that list against the verified-domain inventory inside the workspace.
How to fix it
Remove direct internet exposure of the daemon
Block TCP 2375 and 2376 inbound at the perimeter firewall, the cloud security group, and the load balancer for every host. There is no production scenario where the Docker daemon should be reachable from 0.0.0.0/0. Reach the daemon through SSH with sudo, through a managed bastion service, through a zero-trust access broker, through a private VPC peering route, or through an orchestrator API (Kubernetes, Nomad, ECS) that already federates against the workforce identity provider.
Stop binding dockerd to a TCP socket at all
Audit /etc/docker/daemon.json, /etc/sysconfig/docker, /lib/systemd/system/docker.service, and any drop-in override under /etc/systemd/system/docker.service.d for a hosts entry that adds tcp://. The default Unix socket at /var/run/docker.sock is sufficient for every operator that logs into the host. Remove the TCP bind unless a hardened remote-management requirement justifies it and is reviewed quarterly.
If a remote API is required, enforce TLS mutual authentication
Where a remote daemon is unavoidable, configure --tlsverify, --tlscacert, --tlscert, and --tlskey on the daemon and require --tlsverify, --tlscacert, --tlscert, and --tlskey on every client. Issue client certificates from an internal certificate authority bound to operator identity, rotate them on a defined cadence, and revoke them when the owner leaves the team. A TLS-wrapped daemon without client-certificate enforcement is not authenticated.
Use a docker-socket-proxy in front of the runtime socket
Workloads that genuinely need a subset of daemon commands (Portainer reading container state, Traefik reading service labels, telemetry agents reading container metadata) should reach the socket through a hardened proxy (tecnativa/docker-socket-proxy or equivalent) that exposes only the necessary read-only endpoints. Mounting the raw /var/run/docker.sock is rarely the right answer once the workload has a documented need.
Stop mounting docker.sock into untrusted containers
Audit every Compose file, Helm chart, Kubernetes manifest, and CI/CD job for hostPath /var/run/docker.sock, /var/run/docker.sock:/var/run/docker.sock volume mounts, and DOCKER_HOST environment variables pointing back at the host daemon. Replace docker-in-docker patterns with kaniko, buildah, img, or buildkit running in user-namespace mode. For CI/CD, prefer ephemeral runners that run on isolated hosts dedicated to each build rather than mounting the host runtime into a shared agent.
Move to Docker rootless mode where possible
Docker rootless mode runs the daemon as a non-root user with a per-user runtime socket and reduces the blast radius of a successful socket compromise. Combine rootless mode with user-namespace remapping so a container root user maps to an unprivileged host UID. The combination does not eliminate every escape path, but it removes the unconditional root-equivalence assumption that exposed sockets currently grant.
Constrain orchestrator nodes and Kubernetes pods
On Kubernetes, set a PodSecurity admission baseline that forbids hostPath volumes pointing at /var/run/docker.sock and forbids privileged containers, hostPID, hostIPC, and hostNetwork. Use a NetworkPolicy that denies pod egress to the kubelet, the cluster API server, and the cloud metadata service except for namespaces that legitimately need them. Audit DaemonSets and node-tier agents quarterly to confirm none of them mount the runtime socket without a documented business reason.
Enable audit logging and host integrity monitoring
Turn on Docker daemon audit logging (auditd rules covering /var/run/docker.sock, dockerd, runc, and the cgroups path), forward the audit stream to the SIEM or detection pipeline, and pair the listener with detection rules for container creation requests that bind the host root filesystem, request the privileged flag, or share host namespaces. File integrity monitoring on /etc/cron.d, /root/.ssh, /usr/local/bin, and /etc/docker is the second line that catches a successful socket compromise that the network controls missed.
Operationalising the fix on the workspace
Closing an exposed Docker socket and rebuilding a CI runner without docker.sock is rarely the hard part for an experienced platform or security engineer. The operational hard part is keeping the closure intact across rebuilds, autoscaling events, mergers and acquisitions, new cloud accounts, new orchestrator nodes, new self-hosted runners, and the long tail of legacy hosts that nobody currently lists in a primary asset inventory. The workspace pattern below is what AppSec, cloud security, platform engineering, vulnerability management, and security engineering teams use to keep an exposed-Docker-socket finding from coming back six months later on a new node or a recovered backup image.
Surface every Docker daemon listener on one finding record
External scanning and bulk import land every exposed-Docker-socket detection on the findings management record with CVSS, the discovered host, the captured /version response, the engine and API version, the scan timestamp, and the named owner so the catalogue is a single source of truth rather than a Shodan tab, a spreadsheet, and a Slack thread.
Inspect daemon configuration through authenticated scans
Where the team has authorisation, the authenticated scanning path reads the responding service in more detail and pairs the result with the per-host hardening record. Credentials for authenticated runs live in encrypted credential storage scoped to a verified domain so the same daemon secret is not copied across spreadsheets, chat threads, and pull-request bodies.
Run remediation as a tracked workflow
Each finding flows through the remediation tracking workflow with severity, target close date, named owner, and the agreed compensating control (for example, an inbound source allowlist while a private bastion is provisioned, or a temporary firewall rule while the docker-socket-proxy is rolled out). Permanent exceptions enter the eight-field exception register with a named approver, a rationale, an effective period, and a renewal cadence.
Verify the fix with retest
Once the firewall rule, security-group edit, daemon.json change, host retirement, or CI runner rebuild completes, the retesting workflow re-runs the external scan, confirms the daemon is gone or that the responding service now requires client-certificate authentication on a private interface, and stamps the closure with timestamped evidence rather than a verbal sign-off in a stand-up.
Catch regressions through continuous monitoring
Scheduled continuous monitoring re-scans of verified domains reopen the finding if a Docker daemon returns to the public internet, if a new self-hosted runner spins up with the daemon bound to 2375, or if a new cloud account joins the estate with an open Docker port. A Terraform apply, a runner refresh, an autoscaler event, or a recovered backup image does not silently reintroduce the exposure.
Pair the daemon finding with the docker.sock mount finding
Exposed Docker daemons and mounted /var/run/docker.sock inside CI runners and orchestrator pods are the same root-equivalence pattern with different network shapes. Treating them as one finding family on the workspace record, with the host or workload as the affected asset and the bind-mount or TCP listener as the evidence, keeps the queue ordered against real residual risk rather than reading two unrelated queues. The same record flows through container image vulnerability remediation and Kubernetes security finding remediation where appropriate.
Carry the audit trail through closure
The activity log records who opened, scoped, remediated, and verified each exposed-Docker-socket finding, so the next ISO 27001, SOC 2, PCI DSS, NIST CSF 2.0, or cyber insurance review reads the operating evidence from the record rather than from reconstructed memory, archived ticket exports, or screenshots taken weeks after the fact. The compliance tracking crosswalk maps each finding to the relevant control catalogue so audit fieldwork reads from the same record.
Communicate posture in leadership language
When runtime exposure sits inside a broader programme review, the Claude-drafted AI report composes a leadership-ready narrative from the open and closed findings, the remediation cadence, and the linked compliance evidence. The same record drives security leadership reporting without rebuilding the slide deck by hand each quarter.
Where the exposure usually lands
Exposed Docker socket is not a single shape across the enterprise estate. The conversation a cloud security team has with a platform team about a self-managed Docker host in a development VPC differs from the conversation an AppSec team has with a CI/CD owner about a Jenkins agent mounting /var/run/docker.sock. The four contexts below are the most common landing places, and each one needs a slightly different remediation playbook surfaced on the same finding record.
Standalone Docker hosts on cloud VMs
EC2, Compute Engine, Azure VM, and DigitalOcean droplet deployments of the Docker engine make up the bulk of historic exposure events. Replace standing 0.0.0.0/0 security-group rules with private subnets, security-group source restrictions, and managed bastion access for human operators. Move long-lived production workloads onto a managed orchestrator (ECS, EKS, GKE, AKS, Cloud Run) where the network boundary is built in and the per-node daemon is not directly addressable.
CI/CD runners with docker.sock mounted
Self-hosted GitHub Actions runners, GitLab Runners with the Docker executor, Jenkins agents with docker-plugin or docker.sock mounted, Drone runners, and TeamCity build agents inherit the daemon defaults of whoever first provisioned them. Replace docker-in-docker with kaniko, buildah, img, or buildkit in user-namespace mode. Use ephemeral runners on isolated hosts per build rather than mounting the host runtime into a shared agent. Treat any pipeline that builds untrusted PR code with a host-mounted socket as a finding to triage immediately.
Kubernetes pods with hostPath docker.sock
Helm charts for telemetry, ingress controllers, image-update bots, container-management dashboards, and service-mesh sidecars sometimes ship with hostPath /var/run/docker.sock or /var/run/containerd/containerd.sock for convenience. Apply a PodSecurity admission baseline that forbids the mount, replace the workload with a sidecarless or kubelet-API-only equivalent, or move the workload to a hardened socket proxy. Audit DaemonSets quarterly to catch the pattern after every cluster upgrade.
Edge and legacy Docker installs
Long-lived virtual machines, vendor-distributed appliances, customer-managed instances at acquired companies, and home-grown installs run a Docker engine release several years behind the upstream support window and frequently inherit the legacy plaintext-bind defaults. Track these hosts on the finding record with the engine and API version, the maintainer contact, and the upgrade path. Where the host cannot be upgraded immediately, isolate it behind a managed network access boundary and document the residual risk decision in the exception register.
Compliance impact
OWASP Top 10
A05:2021 - Security Misconfiguration; A01 - Broken Access Control
PCI DSS
Req. 1.3 / 2.2 / 7.1 / 7.2 - Boundary, Config, Least Privilege
ISO 27001
A.8.2 Privileged Access; A.8.20 Networks; A.8.24 Cryptography
NIST 800-53
SC-7 Boundary; AC-3 Access; AC-6 Least Privilege; CM-7 Least Functionality
CIS Controls v8.1
Control 4 Secure Configuration; Control 5 Account Mgmt; Control 12 Network
NIST CSF 2.0
PR.AA Identity Mgmt; PR.PS Platform Security; DE.CM Continuous Monitoring
SOC 2
CC6.1 Logical Access; CC6.6 Logical Access Boundary; CC7.1 Detection
CISA Secure by Design
Default-deny networking; default-enabled authentication
Where Docker socket posture lands inside the team
Exposed Docker socket sits across several teams at once. The internal security team owns the policy, the audit response, and the breach-notification posture. The cloud security team owns the cloud-account perimeter, the security-group baseline, and the private-bastion architecture. The platform engineering team owns the runner fleet, the orchestrator node baseline, and the host-image template. The vulnerability management team owns the backlog, the prioritisation, and the SLA. The security engineering team owns the hardening tooling, the docker-socket-proxy rollout, and the PodSecurity admission baseline. SecPortal keeps the same finding visible to all of them on the workspace record so the handoffs do not lose context as the work moves between owners.
Related vulnerabilities
Related features
Vulnerability scanning tools that map your attack surface
Test web apps behind the login
Monitor continuously catch regressions early
Vulnerability management software that tracks every finding
Verify fixes and track reopens on the same finding record
Bulk finding import bring your scanner data with you
Finding overrides that survive every scan cycle
Compliance tracking without a full GRC platform
Every action recorded across the workspace
AI-powered reports in seconds, not days
Find every exposed Docker socket and mounted docker.sock before an attacker does
SecPortal's external scanner probes TCP 2375 and 2376 across verified domains, identifies responding Docker daemons through banner and HTTP-API detection, raises a critical-severity finding with port, service, and version evidence, tracks remediation through closure, verifies the fix through retest, and re-runs continuously so a security-group edit, a Terraform apply, a CI runner refresh, or a recovered host does not silently re-expose the daemon. Start free.
No credit card required. Free plan available forever.