]> granicus.if.org Git - transmission/commitdiff
mitigate dns rebinding attacks against daemon
authorTavis Ormandy <taviso@google.com>
Thu, 11 Jan 2018 18:00:41 +0000 (10:00 -0800)
committerMike Gelfand <mikedld@mikedld.com>
Mon, 15 Jan 2018 22:12:56 +0000 (01:12 +0300)
libtransmission/quark.c
libtransmission/quark.h
libtransmission/rpc-server.c
libtransmission/rpc-server.h
libtransmission/session.c
libtransmission/transmission.h
libtransmission/web.c

index 30cc2bca4c8d3f68e900edbb258caaf1996578c6..b4fd7aabd31394609fbcb4782c753e20369d636f 100644 (file)
@@ -289,6 +289,8 @@ static const struct tr_key_struct my_static[] =
   { "rpc-authentication-required", 27 },
   { "rpc-bind-address", 16 },
   { "rpc-enabled", 11 },
+  { "rpc-host-whitelist", 18 },
+  { "rpc-host-whitelist-enabled", 26 },
   { "rpc-password", 12 },
   { "rpc-port", 8 },
   { "rpc-url", 7 },
index 7f5212733babdb6da4c3b6d9896af20c3476cab6..17464be8f3f4937d5ba737a90cc698e1875dc7f6 100644 (file)
@@ -291,6 +291,8 @@ enum
   TR_KEY_rpc_authentication_required,
   TR_KEY_rpc_bind_address,
   TR_KEY_rpc_enabled,
+  TR_KEY_rpc_host_whitelist,
+  TR_KEY_rpc_host_whitelist_enabled,
   TR_KEY_rpc_password,
   TR_KEY_rpc_port,
   TR_KEY_rpc_url,
index a3485f3fa2538990e76631ffaf740c49264ea9d4..c80a629caa242ae65f8053b4f36027a7c2bdb551 100644 (file)
@@ -52,6 +52,7 @@ struct tr_rpc_server
     bool               isEnabled;
     bool               isPasswordEnabled;
     bool               isWhitelistEnabled;
+    bool               isHostWhitelistEnabled;
     tr_port            port;
     char             * url;
     struct in_addr     bindAddress;
@@ -63,6 +64,7 @@ struct tr_rpc_server
     char             * password;
     char             * whitelistStr;
     tr_list          * whitelist;
+    tr_list          * hostWhitelist;
 
     char             * sessionId;
     time_t             sessionIdExpiresAt;
@@ -588,6 +590,47 @@ isAddressAllowed (const tr_rpc_server * server, const char * address)
   return false;
 }
 
+static bool
+isHostnameAllowed (const tr_rpc_server * server, struct evhttp_request * req)
+{
+  /* If password auth is enabled, any hostname is permitted. */
+  if (server->isPasswordEnabled)
+    return true;
+
+  /* If whitelist is disabled, no restrictions. */
+  if (!server->isHostWhitelistEnabled)
+    return true;
+
+  const char * const host = evhttp_find_header (req->input_headers, "Host");
+
+  /* No host header, invalid request. */
+  if (host == NULL)
+    return false;
+
+  /* Host header might include the port. */
+  char * const hostname = tr_strndup (host, strcspn (host, ":"));
+
+  /* localhost or ipaddress is always acceptable. */
+  if (strcmp (hostname, "localhost") == 0 || strcmp (hostname, "localhost.") == 0 || tr_addressIsIP (hostname))
+    {
+      tr_free (hostname);
+      return true;
+    }
+
+  /* Otherwise, hostname must be whitelisted. */
+  for (tr_list * l = server->hostWhitelist; l != NULL; l = l->next)
+    {
+      if (tr_wildmat (hostname, l->data))
+        {
+          tr_free (hostname);
+          return true;
+        }
+    }
+
+  tr_free(hostname);
+  return false;
+}
+
 static bool
 test_session_id (struct tr_rpc_server * server, struct evhttp_request * req)
 {
@@ -663,6 +706,22 @@ handle_request (struct evhttp_request * req, void * arg)
           handle_upload (req, server);
         }
 #ifdef REQUIRE_SESSION_ID
