[tor-dev] Internet-wide scanning for bridges

Vlad Tsyrklevich vlad at tsyrklevich.net
Sat Dec 13 00:33:05 UTC 2014

 Hello, while taking a looking at TorCloud I it used static OR and
obfs2+obfs3 ports making them trivial to discover by scanning EC2's IP
address space. This led me to consider the more general attack of
internet-wide scanning to discover Tor bridges. Most Tor relays run their
ORPorts on port 443 or 9001 so I began investigating there.

I began by looking at Project Sonar's port 443 SSL certificate database
looking for certificates matching the tor certificate pattern. In talking
with Eric Wustrow about ZMap, I discovered that he and the other ZMap
authors had already published about this attack in their original paper.
Eric mentioned that in his discussion with Roger that the response to the
attack was going to be the soon-to-be-deployed obfuscated transports which
were intended to be run on random high ports; however, the eventual roll
out of obfsproxy did not change the fact that bridge's ORPorts continue to
be publicly accessible.

While I was analyzing Internet-wide survey-data for port 443 and 9001
(helpfully provided by Project Sonar/H D Moore) I found the Onionoo data
set. I was able to quantify the impact of the attack using the Onionoo data
and verify it using the ZMap scans. There are 4267 bridges, of them 1819
serve their ORPort on port 443 and 383 serve on port 9001. That's 52% of
tor bridges. There are 1926 pluggable-transports enabled bridges, 316 with
ORPort 443 and 33 with ORPort 9001. That's 18% of Tor bridges. 203 (64%) of
PT-enabled bridges with ORPort 443 were TorCloud. I was able to
approximately verify these figures using the Internet-wide scans. By
grabbing bridge's certificates and calculating their hashed fingerprint I
realized I was also discovering a fair amount of private bridges not
included in the Onionoo data set.

The results aren't too awful, just under 20% of PT-enabled bridges are
affected. It's unclear to me if the low number of PT-enabled bridges (and
corresponding high number of vulnerable non-PT-enabled bridges) is a
historical artifact or a consequence of out-of-date documentation like
https://www.torproject.org/docs/bridges.html.en not setting up pluggable

Eliminating the ORPort entirely for PT-enabled bridges seems like the
ultimate solution to this attack, but the implications of such a change are
unclear to me. Another change to mitigate this issue is changing the
behavior of ports specified as 'auto' and then changing defaults and
documentation to use auto by default. Currently specifying 'auto' in your
torrc lets the OS decide the port; however, if tor were to generate a
random port on first use that it continued to use across restarts it would
it possible to use this for ORPorts and pluggable transports by default (as
opposed to the current situation where for bridges you want ORPort auto but
constant PT ports and a constant ORPort for relays which is confusing.)

I've attached a patch to warn bridge operators running with ORPort set to
443 or 9001 as a stop-gap measure.

- Vladislav
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.torproject.org/pipermail/tor-dev/attachments/20141212/b9196c02/attachment.html>
-------------- next part --------------
diff --git a/src/or/config.c b/src/or/config.c
index 28f1df0..4011371 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -523,7 +523,7 @@ static int parse_dir_authority_line(const char *line,
 static int parse_dir_fallback_line(const char *line,
                                    int validate_only);
 static void port_cfg_free(port_cfg_t *port);
-static int parse_ports(or_options_t *options, int validate_only,
+static int parse_ports(or_options_t *options, smartlist_t **ports_out,
                               char **msg_out, int *n_ports_out);
 static int check_server_ports(const smartlist_t *ports,
                               const or_options_t *options);
@@ -1060,7 +1060,13 @@ options_act_reversible(const or_options_t *old_options, char **msg)
     /* Adjust the port configuration so we can launch listeners. */
-    if (parse_ports(options, 0, msg, &n_ports)) {
+    if (configured_ports) {
+      SMARTLIST_FOREACH(configured_ports, port_cfg_t *, p, port_cfg_free(p));
+      smartlist_free(configured_ports);
+      configured_ports = NULL;
+    }
+    if (parse_ports(options, &configured_ports, msg, &n_ports)) {
       if (!*msg)
         *msg = tor_strdup("Unexpected problem parsing port config");
       goto rollback;
@@ -2525,7 +2531,7 @@ options_validate(or_options_t *old_options, or_options_t *options,
         "for details.", uname);
-  if (parse_ports(options, 1, msg, &n_ports) < 0)
+  if (parse_ports(options, NULL, msg, &n_ports) < 0)
     return -1;
   if (parse_outbound_addresses(options, 1, msg) < 0)
@@ -3607,6 +3613,29 @@ options_validate(or_options_t *old_options, or_options_t *options,
       REJECT("BridgeRelay is 1, ORPort is not set. This is an invalid "
+  if (options->BridgeRelay) {
+    smartlist_t *orports = NULL;
+    if (parse_ports(options, &orports, msg, &n_ports) < 0)
+      return -1;
+    if (orports) {
+      SMARTLIST_FOREACH_BEGIN(orports, port_cfg_t *, port) {
+        if (port->type != CONN_TYPE_OR_LISTENER)
+          continue;
+        if (port->port == 443 || port->port == 9001) {
+            COMPLAIN("Your bridge ORPort should be set to a random high "
+                     "port to avoid having it discovered by internet-wide "
+                     "port scanning. Ports 443 and 9001 are common relay "
+                     "ORPorts that should be avoided.");
+        }
+      SMARTLIST_FOREACH(orports, port_cfg_t *, p, port_cfg_free(p));
+      smartlist_free(orports);
+    }
+  }
   return 0;
 #undef REJECT
 #undef COMPLAIN
@@ -5955,11 +5984,11 @@ count_real_listeners(const smartlist_t *ports, int listenertype)
  * <b>options</b>, and return 0.  On failure, set *<b>msg</b> to a
  * description of the problem and return -1.
- * If <b>validate_only</b> is false, set configured_client_ports to the
- * new list of ports parsed from <b>options</b>.
+ * If <b>ports_out</b> is not NULL, set *ports_out to the new list
+ * of ports parsed from <b>options</b>.
 static int
-parse_ports(or_options_t *options, int validate_only,
+parse_ports(or_options_t *options, smartlist_t **ports_out,
             char **msg, int *n_ports_out)
   smartlist_t *ports;
@@ -6082,13 +6111,8 @@ parse_ports(or_options_t *options, int validate_only,
   options->ExtORPort_set =
     !! count_real_listeners(ports, CONN_TYPE_EXT_OR_LISTENER);
-  if (!validate_only) {
-    if (configured_ports) {
-      SMARTLIST_FOREACH(configured_ports,
-                        port_cfg_t *, p, port_cfg_free(p));
-      smartlist_free(configured_ports);
-    }
-    configured_ports = ports;
+  if (ports_out) {
+    *ports_out = ports;
     ports = NULL; /* prevent free below. */

More information about the tor-dev mailing list