Skip to content

Instantly share code, notes, and snippets.

@Milek7

Milek7/attack.md Secret

Last active January 11, 2021 14:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Milek7/8482c4e87947f4748aea5d96b3e89b80 to your computer and use it in GitHub Desktop.
Save Milek7/8482c4e87947f4748aea5d96b3e89b80 to your computer and use it in GitHub Desktop.

Attack

I just realized that security model as implemented in PR is broken when reusing same keypair on multiple servers.

  1. Victim secures their company on legitimate server with pubkey.
  2. Attacker lures victim to connect to malicious server.
  3. Malicious server simultaneously connects to legitimate server and proxies KEYAUTH packets.
  4. After KEYAUTH is completed malicious server is left with authorized socket and can access the company, profit.

Of course it also applies to rcon access if we migrate it to pubkey too.

Possible mitigations:

  1. Generate unique keypair for each IP/port. (permanently enabled privacy mode. cons: friends lists are nonviable, if somebody hosts server on dynamic IP it could lock people out periodically when IP changes)
  2. Client includes server target IP/port in signature, and server verifies that. (doesn't have cons of mitigation 1., but difficult to implement in practice: server might not necessarily know all valid IPs from which it's reachable)
  3. Implement secure key-exchange and authenticate and encrypt all communication.
@TrueBrain
Copy link

First of all, I think it is awesome you think about these things :)

The scenario you describe is absolutely possible. Before I go into the scenario, let's run down your suggestions first, as that might give a bit of insight what we are dealing with.

  1. works for your scenario, but initial feedback was pretty strong that it is kinda unwanted to do this. So lets keep it in our backpocket, and see if we can do better. Additionally, I can think of variants of your scenario where this still fails.

  2. for any NAT server (read: most non-dedicated-server setups), this would indeed not work.

  3. this, in core, is a suggestion always given for problems like this: just encrypt the channel. But if we look closer, you will notice that it doesn't resolve the problem. The attacker is between the network of the client and the target server. So he can just terminate any encryption to<->from the client, and to<->from the target server, none being any of the wiser. This is why we have for HTTPS a CA-system. And I will give a few more examples in a bit, but they all boil down to: you need some out-of-band method to validate trust. But if we can do that, we don't even need encryption. So this won't really work, well, except for the fact that we make it more complicated for the attacker.

So, what if we look around how other people solved this problem. As I always assume it cannot be a new problem, so people must have solved it :D

As mentioned before, HTTPS solve this by introducing an authority (CAs), which tells you that you are really talking to the server you think you are talking too. This is also the reason why leaking private keys is so harmful, or being able to request a certificate for a domain you don't own .. the horror stories :D If the trust breaks, there is nothing on protocol level you can do to notice.

SSH also has a solution for this; they don't use a authority, but they show you the host key if you connect to a server the first time. If the key doesn't change, it must be true you talk to the same server (because of the method of validation used with SSH). So after the initial connect, you can be sure that everything is fine. For us, this would work too: on initial connect you accept the server-key, you setup an encrypted channel, and the server can white-list you. Now no attacker can become between you and the server. If he tries, and you connect next time, it is not possible for him to either have the same public key, or see the traffic between you and the target (either/or; he cannot have both).
But, realistic here, it requires the human to notice that the key has changed and act on it. Most people just ignore it (remove it from there known_hosts file), and think: someone must have changed something, reinstalled the server, what-ever. For OpenTTD that would be much more true, as most of our player-base wouldn't even understand what it means. So I wouldn't consider this a viable solution either.

So, before we look further what solutions might work, we also have to balance out how likely it is this happens. Basically, a few things have to happen:

  1. the attacker needs to know to what server you have access too
  2. the attacker needs to be able to contact you, in order to lure you into another server
  3. he needs to have that server setup to do this MITM attack (or how ever you want to call it; but at this point he is a middle-man in your network path, so yeah)
  4. it allows him a single TCP session with authentication to the target server

This is a very very far fetched scenario, in my opinion. So I keep this in mind when looking for a solution, as the question:

  1. does this need a solution?
  2. the complexity of the solution should not out-weigh the risk it poses

As let's be real, even if it was about an RCON access, the worst the attacker can do is ... restart the server? close it? This is a game after all, with no real access to the machine the server is hosted on. So in terms of risk evaluation, the chance of this happening is very small, and the impact if this happens is very small. That makes this in most definitions a "low risk" situation. So to consider what you implemented "broken" is a false image of reality. In those definitions, all forms of encryption is "broken" ;) It is about risks and what is considered acceptable. I know that sounds a bit weird, but that is how security works :) So your security model is not broken; it just has a risk. And we have 2 choices: mitigate it, or accept it.

