Why UFW Fails Against Docker: Anatomy of Network Bypass and Bot Exploitation of Exposed Database Ports

A common Docker Compose setup with PostgreSQL and Redis using default port mappings like 5432:5432 and 6379:6379 bypasses UFW firewall rules due to Docker's manipulation of Netfilter chains. Docker inserts DNAT rules in the PREROUTING chain and places DOCKER-FORWARD rules ahead of UFW's deny policies in the FORWARD chain, allowing external traffic to reach containers even when ufw status shows default deny incoming and routed. The issue affects both IPv4 and IPv6, with docker-proxy binding to [::] addresses and ip6tables rules enabling similar bypasses in Docker 27+. Bots scan cloud IP ranges within minutes of deployment, targeting open database ports and deploying miners such as kinsing or kdevtmpfsi via Redis commands like REPLICAOF or CONFIG SET for cron persistence. Historical cases include a Hetzner CPX21 instance compromised in under two minutes and Spree Commerce starter issues where unprotected Redis led to immediate infections. Mitigation requires careful use of DOCKER-USER chains, disabling IPv6 where unnecessary, or switching to nftables for unified filtering.

HabrTranslated from Russian

Read at source

A typical fresh VPS deployment on Ubuntu or Debian begins with UFW configured to deny incoming traffic by default while allowing only ports 22, 80, and 443. After installing Docker and launching a docker-compose.yml that maps PostgreSQL on 5432 and Redis on 6379 for convenient local access, administrators often assume the firewall still protects the system because ufw status shows no additional rules.

External connection tests with nc immediately reveal that both ports respond from the public IP, because Docker activates IP forwarding and installs DNAT rules in the nat table's PREROUTING chain. These rules rewrite destination addresses to container IPs on the docker0 or custom bridge before packets reach the routing decision, sending them into the FORWARD chain instead of INPUT.

Docker places its DOCKER-FORWARD and DOCKER chains at the top of FORWARD, ahead of ufw-before-forward rules, so the default deny routed policy never evaluates the traffic. The same behavior occurs with IPv6 when Docker 27+ manages ip6tables or when docker-proxy binds to [::] addresses, creating an open path even if IPv4 appears locked down.

Scans of cloud provider ranges by tools such as masscan detect the newly exposed ports within 30–120 seconds. On Redis without authentication, bots issue FLUSHALL, attempt REPLICAOF to load malicious modules, or use CONFIG SET to write cron jobs for persistence. Older Redis versions or those running as root allow full remote code execution through Lua sandbox escapes such as CVE-2022-0543 or direct module loading.

PostgreSQL instances with weak default credentials suffer immediate unauthorized access and data exfiltration or ransomware deployment. Real-world examples include a test Qdrant stack on Hetzner CPX21 compromised in under two minutes and multiple reports against the Spree Commerce starter template that left Redis unprotected.

Effective isolation requires explicit rules in the DOCKER-USER chain, disabling automatic IPv6 exposure, binding containers only to localhost, or adopting nftables inet tables that apply policy before Docker's NAT rules execute.

Meridian digestThe week's most consequential security stories, distilled into one briefing.See the digest

More in Vulnerabilities & Exploits

New Container, Old Code: Why the Incident Returned After Three Months
Ginux Rust Kernel Receives Security Fixes in 10 Hours While Linux Patch Takes 69 Days to Reach Mainline
OneDrive and SharePoint URLs Enable Corporate Username Enumeration, Microsoft Calls Risk Low