cd457e5
Fix a weakness that allows remote code execution via the Transmission
cd457e5
RPC server using DNS rebinding:
cd457e5
cd457e5
https://bugs.chromium.org/p/project-zero/issues/detail?id=1447
cd457e5
cd457e5
Patch adapted from Tavis Ormandy's patch on the Transmission master
cd457e5
branch to the Transmission 2.92 release by Leo Famulari
cd457e5
<leo@famulari.name>:
cd457e5
cd457e5
https://github.com/transmission/transmission/pull/468/commits
cd457e5
cd457e5
From fe2d3c6e75088f3d9b6040ce06da3d530358bc2f Mon Sep 17 00:00:00 2001
cd457e5
From: Tavis Ormandy <taviso@google.com>
cd457e5
Date: Thu, 11 Jan 2018 10:00:41 -0800
cd457e5
Subject: [PATCH] mitigate dns rebinding attacks against daemon
cd457e5
cd457e5
---
cd457e5
 libtransmission/quark.c        |   2 + 
cd457e5
 libtransmission/quark.h        |   2 + 
cd457e5
 libtransmission/rpc-server.c   | 116 +++++++++++++++++++++++++++++++++++++----
cd457e5
 libtransmission/rpc-server.h   |   4 ++
cd457e5
 libtransmission/session.c      |   2 + 
cd457e5
 libtransmission/transmission.h |   1 + 
cd457e5
 libtransmission/web.c          |   3 ++
cd457e5
 7 files changed, 121 insertions(+), 9 deletions(-)
cd457e5
cd457e5
diff --git a/libtransmission/quark.c b/libtransmission/quark.c
509226b
index 30cc2bca4..b4fd7aabd 100644
cd457e5
--- a/libtransmission/quark.c
cd457e5
+++ b/libtransmission/quark.c
509226b
@@ -289,6 +289,8 @@ static const struct tr_key_struct my_static[] =
509226b
   { "rpc-authentication-required", 27 },
509226b
   { "rpc-bind-address", 16 },
509226b
   { "rpc-enabled", 11 },
cd457e5
+  { "rpc-host-whitelist", 18 },
cd457e5
+  { "rpc-host-whitelist-enabled", 26 },
509226b
   { "rpc-password", 12 },
509226b
   { "rpc-port", 8 },
509226b
   { "rpc-url", 7 },
cd457e5
diff --git a/libtransmission/quark.h b/libtransmission/quark.h
509226b
index 7f5212733..17464be8f 100644
cd457e5
--- a/libtransmission/quark.h
cd457e5
+++ b/libtransmission/quark.h
509226b
@@ -291,6 +291,8 @@ enum
509226b
   TR_KEY_rpc_authentication_required,
509226b
   TR_KEY_rpc_bind_address,
509226b
   TR_KEY_rpc_enabled,
cd457e5
+  TR_KEY_rpc_host_whitelist,
cd457e5
+  TR_KEY_rpc_host_whitelist_enabled,
509226b
   TR_KEY_rpc_password,
509226b
   TR_KEY_rpc_port,
509226b
   TR_KEY_rpc_url,
cd457e5
diff --git a/libtransmission/rpc-server.c b/libtransmission/rpc-server.c
509226b
index a3485f3fa..292cd5fce 100644
cd457e5
--- a/libtransmission/rpc-server.c
cd457e5
+++ b/libtransmission/rpc-server.c
cd457e5
@@ -52,6 +52,7 @@ struct tr_rpc_server
cd457e5
     bool               isEnabled;
cd457e5
     bool               isPasswordEnabled;
cd457e5
     bool               isWhitelistEnabled;
cd457e5
+    bool               isHostWhitelistEnabled;
cd457e5
     tr_port            port;
cd457e5
     char             * url;
cd457e5
     struct in_addr     bindAddress;
cd457e5
@@ -63,6 +64,7 @@ struct tr_rpc_server
cd457e5
     char             * password;
cd457e5
     char             * whitelistStr;
cd457e5
     tr_list          * whitelist;
cd457e5
+    tr_list          * hostWhitelist;
cd457e5
 
cd457e5
     char             * sessionId;
cd457e5
     time_t             sessionIdExpiresAt;
cd457e5
@@ -588,6 +590,49 @@ isAddressAllowed (const tr_rpc_server * server, const char * address)
cd457e5
   return false;
cd457e5
 }
cd457e5
 
