|
Lines 1-302
Link Here
|
| 1 |
Fix a weakness that allows remote code execution via the Transmission |
|
|
| 2 |
RPC server using DNS rebinding: |
| 3 |
|
| 4 |
https://bugs.chromium.org/p/project-zero/issues/detail?id=1447 |
| 5 |
|
| 6 |
Patch adapted from Tavis Ormandy's patch on the Transmission master |
| 7 |
branch to the Transmission 2.92 release by Leo Famulari |
| 8 |
<leo@famulari.name>: |
| 9 |
|
| 10 |
https://github.com/transmission/transmission/pull/468/commits |
| 11 |
|
| 12 |
From fe2d3c6e75088f3d9b6040ce06da3d530358bc2f Mon Sep 17 00:00:00 2001 |
| 13 |
From: Tavis Ormandy <taviso@google.com> |
| 14 |
Date: Thu, 11 Jan 2018 10:00:41 -0800 |
| 15 |
Subject: [PATCH] mitigate dns rebinding attacks against daemon |
| 16 |
|
| 17 |
--- |
| 18 |
libtransmission/quark.c | 2 + |
| 19 |
libtransmission/quark.h | 2 + |
| 20 |
libtransmission/rpc-server.c | 116 +++++++++++++++++++++++++++++++++++++---- |
| 21 |
libtransmission/rpc-server.h | 4 ++ |
| 22 |
libtransmission/session.c | 2 + |
| 23 |
libtransmission/transmission.h | 1 + |
| 24 |
libtransmission/web.c | 3 ++ |
| 25 |
7 files changed, 121 insertions(+), 9 deletions(-) |
| 26 |
|
| 27 |
diff --git a/libtransmission/quark.c b/libtransmission/quark.c |
| 28 |
index 30cc2bca4..b4fd7aabd 100644 |
| 29 |
--- libtransmission/quark.c.orig |
| 30 |
+++ libtransmission/quark.c |
| 31 |
@@ -289,6 +289,8 @@ static const struct tr_key_struct my_static[] = |
| 32 |
{ "rpc-authentication-required", 27 }, |
| 33 |
{ "rpc-bind-address", 16 }, |
| 34 |
{ "rpc-enabled", 11 }, |
| 35 |
+ { "rpc-host-whitelist", 18 }, |
| 36 |
+ { "rpc-host-whitelist-enabled", 26 }, |
| 37 |
{ "rpc-password", 12 }, |
| 38 |
{ "rpc-port", 8 }, |
| 39 |
{ "rpc-url", 7 }, |
| 40 |
diff --git a/libtransmission/quark.h b/libtransmission/quark.h |
| 41 |
index 7f5212733..17464be8f 100644 |
| 42 |
--- libtransmission/quark.h.orig |
| 43 |
+++ libtransmission/quark.h |
| 44 |
@@ -291,6 +291,8 @@ enum |
| 45 |
TR_KEY_rpc_authentication_required, |
| 46 |
TR_KEY_rpc_bind_address, |
| 47 |
TR_KEY_rpc_enabled, |
| 48 |
+ TR_KEY_rpc_host_whitelist, |
| 49 |
+ TR_KEY_rpc_host_whitelist_enabled, |
| 50 |
TR_KEY_rpc_password, |
| 51 |
TR_KEY_rpc_port, |
| 52 |
TR_KEY_rpc_url, |
| 53 |
diff --git a/libtransmission/rpc-server.c b/libtransmission/rpc-server.c |
| 54 |
index a3485f3fa..292cd5fce 100644 |
| 55 |
--- libtransmission/rpc-server.c.orig |
| 56 |
+++ libtransmission/rpc-server.c |
| 57 |
@@ -52,6 +52,7 @@ struct tr_rpc_server |
| 58 |
bool isEnabled; |
| 59 |
bool isPasswordEnabled; |
| 60 |
bool isWhitelistEnabled; |
| 61 |
+ bool isHostWhitelistEnabled; |
| 62 |
tr_port port; |
| 63 |
char * url; |
| 64 |
struct in_addr bindAddress; |
| 65 |
@@ -63,6 +64,7 @@ struct tr_rpc_server |
| 66 |
char * password; |
| 67 |
char * whitelistStr; |
| 68 |
tr_list * whitelist; |
| 69 |
+ tr_list * hostWhitelist; |
| 70 |
|
| 71 |
char * sessionId; |
| 72 |
time_t sessionIdExpiresAt; |
| 73 |
@@ -588,6 +590,49 @@ isAddressAllowed (const tr_rpc_server * server, const char * address) |
| 74 |
return false; |
| 75 |
} |
| 76 |
|
| 77 |
+static bool isHostnameAllowed(tr_rpc_server const* server, struct evhttp_request* req) |
| 78 |
+{ |
| 79 |
+ /* If password auth is enabled, any hostname is permitted. */ |
| 80 |
+ if (server->isPasswordEnabled) |
| 81 |
+ { |
| 82 |
+ return true; |
| 83 |
+ } |
| 84 |
+ |
| 85 |
+ char const* const host = evhttp_find_header(req->input_headers, "Host"); |
| 86 |
+ |
| 87 |
+ // If whitelist is disabled, no restrictions. |
| 88 |
+ if (!server->isHostWhitelistEnabled) |
| 89 |
+ return true; |
| 90 |
+ |
| 91 |
+ /* No host header, invalid request. */ |
| 92 |
+ if (host == NULL) |
| 93 |
+ { |
| 94 |
+ return false; |
| 95 |
+ } |
| 96 |
+ |
| 97 |
+ /* Host header might include the port. */ |
| 98 |
+ char* const hostname = tr_strndup(host, strcspn(host, ":")); |
| 99 |
+ |
| 100 |
+ /* localhost or ipaddress is always acceptable. */ |
| 101 |
+ if (strcmp(hostname, "localhost") == 0 || strcmp(hostname, "localhost.") == 0 || tr_addressIsIP(hostname)) |
| 102 |
+ { |
| 103 |
+ tr_free(hostname); |
| 104 |
+ return true; |
| 105 |
+ } |
| 106 |
+ |
| 107 |
+ /* Otherwise, hostname must be whitelisted. */ |
| 108 |
+ for (tr_list* l = server->hostWhitelist; l != NULL; l = l->next) { |
| 109 |
+ if (tr_wildmat(hostname, l->data)) |
| 110 |
+ { |
| 111 |
+ tr_free(hostname); |
| 112 |
+ return true; |
| 113 |
+ } |
| 114 |
+ } |
| 115 |
+ |
| 116 |
+ tr_free(hostname); |
| 117 |
+ return false; |
| 118 |
+} |
| 119 |
+ |
| 120 |
static bool |
| 121 |
test_session_id (struct tr_rpc_server * server, struct evhttp_request * req) |
| 122 |
{ |
| 123 |
@@ -663,6 +708,23 @@ handle_request (struct evhttp_request * req, void * arg) |
| 124 |
handle_upload (req, server); |
| 125 |
} |
| 126 |
#ifdef REQUIRE_SESSION_ID |
| 127 |
+ else if (!isHostnameAllowed(server, req)) |
| 128 |
+ { |
| 129 |
+ char* tmp = tr_strdup_printf( |
| 130 |
+ "<p>Transmission received your request, but the hostname was unrecognized.</p>" |
| 131 |
+ "<p>To fix this, choose one of the following options:" |
| 132 |
+ "<ul>" |
| 133 |
+ "<li>Enable password authentication, then any hostname is allowed.</li>" |
| 134 |
+ "<li>Add the hostname you want to use to the whitelist in settings.</li>" |
| 135 |
+ "</ul></p>" |
| 136 |
+ "<p>If you're editing settings.json, see the 'rpc-host-whitelist' and 'rpc-host-whitelist-enabled' entries.</p>" |
| 137 |
+ "<p>This requirement has been added to help prevent " |
| 138 |
+ "<a href=\"https://en.wikipedia.org/wiki/DNS_rebinding\">DNS Rebinding</a> " |
| 139 |
+ "attacks.</p>"); |
| 140 |
+ send_simple_response(req, 421, tmp); |
| 141 |
+ tr_free(tmp); |
| 142 |
+ } |
| 143 |
+ |
| 144 |
else if (!test_session_id (server, req)) |
| 145 |
{ |
| 146 |
const char * sessionId = get_current_session_id (server); |
| 147 |
@@ -674,7 +736,7 @@ handle_request (struct evhttp_request * req, void * arg) |
| 148 |
"<li> When you get this 409 error message, resend your request with the updated header" |
| 149 |
"</ol></p>" |
| 150 |
"<p>This requirement has been added to help prevent " |
| 151 |
- "<a href=\"http://en.wikipedia.org/wiki/Cross-site_request_forgery\">CSRF</a> " |
| 152 |
+ "<a href=\"https://en.wikipedia.org/wiki/Cross-site_request_forgery\">CSRF</a> " |
| 153 |
"attacks.</p>" |
| 154 |
"<p><code>%s: %s</code></p>", |
| 155 |
TR_RPC_SESSION_ID_HEADER, sessionId); |
| 156 |
@@ -875,19 +937,14 @@ tr_rpcGetUrl (const tr_rpc_server * server) |
| 157 |
return server->url ? server->url : ""; |
| 158 |
} |
| 159 |
|
| 160 |
-void |
| 161 |
-tr_rpcSetWhitelist (tr_rpc_server * server, const char * whitelistStr) |
| 162 |
+static void |
| 163 |
+tr_rpcSetList (char const* whitelistStr, tr_list** list) |
| 164 |
{ |
| 165 |
void * tmp; |
| 166 |
const char * walk; |
| 167 |
|
| 168 |
- /* keep the string */ |
| 169 |
- tmp = server->whitelistStr; |
| 170 |
- server->whitelistStr = tr_strdup (whitelistStr); |
| 171 |
- tr_free (tmp); |
| 172 |
- |
| 173 |
/* clear out the old whitelist entries */ |
| 174 |
- while ((tmp = tr_list_pop_front (&server->whitelist))) |
| 175 |
+ while ((tmp = tr_list_pop_front (list)) != NULL) |
| 176 |
tr_free (tmp); |
| 177 |
|
| 178 |
/* build the new whitelist entries */ |
| 179 |
@@ -896,7 +953,7 @@ tr_rpcSetWhitelist (tr_rpc_server * server, const char * whitelistStr) |
| 180 |
const char * delimiters = " ,;"; |
| 181 |
const size_t len = strcspn (walk, delimiters); |
| 182 |
char * token = tr_strndup (walk, len); |
| 183 |
- tr_list_append (&server->whitelist, token); |
| 184 |
+ tr_list_append (list, token); |
| 185 |
if (strcspn (token, "+-") < len) |
| 186 |
tr_logAddNamedInfo (MY_NAME, "Adding address to whitelist: %s (And it has a '+' or '-'! Are you using an old ACL by mistake?)", token); |
| 187 |
else |
| 188 |
@@ -909,6 +966,21 @@ tr_rpcSetWhitelist (tr_rpc_server * server, const char * whitelistStr) |
| 189 |
} |
| 190 |
} |
| 191 |
|
| 192 |
+void tr_rpcSetHostWhitelist(tr_rpc_server* server, char const* whitelistStr) |
| 193 |
+{ |
| 194 |
+ tr_rpcSetList(whitelistStr, &server->hostWhitelist); |
| 195 |
+} |
| 196 |
+ |
| 197 |
+void tr_rpcSetWhitelist(tr_rpc_server* server, char const* whitelistStr) |
| 198 |
+{ |
| 199 |
+ /* keep the string */ |
| 200 |
+ char* const tmp = server->whitelistStr; |
| 201 |
+ server->whitelistStr = tr_strdup(whitelistStr); |
| 202 |
+ tr_free(tmp); |
| 203 |
+ |
| 204 |
+ tr_rpcSetList(whitelistStr, &server->whitelist); |
| 205 |
+} |
| 206 |
+ |
| 207 |
const char* |
| 208 |
tr_rpcGetWhitelist (const tr_rpc_server * server) |
| 209 |
{ |
| 210 |
@@ -930,6 +1002,11 @@ tr_rpcGetWhitelistEnabled (const tr_rpc_server * server) |
| 211 |
return server->isWhitelistEnabled; |
| 212 |
} |
| 213 |
|
| 214 |
+void tr_rpcSetHostWhitelistEnabled(tr_rpc_server* server, bool isEnabled) |
| 215 |
+{ |
| 216 |
+ server->isHostWhitelistEnabled = isEnabled; |
| 217 |
+} |
| 218 |
+ |
| 219 |
/**** |
| 220 |
***** PASSWORD |
| 221 |
****/ |
| 222 |
@@ -1063,6 +1140,28 @@ tr_rpcInit (tr_session * session, tr_variant * settings) |
| 223 |
else |
| 224 |
tr_rpcSetWhitelistEnabled (s, boolVal); |
| 225 |
|
| 226 |
+ key = TR_KEY_rpc_host_whitelist_enabled; |
| 227 |
+ |
| 228 |
+ if (!tr_variantDictFindBool(settings, key, &boolVal)) |
| 229 |
+ { |
| 230 |
+ missing_settings_key(key); |
| 231 |
+ } |
| 232 |
+ else |
| 233 |
+ { |
| 234 |
+ tr_rpcSetHostWhitelistEnabled(s, boolVal); |
| 235 |
+ } |
| 236 |
+ |
| 237 |
+ key = TR_KEY_rpc_host_whitelist; |
| 238 |
+ |
| 239 |
+ if (!tr_variantDictFindStr(settings, key, &str, NULL) && str != NULL) |
| 240 |
+ { |
| 241 |
+ missing_settings_key(key); |
| 242 |
+ } |
| 243 |
+ else |
| 244 |
+ { |
| 245 |
+ tr_rpcSetHostWhitelist(s, str); |
| 246 |
+ } |
| 247 |
+ |
| 248 |
key = TR_KEY_rpc_authentication_required; |
| 249 |
if (!tr_variantDictFindBool (settings, key, &boolVal)) |
| 250 |
missing_settings_key (key); |
| 251 |
diff --git a/libtransmission/rpc-server.h b/libtransmission/rpc-server.h |
| 252 |
index e0302c5ea..8c9e6b24e 100644 |
| 253 |
--- libtransmission/rpc-server.h.orig |
| 254 |
+++ libtransmission/rpc-server.h |
| 255 |
@@ -49,6 +49,10 @@ void tr_rpcSetWhitelist (tr_rpc_server * server, |
| 256 |
|
| 257 |
const char* tr_rpcGetWhitelist (const tr_rpc_server * server); |
| 258 |
|
| 259 |
+void tr_rpcSetHostWhitelistEnabled(tr_rpc_server* server, bool isEnabled); |
| 260 |
+ |
| 261 |
+void tr_rpcSetHostWhitelist(tr_rpc_server* server, char const* whitelist); |
| 262 |
+ |
| 263 |
void tr_rpcSetPassword (tr_rpc_server * server, |
| 264 |
const char * password); |
| 265 |
|
| 266 |
diff --git a/libtransmission/session.c b/libtransmission/session.c |
| 267 |
index 844cadba8..58b717913 100644 |
| 268 |
--- libtransmission/session.c.orig |
| 269 |
+++ libtransmission/session.c |
| 270 |
@@ -359,6 +359,8 @@ tr_sessionGetDefaultSettings (tr_variant * d) |
| 271 |
tr_variantDictAddStr (d, TR_KEY_rpc_username, ""); |
| 272 |
tr_variantDictAddStr (d, TR_KEY_rpc_whitelist, TR_DEFAULT_RPC_WHITELIST); |
| 273 |
tr_variantDictAddBool (d, TR_KEY_rpc_whitelist_enabled, true); |
| 274 |
+ tr_variantDictAddStr(d, TR_KEY_rpc_host_whitelist, TR_DEFAULT_RPC_HOST_WHITELIST); |
| 275 |
+ tr_variantDictAddBool(d, TR_KEY_rpc_host_whitelist_enabled, true); |
| 276 |
tr_variantDictAddInt (d, TR_KEY_rpc_port, atoi (TR_DEFAULT_RPC_PORT_STR)); |
| 277 |
tr_variantDictAddStr (d, TR_KEY_rpc_url, TR_DEFAULT_RPC_URL_STR); |
| 278 |
tr_variantDictAddBool (d, TR_KEY_scrape_paused_torrents_enabled, true); |
| 279 |
diff --git a/libtransmission/transmission.h b/libtransmission/transmission.h |
| 280 |
index 4f76adfd6..e213a8f4e 100644 |
| 281 |
--- libtransmission/transmission.h.orig |
| 282 |
+++ libtransmission/transmission.h |
| 283 |
@@ -123,6 +123,7 @@ const char* tr_getDefaultDownloadDir (void); |
| 284 |
#define TR_DEFAULT_BIND_ADDRESS_IPV4 "0.0.0.0" |
| 285 |
#define TR_DEFAULT_BIND_ADDRESS_IPV6 "::" |
| 286 |
#define TR_DEFAULT_RPC_WHITELIST "127.0.0.1" |
| 287 |
+#define TR_DEFAULT_RPC_HOST_WHITELIST "" |
| 288 |
#define TR_DEFAULT_RPC_PORT_STR "9091" |
| 289 |
#define TR_DEFAULT_RPC_URL_STR "/transmission/" |
| 290 |
#define TR_DEFAULT_PEER_PORT_STR "51413" |
| 291 |
diff --git a/libtransmission/web.c b/libtransmission/web.c |
| 292 |
index ee495e9fc..c7f062730 100644 |
| 293 |
--- libtransmission/web.c.orig |
| 294 |
+++ libtransmission/web.c |
| 295 |
@@ -594,6 +594,7 @@ tr_webGetResponseStr (long code) |
| 296 |
case 415: return "Unsupported Media Type"; |
| 297 |
case 416: return "Requested Range Not Satisfiable"; |
| 298 |
case 417: return "Expectation Failed"; |
| 299 |
+ case 421: return "Misdirected Request"; |
| 300 |
case 500: return "Internal Server Error"; |
| 301 |
case 501: return "Not Implemented"; |
| 302 |
case 502: return "Bad Gateway"; |