Fix a weakness that allows remote code execution via the Transmission RPC server using DNS rebinding: https://bugs.chromium.org/p/project-zero/issues/detail?id=1447 Patch adapted from Tavis Ormandy's patch on the Transmission master branch to the Transmission 2.92 release by Leo Famulari : https://github.com/transmission/transmission/pull/468/commits From fe2d3c6e75088f3d9b6040ce06da3d530358bc2f Mon Sep 17 00:00:00 2001 From: Tavis Ormandy Date: Thu, 11 Jan 2018 10:00:41 -0800 Subject: [PATCH] mitigate dns rebinding attacks against daemon --- libtransmission/quark.c | 2 + libtransmission/quark.h | 2 + libtransmission/rpc-server.c | 116 +++++++++++++++++++++++++++++++++++++---- libtransmission/rpc-server.h | 4 ++ libtransmission/session.c | 2 + libtransmission/transmission.h | 1 + libtransmission/web.c | 3 ++ 7 files changed, 121 insertions(+), 9 deletions(-) diff --git a/libtransmission/quark.c b/libtransmission/quark.c index 30cc2bca4..6de4bc221 100644 --- a/libtransmission/quark.c +++ b/libtransmission/quark.c @@ -297,6 +297,8 @@ static const struct tr_key_struct my_static[] = { "rpc-version-minimum", 19 }, { "rpc-whitelist", 13 }, { "rpc-whitelist-enabled", 21 }, + { "rpc-host-whitelist", 18 }, + { "rpc-host-whitelist-enabled", 26 }, { "scrape", 6 }, { "scrape-paused-torrents-enabled", 30 }, { "scrapeState", 11 }, diff --git a/libtransmission/quark.h b/libtransmission/quark.h index 7f5212733..21723dea9 100644 --- a/libtransmission/quark.h +++ b/libtransmission/quark.h @@ -299,6 +299,8 @@ enum TR_KEY_rpc_version_minimum, TR_KEY_rpc_whitelist, TR_KEY_rpc_whitelist_enabled, + TR_KEY_rpc_host_whitelist, + TR_KEY_rpc_host_whitelist_enabled, TR_KEY_scrape, TR_KEY_scrape_paused_torrents_enabled, TR_KEY_scrapeState, diff --git a/libtransmission/rpc-server.c b/libtransmission/rpc-server.c index a3485f3fa..a048dc8aa 100644 --- a/libtransmission/rpc-server.c +++ b/libtransmission/rpc-server.c @@ -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,49 @@ isAddressAllowed (const tr_rpc_server * server, const char * address) return false; } +static bool isHostnameAllowed(tr_rpc_server const* server, struct evhttp_request* req) +{ + /* If password auth is enabled, any hostname is permitted. */ + if (server->isPasswordEnabled) + { + return true; + } + + char const* 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. */ + if (server->isHostWhitelistEnabled) + { + 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 +708,23 @@ handle_request (struct evhttp_request * req, void * arg) handle_upload (req, server); } #ifdef REQUIRE_SESSION_ID + else if (!isHostnameAllowed(server, req)) + { + char* tmp = tr_strdup_printf( + "

Transmission received your request, but the hostname was unrecognized.

" + "

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

" + "

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

" + "

This requirement has been added to help prevent " + "DNS Rebinding " + "attacks.

"); + 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 +736,7 @@ handle_request (struct evhttp_request * req, void * arg) "
  • When you get this 409 error message, resend your request with the updated header" "

    " "

    This requirement has been added to help prevent " - "CSRF " + "CSRF " "attacks.

    " "

    %s: %s

    ", TR_RPC_SESSION_ID_HEADER, sessionId); @@ -875,19 +937,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 (char const* 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)) != NULL) tr_free (tmp); /* build the new whitelist entries */ @@ -896,7 +953,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 +966,21 @@ tr_rpcSetWhitelist (tr_rpc_server * server, const char * whitelistStr) } } +void tr_rpcSetHostWhitelist(tr_rpc_server* server, char const* whitelistStr) +{ + tr_rpcSetList(whitelistStr, &server->hostWhitelist); +} + +void tr_rpcSetWhitelist(tr_rpc_server* server, char const* 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 +1002,11 @@ 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,28 @@ 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 != NULL) + { + 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); diff --git a/libtransmission/rpc-server.h b/libtransmission/rpc-server.h index e0302c5ea..8c9e6b24e 100644 --- a/libtransmission/rpc-server.h +++ b/libtransmission/rpc-server.h @@ -49,6 +49,10 @@ 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, char const* whitelist); + void tr_rpcSetPassword (tr_rpc_server * server, const char * password); diff --git a/libtransmission/session.c b/libtransmission/session.c index 844cadba8..58b717913 100644 --- a/libtransmission/session.c +++ b/libtransmission/session.c @@ -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); diff --git a/libtransmission/transmission.h b/libtransmission/transmission.h index 4f76adfd6..e213a8f4e 100644 --- a/libtransmission/transmission.h +++ b/libtransmission/transmission.h @@ -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" diff --git a/libtransmission/web.c b/libtransmission/web.c index ee495e9fc..c7f062730 100644 --- a/libtransmission/web.c +++ b/libtransmission/web.c @@ -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";