cd457e5
+static bool isHostnameAllowed(tr_rpc_server const* server, struct evhttp_request* req)
cd457e5
+{
cd457e5
+    /* If password auth is enabled, any hostname is permitted. */
cd457e5
+    if (server->isPasswordEnabled)
cd457e5
+    {
cd457e5
+        return true;
cd457e5
+    }
cd457e5
+
cd457e5
+    char const* const host = evhttp_find_header(req->input_headers, "Host");
cd457e5
+
509226b
+    // If whitelist is disabled, no restrictions.
509226b
+    if (!server->isHostWhitelistEnabled)
509226b
+        return true;
509226b
+
cd457e5
+    /* No host header, invalid request. */
cd457e5
+    if (host == NULL)
cd457e5
+    {
cd457e5
+        return false;
cd457e5
+    }
cd457e5
+
cd457e5
+    /* Host header might include the port. */
cd457e5
+    char* const hostname = tr_strndup(host, strcspn(host, ":"));
cd457e5
+
cd457e5
+    /* localhost or ipaddress is always acceptable. */
cd457e5
+    if (strcmp(hostname, "localhost") == 0 || strcmp(hostname, "localhost.") == 0 || tr_addressIsIP(hostname))
cd457e5
+    {
cd457e5
+        tr_free(hostname);
cd457e5
+        return true;
cd457e5
+    }
cd457e5
+
cd457e5
+    /* Otherwise, hostname must be whitelisted. */
509226b
+    for (tr_list* l = server->hostWhitelist; l != NULL; l = l->next) {
509226b
+        if (tr_wildmat(hostname, l->data))
cd457e5
+        {
509226b
+            tr_free(hostname);
509226b
+            return true;
cd457e5
+        }
cd457e5
+    }
cd457e5
+
cd457e5
+    tr_free(hostname);
cd457e5
+    return false;
cd457e5
+}
cd457e5
+
cd457e5
 static bool
cd457e5
 test_session_id (struct tr_rpc_server * server, struct evhttp_request * req)
cd457e5
 {
cd457e5
@@ -663,6 +708,23 @@ handle_request (struct evhttp_request * req, void * arg)
cd457e5
           handle_upload (req, server);
cd457e5
         }
cd457e5
 #ifdef REQUIRE_SESSION_ID