So, let's talk a bit more if/how we can mitigate it. To repeat what I wrote earlier: this is a very common problem for any authentication. You have to trust the other party somehow. So, what are other common solutions can I think off?

  • Server publishes his pubkey to a central place, where a client can check if they match. If they do, they can setup a secure transmission. PGP is an example of this. GitHub does the same for any public key you have on GitHub (https://github.com/truebrain.keys for example). Well, a nuance here: it wouldn't even require the full communication to be encrypted. Just the server needs to have a public/private key too, and what-ever it sends to the client for the client to sign, is encrypted with it. Client decrypts it with the public key he finds on the central place, encrypts it with his private key, and sends it back. This would mean the attacker needs to know the private key of the target server before this becomes viable. This too, is a risk that should be evaluated, but the chance on that happening for OpenTTD is even smaller. So it doesn't solve it completely, but it mitigates this already low risk even further. Biggest downside: we need a central place for this information to be published on.
  • Enforce that the RCON public key cannot be used for companies, and the other way around. For example, by marking them differently, prefixing, etc etc. This would mean in the worst case an attacker can take over the company of a player on another server. I would consider that even less likely anyone puts any effort in .. as the social aspect of luring people in is pretty steep problem to overcome. And best case scenario, you can play for one session as someone else on a target server. Same for admin port ofc. In general, this might be a good idea to do, to split the world in different realms. But it makes it a bit more difficult for users, and the size of the implementation.
  • Install certificates on servers. Well .. I don't think I need to explain why this is an issue that we won't be solving any time soon.

There might be other solutions, but they don't really come to mind atm. Maybe others have some ideas. But I like to stress that we are solving a low-risk problem, which I even consider unlikely to happen in the real world. It requires so much social information about a target, why would you go for OpenTTD in that scenario, and not just bank access or something :P I am not sure .. I can be convinced otherwise :)

I personally think my second option, if we want to do anything about this, is already a huge help in mitigating the impact if this situation starts to exist. The first for sure works, and we have a place to publish public keys (master-server); it just requires additional effort. What the nice thing is about the first solution, it can be added later; no need to do that now. Although adding server private/public keys might be a great help if we do that already.

Anyway, something to think about .. let me know what you think and if this gave you other inspiration and ideas. Would love to hear if you see other ways to mitigate this and/or if you evaluate this risk differently :)

@Milek7
Copy link
Author

Milek7 commented Jan 9, 2021

To reply to your objections: no, encrypting channel does indeed solve the problem. After DH key exchange apart from session secret key, server would have client public key (and client would have server public key, but that is not necessary there). If malicious server terminates connection at their end, it can do that, but then it would connect to target server with its own public key, not the victim public key. What I propose there is equivalent to HTTPS with self-signed certs and using client certificates to authenticate clients.

As for this remark:

Well, a nuance here: it wouldn't even require the full communication to be encrypted.

No, all communication have to be encrypted (or at least every message needs to be authenticated) because whatever complex authorization we do, malicious server can just passthrough it and takeover socket when it is done. (this would apply to my mitigation 2. too, but it is mitigated by including out-of-band information that allow victim server to tell that connection is MITMed (that obviously only works when network stack is trusted, while mitigation 3. works even with malicious network))

HTTPS needs CA for different reason, to tell that bank.com you connect to is really true bank.com, and you want to be sure because you will be sending sensitive information (passwords) to it. We don't care about it, worst that can happen is that you will connect to different server than you wanted, but this shouldn't in any way enable that server to hack into your companies elsewhere.

@Milek7
Copy link
Author

Milek7 commented Jan 9, 2021

And of course we can choose accept the risk from original model, but to emphasise: that the mere attempt to connect to malicious server, can compromise your companies/rcon at different server... sounds bad. It's in some ways worse than current situation with passwords, because you typically don't enter password for your rcon at someone else's server.

@TrueBrain
Copy link

Random thought that came to mind, not related to your scenario:

The thing that triggered me most out of all this, why do we use the same key to "unlock" a company as RCON? I hadn't thought about this yet, but somehow that sounds a bit weird to me. So I had a thought:

What if we have keys of different "levels". As in: when you login, you use a "guest" key, one that is used for your friends-list etc. You can join a server with it (whitelist), you can do AL on companies with it, and everything.
For RCON (and admin) we have a different key. Or two even, don't know. When a server wants to know if you have rcon access, it requests your rcon key. In the client we display this as: "The server requested your administrative key for elevated access on this server.".
I have no idea if this can be implemented or is useful, but I just put it here for the world to read :)

@TrueBrain
Copy link

No, all communication have to be encrypted (or at least every message needs to be authenticated)

