Opened 2 years ago

Closed 22 months ago

Last modified 6 months ago

#14917 closed defect (implemented)

Client's choice of rend point can leak info about hidden service's guard relay

Reported by: arma Owned by: dgoulet
Priority: High Milestone: Tor: 0.2.7.x-final
Component: Core Tor/Tor Version:
Severity: Normal Keywords: tor-hs, 027-triaged-1-in, SponsorU, TorCoreTeam201509, PostFreeze027
Cc: Actual Points:
Parent ID: Points: medium
Reviewer: Sponsor: SponsorR

Description

Imagine the scenario where the attacking client picks a rendezvous point that happens to be the hidden service's primary (first) entry guard. Then

  • The hidden service will look through its preemptively built circuits to see if it can extend any of them. circuit_find_to_cannibalize() will decide that none of the existing circuits are suitable, because they would cause a duplicate hop.
  • So the hidden service decides to make a new circuit. In populate_live_entry_guards() it compares node to chosen_exit and then exit_family, and skips it if there's overlap.

So the result is that even when NumEntryGuards is 1, the hidden service will end up using its *second* entry guard for this circuit. Surprising. This isn't terrible, since I don't see any way to induce it to contact a third entry guard, but still, two is more than one, especially when you thought you were getting only one. (Imagine an attack where the adversary has you rendezvous with every relay that has the Guard flag -- now you're guaranteed to step over to your second guard at least once.)

Where it gets messy is if the user explicitly set EntryNodes, like some hidden service operators think is wise, so they can use an entry node they trust. In that case entry_list_is_constrained() is true, so populate_live_entry_guards() will happily return an empty list if your one choice is inappropriate, resulting in choose_random_entry_impl() returning NULL. The external behavior is that if you send the hidden service the right rendezvous point, it never connects. That's kind of bad, especially since some people argue setting an explicit EntryNode is a wise move.

Now, learning about the hidden service's choice of primary guard isn't the end of the world, because there are other reasonably straightforward ways to enumerate the entry guard of a hidden service currently, e.g. the predecessor attack (run a middle relay, rendezvous with the hidden service many times, and see which prior hop shows up in the circuit the most times). But we'd like to fix those, e.g. by moving to a layered entry guard design, and if we do, this issue will remain.

Reported by 'ghetto' on irc.

Child Tickets

Change History (24)

comment:1 Changed 2 years ago by arma

Sebastian and I pondered this one for a little while and our current thought is that the best way forward is to relax the requirement preventing duplicate hops in the circuit, in the rendezvous circuit case.

That is, if you're picking a guard for a rendezvous circuit, don't do those two comparisons in populate_live_entry_guards().

Now people who set EntryNode specifically will resume connecting. And those who don't set it will use their primary guard and thus not behave differently as a function of the rendezvous point.

It still makes us a bit nervous though because now the client can cause the hidden service to essentially make a short-circuited circuit to a relay that the client controls.

More perspectives welcome!

comment:2 follow-up: Changed 2 years ago by cypherpunks

GuardA -> Middle -> GuardA is too bizarre for anyone monitoring the network and sends red flags.

Other possible solution for the EntryNode situation are:
1) Disregard EntryNode and connect to a second guard as usual.
2) Increase the hop by one just for this situation. GuardA -> Middle1 -> Middle2 -> GuardA
3) Fail and say we told you not to mess with the selection path.

Edit:
4) Take the shortcut and talk to the rend point directly.

Last edited 2 years ago by cypherpunks (previous) (diff)

comment:3 in reply to: ↑ 2 Changed 2 years ago by arma

Replying to cypherpunks:

GuardA -> Middle -> GuardA is too bizarre for anyone monitoring the network and sends red flags.
2) Increase the hop by one just for this situation. GuardA -> Middle1 -> Middle2 -> GuardA

Actually Tor does this one already -- the path length is "three hops not counting ones that somebody else picked for you", so the hidden service makes GuardA -> Middle1 -> Middle2 and then extends that to the rendezvous point.

Edit:
4) Take the shortcut and talk to the rend point directly.

Hm! Not good for timing reasons, for one.

Last edited 2 years ago by arma (previous) (diff)

comment:4 Changed 2 years ago by nickm

  • Status changed from new to assigned

