diff --git a/include/libwebsockets/lws-context-vhost.h b/include/libwebsockets/lws-context-vhost.h index 1746093c5..10627a2cb 100644 --- a/include/libwebsockets/lws-context-vhost.h +++ b/include/libwebsockets/lws-context-vhost.h @@ -134,6 +134,17 @@ enum lws_context_options { * example the ACME plugin was configured to fetch a cert, this lets * you bootstrap your vhost from having no cert to start with. */ + LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK = (1 << 27), + /**< (VH) On this vhost, if the connection is being upgraded, insist + * that there's a Host: header and that the contents match the vhost + * name + port (443 / 80 are assumed if no :port given based on if the + * connection is using TLS). + * + * By default, without this flag, on upgrade lws just checks that the + * Host: header was given without checking the contents... this is to + * allow lax hostname mappings like localhost / 127.0.0.1, and CNAME + * mappings like www.mysite.com / mysite.com + */ /****** add new things just above ---^ ******/ }; diff --git a/lib/roles/http/server/lejp-conf.c b/lib/roles/http/server/lejp-conf.c index cc6a76663..205023821 100644 --- a/lib/roles/http/server/lejp-conf.c +++ b/lib/roles/http/server/lejp-conf.c @@ -109,6 +109,7 @@ static const char * const paths_vhosts[] = { "vhosts[].ssl-client-option-clear", "vhosts[].tls13-ciphers", "vhosts[].client-tls13-ciphers", + "vhosts[].strict-host-check", }; enum lejp_vhost_paths { @@ -164,6 +165,7 @@ enum lejp_vhost_paths { LEJPVP_SSL_CLIENT_OPTION_CLEAR, LEJPVP_TLS13_CIPHERS, LEJPVP_CLIENT_TLS13_CIPHERS, + LEJPVP_FLAG_STRICT_HOST_CHECK, }; static const char * const parser_errs[] = { @@ -754,6 +756,15 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) return 0; + case LEJPVP_FLAG_STRICT_HOST_CHECK: + if (arg_to_bool(ctx->buf)) + a->info->options |= + LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK; + else + a->info->options &= + ~(LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK); + return 0; + case LEJPVP_ERROR_DOCUMENT_404: a->info->error_document_404 = a->p; break; diff --git a/lib/roles/http/server/server.c b/lib/roles/http/server/server.c index ef09a64fd..2bd9ab1b2 100644 --- a/lib/roles/http/server/server.c +++ b/lib/roles/http/server/server.c @@ -1545,6 +1545,75 @@ transaction_result_n: return lws_http_transaction_completed(wsi); } +int +lws_confirm_host_header(struct lws *wsi) +{ + struct lws_tokenize ts; + lws_tokenize_elem e; + char buf[128]; + int port = 80; + + /* + * this vhost wants us to validate what the + * client sent against our vhost name + */ + + if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) { + lwsl_info("%s: missing host on upgrade\n", __func__); + + return 1; + } + +#if defined(LWS_WITH_TLS) + if (wsi->tls.ssl) + port = 443; +#endif + + lws_tokenize_init(&ts, buf, LWS_TOKENIZE_F_DOT_NONTERM /* server.com */| + LWS_TOKENIZE_F_NO_FLOATS /* 1.server.com */| + LWS_TOKENIZE_F_MINUS_NONTERM /* a-b.com */); + ts.len = lws_hdr_copy(wsi, buf, sizeof(buf) - 1, WSI_TOKEN_HOST); + if (ts.len <= 0) { + lwsl_info("%s: missing or oversize host header\n", __func__); + return 1; + } + + if (lws_tokenize(&ts) != LWS_TOKZE_TOKEN) + goto bad_format; + + if (strncmp(ts.token, wsi->vhost->name, ts.token_len)) { + buf[(ts.token - buf) + ts.token_len] = '\0'; + lwsl_info("%s: '%s' in host hdr but vhost name %s\n", + __func__, ts.token, wsi->vhost->name); + return 1; + } + + e = lws_tokenize(&ts); + if (e == LWS_TOKZE_DELIMITER && ts.token[0] == ':') { + if (lws_tokenize(&ts) != LWS_TOKZE_INTEGER) + goto bad_format; + else + port = atoi(ts.token); + } else + if (e != LWS_TOKZE_ENDED) + goto bad_format; + + if (wsi->vhost->listen_port != port) { + lwsl_info("%s: host port %d mismatches vhost port %d\n", + __func__, port, wsi->vhost->listen_port); + return 1; + } + + lwsl_debug("%s: host header OK\n", __func__); + + return 0; + +bad_format: + lwsl_info("%s: bad host header format\n", __func__); + + return 1; +} + int lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len) { @@ -1733,6 +1802,11 @@ raw_transition: /* callback said 0, it was allowed */ + if (wsi->vhost->options & + LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK && + lws_confirm_host_header(wsi)) + goto bail_nuke_ah; + if (!strcasecmp(up, "websocket")) { #if defined(LWS_ROLE_WS) wsi->vhost->conn_stats.ws_upg++; diff --git a/lib/roles/ws/server-ws.c b/lib/roles/ws/server-ws.c index db5d8ee08..cef758e20 100644 --- a/lib/roles/ws/server-ws.c +++ b/lib/roles/ws/server-ws.c @@ -315,14 +315,6 @@ bad_conn_format: } } while (e > 0); - /* let's also confirm that Host at least exists for h1 */ - - if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) { - lwsl_err("%s: missing host: hdr on h1 ws upgrade\n", __func__); - - return 1; - } - #if defined(LWS_WITH_HTTP2) check_protocol: #endif diff --git a/minimal-examples/http-server/minimal-http-server-tls/minimal-http-server-tls.c b/minimal-examples/http-server/minimal-http-server-tls/minimal-http-server-tls.c index a209bdee6..9ab2140ac 100644 --- a/minimal-examples/http-server/minimal-http-server-tls/minimal-http-server-tls.c +++ b/minimal-examples/http-server/minimal-http-server-tls/minimal-http-server-tls.c @@ -76,6 +76,9 @@ int main(int argc, const char **argv) info.ssl_cert_filepath = "localhost-100y.cert"; info.ssl_private_key_filepath = "localhost-100y.key"; + if (lws_cmdline_option(argc, argv, "-h")) + info.options |= LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK; + context = lws_create_context(&info); if (!context) { lwsl_err("lws init failed\n"); diff --git a/minimal-examples/ws-server/minimal-ws-server/README.md b/minimal-examples/ws-server/minimal-ws-server/README.md index f02a6679e..9b0a094bf 100644 --- a/minimal-examples/ws-server/minimal-ws-server/README.md +++ b/minimal-examples/ws-server/minimal-ws-server/README.md @@ -12,6 +12,7 @@ Option|Meaning ---|--- -d|Set logging verbosity -s|Serve using TLS selfsigned cert (ie, connect to it with https://...) +-h|Strict Host: header checking against vhost name (localhost) and port ## usage diff --git a/minimal-examples/ws-server/minimal-ws-server/minimal-ws-server.c b/minimal-examples/ws-server/minimal-ws-server/minimal-ws-server.c index 19fa87034..fe7fd2989 100644 --- a/minimal-examples/ws-server/minimal-ws-server/minimal-ws-server.c +++ b/minimal-examples/ws-server/minimal-ws-server/minimal-ws-server.c @@ -79,6 +79,7 @@ int main(int argc, const char **argv) info.port = 7681; info.mounts = &mount; info.protocols = protocols; + info.vhost_name = "localhost"; info.ws_ping_pong_interval = 10; if (lws_cmdline_option(argc, argv, "-s")) { @@ -88,6 +89,9 @@ int main(int argc, const char **argv) info.ssl_private_key_filepath = "localhost-100y.key"; } + if (lws_cmdline_option(argc, argv, "-h")) + info.options |= LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK; + context = lws_create_context(&info); if (!context) { lwsl_err("lws init failed\n");