From cf7173df930cfa7ac1b1b0e9027c1deffd0b3c84 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 | 119 ++++++++++++++++++++++++++++++--- libtransmission/rpc-server.h | 4 ++ libtransmission/session.c | 2 + libtransmission/transmission.h | 1 + libtransmission/web.c | 3 + 7 files changed, 124 insertions(+), 9 deletions(-) diff --git a/libtransmission/quark.c b/libtransmission/quark.c index 861050057..e19ac9a2f 100644 --- a/libtransmission/quark.c +++ b/libtransmission/quark.c @@ -288,6 +288,8 @@ static struct tr_key_struct const 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 }, diff --git a/libtransmission/quark.h b/libtransmission/quark.h index d40ab75fa..9dc534560 100644 --- a/libtransmission/quark.h +++ b/libtransmission/quark.h @@ -290,6 +290,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, diff --git a/libtransmission/rpc-server.c b/libtransmission/rpc-server.c index 7c78f92ac..e8d37d5db 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,52 @@ 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) +{ + /* 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; + } + + 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. */ + 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 +684,22 @@ static void handle_request(struct evhttp_request* req, void* arg) #ifdef REQUIRE_SESSION_ID + else if (!isHostnameAllowed(server, req)) + { + char* const 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 +711,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 +908,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 +925,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 +948,21 @@ 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) +{ + /* keep the string */ + char* const 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 +978,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 +1133,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.40.0