From a82c9d154613a5f99394029c28a01148749735af Mon Sep 17 00:00:00 2001 From: Tavis Ormandy Date: Fri, 1 Dec 2017 12:49:25 -0800 Subject: [PATCH] mitigate dns rebinding attacks --- 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 861050057..87ed9f3ef 100644 --- a/libtransmission/quark.c +++ b/libtransmission/quark.c @@ -296,6 +296,8 @@ static struct tr_key_struct const 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 d40ab75fa..d82c7051b 100644 --- a/libtransmission/quark.h +++ b/libtransmission/quark.h @@ -298,6 +298,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 7c78f92ac..a33f07e10 100644 --- a/libtransmission/rpc-server.c +++ b/libtransmission/rpc-server.c @@ -51,6 +51,7 @@ struct tr_rpc_server bool isEnabled; bool isPasswordEnabled; bool isWhitelistEnabled; + bool isHostWhitelistEnabled; tr_port port; char* url; struct tr_address bindAddress; @@ -62,6 +63,7 @@ struct tr_rpc_server char* password; char* whitelistStr; tr_list* whitelist; + tr_list* hostWhitelist; int loginattempts; bool isStreamInitialized; @@ -547,6 +549,47 @@ static bool isAddressAllowed(tr_rpc_server const* server, char const* address) return false; } +static bool isHostnameAllowed(tr_rpc_server const* server, struct evhttp_request* req) +{ + const char *host = evhttp_find_header(req->input_headers, "Host"); + char *hostname; + + // If password auth is enabled, any hostname is permitted. + if (server->isPasswordEnabled) + return true; + + // No host header, invalid request. + if (!host) + return false; + + // Host header might include the port. + 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) { char const* ours = get_current_session_id(server); @@ -636,6 +679,22 @@ static void handle_request(struct evhttp_request* req, void* arg) #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)) { char const* sessionId = get_current_session_id(server); @@ -647,7 +706,7 @@ static void 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); @@ -844,17 +903,12 @@ char const* tr_rpcGetUrl(tr_rpc_server const* server) return server->url != NULL ? server->url : ""; } -void tr_rpcSetWhitelist(tr_rpc_server* server, char const* whitelistStr) +static void tr_rpcSetList(char const* whitelistStr, tr_list** list) { void* tmp; - /* 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)) != NULL) + while ((tmp = tr_list_pop_front(list)) != NULL) { tr_free(tmp); } @@ -866,7 +920,7 @@ void tr_rpcSetWhitelist(tr_rpc_server* server, char const* whitelistStr) size_t const 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) { @@ -889,6 +943,23 @@ void tr_rpcSetWhitelist(tr_rpc_server* server, char const* 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) +{ + void* tmp; + + /* keep the string */ + tmp = server->whitelistStr; + server->whitelistStr = tr_strdup(whitelistStr); + tr_free(tmp); + + tr_rpcSetList(whitelistStr, &server->whitelist); +} + char const* tr_rpcGetWhitelist(tr_rpc_server const* server) { return server->whitelistStr != NULL ? server->whitelistStr : ""; @@ -904,6 +975,11 @@ bool tr_rpcGetWhitelistEnabled(tr_rpc_server const* server) return server->isWhitelistEnabled; } +void tr_rpcSetHostWhitelistEnabled(tr_rpc_server* server, bool isEnabled) +{ + server->isHostWhitelistEnabled = isEnabled; +} + /**** ***** PASSWORD ****/ @@ -1054,6 +1130,28 @@ tr_rpc_server* tr_rpcInit(tr_session* session, tr_variant* settings) 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)) diff --git a/libtransmission/rpc-server.h b/libtransmission/rpc-server.h index 46e8a871f..ad1eb5204 100644 --- a/libtransmission/rpc-server.h +++ b/libtransmission/rpc-server.h @@ -42,6 +42,10 @@ void tr_rpcSetWhitelist(tr_rpc_server* server, char const* whitelist); char const* tr_rpcGetWhitelist(tr_rpc_server const* 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, char const* password); char const* tr_rpcGetPassword(tr_rpc_server const* server); diff --git a/libtransmission/session.c b/libtransmission/session.c index 86d054f7f..9a0b7c104 100644 --- a/libtransmission/session.c +++ b/libtransmission/session.c @@ -371,6 +371,8 @@ void 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 ac1871adb..08a24eca4 100644 --- a/libtransmission/transmission.h +++ b/libtransmission/transmission.h @@ -109,6 +109,7 @@ char const* 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,::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 cca888b66..a57c85457 100644 --- a/libtransmission/web.c +++ b/libtransmission/web.c @@ -678,6 +678,9 @@ char const* tr_webGetResponseStr(long code) case 417: return "Expectation Failed"; + case 421: + return "Misdirected Request"; + case 500: return "Internal Server Error"; -- 2.15.0.531.g2ccb3012c9-goog