caddyserver / caddy

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
https://caddyserver.com
Apache License 2.0
58.6k stars 4.05k forks source link

redir + header options not working (on http) #5422

Closed iocron closed 1 year ago

iocron commented 1 year ago

OS: Ubuntu 22.04.2 LTS Caddy Version: v2.6.4

Problem: Maybe I am missing something, but it seems like when I use redir and header in combination, then the custom header definitions are getting completely ignored on http.

Expected Result: The custom headers should be set and then the redirect should happen.

Example caddy config:

(security_headers) {
    header {
        -server
        -X-Powered-By
        # -Link

        # disable FLoC tracking
        Permissions-Policy interest-cohort=()

        # enable HSTS
        Strict-Transport-Security max-age=31536000;

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

        # clickjacking protection
        X-Frame-Options SAMEORIGIN # DENY

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

xxxxxxxxxxxxx.com {
    import security_headers
    redir https://www.xxxxxxxxxxxxx.com{uri} permanent
}

www.xxxxxxxxxxxxx.com {
    import security_headers
    # default config stuff..
}

Curl Test on http (custom headers not working):

curl -I http://xxxxxxxxxxxxx.com
HTTP/1.1 308 Permanent Redirect
Connection: close
Location: https://xxxxxxxxxxxxx.com/
Server: Caddy

curl -I http://www.xxxxxxxxxxxxx.com
HTTP/1.1 308 Permanent Redirect
Connection: close
Location: https://www.xxxxxxxxxxxxx.com/
Server: Caddy

Curl Test on https (custom headers working):

curl -I https://xxxxxxxxxxxxx.com
HTTP/2 301
alt-svc: h3=":443"; ma=2592000
location: https://www.xxxxxxxxxxxxx.com/
permissions-policy: interest-cohort=()
referrer-policy: no-referrer-when-downgrade
strict-transport-security: max-age=31536000;
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN

curl -I https://www.xxxxxxxxxxxxx.com
HTTP/2 200
alt-svc: h3=":443"; ma=2592000
content-language: de
content-security-policy: default-src 'self'  'unsafe-inline' 'unsafe-eval' data:;
content-type: text/html; charset=utf-8
permissions-policy: interest-cohort=()
referrer-policy: no-referrer-when-downgrade
strict-transport-security: max-age=31536000;
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
x-xss-protection: 1; mode=block
content-length: 196926
mholt commented 1 year ago

Thanks for your question, and we're glad that you're using Caddy! So far, this doesn't look like a bug in Caddy. It looks more like a question about how to use Caddy rather than a bug report or feature request.

Caddy is redirecting HTTP to HTTPS for you. Your sites are defined for HTTPS (as that is Caddy's default), so the header directives don't apply, unless you put http:// in the site name.

Since this issue tracker is reserved for actionable development items, I'm going to close this, but we have a community forum at caddy.community where more people will be exposed to your question, including people who may be more expert or experienced than I am with the specific issue you're facing. I hope you'll ask your question there. The Help category in the forum even provides a template you can fill out.

If I'm wrong, and this does turn out to be a bug after further investigation, we can reopen this. Thanks for understanding!

iocron commented 1 year ago

Thank you @mholt That makes a lot more sense now, I thought using a domain name without a explicit port/protocol (e.g. example.com) is like using a domain name with all http ports (e.g. example.com:80 and example.com:443), so I thought this might be a bug. The behavior can be a bit confusing at first. I still haven't found this explicit info in the docs (yet).

Is there any feature (this could result in a feature request?) to use a hook functionality to implicitly force a default behavior for all (http) ports of a domain?

Otherwise I am kind of forced to use ugly looking configuration, e.g.:

xxxxxxxxxxxxx.com xxxxxxxxxxxxx.com:80 www.xxxxxxxxxxxxx.com:80 {
    import security_headers
    redir https://www.xxxxxxxxxxxxx.com{uri} permanent
}

www.xxxxxxxxxxxxx.com {
    import security_headers
    # default config stuff..
}
mholt commented 1 year ago

@iocron

I thought using a domain name without a explicit port/protocol (e.g. example.com) is like using a domain name with all http ports (e.g. example.com:80 and example.com:443), so I thought this might be a bug. The behavior can be a bit confusing at first. I still haven't found this explicit info in the docs (yet).

I can see why it could be confusing at first; however, the baseline rules are that Caddy serves HTTPS by default and that implicit config never overrides explicit config.

On this page (in bold):

By default, Caddy serves all sites over HTTPS.

And on this page:

From the address, Caddy can potentially infer the scheme, host and port of your site. If the address is without a port, the Caddyfile will choose the port matching the scheme if specified, or the default port of 443 will be assumed.

Is there any feature (this could result in a feature request?) to use a hook functionality to implicitly force a default behavior for all (http) ports of a domain?

Sure, you can always do the redirects yourself, along with any other logic you want:

:80 {
    redir https://{host}{uri}
    ...
}

But in general we discourage serving over HTTP.

iocron commented 1 year ago

Thanks @mholt Your explanation (..the baseline rules are that Caddy serves HTTPS by default and that implicit config never overrides explicit config..) is definitely better to understand than the docs :D Because the docs don't mention anything explicitly about the config not beeing overwritable (or I am overlooking it).

But in general we discourage serving over HTTP.

As do I, but the first initial request (if HSTS is not already active to the user) can be potentially on http. So I want to set security headers (and some other measures) on the very first request, possibly for all domain configurations. For example if for some reason the first request on http is beeing incepted by malicious code, then some security measures can negate or partly prevent the attack. I hope my sentence makes sense :D Sadly I can't find a hook to inject those measures globally, so I would probably need to extend caddy by writing my own caddy module (it seems).

mholt commented 1 year ago

So I want to set security headers (and some other measures) on the very first request, possibly for all domain configurations. For example if for some reason the first request on http is beeing incepted by malicious code, then some security measures can negate or partly prevent the attack.

Sorry, but setting headers on plaintext HTTP won't protect any clients if the request is already being intercepted.

Sadly I can't find a hook to inject those measures globally, so I would probably need to extend caddy by writing my own caddy module (it seems).

What's wrong with the one-liner redir https://{host}{uri} ??

iocron commented 1 year ago

Sorry, but setting headers on plaintext HTTP won't protect any clients if the request is already being intercepted.

I am talking about maliciously incepted applications (not intercepted => I don't mean MITM Attacks), instead I do mean XSS, XFS Attacks, Content spoofing and so on. I chose the wrong word to explain :D

What's wrong with the one-liner redir https://{host}{uri} ??

Thats a bit more complicated, just to add some context to the overall problem. I am currently developing a server management system (which includes caddy as the webserver, the system currently manages the OS, Packages, Sec Hardening, Firewall, PHP, Node, and so on). The BE User should be able to add new domains in the GUI and adjust simple domain settings and possibly custom settings (in progress). Therefore I need multiple security layers, just one of them includes removing sensitive header informations of well known systems (e.g. Wordpress, Drupal, Strapi, ..). But I have to make sure the user can't override those settings, so little or no (sensitive/partly sensitive) information leaks. A global setting would be easiest to implement by a middleware or hook if no other option is available for caddy. Also supressing the server header globally (e.g. "caddy", "nginx", "apache") can help a tiny bit to obscure informations (Security by Obscurity), which makes it a tiny bit harder for an attacker, because information gathering is a crucial part of an successful attack (and most of the times the attacks are automated anyway by gathering informations and then executing related exploit-lists to be as efficient as possible). Those are a very few examples.

Now I've deviated a bit too much from my original post xD (and probably bored you to death :D). That's why I need a global approach, the user should not be able to override or extend beyond those rules, except predefined edge cases. I am not a Golang developer (instead only c#, c++, bash, php and nodejs), so it will take me some time. But thanks a lot for your time and help so far.

francislavoie commented 1 year ago

Also supressing the server header globally (e.g. "caddy", "nginx", "apache") can help a tiny bit to obscure informations (Security by Obscurity), which makes it a tiny bit harder for an attacker, because information gathering is a crucial part of an successful attack (and most of the times the attacks are automated anyway by gathering informations and then executing related exploit-lists to be as efficient as possible). Those are a very few examples.

I disagree. The server header contains no information that couldn't otherwise be discovered trivially via other means, like looking at the byte structure of the TLS handshake, or timing information. If it was in any way useful for attackers, we would not be including the header by default. But there's absolutely no evidence of it being useful for attacks.

That's why I need a global approach

Caddy is designed to be configured explicitly. There's no implicit config. There's no way to configure routes globally, and we have no plans to allow that.

If we did allow implicit config, then users would immediately ask for a way to override that implicit config in some way or another. And that means we would need to design an escape hatch for that, and so on. It's not worth it, it would significantly increase complexity for very little gain.

whitestrake commented 1 year ago

Stripping a server header is like filing the brand name off a lock.

Casual observers now might not know which lock it is, but locksmiths and thieves with the tools and the knowledge never needed to see the brand name anyway.

mholt commented 1 year ago

Or like, the server doesn't even matter. Attackers don't care what the Server header says or whether it exists. They'll just try the exploit.

That's why you see a bunch of requests for /wp-admin even though you're not even running WordPress.

I guess I still don't understand why the solution I wrote above doesn't work for you. What's not global enough about it?

iocron commented 1 year ago

Also supressing the server header globally (e.g. "caddy", "nginx", "apache") can help a tiny bit to obscure informations (Security by Obscurity), which makes it a tiny bit harder for an attacker, because information gathering is a crucial part of an successful attack (and most of the times the attacks are automated anyway by gathering informations and then executing related exploit-lists to be as efficient as possible). Those are a very few examples.

I disagree. The server header contains no information that couldn't otherwise be discovered trivially via other means, like looking at the byte structure of the TLS handshake, or timing information. If it was in any way useful for attackers, we would not be including the header by default. But there's absolutely no evidence of it being useful for attacks.

I partly disagree. When you are writing a pentest tool or a bot automation with a scan/attack pattern, then you want to be as efficient and fast as possible, because even attackers have to pay for the infrastructure. Scanning millions of websites while executing blind scans/attacks will make them pay way more or even (automatically) give up on your website(s), therefore this is not efficient for an attacker. Efficient Information Gathering is key, not the attack (at least not at the beginning). The attack comes actually much later in the process. The more information you can gather (without too much overhead), the easier and faster it will be to launch a successful attack. I am not talking solely hiding the server header (which is by itself useless, unless combined with other security measures, thats where our opinions diverge). I am talking about applying different security defense mechanisms/layers to make it harder for the automated scanner/attacker. The simplest way would have been to implement the security measures (its not just about the headers) in a global routing and/or some similar places, alternatively in a custom middleware (I am not in the Golang universe yet).

A very simplified example. You are writing such automation, to exploit webservers, then one very easy and quick way would be to obtain the server header (e.g. "caddy", yes there are other methods as well, but they can be cumbersome/slow/inconsistent sometimes) and then apply the related attacks based of this information: https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=caddy

Or the slowest approach (script kiddies, badly configured scans/bots) you can go through all cve's, public/private exploit lists and lots of other patterns to detect vulnerabilities or attack directly and blindly, which is not efficient for the attacker. For example you would need to go through caddy, nginx, apache, litespeed, and so on.. https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=apache https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=nginx ..

We can take this one step further, you can add a automated codebase analysis of the projects (caddy, nginx, apache, ..) and find (new) potential vulnerability patterns in their codebase (e.g. through github or whatsoever) to fill your own exploit lists. If you take this another step further nowadays, put this into a ML/AI Pipeline (very early stage, but this will come sooner or later, even real-time scans on high traffic) to make it even more scary, so you can detect even more patterns, then your written pentest tool is half done (for the attacker its more than a pentest tool, it's a gold mine). This is just one way of many to discover new CVE's. For the attack, you only need to apply the entries related to the software you attack (e.g. caddy). So it does make sense to not ship informations that are not essentially really needed / required to run a application. What I've learned so far from experience, information which has no real functional benefit to the user, should not be public in the first place, no matter how minor the information is (which prevents information gathering as a side-effect).

I am not talking about a recommendation or preferability of removing the server header alone (which is again, useless, unless combined with other security measures). Some users are using a Proxy/LB anyway, others not, there are many many security layers, but the weakest link can break them all. I hope this topic doesn't get stuck on the server header :D because it was only meant as an example.

That's why I need a global approach

Caddy is designed to be configured explicitly. There's no implicit config. There's no way to configure routes globally, and we have no plans to allow that.

If we did allow implicit config, then users would immediately ask for a way to override that implicit config in some way or another. And that means we would need to design an escape hatch for that, and so on. It's not worth it, it would significantly increase complexity for very little gain.

Thank you for the info, I had a similar apprehension (while reading some caddy community posts). I'll think about it and maybe I'll come back to this topic another time, once I've learned some Golang, because the things I want to accomplish would need deeper changes, therefore I will need to implement it by using some kind of middleware.

whitestrake commented 1 year ago

There are a lot of confident claims there that come without citations.

In particular, the proposal that attackers are primarily constrained by infrastructure costs, and the conclusion that efficiency of target identification is the key to malicious actors being able to break even on their investment in illegal activities, is rather bold to be unsourced. Especially in a world of botnets, reflection attacks, and cheap commodity virtual infrastructure, some of those claims are just - with all due respect - less believable. I'm not going to touch your points about CVE discovery; that's all well reasoned enough, but I don't see how it changes anything with respect to HTTP headers.

On top of that, I don't see how HTTP headers specifically factor into this. It's not enough to merely postulate how attacks might be mitigated by making identification of your server less efficient; there also needs to be evidence that HTTP header manipulation will effectively achieve that, and we don't see any evidence of this. I'm not just talking about server headers here; attackers don't care about HSTS, either. HSTS protects your clients, not your server. There are no HTTP headers which will mitigate attacks against your server.

If you'd like to indicate other defensive layers you think are appropriate - trust me, we're all ears. If there's a way to make Caddy more secure, we want that, too. I think I speak for everyone when I say we'd be thrilled to see another Gopher emerging with an interest in keeping Caddy measurably secure.

iocron commented 1 year ago

On top of that, I don't see how HTTP headers specifically factor into this. It's not enough to merely postulate how attacks might be mitigated by making identification of your server less efficient; there also needs to be evidence that HTTP header manipulation will effectively achieve that, and we don't see any evidence of this. I'm not just talking about server headers here; attackers don't care about HSTS, either. HSTS protects your clients, not your server. There are no HTTP headers which will mitigate attacks against your server.

I disagree (or misunderstand you), you can google some bad http headers in regard to the server security, just another example (from owasp, ibm, scanrepeat) about x-powered-by: https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-powered-by https://www.ibm.com/docs/en/control-desk/7.6.1.x?topic=checklist-vulnerability-server-leaks-information https://scanrepeat.com/web-security-knowledge-base/server-leaks-information-via-x-powered-by-http-response-header-fields

The X-Powered-By header describes the technologies used by the webserver. This information exposes the server to attackers. Using the information in this header, attackers can find vulnerabilities easier.

Why “Server Leaks Information via "X-Powered-By" HTTP Response Header Field(s)” can be dangerous

Vulnerability Summary: Server leaks information via X-Powered-By HTTP response header field(s)

Some information should optimally be always globally deactivated by default (or by the user or caddy module in a global manner).

The more time the thief spends time to unlock or break the lock (speaking in general, not just about the headers) depending on the difficulty, the more likely it is that he will leave and go to another house. Of course there is no unbreakable / unhackable software, but from a security standpoint there is always the possibility to harden the security in that regard.

In particular, the proposal that attackers are primarily constrained by infrastructure costs, and the conclusion that efficiency of target identification is the key to malicious actors being able to break even on their investment in illegal activities, is rather bold to be unsourced. Especially in a world of botnets, reflection attacks, and cheap commodity virtual infrastructure, some of those claims are just - with all due respect - less believable. I'm not going to touch your points about CVE discovery; that's all well reasoned enough, but I don't see how it changes anything with respect to HTTP headers.

I've never said anything about "..primarily constrained by infrastructure costs..", I've talked about efficiency. So your counter-claim seems to be that costs don't really matter to botnets & co.? I am confused, where do I find this citation? I would argue, logic sense (for some of them it will not matter as much and the other way around), but at least for the ones who try to reduce the costs to operate, because operating efficiently also means making more money, this is common business logic, also called "operational efficiency". So I really don't get your point. But this deserves another thread/topic, because I've been also going off-topic too much in this thread :D

If you'd like to indicate other defensive layers you think are appropriate - trust me, we're all ears. If there's a way to make Caddy more secure, we want that, too. I think I speak for everyone when I say we'd be thrilled to see another Gopher emerging with an interest in keeping Caddy measurably secure.

Just a few ideas from memory about security / features:

I have not nearly enough experience in the caddy source code or golang, or about the stuff caddy is doing in the background, so I can only do some random guess work for now, until I learn golang and the sourcecode of caddy (which might take me quite some time due to other projects). Otherwise I am accidentally talking about stuff that is already implemented or might not be applicable in the context of Go.

mholt commented 1 year ago

@iocron Can you please help me understand why this:

:80 {
    redir https://{host}{uri}
    ...
}

is not a sufficient solution for you? That's as "global" as it gets -- you only need it once, and you can set any of the headers you need to.

whitestrake commented 1 year ago

I disagree (or misunderstand you), you can google some bad http headers in regard to the server security, just another example (from owasp, ibm, scanrepeat) about x-powered-by:

The first link explicitly notes that attackers have other means of fingerprinting your tech stack. The second link never actually makes any claims that HTTP headers impact your security, only that it flags them in case a client is concerned. The third link makes the claim that "[the X-Powered-By header] allows attackers to exploit vulnerabilities of the technologies to attack your web application", which is a huge and unsupported stretch.

None of them actually go so far as to lay out any evidence that stripping HTTP headers will mitigate attacks.

Some information should optimally be always globally deactivated by default (or by the user or caddy module in a global manner).

Caddy already allows you to address this, although it doesn't do it by default. Doing something by default generally requires a decent standard of supporting evidence that it's a more useful or beneficial approach than the current operation.

The more time the thief spends time to unlock or break the lock (speaking in general, not just about the headers) depending on the difficulty, the more likely it is that he will leave and go to another house.

Unequivocally true, I just haven't seen any evidence yet that header manipulation meaningfully slows a thief. This is a controversial claim and there are many many voices across the internet that like to state one way or another - some with very strong confidence - but very little actual evidence.

Worth noting - security through obscurity does not actually reduce your attack surface and does not constitute 'hardening' like some of your other examples do.

I've never said anything about "..primarily constrained by infrastructure costs..", I've talked about efficiency.

You made the explicit claim above that attackers may give up on your website if you cost them too much to identify prior to attacking. I know you never said the literal words "primarily constrained by infrastructure costs"; I presupposed that you believed it because I'm not sure how your claim stands up if you don't think attackers have a cost constraint.

So your counter-claim seems to be that costs don't really matter to botnets & co.? I am confused, where do I find this citation?

I've already provided equivalent quantity and quality of evidence to dismiss the claims regarding cost constraints and exploit monetization strategy as has been provided to support them.

So I really don't get your point.

To clarify, my point is that your argument was not persuasive.

As it stands:

Is this not the case?