[Tutorial] HTML5 games and Cross-Site Scripting (XSS)

on August 15, 2017 - 1520 Views

GameMakerBlog Tutorials

HTML5 Games and Cross-Site Scripting

If you have ever looked up any of the http_* methods in the Gamemaker: Studio documentation you have surely seen the following warning about cross domain issues:

NOTE: You should be aware that due to XSS protection in browsers, requests to and attempts to load resources from across domains are blocked and may appear to return blank results. Please see the section on Cross Domain Issues for further details.

But what exactly is Cross-Site Scripting and why would anyone be trying to use it in a game? Further, are there any ways to get around browser restrictions to make this work in case we really need this functionality? We’ll step through these issues piece by piece and hopefully make sense of everything by the end.

Cross-Site Scripting or XSS

A cross-site script refers to any script running on a user’s browser that attempts to load or pull data from a different domain than the one the user originally loaded. Much like the old sql injection methods used for dumping database tables, cross-site scripting allows for embedding malicious scripts (<script> tags) inside user input fields like custom search queries or comments which would then be run by an unsuspecting user’s browser when they loaded the infected page that would then make connections to the attackers server to, for example, store the unsuspecting user’s form data. In order to prevent these types of attacks, modern browsers will employ what is called: “Same-Origin policy” that will only allow scripts to access data from the same “origin” or domain. What this means for games, however, is that we cannot load external data from different domains into our game since the browser will see that as no different than a regular cross-site script and block the connection. If we use a very simple example of building a League of Legends fight simulator game that loads different champions and simulates battles against them we can easily see the problems.

Step 1: Get a list of Champions from the LoL API in a GML Event:
getChampionJson = http_get("https://na1.api.riotgames.com/lol/static-data/v3/champions?locale=en_US&dataById=false&api_key=API_KEY"); //This method returns a unique ID and assigns it to the variable "getChampionJson", we will then look for this ID later on when checking the Async callbacks for a response to this exact message

Then create an Async_Http event to handle the callback from the http_get:

if (ds_map_find_value(async_load, "id") == getChampionJson) //This checks if the 
async message we got back matches the getChampionJson ID from the message we sent earlier
{
     if (ds_map_find_value(async_load, "status") == 0) //If the async status is 0, 
it means the http event was successful, if it  is -1 then it was not successful
     {
          show_message(ds_map_find_value(async_load, "result")); //Here we just dump the 
"result" of the http_get we performed as a popup window so we can easily view it 
     }
     else
     {
          show_message("HTTP_GET message attempt failed."); //Debugging message popup
     }
}

Now if you run the above with a target of Windows, you will get the following popup with a lot of JSON, full of LoL Champion Data:

LoL Champion Data Json

That is exactly what we expected and wanted, but the goal was not to make a Windows game; so let’s change the target to HTML5 and run it again:

LoL Failed Http Get Request

So why did this work when the target was Windows but not on HTML5? The answer being of course that the browser saw the http_get to na1.api.riotgames.com/lol/static-data/v3/champions as a cross domain call and properly blocked it. So as of right now, our attempt to make an HTML5 League of Legends simulator has completely failed as we could not even load the champion data which was step 1.

Work Through not Around

All is not lost, as there are some things we can do to get by the cross-scripting limitations though some are not easy to implement and others are frowned upon; but let’s investigate our options anyway.

  1. Load All the data into the included files of the game
    1. This is certainly easy to do and removes the need to make any http_get calls at all. The downside, of course is now the game size inflates and if the data ever changes you will need to update the game for the changes to take effect. When building a game based on data you do not control; this may not be the best method, such is the case for our LoL simulator as the data is patched very frequently and our simulation algorithms will become outdated
  2. Host the game on the domain that contains all the data
    1. If you host the game on the website that contains the data for the http_get, then it is no longer a cross domain call; which solves all of our problems. However, if you do not have access to the data domain host, you will not be able to implement this solution as is, and we certainly cannot upload our HTML5 simulator game to na1.api.riotgames.com
  3. Use a Reverse Proxy on the host you are hosting the game on to fetch the data being requested in the http_get
    1. This solution gets tricky, a Reverse Proxy retrieves data on behalf of a client so the client never knows where the source came from (it always looks like the Reverse Proxy was the source of the data). In our case, we would create a Reverse Proxy that handles the http_get requests from our game and gets the data from the LoL API for us. Using this solution we need to change the http_get code slightly
      1. http_get(“https://YOUR_GAME_HOST/lol/static-data/v3/champions?locale=en_US&dataById=false&api_key=API_KEY”);
      2. The Reverse Proxy can be setup to then makes a request for path “/lol/static-data/” to map to “https://na1.api.riotgames.com/lol/static-data/” and send the rest of the query as is to the target host
    2. This solution can work if you are choosing to host the HTML5 games yourself and have access to your webhost so you can install and run the Reverse Proxy software. Sometimes this is not an option though, especially if you are uploading your HTML5 games to a game portal like Kongregate
  4. Add CORS headers to a server that can serve the data to your game
    1. CORS stands for Cross-Origin Resource Sharing and adding special CORS access headers on the server that is giving the data to the requesting web application will bypass the XSS restrictions. This is the method officially documented on YoYo Games: Cross Domain Issues
    2. Since the website that serves the data needs to supply the CORS headers, this method would not work directly for our League of Legends example since we cannot ask Riot Games to simply add the headers for us.
    3. We can, however, setup a proxy server on one of our own hosts that will fetch the data for us from the LoL API and then add the CORS headers to the data before sending it back to the game. In this way, we can upload the game to a hosting site like GameJolt and have it call our servers to get the requested data without any XSS issues.

Keep in mind the differences, though small they add up

YoYo Games has a great article on other issues when working with HTML5 (originally written for GameMaker: Studio 1.4), which if you want can be read here: HTML5 Issues and Differences; while the differences are not what I would consider game breaking they are important to keep in mind when designing a multi-platform game that is intended to run on the web. Any designs that do not account for getting, loading and using resources early on could become a major blocker to releasing a game; so as always, constant testing on all target platforms is advised. While we can all appreciate the safety modern browsers have built into them, it can pose challenges to basic functions we may take for granted coming from an application background like Windows but with proper investigation and a little help, almost any issue can be resolved.

Leave any questions or comments below.

Good Luck in all things,
Hikati Games
Hikati Games LLC Logo

 

 

Recent Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

UA-103187421-1