AWS Mobile Blog

Using Amazon Cognito and AWS Lambda to Detect Cheating

by Scott Willeke | on | Permalink | Comments |  Share

This blog post was contributed by Steve Johnson, Senior Solutions Architect

Today we’re going to talk about cheating in mobile games. Specifically, we’ll talk about how to prevent an exploit using Amazon Cognito and AWS Lambda. An exploit is an unplanned use case – something the player does that is unexpected or that has an unexpected result. Two examples of exploits are when a user changes the time on the device to get more resources (time skew) or when a user deliberately submits a false score for a game.

Exploits are different from traditional security issues. For example, if someone launched a denial-of-service attack on your servers you would shut it down as soon as possible, not worrying about the attacker’s "user experience." However, in the case of game exploits, that user is also a player. We want to keep players engaged in the game, while ensuring that their exploits don’t ruin the experience for other players.

Last month, we talked about using Amazon Cognito Sync Trigger events with AWS Lambda functions. Today, we will demonstrate how to use that combination to detect and mitigate common exploits.

Amazon Cognito and AWS Lambda: Mobile Detectives

Amazon Cognito is both an identity management and data synchronization service for mobile developers. Using Amazon Cognito, your players can log in to your game using a social login such as Amazon, Facebook, Twitter, Google, or your own backend-authentication system, or they can play anonymously. Amazon Cognito syncs data for users across their devices, maintaining their data in the cloud. Every time Amazon Cognito syncs data, you can trigger an AWS Lambda function that operates on that data before it is saved. Think of an AWS Lambda function as event-driven code living in the cloud.

Identifying Exploits Through Validation

The purpose of pairing AWS Lambda with the Amazon Cognito Sync Trigger event is to analyze the incoming game state and make changes, if necessary. In our case, we want to identify anomalies in the data that might be caused by exploits.

Let’s start with a simple case. You know the maximum possible score in your game (2,112), and would like to make sure that no one posts a score higher than that. So let’s write an AWS Lambda function that throws out any scores higher than 2,112.

The process for setting this up is as follows:

  1. Create an AWS Lambda function that examines the Sync Trigger event passed by Amazon Cognito and removes any scores that violates our validation rules.
  2. Associate that AWS Lambda function with the Sync Trigger event of the Amazon Cognito Identity Pool of our app.

That’s all there is to it. All of this can be done from within a web browser using the Amazon Cognito and AWS Lambda consoles.

Before we begin, let’s talk about how Amazon Cognito data is stored. When a device syncs data, just before Amazon Cognito saves the data to the cloud, it sends the data, in JSON format, to an Amazon Cognito Event called the Sync Trigger event. You can attach an AWS Lambda function to the event to change or remove values before they are stored. So if we remove a value from the Sync Trigger event, it will not be saved. This is the feature we will take advantage of in our example.

From the AWS Lambda console, create a new function by selecting Edit code inline and use the Cognito Sync Trigger code template. Enter the following code:

exports.cognito_sync_trigger_handler = function(event, context) {
	//Check for the event type
	if (event.eventType === 'SyncTrigger') {
		//Look for suspicious score
		if ('scoreKey' in event.datasetRecords) {
			//Test the score
			if ( event.datasetRecords.scoreKey.newValue > 2112) {
				delete event.datasetRecords.scoreKey;
			}
		}
	}
context.done(null, event);
};

The function is coded in Node.js (JavaScript), which makes parsing the JSON object a snap. The code looks for a dataset record key called "scoreKey" and deletes it from the JSON graph if it exceeds our maximum allowed score.

You can test your code by selecting Cognito Sync Trigger for the Sample Event on the Edit/Test page, and adding a record set called "scoreKey":

{
	"version": 2,
	"eventType": "SyncTrigger",
	"region": "us-east-1",
	"identityPoolId": "identityPoolId",
	"identityId": "identityId",
	"datasetName": "datasetName",
	"datasetRecords": {
		"scoreKey": {
			"oldValue": "0",
			"newValue": "10000",
			"op": "replace"
		}
	}
} 

Now, go to the Amazon Cognito console and use the drop-down list to link your Identity Pool’s SyncTrigger event to your new AWS Lambda function.

To see your code in action, run the Amazon Cognito Sync sample application (download for iOS, Android, or Unity from the AWS GitHub repositories) using the Identity Pool you associated with your AWS Lambda function. Using the sample app, create a "scoreKey" record, assign it a value, and then press sync. Any time the value exceeds the maximum, the sync will occur successfully but will not update the suspicious value.

Congratulations! You now have a framework for evaluating your mobile app’s data submissions. We can use this same framework to prevent a time-skew exploit.

Many mobile games offer rewards or loot that accrue after a certain amount of time. A common exploit is moving the device’s clock forward to accelerate the process:

  • Put device in Airplane mode
  • Set date forward (months, years)
  • Turn off Airplane mode and sync with game server

Because mobile devices are not always connected to the Internet, the time spent playing the game is often saved on the device itself. The time is synced with the game server when it reconnects. Although it is difficult to determine the amount of time a game is played on a disconnected device, we can certainly eliminate time travel as a possibility.

In this next sample, we will compare the time offered by the client (in a key named "timeKey") with the system time on the server. If the offered time is after the current time, we simply reset it to the current time.

var now = new Date();
var offered = new Date( event.datasetRecords.timeKey );

if (offered > now) {
	timeValue = now.toJSON();
}
event.datasetRecords.timeKey = {
	'newValue': timeValue , 
	'op' : 'replace'
};

In both examples, the exploit was handled quietly — the player of the game is not given an indication that anything was detected or corrected. We don’t make any assumptions about cheating or the player’s intent — we simply continue processing data in the expected ranges.

That being said, any anomaly in your game should be tracked for your own benefit. Something that looks like an exploit by a player might actually be a bug in the software. AWS Lambda functions can interact with AWS data storage services like Amazon DynamoDB and Amazon S3, giving you the opportunity to collect metrics on these events. You can create reports by injecting custom events in Amazon CloudWatch. If you’re a fan of big data, Amazon Cognito can stream sync data to Amazon Kinesis as well.

Moving Forward

Even though we’ve shown a simple task, keep in mind AWS Lambda is a first-class compute engine that scales automatically to support your workload. AWS Lambda functions have access to the full AWS Node.js API, giving you the ability to:

  1. Inject metrics into Amazon CloudWatch
  2. Store data in Amazon S3 and Amazon DynamoDB
  3. Examine the CognitoSync datastore metadata

You can also invoke Java, Ruby, bash, and Python libraries in your functions. Soon you will be able to write your AWS Lambda functions in Java as well.

Amazon Cognito and AWS Lambda are two of the newest tools for AWS Mobile, and are a lot of fun to experiment with. Both have a free tier that provides you plenty of runway to play and discover different ways to process your mobile app’s data.

Feel free to use the comment section to share your ideas about how Amazon Cognito and AWS Lambda can make your games (and apps) more fun.