@marcel91200 / caddy Mirror · read-only / commits / a3bdc222
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 14, 05:15 UTC

admin: Always enforce Host header checks

With a simple heuristic for loopback addresses, we can enable this by default without adding unnecessary inconvenience.

Matthew Holt committed Apr 10, 2020 at 17:31 UTC a3bdc22234b75e9420f8810918072fa34732ffb7
3 files changed +69 -26
admin.go
+37 -19
@@ -21,6 +21,7 @@ import (
21 "expvar"
22 "fmt"
23 "io"
24 + "net"
25 "net/http"
26 "net/http/pprof"
27 "net/url"
@@ -78,27 +79,27 @@ type ConfigSettings struct {
79
80 // listenAddr extracts a singular listen address from ac.Listen,
81 // returning the network and the address of the listener.
81 -func (admin AdminConfig) listenAddr() (string, string, error) {
82 +func (admin AdminConfig) listenAddr() (NetworkAddress, error) {
83 input := admin.Listen
84 if input == "" {
85 input = DefaultAdminListen
86 }
87 listenAddr, err := ParseNetworkAddress(input)
88 if err != nil {
88 - return "", "", fmt.Errorf("parsing admin listener address: %v", err)
89 + return NetworkAddress{}, fmt.Errorf("parsing admin listener address: %v", err)
90 }
91 if listenAddr.PortRangeSize() != 1 {
91 - return "", "", fmt.Errorf("admin endpoint must have exactly one address; cannot listen on %v", listenAddr)
92 + return NetworkAddress{}, fmt.Errorf("admin endpoint must have exactly one address; cannot listen on %v", listenAddr)
93 }
93 - return listenAddr.Network, listenAddr.JoinHostPort(0), nil
94 + return listenAddr, nil
95 }
96
97 // newAdminHandler reads admin's config and returns an http.Handler suitable
98 // for use in an admin endpoint server, which will be listening on listenAddr.
98 -func (admin AdminConfig) newAdminHandler(listenAddr string) adminHandler {
99 +func (admin AdminConfig) newAdminHandler(addr NetworkAddress) adminHandler {
100 muxWrap := adminHandler{
101 enforceOrigin: admin.EnforceOrigin,
101 - allowedOrigins: admin.allowedOrigins(listenAddr),
102 + allowedOrigins: admin.allowedOrigins(addr),
103 mux: http.NewServeMux(),
104 }
105
@@ -140,14 +141,30 @@ func (admin AdminConfig) newAdminHandler(listenAddr string) adminHandler {
141 // If admin.Origins is nil (null), the provided listen address
142 // will be used as the default origin. If admin.Origins is
143 // empty, no origins will be allowed, effectively bricking the
143 -// endpoint, but whatever.
144 -func (admin AdminConfig) allowedOrigins(listen string) []string {
144 +// endpoint for non-unix-socket endpoints, but whatever.
145 +func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []string {
146 uniqueOrigins := make(map[string]struct{})
147 for _, o := range admin.Origins {
148 uniqueOrigins[o] = struct{}{}
149 }
150 if admin.Origins == nil {
150 - uniqueOrigins[listen] = struct{}{}
151 + if addr.isLoopback() {
152 + if addr.IsUnixNetwork() {
153 + // RFC 2616, Section 14.26:
154 + // "A client MUST include a Host header field in all HTTP/1.1 request
155 + // messages. If the requested URI does not include an Internet host
156 + // name for the service being requested, then the Host header field MUST
157 + // be given with an empty value."
158 + uniqueOrigins[""] = struct{}{}
159 + } else {
160 + uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{}
161 + uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{}
162 + uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{}
163 + }
164 + }
165 + if !addr.IsUnixNetwork() {
166 + uniqueOrigins[addr.JoinHostPort(0)] = struct{}{}
167 + }
168 }
169 allowed := make([]string, 0, len(uniqueOrigins))
170 for origin := range uniqueOrigins {
@@ -195,14 +212,14 @@ func replaceAdmin(cfg *Config) error {
212 }
213
214 // extract a singular listener address
198 - netw, addr, err := adminConfig.listenAddr()
215 + addr, err := adminConfig.listenAddr()
216 if err != nil {
217 return err
218 }
219
220 handler := adminConfig.newAdminHandler(addr)
221
205 - ln, err := Listen(netw, addr)
222 + ln, err := Listen(addr.Network, addr.JoinHostPort(0))
223 if err != nil {
224 return err
225 }
@@ -219,7 +236,7 @@ func replaceAdmin(cfg *Config) error {
236
237 Log().Named("admin").Info(
238 "admin endpoint started",
222 - zap.String("address", addr),
239 + zap.String("address", addr.String()),
240 zap.Bool("enforce_origin", adminConfig.EnforceOrigin),
241 zap.Strings("origins", handler.allowedOrigins),
242 )
@@ -263,6 +280,7 @@ type adminHandler struct {
280 func (h adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
281 Log().Named("admin.api").Info("received request",
282 zap.String("method", r.Method),
283 + zap.String("host", r.Host),
284 zap.String("uri", r.RequestURI),
285 zap.String("remote_addr", r.RemoteAddr),
286 zap.Reflect("headers", r.Header),
@@ -274,14 +292,14 @@ func (h adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
292 // be called more than once per request, for example if a request
293 // is rewritten (i.e. internal redirect).
294 func (h adminHandler) serveHTTP(w http.ResponseWriter, r *http.Request) {
277 - if h.enforceOrigin {
278 - // DNS rebinding mitigation
279 - err := h.checkHost(r)
280 - if err != nil {
281 - h.handleError(w, r, err)
282 - return
283 - }
295 + // DNS rebinding mitigation
296 + err := h.checkHost(r)
297 + if err != nil {
298 + h.handleError(w, r, err)
299 + return
300 + }
301
302 + if h.enforceOrigin {
303 // cross-site mitigation
304 origin, err := h.checkOrigin(r)
305 if err != nil {
listeners.go
+24 -7
@@ -289,14 +289,31 @@ func (na NetworkAddress) PortRangeSize() uint {
289 return (na.EndPort - na.StartPort) + 1
290 }
291
292 +func (na NetworkAddress) isLoopback() bool {
293 + if na.IsUnixNetwork() {
294 + return true
295 + }
296 + if na.Host == "localhost" {
297 + return true
298 + }
299 + if ip := net.ParseIP(na.Host); ip != nil {
300 + return ip.IsLoopback()
301 + }
302 + return false
303 +}
304 +
305 +func (na NetworkAddress) port() string {
306 + if na.StartPort == na.EndPort {
307 + return strconv.FormatUint(uint64(na.StartPort), 10)
308 + }
309 + return fmt.Sprintf("%d-%d", na.StartPort, na.EndPort)
310 +}
311 +
312 // String reconstructs the address string to the form expected
293 -// by ParseNetworkAddress().
313 +// by ParseNetworkAddress(). If the address is a unix socket,
314 +// any non-zero port will be dropped.
315 func (na NetworkAddress) String() string {
295 - port := strconv.FormatUint(uint64(na.StartPort), 10)
296 - if na.StartPort != na.EndPort {
297 - port += "-" + strconv.FormatUint(uint64(na.EndPort), 10)
298 - }
299 - return JoinNetworkAddress(na.Network, na.Host, port)
316 + return JoinNetworkAddress(na.Network, na.Host, na.port())
317 }
318
319 func isUnixNetwork(netw string) bool {
@@ -378,7 +395,7 @@ func JoinNetworkAddress(network, host, port string) string {
395 if network != "" {
396 a = network + "/"
397 }
381 - if host != "" && port == "" {
398 + if (host != "" && port == "") || isUnixNetwork(network) {
399 a += host
400 } else if port != "" {
401 a += net.JoinHostPort(host, port)
listeners_test.go
+8
@@ -138,6 +138,14 @@ func TestJoinNetworkAddress(t *testing.T) {
138 network: "unix", host: "/foo/bar", port: "",
139 expect: "unix//foo/bar",
140 },
141 + {
142 + network: "unix", host: "/foo/bar", port: "0",
143 + expect: "unix//foo/bar",
144 + },
145 + {
146 + network: "unix", host: "/foo/bar", port: "1234",
147 + expect: "unix//foo/bar",
148 + },
149 {
150 network: "", host: "::1", port: "1234",
151 expect: "[::1]:1234",