43

I'm creating an web-application that has a simple javascript game in it. Once the player finished playing the game the high-score is sent to the server and saved.

After a specific period the player with the best score receives a prize.

Is there a way to send the high-score securely and prevent the client sending 'false' high-scores?

Currently we are using:

  • Https.
  • Server random token before each game and sent after the game is finished alongside the score.
Anders
  • 64,406
  • 24
  • 178
  • 215
  • 37
    There is no foolproof way to solve this problem. The only way is to keep track of each games individual score on the server. – Luke Park Jan 15 '17 at 10:20
  • 10
    TLS has nothing to do here, and you can't ensure anything on client side if it's *trying* to be malicious. You should read an [answer about your actual problem](http://security.stackexchange.com/a/147047/91111). – transistor09 Jan 15 '17 at 18:35
  • There's a similar question here, the top answer has a bunch of good suggestions. https://stackoverflow.com/questions/73947/what-is-the-best-way-to-stop-people-hacking-the-php-based-highscore-table-of-a-f – Jezzamon Jan 15 '17 at 22:57
  • 2
    By the way, you are asking this question *way* too late. If it was required, this kind of security should have been built in from the very beginning, not grafted on later. – David Schwartz Jan 16 '17 at 00:42
  • OTOH, if the incentive to crack your game (and so fake high scores) is smaller than the difficulty of cracking it, your game might do well simply because there isn't enough interest in faking the score. – woliveirajr Jan 16 '17 at 17:00

6 Answers6

116

The server can't fully trust any data it receives from the client, so validating high scores is difficult.

Here are a few options:

  1. Obfuscate the client-side code and traffic to the server. This is the easiest option - it will still be possible to cheat, but probably won't be worth anyone's time.

  2. Send a full or partial replay of the game to the server for validation. The player's moves can be run on the server to determine the legitimate score. This will work for some games and not for others.

  3. Move the game logic and validation to the server. The client simply relays each move to the server, where it is validated and the game state updated.

It comes down to how your game works, how likely it is that people will cheat, and how much you care if they do.

grc
  • 1,845
  • 2
  • 15
  • 9
  • 68
    #3 is the only proper way to do this – theonlygusti Jan 15 '17 at 11:42
  • 7
    @theonlygusti Maybe I misunderstand the point but you don't have to move the game logic - you only need to replicate it on the server. If all you send is all the player's inputs then the server can replay them and verify the score. Then you only have to care about 'TASbot' players :). For an example of a game that does that well - DROD. Because it's a logical game it's also resistant to tool-assisted playthroughs. The only unfixable issue is sniping, where someone watches other player's demo and looks for points of optimization. – Maurycy Jan 15 '17 at 12:40
  • 3
    @Maurycy I was just saying that the only way to be sure that a highscore is valid is to handle the game on the server. Moving/replicating doesn't really matter – theonlygusti Jan 15 '17 at 12:41
  • 1
    @theonlygusti I mean the point says "move the game logic" and I felt it needs to be nitpicked that you merely need to replicate it. I probably should've tagged you in the comment because I really meant to reply to the answer as a whole, not your comment :) – Maurycy Jan 15 '17 at 12:58
  • 28
    @theonlygusti: For many types of games, 2 is just as good as 3. – R.. GitHub STOP HELPING ICE Jan 15 '17 at 15:17
  • 5
    @theonlygusti Correction, #3 is the only *foolproof* way to do this (but that's actually not true as it's not foolproof!). It's just the most foolproof, and most difficult, of the options given. – user253751 Jan 16 '17 at 00:32
  • 10
    @theonlygusti, for deterministic games (think: Tetris), #2 will work so long as you don't mind granting high-score records to AIs. – Mark Jan 16 '17 at 09:05
  • 2
    @Mark #2 also has the disadvantage that a client could optimize based on future knowledge. E.g. in Tetris if you know all the pieces you will get, you can find a better strategy than if you only get one piece, then place it somewhere, then get the next. – Josef Jan 16 '17 at 14:10
  • 3
    #1 is useless with a javascript game, as it's is _much_ easier to re-generate readable code than with any other language. – Stephan Bijzitter Jan 16 '17 at 21:31
29

If your attacker were a man-in-the-middle attacker on the network layer, then https would be enough. But unfortunately your attacker is the client, so this is rather pointless.

Rule number one of cheat-proofing games: Never trust the client! The client is in the hands of the enemy.

Javascript games run in the user's web browser. As you as a web developer surely know, every web browser nowadays comes with a build-in debugger which can view and change all variables while an application is running. The user can simply use the debugger to change their score before it is submitted. There is nothing you can do to prevent that. With browser-based games you can't even slap a 3rd party anti-cheat tool on them (which are of dubious value and ethically questionable anyway).

