"server" HTTP header can't be removed when part of the "basicauth" matcher

1. Caddy version (caddy version):

caddy 2.4.5,

2. How I run Caddy:

a. System environment:

Ubuntu, systemctl, using a Caddyfile

d. My complete Caddyfile or JSON config:

example.com {
	header {
		-server
		-Link
		-X-Powered-By

		# Disable Google FLOC tracking if not enabled explicitly: https://plausible.io/blog/google-floc
		?Permissions-Policy interest-cohort=()

		# disable clients from sniffing the media type
		X-Content-Type-Options nosniff

		# clickjacking protection
		X-Frame-Options DENY

		# keep referrer data off of HTTP connections
		Referrer-Policy no-referrer-when-downgrade
	}

	basicauth /* {
		 foo JDJhJDE0JHhVbU0vYnVSeEVYeVlqYnZVdXk3cy5ocWJKTlkxd01tczYvN3BySTMxZGpwZGRDcmExM0JL
	}

	log {
		output file /var/log/caddy/access.log {
			format single_field common_log
			roll_size 128MiB
			roll_keep 10
			roll_keep_for 240h
		}
		output file /var/log/caddy/caddy-json.log {
			format filter {
				fields {
					common_log delete
					request>remote_addr ip_mask {
						ipv4 24
						ipv6 32
					}
					request>headers>Authorization delete
				}
			}
			roll_size 128mb
			roll_keep 10
			roll_keep_for 240h
		}
	}

	root * /var/www/projects/

	file_server
	encode zstd gzip
}

3. The problem I’m having:

I always try to remove the server signature in my setups.

I found that header -server is ignored when basicauth /* is set. server: Caddy is emitted in the response when requesting any ressource (e.g. curl -I https://example.com).

Changing path to e.g. basicauth /foo/* and requesting the “homepage” / removes the server signature header (=respects the header directive removal). A request to /foo again includes it.

6. Links to relevant resources:

I considered this a bug:

(Status: closed)

When HTTP handlers return an error (such as “Unauthorized”), Caddy returns the error value back and executes the error handler chain defined by handle_errors, and changes to the request from the primary handler chain are elided. This makes it so that the request is either handled completely or not at all.

To strip the header even in the case of errors, do so in the error handler chain as well.

(FYI removing the server header doesn’t grant you any additional security.)

Hi Matt,

thanks for the explanation. This makes conceptually sense of course.

I made a snippet:

(headerz) {
    header {
    -server
    -Link
    -X-Powered-By
    }
}

and imported it in two places, globally and in handle_errors. Testing again, it works as expected. Thank you!

(FYI removing the server header doesn’t grant you any additional security.)

It doesn’t if you have a dedicated attacker and it definitely helps Caddy does not respond with a version number as others still do in 2021 (“server: Apache/2.4.41 (Ubuntu)”).
But avoiding the machinery being part of databases that know which systems (seem to) run Apache 2.4.41 which has a new (for the sake of the example) zero day or the OS… can give you the small time advantage for a patch / update / solution. Everything is automated these days. This is just my experience.

May I ask in this post (change of topic) if the basic_auth set to /* breaks the TLS certificate provisioning? Or does it switch to TLS-ALPN challenges in such a case?

Thank you

Requests pertaining to the HTTP challenge never reach the request handling chain – Caddy responds to those requests immediately.

Also, you should remove the /*, because it’s very slightly less efficient than not specifying a matcher, because it makes Caddy make a tautological path comparison on every request (nanoseconds at most, but still). Omitting the matcher means “match all requests” anyways.

Also, be aware that the common_log field is deprecated and slated for removal in a future release. Use this plugin instead if you absolutely need common log:

Requests pertaining to the HTTP challenge never reach the request handling chain – Caddy responds to those requests immediately.

Perfect! That’s way better than workarounds with open routes like in other servers for the challenge.

Also, you should remove the /* , because it’s very slightly less efficient than not specifying a matcher […]

Good point!

Also, be aware that the common_log field is deprecated and slated for removal in a future release.

I know. Work in progress :slight_smile: I’m still working on putting together my perfect base configuration.

It doesn’t matter whether the attacker is dedicated or not. They will just spam your server with known vulns and see if any stick. That’s all there is to it, they don’t care about the Server header.

You missed my point. In my definition of “dedicated” I don’t place the time wasting script kiddies that periodically check my Caddy server against Apache vulns. It’s someone that already knows (or assumes) that this particular server runs it and waits for the moment:

If there is a new exploit for X bad guy wants to focus on X and selects X from a search engine, an existing own database, or shodan.io, or nerdydata.com, or for the sake of the argument search for caddy and that’s that.

If a simple header removal hides me on one+ of these databases I’m fine with that. I would also rewrite a regexp of a matcher if the result is only 1ms faster than before because it becomes a nobrainer in my Ansible file :wink:

Of course, in reality, the topic is (completely unimportant)^2 compared to the actual challenges of DDoS or Layer7 attacks.

Ok, well, I’ve got bad news for you though, you can still fingerprint Caddy instances. They’re fairly unique in subtle ways, even distinctly from generic Go servers. Especially when using basicauth. Because Caddy compares hashes for basicauth, you could time requests to protected endpoints to try to determine whether there is a hashing latency. (Caddy is the only server I know of that compares secure hashes of basicauth credentials.) That’s just one example, but I’m just saying there are still ways to be added to lists without the Server header.

1 Like

Of course, of course.

E.g. nmap does not stop here:

match http m|^HTTP/1.[01] \d\d\d (?:[^\r\n]\r\n(?!\r\n))?Server: Caddy\r\n|s p/Caddy httpd/ cpe:/a:matt_holt:caddy/

when running a
nmap --version-trace -A -sV --version-all -p 443 …

But still, after staring at aggregated logfiles for quite some years, most “non dedicated” scanners don’t spend much time due to their ressources or don’t want to risk being blocked.

Conclusion: Initially, I only wanted to track down a potential error because the response with basic_auth behaved differently in relation to the headers. I only use basic_auth during a pre-launch. I learned new things about the configuration. Thank you very much for that.

1 Like

This topic was automatically closed after 30 days. New replies are no longer allowed.