+      else if (!isHostnameAllowed (server, req))
+        {
+          char * const tmp = tr_strdup_printf (
+            "<p>Transmission received your request, but the hostname was unrecognized.</p>"
+            "<p>To fix this, choose one of the following options:"
+            "<ul>"
+            "<li>Enable password authentication, then any hostname is allowed.</li>"
+            "<li>Add the hostname you want to use to the whitelist in settings.</li>"
+            "</ul></p>"
+            "<p>If you're editing settings.json, see the 'rpc-host-whitelist' and 'rpc-host-whitelist-enabled' entries.</p>"
+            "<p>This requirement has been added to help prevent "
+            "<a href=\"https://en.wikipedia.org/wiki/DNS_rebinding\">DNS Rebinding</a> "
+            "attacks.</p>");
+          send_simple_response (req, 421, tmp);
+          tr_free (tmp);
+        }
       else if (!test_session_id (server, req))
         {
           const char * sessionId = get_current_session_id (server);
@@ -674,7 +733,7 @@ handle_request (struct evhttp_request * req, void * arg)
             "<li> When you get this 409 error message, resend your request with the updated header"
             "</ol></p>"
             "<p>This requirement has been added to help prevent "
-            "<a href=\"http://en.wikipedia.org/wiki/Cross-site_request_forgery\">CSRF</a> "
+            "<a href=\"https://en.wikipedia.org/wiki/Cross-site_request_forgery\">CSRF</a> "
             "attacks.</p>"
             "<p><code>%s: %s</code></p>",
             TR_RPC_SESSION_ID_HEADER, sessionId);
@@ -875,19 +934,14 @@ tr_rpcGetUrl (const tr_rpc_server * server)
   return server->url ? server->url : "";
 }
 
-void
-tr_rpcSetWhitelist (tr_rpc_server * server, const char * whitelistStr)
+static void
+tr_rpcSetList (const char * whitelistStr, tr_list ** list)
 {
   void * tmp;
   const char * walk;
 
-  /* keep the string */
-  tmp = server->whitelistStr;
-  server->whitelistStr = tr_strdup (whitelistStr);
-  tr_free (tmp);
-
   /* clear out the old whitelist entries */
-  while ((tmp = tr_list_pop_front (&server->whitelist)))
+  while ((tmp = tr_list_pop_front (list)))
     tr_free (tmp);
 
   /* build the new whitelist entries */
@@ -896,7 +950,7 @@ tr_rpcSetWhitelist (tr_rpc_server * server, const char * whitelistStr)
       const char * delimiters = " ,;";
       const size_t len = strcspn (walk, delimiters);
       char * token = tr_strndup (walk, len);
-      tr_list_append (&server->whitelist, token);
+      tr_list_append (list, token);
       if (strcspn (token, "+-") < len)
         tr_logAddNamedInfo (MY_NAME, "Adding address to whitelist: %s (And it has a '+' or '-'!  Are you using an old ACL by mistake?)", token);
       else
@@ -909,6 +963,23 @@ tr_rpcSetWhitelist (tr_rpc_server * server, const char * whitelistStr)
     }
 }
 
+void
+tr_rpcSetHostWhitelist (tr_rpc_server* server, const char * whitelistStr)
+{
+  tr_rpcSetList (whitelistStr, &server->hostWhitelist);
+}
+
+void
+tr_rpcSetWhitelist (tr_rpc_server * server, const char * whitelistStr)
+{
+  /* keep the string */
+  char* const tmp = server->whitelistStr;
+  server->whitelistStr = tr_strdup (whitelistStr);
+  tr_free (tmp);
+
+  tr_rpcSetList (whitelistStr, &server->whitelist);
+}
+
 const char*
 tr_rpcGetWhitelist (const tr_rpc_server * server)
 {
@@ -930,6 +1001,12 @@ tr_rpcGetWhitelistEnabled (const tr_rpc_server * server)
   return server->isWhitelistEnabled;
 }
 
+void
+tr_rpcSetHostWhitelistEnabled(tr_rpc_server * server, bool isEnabled)
+{
+  server->isHostWhitelistEnabled = isEnabled;
+}
+
 /****
 *****  PASSWORD
 ****/
@@ -1063,6 +1140,18 @@ tr_rpcInit (tr_session  * session, tr_variant * settings)
   else
     tr_rpcSetWhitelistEnabled (s, boolVal);
 
+  key = TR_KEY_rpc_host_whitelist_enabled;
+  if (!tr_variantDictFindBool (settings, key, &boolVal))
+    missing_settings_key (key);
+  else
+    tr_rpcSetHostWhitelistEnabled (s, boolVal);
+
+  key = TR_KEY_rpc_host_whitelist;
+  if (!tr_variantDictFindStr (settings, key, &str, NULL) && str)
+    missing_settings_key (key);
+  else
+    tr_rpcSetHostWhitelist (s, str);
+
   key = TR_KEY_rpc_authentication_required;
   if (!tr_variantDictFindBool (settings, key, &boolVal))
     missing_settings_key (key);
index e0302c5ea738f21018b99583201a49dc95bb01ba..c91d160f44662658409358c6dec2d1f637d22fd7 100644 (file)
@@ -49,6 +49,12 @@ void            tr_rpcSetWhitelist (tr_rpc_server * server,
 
 const char*     tr_rpcGetWhitelist (const tr_rpc_server * server);
 
+void            tr_rpcSetHostWhitelistEnabled (tr_rpc_server * server,
+                                               bool            isEnabled);
+
+void            tr_rpcSetHostWhitelist (tr_rpc_server * server,
+                                        const char *    whitelist);
+
 void            tr_rpcSetPassword (tr_rpc_server * server,
                                    const char *    password);
 
index 844cadba88b9f4ed8c5a7630921e353d14280788..bb60c23b53f9c1a29823dff50d2a4b71e94a070c 100644 (file)
@@ -359,6 +359,8 @@ tr_sessionGetDefaultSettings (tr_variant * d)
   tr_variantDictAddStr  (d, TR_KEY_rpc_username,                    "");
   tr_variantDictAddStr  (d, TR_KEY_rpc_whitelist,                   TR_DEFAULT_RPC_WHITELIST);
   tr_variantDictAddBool (d, TR_KEY_rpc_whitelist_enabled,           true);
+  tr_variantDictAddStr  (d, TR_KEY_rpc_host_whitelist,              TR_DEFAULT_RPC_HOST_WHITELIST);
+  tr_variantDictAddBool (d, TR_KEY_rpc_host_whitelist_enabled,      true);
   tr_variantDictAddInt  (d, TR_KEY_rpc_port,                        atoi (TR_DEFAULT_RPC_PORT_STR));
   tr_variantDictAddStr  (d, TR_KEY_rpc_url,                         TR_DEFAULT_RPC_URL_STR);
   tr_variantDictAddBool (d, TR_KEY_scrape_paused_torrents_enabled,  true);
index 4f76adfd622dd18f4651f110ab52a3bc8e036e10..e213a8f4e02d3aadf40ec52003e2eca0a1903110 100644 (file)
@@ -123,6 +123,7 @@ const char* tr_getDefaultDownloadDir (void);
 #define TR_DEFAULT_BIND_ADDRESS_IPV4        "0.0.0.0"
 #define TR_DEFAULT_BIND_ADDRESS_IPV6             "::"
 #define TR_DEFAULT_RPC_WHITELIST          "127.0.0.1"
+#define TR_DEFAULT_RPC_HOST_WHITELIST              ""
 #define TR_DEFAULT_RPC_PORT_STR                "9091"
 #define TR_DEFAULT_RPC_URL_STR       "/transmission/"
 #define TR_DEFAULT_PEER_PORT_STR              "51413"
index ee495e9fcb185ab04d4d3874c7ccb41be853dcbc..c7f06273040ba073c1bb6606ad3b2a43ed69be18 100644 (file)
@@ -594,6 +594,7 @@ tr_webGetResponseStr (long code)
       case 415: return "Unsupported Media Type";
       case 416: return "Requested Range Not Satisfiable";
       case 417: return "Expectation Failed";
+      case 421: return "Misdirected Request";
       case 500: return "Internal Server Error";
       case 501: return "Not Implemented";
       case 502: return "Bad Gateway";