cd457e5
+        else if (!isHostnameAllowed(server, req))
cd457e5
+        {
cd457e5
+            char* tmp = tr_strdup_printf(
cd457e5
+                "

Transmission received your request, but the hostname was unrecognized.

"
cd457e5
+                "

To fix this, choose one of the following options:"

cd457e5
+                "
    "
cd457e5
+                "
  • Enable password authentication, then any hostname is allowed.
  • "
    cd457e5
    +                "
  • Add the hostname you want to use to the whitelist in settings.
  • "
    cd457e5
    +                "

    "
    cd457e5
    +                "

    If you're editing settings.json, see the 'rpc-host-whitelist' and 'rpc-host-whitelist-enabled' entries.

    "
    cd457e5
    +                "

    This requirement has been added to help prevent "

    cd457e5
    +                "DNS Rebinding "
    cd457e5
    +                "attacks.

    ");
    cd457e5
    +            send_simple_response(req, 421, tmp);
    cd457e5
    +            tr_free(tmp);
    cd457e5
    +        }
    cd457e5
    +
    cd457e5
           else if (!test_session_id (server, req))
    cd457e5
             {
    cd457e5
               const char * sessionId = get_current_session_id (server);
    cd457e5
    @@ -674,7 +736,7 @@ handle_request (struct evhttp_request * req, void * arg)
    cd457e5
                 "
  • When you get this 409 error message, resend your request with the updated header"
  • cd457e5
                 "

    "
    cd457e5
                 "

    This requirement has been added to help prevent "

    cd457e5
    -            "CSRF "
    cd457e5
    +            "CSRF "
    cd457e5
                 "attacks.

    "
    cd457e5
                 "

    %s: %s

    ",
    cd457e5
                 TR_RPC_SESSION_ID_HEADER, sessionId);
    cd457e5
    @@ -875,19 +937,14 @@ tr_rpcGetUrl (const tr_rpc_server * server)
    cd457e5
       return server->url ? server->url : "";
    cd457e5
     }
    cd457e5
     
    cd457e5
    -void
    cd457e5
    -tr_rpcSetWhitelist (tr_rpc_server * server, const char * whitelistStr)
    cd457e5
    +static void
    cd457e5
    +tr_rpcSetList (char const* whitelistStr, tr_list** list)
    cd457e5
     {
    cd457e5
       void * tmp;
    cd457e5
       const char * walk;
    cd457e5
     
    cd457e5
    -  /* keep the string */
    cd457e5
    -  tmp = server->whitelistStr;
    cd457e5
    -  server->whitelistStr = tr_strdup (whitelistStr);
    cd457e5
    -  tr_free (tmp);
    cd457e5
    -
    cd457e5
       /* clear out the old whitelist entries */
    cd457e5
    -  while ((tmp = tr_list_pop_front (&server->whitelist)))
    cd457e5
    +  while ((tmp = tr_list_pop_front (list)) != NULL)
    cd457e5
         tr_free (tmp);
    cd457e5
     
    cd457e5
       /* build the new whitelist entries */
    cd457e5
    @@ -896,7 +953,7 @@ tr_rpcSetWhitelist (tr_rpc_server * server, const char * whitelistStr)
    cd457e5
           const char * delimiters = " ,;";
    cd457e5
           const size_t len = strcspn (walk, delimiters);
    cd457e5
           char * token = tr_strndup (walk, len);
    cd457e5
    -      tr_list_append (&server->whitelist, token);
    cd457e5
    +      tr_list_append (list, token);
    cd457e5
           if (strcspn (token, "+-") < len)
    cd457e5
             tr_logAddNamedInfo (MY_NAME, "Adding address to whitelist: %s (And it has a '+' or '-'!  Are you using an old ACL by mistake?)", token);
    cd457e5
           else
    cd457e5
    @@ -909,6 +966,21 @@ tr_rpcSetWhitelist (tr_rpc_server * server, const char * whitelistStr)
    cd457e5
         }
    cd457e5
     }
    cd457e5
     
    cd457e5
    +void tr_rpcSetHostWhitelist(tr_rpc_server* server, char const* whitelistStr)
    cd457e5
    +{
    cd457e5
    +    tr_rpcSetList(whitelistStr, &server->hostWhitelist);
    cd457e5
    +}
    cd457e5
    +
    cd457e5
    +void tr_rpcSetWhitelist(tr_rpc_server* server, char const* whitelistStr)
    cd457e5
    +{
    cd457e5
    +    /* keep the string */
    cd457e5
    +    char* const tmp = server->whitelistStr;
    cd457e5
    +    server->whitelistStr = tr_strdup(whitelistStr);
    cd457e5
    +    tr_free(tmp);
    cd457e5
    +
    cd457e5
    +    tr_rpcSetList(whitelistStr, &server->whitelist);
    cd457e5
    +}
    cd457e5
    +
    cd457e5
     const char*
    cd457e5
     tr_rpcGetWhitelist (const tr_rpc_server * server)
    cd457e5
     {
    cd457e5
    @@ -930,6 +1002,11 @@ tr_rpcGetWhitelistEnabled (const tr_rpc_server * server)
    cd457e5
       return server->isWhitelistEnabled;
    cd457e5
     }
    cd457e5
     
    cd457e5
    +void tr_rpcSetHostWhitelistEnabled(tr_rpc_server* server, bool isEnabled)
    cd457e5
    +{
    cd457e5
    +    server->isHostWhitelistEnabled = isEnabled;
    cd457e5
    +}
    cd457e5
    +
    cd457e5
     /****
    cd457e5
     *****  PASSWORD
    cd457e5
     ****/
    cd457e5
    @@ -1063,6 +1140,28 @@ tr_rpcInit (tr_session  * session, tr_variant * settings)
    cd457e5
       else
    cd457e5
         tr_rpcSetWhitelistEnabled (s, boolVal);
    cd457e5
     
    cd457e5
    +  key = TR_KEY_rpc_host_whitelist_enabled;
    cd457e5
    +
    cd457e5
    +  if (!tr_variantDictFindBool(settings, key, &boolVal))
    cd457e5
    +  {
    cd457e5
    +      missing_settings_key(key);
    cd457e5
    +  }
    cd457e5
    +  else
    cd457e5
    +  {
    cd457e5
    +      tr_rpcSetHostWhitelistEnabled(s, boolVal);
    cd457e5
    +  }
    cd457e5
    +
    cd457e5
    +  key = TR_KEY_rpc_host_whitelist;
    cd457e5
    +
    cd457e5
    +  if (!tr_variantDictFindStr(settings, key, &str, NULL) && str != NULL)
    cd457e5
    +  {
    cd457e5
    +      missing_settings_key(key);
    cd457e5
    +  }
    cd457e5
    +  else
    cd457e5
    +  {
    cd457e5
    +      tr_rpcSetHostWhitelist(s, str);
    cd457e5
    +  }
    cd457e5
    +
    cd457e5
       key = TR_KEY_rpc_authentication_required;
    cd457e5
       if (!tr_variantDictFindBool (settings, key, &boolVal))
    cd457e5
         missing_settings_key (key);
    cd457e5
    diff --git a/libtransmission/rpc-server.h b/libtransmission/rpc-server.h
    cd457e5
    index e0302c5ea..8c9e6b24e 100644
    cd457e5
    --- a/libtransmission/rpc-server.h
    cd457e5
    +++ b/libtransmission/rpc-server.h
    cd457e5
    @@ -49,6 +49,10 @@ void            tr_rpcSetWhitelist (tr_rpc_server * server,
    cd457e5
     
    cd457e5
     const char*     tr_rpcGetWhitelist (const tr_rpc_server * server);
    cd457e5
     
    cd457e5
    +void tr_rpcSetHostWhitelistEnabled(tr_rpc_server* server, bool isEnabled);
    cd457e5
    +
    cd457e5
    +void tr_rpcSetHostWhitelist(tr_rpc_server* server, char const* whitelist);
    cd457e5
    +
    cd457e5
     void            tr_rpcSetPassword (tr_rpc_server * server,
    cd457e5
                                        const char *    password);
    cd457e5
     
    cd457e5
    diff --git a/libtransmission/session.c b/libtransmission/session.c
    cd457e5
    index 844cadba8..58b717913 100644
    cd457e5
    --- a/libtransmission/session.c
    cd457e5
    +++ b/libtransmission/session.c
    cd457e5
    @@ -359,6 +359,8 @@ tr_sessionGetDefaultSettings (tr_variant * d)
    cd457e5
       tr_variantDictAddStr  (d, TR_KEY_rpc_username,                    "");
    cd457e5
       tr_variantDictAddStr  (d, TR_KEY_rpc_whitelist,                   TR_DEFAULT_RPC_WHITELIST);
    cd457e5
       tr_variantDictAddBool (d, TR_KEY_rpc_whitelist_enabled,           true);
    cd457e5
    +  tr_variantDictAddStr(d, TR_KEY_rpc_host_whitelist, TR_DEFAULT_RPC_HOST_WHITELIST);
    cd457e5
    +  tr_variantDictAddBool(d, TR_KEY_rpc_host_whitelist_enabled, true);
    cd457e5
       tr_variantDictAddInt  (d, TR_KEY_rpc_port,                        atoi (TR_DEFAULT_RPC_PORT_STR));
    cd457e5
       tr_variantDictAddStr  (d, TR_KEY_rpc_url,                         TR_DEFAULT_RPC_URL_STR);
    cd457e5
       tr_variantDictAddBool (d, TR_KEY_scrape_paused_torrents_enabled,  true);
    cd457e5
    diff --git a/libtransmission/transmission.h b/libtransmission/transmission.h
    cd457e5
    index 4f76adfd6..e213a8f4e 100644
    cd457e5
    --- a/libtransmission/transmission.h
    cd457e5
    +++ b/libtransmission/transmission.h
    cd457e5
    @@ -123,6 +123,7 @@ const char* tr_getDefaultDownloadDir (void);
    cd457e5
     #define TR_DEFAULT_BIND_ADDRESS_IPV4        "0.0.0.0"
    cd457e5
     #define TR_DEFAULT_BIND_ADDRESS_IPV6             "::"
    cd457e5
     #define TR_DEFAULT_RPC_WHITELIST          "127.0.0.1"
    cd457e5
    +#define TR_DEFAULT_RPC_HOST_WHITELIST              ""
    cd457e5
     #define TR_DEFAULT_RPC_PORT_STR                "9091"
    cd457e5
     #define TR_DEFAULT_RPC_URL_STR       "/transmission/"
    cd457e5
     #define TR_DEFAULT_PEER_PORT_STR              "51413"
    cd457e5
    diff --git a/libtransmission/web.c b/libtransmission/web.c
    cd457e5
    index ee495e9fc..c7f062730 100644
    cd457e5
    --- a/libtransmission/web.c
    cd457e5
    +++ b/libtransmission/web.c
    cd457e5
    @@ -594,6 +594,7 @@ tr_webGetResponseStr (long code)
    cd457e5
           case 415: return "Unsupported Media Type";
    cd457e5
           case 416: return "Requested Range Not Satisfiable";
    cd457e5
           case 417: return "Expectation Failed";
    cd457e5
    +      case 421: return "Misdirected Request";
    cd457e5
           case 500: return "Internal Server Error";
    cd457e5
           case 501: return "Not Implemented";
    cd457e5
           case 502: return "Bad Gateway";