Okay, this is not how I understood your initial one-liner; I assumed you meant something else, but this is something we can agree on: the encryption is not the important part, but the authenticity of the packets is. Commonly this is rather difficult to setup correctly btw, but I see libhydrogen has an exchange for this; so that might be doable. I am not sure how good it is, as it strongly depends on how they implemented it, and as such I do not know how MITM resistant it is ;)

Either way, I think there are two things at play here:

  1. is the initial scenario likely and do we need to mitigate it? My answer to that is a very simple: it is very unlikely and has a very low impact. It requires a crazy amount of social engineering. Can it be done? Yes. Is it like to happen? Well, I am sure you will try now :P So let me put the amount of times it was successfully done at 1. Especially if we split RCON keys from login keys, that mitigates the risk even further. This is how I balance the risk, YMMV.

  2. you want to encrypt the protocol fully between client and server. This is always a good goal to have, but let's not do it because we see this scenario as the reason to do it. Let's add encryption because it is a good thing to add! That makes it a lot easier for anyone to process, and requires less babbling about: does it really solve NNN ;) Just easier, and sooner or later we need it anyway!

That does means there is a choice in order of implementation: either do the encrypting of the client <-> server traffic first, or build the authentication first.
I think, and correct me if I am wrong, that they are pretty separate from each other. Encryption gives you pubkeys, I think there might be the overlap, but I leave that judgement to you.

Encryption will require a whole different form of testing than authentication. So please look carefully how you want to PR this; I have closed more than one PR in the last few weeks as they became too big for us to process, and in result nobody really did anything with it for 2 years. That is partially on us, as developers, but you can help avoid it by keeping things in small-ish chunks that can already be validated and merged :) Especially merging encryption will be a tough process. Just something to keep in mind please!

@Milek7
Copy link
Author

Milek7 commented Jan 9, 2021

is the initial scenario likely and do we need to mitigate it?

That obviously for discussion, but I vote it is. It only requires knowing that someone is admin on some server, and luring them into malicious server to gain access to their rcon. Splitting rcon keys lowers the risk, but: it still isn't perfect, and complicates implementation (it needs some another variant of NEED_KEYAUTH/KEYAUTH that is only requested with first use of rcon). I don't like adding extra code and then still ending up with mediocre solution.

correct me if I am wrong, that they are pretty separate from each other

DH exchange gives pubkey without extra work. If we implement pubkey auth without encryption, we need these NEED_KEYAUTH/KEYAUTH packets, which are vulnerable to discussed attack. To protect against this attack pubkey must be derived from DH exchange. We can PR pubkey first and encryption later, but then insecure NEED_KEYAUTH/KEYAUTH/(separate variants for rcon keys) would need to be added only to be removed later.

Encrypted communication is implemented there: (quick implementation for demo, likely needs discussion if encapsulating into PACKET_CIPHERTEXT is right approach and whether it should be in tcp_game or somewhere else) (this removes NEED_KEYAUTH/KEYAUTH and instead uses pubkey from DH exchange)
https://github.com/Milek7/OpenTTD/commits/pubkey_encrypt

@TrueBrain
Copy link

I took some effort to look into how libhydrogen does their XX-variant secure key exchange, and basically it is a pretty standard 3-way DH secure-key exchange. An ephemeral key is generated for the encryption on both the client and the server, and the second and third packet as encrypted with the "static" keypair to proof the public key which you get at the end of this exchange are really owned by the other party. (on a side-note, we need to document this somewhere, just so people can read how security is done).

