summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJens Georg <mail@jensge.org>2021-05-10 10:34:36 +0200
committerJens Georg <mail@jensge.org>2021-05-24 08:49:22 +0000
commitca6ec9dcb26fd7a2a630eb6a68118659b589afac (patch)
treec7b40c0de3aa297b4c522417bdb2da7a875ecaae
parent4a68246fe21df78bd1c3a0460d2d1eaaaf5747f7 (diff)
downloadgupnp-ca6ec9dcb26fd7a2a630eb6a68118659b589afac.tar.gz
service: Validate host header
Make sure that the host header matches the ip:port of the context. This is in line with UDA (Host header is required and must match the location url) and DLNA 7.2.24.1 (All communication has to use ip addresses and not names) Prevents DNS rebinding attacs against agains UPnP services
-rw-r--r--libgupnp/gupnp-context-private.h3
-rw-r--r--libgupnp/gupnp-context.c51
-rw-r--r--libgupnp/gupnp-service.c13
3 files changed, 67 insertions, 0 deletions
diff --git a/libgupnp/gupnp-context-private.h b/libgupnp/gupnp-context-private.h
index 6aa1acd..a143885 100644
--- a/libgupnp/gupnp-context-private.h
+++ b/libgupnp/gupnp-context-private.h
@@ -43,6 +43,9 @@ gupnp_context_rewrite_uri_to_uri (GUPnPContext *context,
G_GNUC_INTERNAL gboolean
gupnp_context_ip_is_ours (GUPnPContext *context, const char *address);
+G_GNUC_INTERNAL gboolean
+gupnp_context_validate_host_header (GUPnPContext *context, const char *host);
+
G_END_DECLS
#endif /* GUPNP_CONTEXT_PRIVATE_H */
diff --git a/libgupnp/gupnp-context.c b/libgupnp/gupnp-context.c
index 58d3abf..66b1750 100644
--- a/libgupnp/gupnp-context.c
+++ b/libgupnp/gupnp-context.c
@@ -1709,3 +1709,54 @@ out:
return retval;
}
+
+gboolean
+gupnp_context_validate_host_header (GUPnPContext *context,
+ const char *host_header)
+{
+ gboolean retval = FALSE;
+ // Be lazy and let GUri do the heavy lifting here, such as stripping the
+ // [] from v6 addresses, splitting of the port etc.
+ char *uri_from_host = g_strconcat ("http://", host_header, NULL);
+
+ char *host = NULL;
+ int port = 0;
+ GError *error = NULL;
+
+ g_uri_split_network (uri_from_host,
+ G_URI_FLAGS_NONE,
+ NULL,
+ &host,
+ &port,
+ &error);
+
+ if (error != NULL) {
+ g_debug ("Failed to parse HOST header from request: %s",
+ error->message);
+ goto out;
+ }
+
+ const char *host_ip = gssdp_client_get_host_ip (GSSDP_CLIENT (context));
+ gint context_port = gupnp_context_get_port (context);
+
+ if (!g_str_equal (host, host_ip)) {
+ g_debug ("Mismatch between host header and host IP (%s, "
+ "expected: %s)",
+ host,
+ host_ip);
+ }
+
+ if (port != context_port) {
+ g_debug ("Mismatch between host header and host port (%d, "
+ "expected %d)",
+ port,
+ context_port);
+ }
+
+ retval = g_str_equal (host, host_ip) && port == context_port;
+
+out:
+ g_clear_error (&error);
+ g_free (uri_from_host);
+ return retval;
+}
diff --git a/libgupnp/gupnp-service.c b/libgupnp/gupnp-service.c
index b061c34..ad9d40d 100644
--- a/libgupnp/gupnp-service.c
+++ b/libgupnp/gupnp-service.c
@@ -954,6 +954,19 @@ control_server_handler (SoupServer *server,
context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
+ const char *host_header =
+ soup_message_headers_get_one (msg->request_headers, "Host");
+
+ if (!gupnp_context_validate_host_header (context, host_header)) {
+ g_warning ("Host header mismatch, expected %s:%d, got %s",
+ gssdp_client_get_host_ip (GSSDP_CLIENT (context)),
+ gupnp_context_get_port (context),
+ host_header);
+
+ soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
+ return;
+ }
+
/* Get action name */
soap_action = soup_message_headers_get_one (msg->request_headers,
"SOAPAction");