The only countermeasure is to implement all game mechanics worth manipulating on the server. The client should do nothing but forward the user's commands to the server and visualize the gameplay based on the server's messages. The actual gameplay should happen on the server where it is out of reach of the cheaters.

Yes, this means that you will have to redesign your game's software architecture from scratch. It also means you will have to add some prediction and interpolation code to make network lag less visible. And it also means that you will need far better server hardware and a better internet connection for your server(s). But when you want a cheat-free online game, that's the only option.

Philipp
  • 48,867
  • 8
  • 127
  • 157
  • 3
    There's the usual **trust, but verify** response - see option #2 in grc's answer. – MSalters Jan 15 '17 at 15:02
  • 2
    @MSalters Is not **trust, but verify** an oxymoron? You don't trust the client which is why you verify it's response. If you trusted the client, why the need to verify it? – Tom Jan 16 '17 at 01:37
  • 3
    @Tom "Trust but verify" is common phrase that comes from a Russian proverb (see [Wikipedia](https://en.wikipedia.org/wiki/Trust,_but_verify)). And yes, it's oxymoronic, but you can also read it as being in same vein as "innocent unless proven guilty". Or, apropos, when Wikipedia assumes users are acting in good faith when editing (it's only a wiki because it's not locked down), but still requires them to cite references. – Flambino Jan 16 '17 at 02:17
17

To achieve secure game high score, what you need is a "proof of work", or rather, more appropriately called proof of play.

A proof of work/play is any data that's hard to compute before finishing the game, but easy to compute once you finished the game and is easy to be verified by the server. For example, for a Sudoku game, a proof of work is the solution to the puzzle.

Unfortunately, there's no generic way to integrate suitable proof of play system in many games. It's not always possible to integrate a proof of play without tweaking or rehauling the game play, or limiting the scope of the leaderboard to just elements where you can include sufficiently secure proof of play.

Proof of play on a game starts by the server issuing the seed for the game's RNG. The client then will have to find a proof of play that matches the seed that the player had been issued. In many games, a proof of play can be formed by submitting the game's solution to the server with the high score submission, in others it may be necessary to record the entire game session in various level of details depending on the game and the proof of play you're using.

A proof of play need to be replay resistant (one player should not be able to use another player's finished game to make their own proof of play easier to compute). This limits proof of play to games where there's some randomness that players cannot just reuse another player's proof of play, but also it cannot have too much non deterministic behavior to the point where verifying the play becomes impossible.

Proof of play is also limited. For example, in a Sudoku game, it's not possible to prove the amount of time the player take to solve the puzzle. Proof of play also cannot always distinguish between game played by the player themselves vs the player having written a script that played the game for them.

Lie Ryan
  • 31,089
  • 6
  • 68
  • 93
  • One approach to prevent "plagiarism" of solutions would be to have the player's name specified before the game starts, hash the random number seed according to that name. and have the game report the unhashed seed along with the name and the solution. – supercat Jan 15 '17 at 22:21
  • 1
    Amount of time is easy to track since we **do** have a server side here. It is the amount between the generation of the puzzle and the submission of the result. – Tom Jan 16 '17 at 11:40
8

I just wanted to mention that there is a sort of solution to this problem but it is likely to not be available for you; the solution is called "homomorphic encryption" and it allows for a client to perform a known computation without knowing exactly what values they're computing with, and a server to therefore check the structure of the computed value to prove that the client did not just send a random string back but actually built it out of the several values provided.

The problem is, it is either slow or incomplete, with "incomplete" meaning "doesn't incorporate a full range of logical primitives." Very often it is a partial system allowing some encryption and decryption operations E(), D(), plus some complicated operation ⊕ such that

D(E(A) ⊕ E(B)) = A + B,

or the like.

So let's see how this solves the problem for some games. Consider a "lights out" type game where you are pressing on the N hexes of a hexagonal grid and each one toggles both its state and the states of those around it. This is a commutative algebra on those "presses" and hence there will be no more than N presses total in a given solution, plus each hex only depends on the initial state plus the presses of its own hex plus the 6 hexes around it.

Since our homomorphic encryption scheme only allows +, not XOR, we give each hex a 3-bit counter for the number of times it has been flipped. (The client software will automatically shrink each double-press of a hex to just one press.) The actual flip actions are therefore bit-vectors looking like,

001 001 000 000 000 001 001 001 000 001 001 000 000 ... 000 00000000 00000001

In other words they have 1s in each of these 3-bit fields that it flips, plus a 1 in some 16-bit counter.

We encrypt all of these with a homomorphic encryption scheme, send each of them to the client, and the client sends us back an encrypted value computed from these encrypted values we sent back. We then decrypt this and AND the decrypted value with the bitstring,

001 001 001 001 ... 001 11111111 00000000

and compare with the initial game-state adjoined with 0 for those 8 counter bits.

If they send us a random value, their chance that we accept it is 2-(N+8) and therefore their only useful way to pass the tests is to use the values we gave them in some allowed combination. They have access to some moves that we are not allowing them directly due to integer overflows, but we can always make the fields wider than 3 bits to make these costlier for the counter at right. But we were never transmitted their individual button-presses, much less replay the history: we accepted a vector saying "here is how I flipped the grid," which is the "super-insecure thing" that everyone is cautioning you about, but we did it in such a way that they cannot do the things we're worried about without access to a secret key.

Instead you see this sort of thing in cryptographic voting where you want some assurance that a voting machine doesn't spontaneously give 1000 votes to Alice.

CR Drost
  • 221
  • 1
  • 6
  • I do not see why the encryption is helpful here. The only way we are making sure that the client actually solved the puzzle is to include the "proof of work" he did. What difference does it make if the proof of work was not encrypted? – Agnishom Chattopadhyay Jan 18 '17 at 06:05
  • @AgnishomChattopadhyay Can you think of any situation where the proof of work of some solution S is substantially longer than S itself? Thanks to homomorphic encryption, you basically don't have to. – CR Drost Jan 18 '17 at 15:06
5

Everything on client side can be spoofed. So the answer is no.

EDIT I removed the security mechanism I wrote here because it only assure legitime sessions... but spoofed scores can be sent through legitime sessions. Thanks @Luke Park

OscarAkaElvis
  • 5,185
  • 3
  • 17
  • 48
  • I didn't get your second paragraph. Let's say I keep the 'random' token in the server-side. How can I validate it when the client sends score? And on the third paragraph, What do you mean by Sending initial random token securely? – StationaryTraveller Jan 15 '17 at 10:08
  • Send it on server side means that if you are using php for example, you must send it using php on backend, not by js. I guess the clients are unable to sniff anything and if you use https as you said is enough. To validate it, you must receive and store initial token (in database in session or wherever) on score server and then, after receiving the client's token (which could be spoofed), you must compared with initial token sent (which is secured) so no possibility of tricking it. – OscarAkaElvis Jan 15 '17 at 10:18
  • Anything client side can be spoofed, you said so yourself. The rest of your answer is therefore unnecessary. – Luke Park Jan 15 '17 at 10:20
  • @Luke Park, he asked `Is there a way to send the high-score securely and prevent the client sending 'false' high-scores?` so I'm trying to explain the entire process – OscarAkaElvis Jan 15 '17 at 10:22
  • There is no entire process. You can't prevent it. Anything that the legitimate client does can be spoofed by an illegitimate client. – Luke Park Jan 15 '17 at 10:23
  • You are wrong. The point is the server can create an initial token for the game sent in backend to the score server. This token is SECURE. Then, after games, the client token can be spoofed, ok, but the security mechanism is the token sent by client must match the secure token sent first when game was created. – OscarAkaElvis Jan 15 '17 at 10:25
  • But the client is still the one that sends it's score to the server right? With the token? – Luke Park Jan 15 '17 at 10:27
  • Let us [continue this discussion in chat](http://chat.stackexchange.com/rooms/51820/discussion-between-oscarakaelvis-and-luke-park). – OscarAkaElvis Jan 15 '17 at 10:32
  • 1
    After a while thinking more deeply about it, I think you are right Luke Park, sorry. I edited my answer. – OscarAkaElvis Jan 15 '17 at 10:36
0

My recommendation is more of a technique for obfuscation, and could possibly be a frustrating hurdle to many users. However advanced or determined users could still analyze your source code to reproduce the results.

Use a tool like Hashids to create a hash of the score, and send this in your server request along with the plaintext score. The string value of the user id could be the salt used to encode a hashid of the score, making the hash useless if shared. On the server side you could decode this hash and compare the result with the plaintext score that was sent to make sure they match as expected.

Ben Harrison
  • 101
  • 2
  • I wanted to note that hashids is just one tool that could be used. One limitation with this particular library is that it doesn't support negative numbers (assuming your game might need it). – Ben Harrison Jan 18 '17 at 16:11
  • It's only a little better than sending a plain text because the user knows his id and can decode it just like the server does. – StationaryTraveller Dec 20 '19 at 07:33