Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ensuring retrieved JSON is validated from the correct server for Javascript games

I'm currently trying to establish a technique to verify some data from my server, for a simple Javascript game. I understand that there are many issues with trying to protect your Javascript application as, by definition, all of the code is available client-side.

For example, as it's a game, if:

  var playerScore = 100

to stop people from simply editing the Javascript and running it as such...

  var playerScore = 10000000

...I will validate it on the server. However, this (to me) seems to just add a very trivial extra step to hack the game. Couldn't the hacker\tweaker just change the destination of my Ajax request to their own local files?

I thought about maybe trying to compare an on-the-fly key\encryption of some common value, e.g. date, between client and the server, but again, the encryption methods would be visible so trivial to avoid.

I know this is a very broad subject, but are there any best practices that you anyone can recommend? I've seen this post ( Prevent Javascript games tweaking/hacking ) and it was helpful with regards to preventing certain tampering, but I'm not sure about how to verify the correct server-side validation is taking place.

As always, many thanks in advance.

like image 355
flukeflume Avatar asked May 19 '13 10:05

flukeflume


2 Answers

To answer your questions directly:

Q - "Couldn't the hacker\tweaker just change the destination of my Ajax request to their own local files?"

A - If your script is not obfuscated yes they can change the AJAX request destination to use their own game server. Even obfuscated scripts can be unpacked to change the request destination of the hackers game but what are the implications of them doing so? They will have their own version of the game doing its own thing, talking to their own server... Hardly a real problem unless there are secrets to unlock in the client game, having your own server might make these secrets marginally easier to uncover than just reading the client source.

The game server could, to protect game secrets, however, drop these secrets as script payloads during the game... In which case you will need to read on.

Q - I know this is a very broad subject, but are there any best practices that you anyone can recommend? ... I'm not sure about how to verify the correct server-side validation is taking place.

A - For preventing the forgery of game state and stats on a game server, which, I don't believe it is this you are asking... I believe you are asking how to prevent the game client from having incorrect game state, which is impossible, as soon as the client code leaves your server it can be tampered with and any measures to prevent would also be contained in the client code and are by nature, reversible and therefore circumventable...

Countermeasures to prevent hacking game server stats

Assuming the client is the authority on the current game score and the server is not running an authoritative version of the game session and receiving user input updates from the client (in which case, the user input only needs to be sanitized, but introduces complexity in that state needs to be synchronized from the server to the client and it will be sensitive to latency issues).

It seems that it is a problem that in general has no perfect solution that completely mitigates hacking, let alone a JavaScript game, for example, in Flash which is compiled the problem is still apparent: What is the best way to stop people hacking the PHP-based highscore table of a Flash game, even at a executable level on a system: How can I protect my .NET assemblies from decompilation?.

JavaScript is not compiled and is sent as plain text so it doesn't have these barriers (that are circumventable) to hacking.

I've compiled an approach to mitigating this problem, from the various resources linked and some Google searching, my game:

var score = 0;
function increaseScore() {
    score ++;
    sendScore();
}
function sendScore() {
    // Ajax
}
function run() {
    setTimeout(function() {
        increaseScore();
    }, 1000);
}
run();
  1. Closure: Using a closure around the running code will prevent the functions and variables from being called through the browser console. It doesn't prevent breakpoints or simply modifying the script to remove the closure.

    (function() {
        var score = 0;
        function increaseScore() {
            score ++;
            sendScore();
        }
        function sendScore() {
            // Ajax
        }
        function run() {
            setTimeout(function() {
                increaseScore();
            }, 1000);
        }
        run();
    }) ();
    
  2. Obfuscation: you can use Dean Edwards packer, or simply minify the code using something like closure compiler. Minification wont prevent watching and changing variables through the browser console but both will prevent the source from being human readable, which will be required for the hacker to find sensible breakpoints to watch.

    The dean edwards packer turns the script into a string which is eval-ed which makes it impossible to add breakpoints to the original script. It is, however, possible to unpack the script and for the hacker to use this script instead to add breakpoints.

    eval(function(p,a,c,k,e,r){e=String;if(!''.replace(/^/,String)){while(c--)r[c]=k[c]||c;k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(1(){6 2=0;1 3(){2++;4()}1 4(){}1 5(){7(1(){3()},8)}5()})();',9,9,'|function|a|increaseScore|sendScore|run|var|setTimeout|1000'.split('|'),0,{}))
    
  3. Encryption: the client encrypts the scores before sending, this wont prevent modifying the scores through the browser debugger if the hacker has access to the encryption function, but will prevent simple forgery of the scores in the server request.

  4. Game instance token: The server includes a unique token in the request which is sent back to the server on the start of the game. This prevents simple modification of the script (as in unpacking or removing the closure) as the instance token will have already been used by the original script, invalidating it.

    The determined hacker will need to remove the code that sends the token back to get around this, they will also need to ensure that the scores being sent back logically pick up from the original game instance (see step 6. and made much more complex because of step 5.).

    (function() {   
        var score = 0;
        var token = "abcdef123456789"; // Inserted by server side code 
        function increaseScore() {
            score ++;
            sendScore();
        }
        function sendScore() {
            var encrypted = encryptScore();
            // Ajax send encrypted
        }
        function encryptScore() {
            // Uses some encryption lib
        }
        function sendToken() {
            // Ajax send token
        }
        function run() {
            sendToken();
            setTimeout(function() {
                increaseScore();
            }, 1000);
        }
        run();
    }) ();
    
  5. Scoring token: The server sends an token for the next score to be encrypted with. This reduces the size of the attack window. If the hacker has found the code to encrypt the score to send they still need to get the correct token for the current score update.

    (function() {   
        var score = 0;
        // Yes it would be easy to simply add items onto this queue
        // the closure and packing to prevent breakpoints, plus the
        // session token are required to prevent this
        var queue = [];
        var token = "abcdef123456789"; // Inserted by server side code 
        var sending = false;
        function increaseScore() {
            score ++;
            queue.push(score);
            if(!sending)
                sendScores();
        }
        // Scores now need to be sent sequentially, the last
        // score sent response will have the next scores token
        function sendScores() {
            sending = true;
            var encrypted = encryptScore(queue.shift(), token);
            // Ajax send encrypted, send next score when 
            // response received, if there is a next score 
            // in the queue, otherwise stop sending 
            // (sending = false)
        }
        function encryptScore(score, token) {
            // Uses some encryption lib
        }
        function sendToken(callback) {
            // Ajax send token, sets new token value from server response
        }
        function run() {
            sendToken(function() {
                setTimeout(function() {
                    increaseScore();
                }, 1000);
            });
        }
        run();
    }) ();
    
  6. Server side sense checking: The server checks if the user's score has increased more than would be deemed possible, (or even decreased in the case of reusing a instance token above), this could be the step in sequential score updates is too great, illogical or that the score has increased too rapidly within a given period.

  7. Blacklisting: Quarantine users and sessions that show cheating behaviour so that it is not apparent to the hacker that they have been found out and their scores are silently removed from the valid score pool.

  8. Greylisting: Flagging users or game sessions that appear to be very close to the perfect scoring scenario for manual investigation to decide whether to blacklist.

In general I would create as much audit data as possible to try and monitor the situation, manually intervene where necessary and add more preventative measures as problems reoccur in a live situation.

Also, because there is no perfect solution to this problem it is simply about making it as difficult as is needed for it to be not worth the time required to hack it. There are always the persistent hackers that are up for the challenge, but keep it in perspective: If a hacker did try to hack the scores with the current countermeasures in place what would be the result and is this worth your time to circumvent?

At a guess commercial game producers use most tricks in the book, but even this would depend on the budget vs the risk.

like image 154
15 revs Avatar answered Oct 24 '22 11:10

15 revs


I think your first step would be to hide the javascript variables from the user.

Wrap game variables inside javascript closures, so they are not accessible from outside.

var gameSettings = {};
(function () {
    var playScore = 100;

    $.get("/Game/UserPlayScore", { userId: 'userId' }, function (data, status) {
        if (status === "success") {
            playScore = data.playScore;
        }
    });

    gameSettings.userPlayScore = function() {
        return playScore;
    };
}());

// playScore is unaccessible from outside the function
// but you can read the value by calling gameSettings.userPlayScore();
like image 1
Catalin Avatar answered Oct 24 '22 10:10

Catalin