Blob Blame History Raw
From 1d399272672308b05c964aedd61c11a2d4e2a3ac Mon Sep 17 00:00:00 2001
From: Jens Georg <mail@jensge.org>
Date: Mon, 10 May 2021 10:34:36 +0200
Subject: [PATCH 1/3] 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

 Conflicts:
	libgupnp/gupnp-context-private.h
	libgupnp/gupnp-context.c
---
 libgupnp/gupnp-context-private.h |  3 ++
 libgupnp/gupnp-context.c         | 51 ++++++++++++++++++++++++++++++++
 libgupnp/gupnp-service.c         | 13 ++++++++
 3 files changed, 67 insertions(+)

diff --git a/libgupnp/gupnp-context-private.h b/libgupnp/gupnp-context-private.h
index c088563..f0fe52e 100644
--- a/libgupnp/gupnp-context-private.h
+++ b/libgupnp/gupnp-context-private.h
@@ -36,6 +36,9 @@ _gupnp_context_add_server_handler_with_data (GUPnPContext *context,
                                              const char *path,
                                              AclServerHandler *data);
 
+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 641c59a..7f13f47 100644
--- a/libgupnp/gupnp-context.c
+++ b/libgupnp/gupnp-context.c
@@ -1558,3 +1558,54 @@ gupnp_context_remove_server_handler (GUPnPContext *context, const char *path)
 
         soup_server_remove_handler (context->priv->server, path);
 }
+
+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 0417852..1c7896b 100644
--- a/libgupnp/gupnp-service.c
+++ b/libgupnp/gupnp-service.c
@@ -949,6 +949,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");
-- 
2.31.1