comment:5 Changed 2 years ago by nickm

  • Keywords 027-triaged-1-in added

Marking more tickets as triaged-in for 0.2.7

comment:6 Changed 2 years ago by isabela

  • Keywords SponsorU added
  • Points set to medium
  • Version set to Tor: 0.2.7

comment:7 Changed 2 years ago by dgoulet

I thought of this last week and decided to try it. I hacked my tor client to always use a specific RP node and set that node as EntryNode for an HS I control. One single circuit and the client received a failure thus confirming the attack. There are ~3000 guards right now in the network, testing them all takes few minutes thus the guard discovery is serious (of course considering EntryNode being used).

I don't think by passing EntryNodes if defined is a good idea here. Apart from doing things in the background that the user explicitly asked not to do (bad), a far fetched example is that if the operator decided to firewall all nodes except the entry one and then Tor tries to connect to it and fails, well the attack is still usable. What I mean by this is that there are maybe external variables on why an operator sets EntryNodes thus we should respect it.

Accepting GuardA -> Middle1 -> Middle2 -> GuardA for rendezvous circuit seems to me the straight fix for a situation that is not really good right now.

We could go as far as denying the use of EntryNodes for an HS because of this serious security issue but that sounds maybe a bit too much.

comment:8 Changed 2 years ago by dgoulet

After a discussion on IRC with nickm, we agree on a solution. This issue only happens if EntryNodes has only one single entry so we should trigger a warning in that case if you are an HS and add a comment in the EntryNodes documentation about this issue with one node.

Will try to provide a patch soon.

comment:9 follow-up: Changed 2 years ago by arma

Trigger a warning and fail the circuit? Or trigger a warning and do the four hop circuit as described above? I assume you mean the former. But isn't having only one EntryNode the safest, best recommendation we can have for when you're setting EntryNodes? Especially in the scenario where you run that relay or know its operator.

I worry that we're warning people away from what might actually be the best recommended behavior for some of them.

comment:10 in reply to: ↑ 9 Changed 2 years ago by dgoulet

Replying to arma:

Trigger a warning and fail the circuit? Or trigger a warning and do the four hop circuit as described above? I assume you mean the former. But isn't having only one EntryNode the safest, best recommendation we can have for when you're setting EntryNodes? Especially in the scenario where you run that relay or know its operator.

No, it would be trigger a warning and don't fail. But now that I think of this that doesn't make much sense because between failing voluntarily or failing because we can't connect to the guard as the chosen exit is roughly the same... So this solution doesn't work.

I worry that we're warning people away from what might actually be the best recommended behavior for some of them.

If you are an HS and you set only one single EntryNodes, it's obvisouly not the recommended behavior for now.

We use one guard right now (primary) but we always have a secondary in case we have an unstable primary (and also to avoid this issue). With EntryNodes, this "secondary" guard is bypassed leading to that issue.

IMO, HS + one EntryNodes, you shouldn't be able to start tor at all, period. It's either that or we allow exiting at your guard which I don't think is ideal.

comment:11 Changed 2 years ago by dgoulet

  • Priority changed from normal to major
  • Version Tor: 0.2.7 deleted

comment:12 Changed 2 years ago by dgoulet

I'll try to summarize the above and have a pros/cons list for each possible solution. We assume an operator running an HS and EntryNode is set with a single entry. Please correct any wrong reasons, add any new ones and argue with some possible improvement.

1) Warning at startup + do NOT fail the circuit

  • Pros:
    • Relay operator is notified iff she is looking at the logs.
    • HS will be able to pin a single guard because one guard is recommended.
  • Cons:
    • Attack NOT mitigated.

2) Warning at startup + fail the circuit

  • Pros:
    • Relay operator is notified iff she is looking at the logs.
  • Cons:
    • Does not mitigate the attack at all because that's the current behavior without the warning. We can't exit at our entry point and we don't have a secondary guard.

3) Error at startup. Tor doesn't start and we tell operator why.

  • Pros:
    • Attack is mitigated
  • Cons:
    • Confuses the operator since one single guard is what's recommended?
    • Could break some HS configuration out there raising questions and paranoia (maybe good?)

4) Exit at your guard *only* for rendezvous point.

  • Pros:
    • Attack is mitigated
    • Not breaking any current configuration nor confusing operator.
  • Cons:
    • Bad for anonymity reason to exit at your entry? Could be maybe issues with timing?
    • Breaks tor path selection for a specific case which might be bad.

comment:13 Changed 2 years ago by dgoulet

From IRC, we'll got with 3). I'll propose a patch soon hopefully.

< nickm> dgoulet: if you had to pick tomorrow, which option would you pick (for #14917)
< dgoulet> nickm: "Error at startup. Tor doesn't start and we tell operator why.", it's either that or we allow exiting at our Guard which I'm unsure of the implication of that
< nickm> what about "Error at startup. Tor doesn't start and we tell the operator why, and how to fix it"
< dgoulet> nickm: ah well of course, error means lots of info to help op :) 
< dgoulet> nickm: with man page update also
< nickm> dgoulet: okay.  I think that 1-3 are mostly the same code. 
< nickm> dgoulet: and if you think 3 is best, 3 is probably better than nothing
< nickm> so the only question is, is 3 better than 4?
< nickm> I think it is.
< nickm> so let's go with your idea there.
< nickm> copy our discussion, minus other people's text, into the ticket, and code as seems best? 

comment:14 Changed 2 years ago by arma

haven't read ticket, but from brief discussion with dgoulet, it sounds like we should roll out the conservative choice for now (I think that's warn and fail), and then call out the research questions from #4 so people don't forget about them. As opposed to picking the easy route, closing the ticket, and then pretending that there were no research questions here.

comment:15 Changed 2 years ago by dgoulet

  • Keywords TorCoreTeam201508 added

comment:16 Changed 22 months ago by nickm

  • Keywords TorCoreTeam201509 PostFreeze027 added; TorCoreTeam201508 removed

This can tentatively be PostFreeze027, if the patch is simple, but it really needs an owner!

comment:17 Changed 22 months ago by dgoulet

  • Owner set to dgoulet

comment:18 Changed 22 months ago by dgoulet

  • Status changed from assigned to needs_review

See branch bug14917_027_01.

Feel free to change the log message with a cleaner better English :).

I have not added "bugfix on ..." in the change file since I think this is an issue since EntryNodes and hidden service have been coexisting. Not sure what version of Tor that would apply on :S ...

comment:19 follow-up: Changed 22 months ago by nickm

  • Status changed from needs_review to needs_revision

Problem: this will also reject "EntryNodes {de}", I think, since that also has routerset_t.list set to a one-element list. (Let me know if I'm wrong)

comment:20 in reply to: ↑ 19 Changed 22 months ago by dgoulet

  • Status changed from needs_revision to needs_review

Replying to nickm:

Problem: this will also reject "EntryNodes {de}", I think, since that also has routerset_t.list set to a one-element list. (Let me know if I'm wrong)

Indeed... good catch.

See fix: bug14917_027_02.

I've used routerset_is_list() to make sure we only have either digests or nicknames in the set. Works fine in chutney now.

comment:21 Changed 22 months ago by nickm

  • Resolution set to implemented
  • Status changed from needs_review to closed

Merged!

comment:22 Changed 22 months ago by nickm

Fixed a crash bug in bc64a6b. :)

comment:23 Changed 21 months ago by dgoulet

  • Keywords SponsorR removed
  • Sponsor set to SponsorR

comment:24 Changed 6 months ago by Jaym

  • Severity set to Normal

Hello !

I discovered this thread while configuring an onion service with the EntryNodes option set. I believe (after checking the tor-0.2.9.8 source code) that a similar problem arises when the EntryNodes option is set AND the operator configures entry nodes that are part of the same family or the same /16. (let's say that the operator configures the service with its own guard nodes running in the same cloud provider, thinking this move is wise). Then this happens:

  • When someone use a RDV point of the same family or the same /16 than the onion's guards, then as you said: "entry_list_is_constrained() is true, so populate_live_entry_guards() will happily return an empty list if your one choice is inappropriate, resulting in choose_random_entry_impl() returning NULL".

Is there a reason why we do not check family, /16 and user misconfiguration ? "EntryNodes fingerprint1, fingerprint1" works just fine for example. What do you think ?

EDIT: created #21155 to reopen the discussion.

Last edited 6 months ago by Jaym (previous) (diff)
Note: See TracTickets for help on using tickets.