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"; |