This means that after this exchange, assuming there are no implementation errors in the encryption of course (and looking at libhydrogen, I wouldn't expect that) and implementation errors on our side (I have briefly looked over your code; it would need a further look to make sure that with all the quirks OpenTTD tends to do, the flow is sufficiently guarded), the server knows the public key of the client.

Important to note, for those that do not know what DH does: it does not authenticate the client nor the server. It merely facilitates a secure key (a so called "non-authenticated key-agreement protocol") exchange which, in this case, is used for channel encryption and in-game authentication. For the public key of the client, not much authentication is required, as this is nearly impossible anyway. We simply use the public key as-is, and if it has a match in the access list, the linked role is granted.

However, the lack of authenticating the server does allow for certain attacks, which are variations on the above. To be clear, the above scenario is mitigated with this method; but minor variations on this are not.

One such scenario is very simple: an attack sets up a proxy server, where he terminates all clients on one side, and sets up new connections (per client ofc) to the server.
Next, he replaces the link on a website, that if you click this, you end up on the proxy of the attacker, which routes your traffic to the right server. Especially as OpenTTD is most likely going to get protocol-support, where openttd:// opens the game, and connects to an indicated server, it is to be expected that people will promote their server via a "Join now" button on their website. So if an attack manages to change this, via either social engineering or hacking, he can intercept all clients. Another example is an IP given on Discord, etc.
In the beginning, nothing changes. All existing trust is not compromised. All new trust, how ever, is.

To put this in other words (sorry if you already got the point; but miscommunication is easy, so I am just using wayyyyy to many words for this. Feel free to skip till the next line if you already got the point :D):

  • Client connects to proxy, exchanges public keys.
  • The proxy generates a new secret/public key specific for this client, and connects to the real server.
  • The server sees a new player and lets him in.
  • Neither the client nor the server are aware someone is between, as, neither has authenticated the pubkey of the other (the server cannot, and the client didn't).
  • When the legit client disconnects, the proxy can at any moment reconnect to the server and plays as he was the client.
  • If this proxy is in the path long enough, it is also likely people get assigned owner permissions or even RCON permissions to such players, as the server-admin is not aware there is an attack in his path.
  • This attack works best if you are in front of a server from the start.

As said, this is just a slight variation on the scenario presented earlier. The attack does need a bit more patience, but both cases require effort and both cases need to target a server specific.

In case you, as reader, got lost in this story, compare it with HTTPS: if you use a self-signed certificate on your server which I as client cannot validate, anyone in between you and me on the network path can terminating the encryption on both sides, and none of us would be able to detect this. This is why we have CAs, signed certificates which client should validate (!), and the likes: to validate the "pubkey" of the server is authentic. If you are more curious about how realistic this is, please take a look at https://mitmproxy.org/, which fully automated this MITM attack. So next time you see a Python application that sets verify=False on an SSL.open .. just shake your head and run.


So, as I did earlier, we have to balance this scenario. How likely do we consider this, what is the impact if this happens, and how much effort is the mitigation (as in, is it worth putting N effort to mitigate risk with X chance and Z impact).

In my opinion, especially in combination with protocol-support (openttd://), I would consider this scenario slightly more likely. Still, the impact is very low. How-ever, it would be a witch-hunt to track down what happened if this happens .. so it might be worth putting effort in this.

The 3 mitigations I mentioned earlier are still a valid option for this, but I also still think creating our own CA might be a bit overkill. I am sure other solutions exist too, I would consider this a: the floor is open, situation :)

What might be good to know, this doesn't have to be resolved here and now. Pubkey validation can be added on later to harden the security further. That would also give a bit of time to think it through properly without blocking existing work to continue ahead. But I am sure you can better balance this atm than I can, so I leave this to you @Milek7.

Sadly, generating a new pubkey for every server you connect to (as client) does not resolve this issue :( As that would have been a simple cheesy solution ;)

Anyway, just a random thought that pop'd in my mind. Let me know what you think, if you see the world differently, etc. Floor is yours :)

@Milek7
Copy link
Author

Milek7 commented Jan 11, 2021

More words to follow later (on encryption implementation approach and ways to implement server pubkey trust), but quickly for now:

In my opinion this attack is highly improbable, contrary to original attack described. It is another matter to lure someone you know who have rcon access on particular existing server onto your malicious server, than it is to somehow make them join wrong server in the first place and then expect they will get rcon access there. (assuming trusted network). But obviously this is subjective opinion.

Another take on this, if we consider ideal scenario of model that is currently used: network is trusted, use high-entropy passwords, never reuse them. In that case, KEYAUTH challenge scheme without DH is worse than currently used passwords! While this with DH and encryption is no-worse than passwords. That's why I'm somewhat reluctant on going forward with pubkey PR without encryption.

@TrueBrain
Copy link

That's why I'm somewhat reluctant on going forward with pubkey PR without encryption.

Just so there is no confusion: this is completely up to you. My only request here is that we process encryption as a separate PR, so we can do all kinds of testing to see if nothing breaks. Something not for this gist, but I currently don't have another place to put it, so here we go :P (this just talks easier than IRC, honestly)

Lowering the MTU has consequences, as I believe some packets are nearly exactly full. Some other packets check for MTU size to split into multiple if needed. So we need to check those and make sure they work properly. Nothing we cannot do, just something that requires time :) I believe one of the packets was if you have full companies with maximum custom names and presidents, or something silly.

Another thing we have to validate, but don't have an active OS for anymore, is Big Endian machines. I think it should just work, as all these operations are done on bytes as far as I could see, but that doesn't mean we shouldn't validate. I guess Qemu with ppc64 will do the trick.

And I am sure more of these nasty things pop-up .. it will be "fun" to evaluate the PR, but something we need to do sooner or later anyway, so .. yeah .. let's get it over with :) But this is the reason I would prefer to have it as a single PR, without the password changes etc. As this on its own will already be a few hours if not days to test, validate, etc :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment