We take security very seriously at GameSparks, as any Backend-as-a-Service provider should. We adhere stringently to best practices at every level in our technology stack. At each networking layer, we employ the latest means of protecting and securing data from a potential attacker:
- At the Application/Transport layer, we secure your WebSocket connection with TLS, utilizing the WSS protocol.
- At the Network layer, we ensure that certificates are locked down and that access to the instances servicing your games are locked down.
- The service is tested regularly to ensure that no attack vectors are unaccounted for.
However, despite this raft of safeguards, game players are smart and might try to get around them. If there's a way for them to game the system, you can be sure they will. So, although the infrastructure supporting your game as you build is secure and the data being fed to that infrastructure is appropriately encrypted using modern cryptographic protocols, the systems driving game play and retention for your game might be exploitable. We therefore need to keep in mind the following points/best practices when developing multiplayer games:
- Security through Obscurity
- Suspecting User Input
- Using Credentials to Reduce Blast Radius
Security through Obscurity
Although it's not a complete preventative measure, implementing a system which will be programmatically obscure to the end user can be an effective means of maintaining security.
It's rare, yet the reality is that games can be decompiled and their codebase scrutinized by a potential attacker. Whether an abusive player wishes to award currency, level up, deny services, or interrupt games, the way in which you are writing your client-side code could be enabling them to do any of these disruptive things. Here's some tips on how to use obscurity to frustrate those users who might be attempting to disrupt your game in this way:
- Avoid Descriptive Naming for Functions: Take for example currency rewards. Simply naming a function as AwardCurrency() will be an advertisement for any would be attacker. It's descriptive in that the function's purpose is made clear in its naming, easily affording someone the ability to leverage this and see its result.
- Avoid Descriptive Errors: As with the dangers carried by descriptive naming of functions on the client side, equal risks apply for systems whose logic resides in the cloud when returning descriptive errors - it's through the enumerating and variation of inputs that someone can determine how your Events and Cloud Code function through the errors they return. For example, although you might have obscured the naming of your AwardCurrency() function, returning an error of: "Must specify currency ShortCode: [ XP or RUNES]" will defeat the purpose of such a tactic. An attacker will know what this function does and how to exploit it.
By keeping in mind the curious nature of gamers, as well as the potential for the exploitation of your client-side logic, you can use obscurity to remove the bread crumb trail which could otherwise lead to your game being compromised.
Suspecting User Input
There is a very simple yet often overlooked principle of system security: user input should never be trusted. This is as true for security as it is for fault tolerance in your applications. You cannot guarantee you are receiving the expected input for a system for any interaction with the client. To avoid unexpected results, or more importantly disruption to gameplay, input should be sanitized and checked to ensure it lies within sensible bounds.
Imagine you are playing an Arcade style fighting game using GameSparks Realtime. Your opponent is somehow striking you multiple times per millisecond, making it impossible for you to return attacks and killing you before you can figure out what is going on. In this scenario, it's likely that the user has found a way to inject packets into the instance via your client. These packets are triggering the 'attack' event and are being exploited by the user to cheat their way to winning the game.
To prevent this, a timer can be established in Cloud Code to ensure the frequency of attack is within a sensible range. This will do two things:
- Prevent exploitation of one player by another.
- Identify players who are cheating by potentially writing them to a log file for Auditing by Game Administrators at a later time.
This technique enforces server authority on what the bounds of input should be.
Another example where you should not trust user input is in the context of database querying. When you build a query that uses data from the client, ensure you do this in a way that only allows the client to pass up values for the query parameters, and which does not allow the client to specify the fields used for the query. If you build the query in Cloud Code, your query is safe from the injection of unexpected query parameters entered through the client.
Using Credentials to Reduce Blast Radius
All of the above will certainly help in maintaining a secure environment in which players are protected against those who wish to cheat or simply disrupt the state of play. However, it's dangerous to assume that any system is immune to exploitation. This is where one must consider the potential damage an attacker might be able to do.
The worst case scenario would be for an attacker to gain the ability to send Administrative level requests to your game. These could be Requests configured by you for game management or even System requests such as Bulk Job scheduling.
Credentials in GameSparks provide an out-of-the-box solution for restricting what users can do when they connect to the service. Credentials can be used to great effect to limit the requests available to the client. They can be configured to create a whitelist for sendable requests using a Credential's Short Code.
By using Credentials on your Client, a would-be attacker will be limited in what they can do, turning a potential catastrophe into a simple nuisance. You can read more on how to create and configure Credentials here.