Docker IP scanned when hostname container matches
Opened this issue ยท 5 comments
When running latest depending on the hostname of the docker container it sometimes adds the internal IP address to the list of IPs to scan even with --nodns none and a --ip supplied, example:
docker run --rm --hostname cert-chief-app -it ghcr.io/testssl/testssl.sh:latest --assume-http --nodns none --hints --ip '[2606:4700:20::ac43:4b0f]' -6 cert.chief.app:443
#####################################################################
testssl.sh version 3.3dev from https://testssl.sh/dev/
This program is free software. Distribution and modification under
GPLv2 permitted. USAGE w/o ANY WARRANTY. USE IT AT YOUR OWN RISK!
Please file bugs @ https://testssl.sh/bugs/
#####################################################################
Using OpenSSL 1.0.2-bad (Mar 28 2025) [~179 ciphers]
on cert-chief-app:/home/testssl/bin/openssl.Linux.x86_64
Testing all IP addresses (port 443): 172.17.0.24 2606:4700:20::ac43:4b0f
--------------------------------------------------------------------------------------------------------------
Start 2025-07-22 21:31:12 -->> 172.17.0.24:443 (cert.chief.app) <<--
Further IP addresses: 2606:4700:20::ac43:4b0f
A record via: /etc/hosts
rDNS (172.17.0.24): (instructed to skip DNS queries)bash: connect: Connection refused
bash: line 1: /dev/tcp/172.17.0.24/443: Connection refused
Oops: TCP connect problem
Unable to open a socket to 172.17.0.24:443.
Fatal error: Couldn't connect to 172.17.0.24:443, proceeding with next IP (if any)
...
It will continue with the correct IP and finish a scan, but it first tries to connect to the Docker IP anyway. This is only if the hostname of the container "matches" the domain name.
I am guessing there is probably a very logical networking explanation for this but this behaviour seems really interesting and it took a while before I found the cause for this ๐คฃ
This also mentions a v6 address when doing a IPv4 scan:
docker run --rm --hostname cert-chief-app -it ghcr.io/testssl/testssl.sh:latest --assume-http --nodns none --hints --ip '104.26.3.199' cert.chief.app:443
#####################################################################
testssl.sh version 3.3dev from https://testssl.sh/dev/
This program is free software. Distribution and modification under
GPLv2 permitted. USAGE w/o ANY WARRANTY. USE IT AT YOUR OWN RISK!
Please file bugs @ https://testssl.sh/bugs/
#####################################################################
Using OpenSSL 1.0.2-bad (Mar 28 2025) [~179 ciphers]
on cert-chief-app:/home/testssl/bin/openssl.Linux.x86_64
Testing all IPv4 addresses (port 443): 104.26.3.199
--------------------------------------------------------------------------------------------------------------
Start 2025-07-22 21:40:12 -->> 104.26.3.199:443 (cert.chief.app) <<--
Further IP addresses: (fd00::3)
A record via: /etc/hosts
rDNS (104.26.3.199): (instructed to skip DNS queries)
...
However, it won't scan it (even with -6 provided) so that is less of an issue but still odd.
This is all not happening on 3.2.
Thanks, @stayallive . Will take a while . Vaguely looks like some pattern matching is not as strict as it should be.
Ah. Interesting. No worries, I can work around this pretty easily so not a major blocker at all here, just figured it was worth a report.
Although I could appreciate some combination of flags that would allow me to force the IP(s) scanned and force it to assume HTTP (still checks as far as I can tell with --assume-http) without any processing. But this might be better in another issue since it's a little offtopic.
Anyway; appreciate the work as always ๐
If there is a concern for the Docker environment affecting this, I can link to a (rather verbose) comment where I did some investigation on what Docker was doing internally with it's embedded DNS
Even when I configured the container DNS to use my own separate DNS service, the embedded DNS from Docker takes priority where it only uses the other one as a fallback. As that was problematic for my rDNS tests that were dependent upon PTR records from my CoreDNS instance, to prioritize CoreDNS instead I had to modify the containers /etc/resolv.conf but that was a bit complicated due to that file being a direct bind mount from Docker (you cannot modify the file if it would change the inode).
Preserving an inode can be done with cp (but this command varies by base distro in compatibility for this operation) or with sponge (from the moreutils package). The files /etc/hosts and /etc/hostname also have this problem.
/etc/hosts in some scenarios will have an entry that conflicts with public DNS, which I've observed can cause other issues, however sometimes you want this entry to remain instead.
- One scenario I had was when services tried to resolve the container hostname, they queried through glibc/NSS which resolved an IP via
libc gethostbyname()that takes the kernel hostname IIRC (random hexadecimal assigned if a container wasn't given an explicit hostname) and then that was first matched to/etc/hosts(and if NSS permitted it, then as a fallback queried DNS). The software that interacted with that on the other end expected a valid FQDN however, and that method used the first value mapped to the IP (commands likehostname/hostname --fqdnwould do the samegethostbyname()call in the same manner picking the first host on the matched/etc/hostsline). - The other scenario I recall was software receiving a connection and likewise verifying a host + IP, where the public DNS IP was not matched as
/etc/hostscame first with a conflicting IP than the source IP of the connection (both claiming to have the same FQDN).
Additionally, this behaviour differs between:
docker rundefault network bridge (legacydocker0).docker compose runwhich defaults to a custom user-defined network bridge percompose.yaml. Only user-defined networks include the embedded DNS feature (indicated by127.0.0.11entry in/etc/resolv.conf).- When using
docker compose runinstead ofdocker compose upyou'll also encounter some other network differences, such as no network aliases by default (requires an opt-in flag).
Just something to be mindful of ๐ (oh and depending on version of the Docker engine, there is a variety of fixes that land, notably for IPv6 so ensure you're using the latest there, v27 I think had a regression in one of it's point releases too)
Collapsed as not relevant
This also mentions a v6 address when doing a IPv4 scan
Probably unrelated due to DNS opt-out, but Alpine is known to send out requests for A + AAAA records concurrently IIRC, whereas with glibc distro like openSUSE the DNS queries should be more predictable (if perfoming both then only falling back sequentially to the other afterwards AFAIK).
Although I do recall Alpine / musl in the past year or so did improve on some DNS related issue (might have been one related to switching to TCP when UDP wasn't sufficient due to response length).
This is all not happening on 3.2.
๐ I can confirm the 3.2 vs 3.3dev / latest tag difference for that testssl.sh command output (NOTE: IPv6 fails below as the test system lacks IPv6 connectivity):
# 3.3dev resolves rDNS via /etc/hosts resulting in IPv4 connection:
$ docker run --rm --hostname cert-chief-app -it ghcr.io/testssl/testssl.sh:3.3dev --assume-http --nodns none --hints --ip '[2606:4700:20::ac43:4b0f]' -6 cert.chief.app:443
# ...
Further IP addresses: (2606:4700:20::ac43:4b0f)
A record via: /etc/hosts
rDNS (172.17.0.2): (instructed to skip DNS queries)/usr/local/bin/testssl.sh: connect: Connection refused
/usr/local/bin/testssl.sh: line 12196: /dev/tcp/172.17.0.2/443: Connection refused
Oops: TCP connect problem
Unable to open a socket to 172.17.0.2:443.
Fatal error: Can't connect to "172.17.0.2:443"
Make sure a firewall is not between you and your scanning target!$ docker run --rm --hostname cert-chief-app -it ghcr.io/testssl/testssl.sh:3.2 --assume-http --nodns none --hints --ip '[2606:4700:20::ac43:4b0f]' -6 cert.chief.app:443
# ...
AAAA record via: supplied IP "2606:4700:20::ac43:4b0f"
rDNS (2606:4700:20::ac43:4b0f): (instructed to skip DNS queries)/usr/local/bin/testssl.sh: connect: Network unreachable
/usr/local/bin/testssl.sh: line 12059: /dev/tcp/2606:4700:20::ac43:4b0f/443: Network unreachable
Oops: TCP connect problem
Unable to open a socket to [2606:4700:20::ac43:4b0f]:443.
Fatal error: Can't connect to "[2606:4700:20::ac43:4b0f]:443"
Make sure a firewall is not between you and your scanning target!This also mentions a v6 address when doing a IPv4 scan:
I could not reproduce this (likely due to lack of IPv6 configured container as noted below):
$ docker run --rm --hostname cert-chief-app -it ghcr.io/testssl/testssl.sh:3.3dev --assume-http --nodns none --hints --ip '104.26.3.199' cert.chief.app:443
# ...
A record via: /etc/hosts
rDNS (104.26.3.199): (instructed to skip DNS queries)
Service detected: HTTPMy Docker host used was running Docker Engine 28.2.2 but without IPv6 preventing connecting to IPv6 hosts.
I believe from Docker Engine v27, IPv6 support no longer requires configuring the experimental opt-in setting, and with an IPv6 enabled host the default bridge network assigned to the container should be IPv6 capable as the ip6tables setting would be inferred as true (without requiring explicit configuration in /etc/docker/daemon.json anymore AFAIK).
- With that change IPv6 ULA private subnets also should be provided by default for containers to use implicitly, whereas before you had to explicitly configure those for a network to have IPv6 addresses to assign per container_).
There's an additional complication related to this when a host has multiple NICs that could be used for outbound connections, where the service will identify a different IP from your system. In a project I've managed that was relevant for rDNS checks to ensure the correct IP / NIC was used. So if the default outbound connection is routed through the wrong host NIC / IP, that can be configured too at the bridge network config.
Anyway, I think all that's happening here is additional discovery of IPs that could be used from within the container? In your case fd00::3 would be an IPv6 ULA IP assigned to the container, so I'm assuming that's also in the /etc/hosts mapping to the container IP like there was for IPv4.
I've documented instructions for a workaround below, if your testssl.sh container really must have the same hostname as external site (or separate container) for some reason?
Hopefully the information here is helpful to troubleshoot/resolve any potential bug, but it technically seems testssl.sh is working correctly and respecting /etc/hosts (which --nodns shouldn't ignore).
I think (UPDATE: Actually seems to be according to referenced docs below), so it would be helpful to better understand the requirement for setting the hostname to the FQDN under test?testssl.sh isn't misbehaving in this scenario
FWIW, the docs do mention that --nodns expects an IP directly to test, or it will fallback to matching the FQDN provided in /etc/hosts, unless --ip was set (thus this is regression ๐
):
Line 128 in e75ef95
Line 147 in e75ef95
Workaround for /etc/hosts
Actually as your error output notes, it is using /etc/hosts to perform a lookup without DNS, so your entry there is likely the issue.
See docker-mailserver/docker-mailserver#3520 (comment) for details / reproduction like I described above.
# Replace the default hostname in /etc/hosts assigned to a containers network IP (fails):
$ sed -i "s|${HOSTNAME}|mail.example.test|" /etc/hosts
sed: cannot rename /etc/sedzTVoF6: Device or resource busyYou can't rely on cp for the Alpine image, but it might work with the openSUSE one. I've only documented this for Debian by installing moreutils:
# Docker bind mounts `/etc/resolv.conf` into containers implicitly.
# Updating a bind mounted file must preserve the inode, this can be done via `cp` or `sponge`.
export DEBIAN_FRONTEND=noninteractive
apt-get -qq update && apt-get -qq install moreutils >/dev/nullThen update /etc/hosts:
# Relevant when the container lacks an FQDN hostname and services try to use it.
# WARNING: With an FQDN set via `--hostname` rDNS on a public IP with PTR to the same FQDN
# will fail resolving the FQDN back to the public IP, instead resolving the private container IP.
#
# Prepend preferred FQDN for services to resolve as the hostname `--fqdn` / `--domain`,
# Rather than the kernel hostname of this UTS namespace:
# https://docs.docker.com/reference/cli/docker/container/run/#uts
# https://docs.docker.com/reference/compose-file/services/#uts
export WITH_HOSTNAME='hello.world.test'
sed -E "s|($(cat /proc/sys/kernel/hostname))|${WITH_HOSTNAME} \1|" /etc/hosts | sponge /etc/hosts
In my linked issue on this topic, it was important to prepend the desired hostname to match to the containers IP for rDNS. This was only needed as mentioned when the container did not have the desired hostname configured for the container (opposite issue you have), as that entry would then be a random hexadecimal hostname generated to associate to the container (excluded from the embedded DNS too IIRC).
You should be able to perform similar modification to match the hostname like shown above and just delete it instead of that sed replacement expression or just provide an empty replacement.
$ cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00:: ip6-localnet
ff00:: ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 hello.example.test hello
# Empty replacement:
$ sed -E "s|($(cat /proc/sys/kernel/hostname))||" /etc/hosts | sponge /etc/hosts
# Alternatively delete the entire line with:
# sed -E "/($(cat /proc/sys/kernel/hostname))/d" /etc/hosts | sponge /etc/hosts
$ cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00:: ip6-localnet
ff00:: ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 helloWorkaround compatibility for end-user of published image variants
zypper install moreutilsfor the openSUSE Leap image won't work as that image discards the package manager during the build. The package would need to be added officially.apk add moreutilswill be fine though with the Alpine image.
Also keep in mind that DockerHub registry vs GHCR differ in the image published:
# openSUSE Leap 15.6 (3.3dev unavailable):
docker run --rm -it --entrypoint bash docker.io/drwetter/testssl.sh:3.2
# Alpine 3.21.4 (3.3dev available):
docker run --rm -it --entrypoint bash ghcr.io/testssl/testssl.sh:3.2The Alpine image is published by the CI Github Actions workflow, while DockerHub has remained manually published by the project author.
Reproduction example of fix applied via custom entrypoint
NOTE: Only compatible with the published Alpine image (GHCR), the openSUSE published image (DockerHub) must patch the Dockerfile instead to include moreutils (sed + sponge workaround with custom entrypoint still required).
We can verify this fix from modifying /etc/hosts with Docker Compose and this compose.yaml:
services:
testssl:
scale: 0 # This avoids running the container during `docker compose up`
image: ghcr.io/testssl/testssl.sh:3.3dev
hostname: cert.chief.app
# Adjust default command to use (NOTE: IPv6 requires IPv6 interface on the host):
#command: --assume-http --nodns none --hints --ip '[2606:4700:20::ac43:4b0f]' -6 cert.chief.app:443
command: --assume-http --nodns none --hints --ip '104.26.3.199' cert.chief.app:443
# Custom entrypoint support to install `moreutils` package:
user: "0:0"
entrypoint: /usr/local/bin/entrypoint
configs:
- source: entrypoint
target: /usr/local/bin/entrypoint
mode: 0755
# NOTE: `compose.yaml` must escape `$` as `$$` to prevent accidental ENV var interoplation feature of Compose.
configs:
entrypoint:
content: |
#!/bin/bash
# Remove the /etc/hosts line for the container IP mapping to container hostname
apk add moreutils
sed -E "/($$(cat /proc/sys/kernel/hostname))/d" /etc/hosts | sponge /etc/hosts
# Call the original entrypoint and pass over the command options received:
testssl.sh $${@}Verification:
$ docker compose run --rm testssl
# ...
A record via: supplied IP "104.26.3.199"
rDNS (104.26.3.199): (instructed to skip DNS queries)
Service detected: HTTPNo A record via: /etc/hosts ๐
The same should work for IPv6 too AFAIK :)
Although I could appreciate some combination of flags that would allow me to force the IP(s) scanned
FWIW curl does offer something similar. It hard-codes resolution for localhost for example (including subdomains IIRC?). To skip DNS and that localhost IP mapping, you'd instead use --connect-to to instruct which IP to actually connect to.
# Regardless of the URL/port, ensure the connection is to the given IP:
curl --connect-to ::172.22.0.1 hello.localhostThere's a similar option --resolve, but IIRC it had some limitations/caveats that I preferred --connect-to.