Be careful with choices that carry over multiple games

As I said before, I absolutely love story driven games. One thing that often turns a great game into a great series are expansions and sequels that actually continue the story and allow a flow between them. Might and Magic 1-5 had a pretty epic saga between Corak and Sheltem, Ultima was essentially 3 trilogies with Ultima 7 split into 4 parts as well, and Wizardry 6-8 formed the Cosmic Bane saga.

One of the really intriguing features that sequels offer is a character import that alters the story of the game before it is even started, based on the choices made in the previous game. Wizardry 6 had 3 different endings, and Wizardry 7 had 4 different beginnings (one for each ending and one for brand new players). In Wizardry 6, you could get a special ring which you could give to an NPC in Wizardry 8 for bonus xp and a bit of extra story. Dragon Age: Awakening and Dragon Age II picked up on a few choices made in Origins, depending on who is made King.

Playing a sequel and seeing how earlier choices made an impact – even if it’s small – is very intriguing. It encourages experimentation and adds replay value. But it can also cause a lot of grief. Let me tell you about my relationship with Mass Effect and it’s sequels.

The first Mass Effect is one of my favorite games. The universe is spectacular, the races are diverse, the dialogue is immortal and the gameplay is fun without getting in the way of the story (although yes, the sidequest are one of three possible choices, repeated dozens of times). I finished that game 6 or 7 times and ended up with a Level 60 character and every single side quest done. But most importantly, I had a character that I really identified with because all the choices were mine, set through hundreds of dialogues, dozens of Renegade/Paragon choices. But there was one decision that is really important to me. Without wanting to spoil too much, you have to make a choice between 2 side characters, one will live and one will die. I picked the one I really enjoyed as a character, the one who had a really interesting attitude and some great dialogue. The character that I let die is a shallow, generic red shirt in my eyes. There is also another character that can live or die, and since it’s a really awesome character I let him live. And then, there are two really interesting choices at the end.

Mass Effect 2 came out in 2010, and I had to start a new game because of a new Gamertag (that’s a whole different rant). With no option to import my ME1 character, I had to start a brand new game with the default choices. And to this day, I’m convinced that the ME2 default choices are meant as a punishment for the people who didn’t play ME1, because they are horrible. However, while ME2 mentions them, it doesn’t actually do much with those choices yet and focuses on its own story. You start the game, play through the first part and then a character offers to tell you about the past. Because I was already playing ME2, it was much easier to swallow. It then goes on doing its own thing and only much later you actually run into some of the ME1 choices. That made it much, much easier to live with them and just go on.

Today, I received my copy of Mass Effect 3. I was really enthusiastic about it, until I started it. The character import immediately shows up and tells you the choices before you even start the game. That’s already a downer that made me consider whether I wanted to first play the prequel to get the right choices. I resigned and started a new ME3 game. 5 minutes into the game (as part of the intro), I run into the character I wanted to die in ME1 in favor of the much better other character. A few seconds later, I switched off my Xbox.

Any enthusiasm I had for ME3 is gone. I can’t play it with the default choices, so now I must play through ME2 again, and it may be months before I even start playing ME3. For ME2, BioWare actually did the right thing: They offer DLC (“Genesis”) that is essentially an interactive comic in which you can make all the ME1 choices. The DLC came out much later than the game, so I didn’t have it for my character back then, but right now it saves me from playing through ME1 again (it would still be nice to get a character from another Gamertag).

This option should exist in ME3, because it’s the right thing to do. A lot of you might say that I should just play the game and that I’m being silly and that the changes made by these choices are really, really tiny. Fair enough, many people play games for gameplay reasons alone. But I’m a story gamer, and for me, the default choice in ME3 is a show stopper, even worse that you are confronted with them before you even start to play the game.

That is why multi-game storylines can be dangerous, because it may really ruin the motivation and enthusiasm a person puts into a game. I’m attached to the Universe and the people in them. I believe that any game which allows savegame import and adjust story choices based on that needs a way for people to set the variables themselves in case they don’t have a character to import. I realize that part of the problem is that I’m playing on an Xbox. On a PC, I would have just grabbed a Savegame editor and created the ME2 character I want for import into ME3.

Still, a game that’s story driven should make sure that such an option exists.

Always check all fields in the model

Today, Github was hit by a security vulnerability that was caused by not properly dealing with model class fields that were not passed in by the form. Basically, by forging a HTTP Request, it was possible to set values for any field in the model, even the ones the forms on the website normally don’t pass in.

This type of issue is not limited to Ruby on Rails though, a similar issue can happen with any framework. Let me demonstrate how this could affect ASP.net MVC. Assume you have a Business Object:

public class BusinessObject
{
    public int? UserId { get; set; }
    public string Content { get; set; }
}

Your underlying service makes sure to set the UserId to the current user if it’s not set:

public void Upsert(BusinessObject business)
{
    if (!business.UserId.HasValue)
    {
        business.UserId = _userService.GetCurrentUserId();
    }

    using (var repo = _businessObjectRepositoryFactory())
    {
        repo.Upsert(business);
    }
}

On your form, you don’t set the user id:

<form action="@Url.Action("Index")" method="post">
    @Html.TextBoxFor(m => m.Content)
    <input type="submit" value="send" />
</form>

And in your controller, you just pass through the model to the service, since the service deals with it:

[HttpPost]
public ActionResult Index(BusinessObject model)
{
    try
    {
        _businessService.Upsert(model);
        return RedirectToAction("Index");
    }
    catch (Exception ex)
    {
        Trace.TraceError(ex.ToDebuggingOutput());
        return RedirectToAction("Error");
    }
}

This works great, your service enters the branch that sets the current user id, so you can move on to the next feature!

image

But wait, what if I just forge a HTTP POST using cUrl or Fiddler or JavaScript or the Firefox Poster plugin or any of the other many many ways to create arbitrary HTTP Requests? Let’s try!

image

Well, look at that!

image

Since no one checks that the UserId should be null, this would happily let me post as User 12.

This isn’t a vulnerability in the Framework, and it is not a new issue. In ASP.net MVC, there are many ways to avoid this, one example being the BindAttribute.

[HttpPost]
public ActionResult Index(
    [Bind(Include="Content")] BusinessObject model
)

With that one set, UserId will remain null even if I POST it in:

image

There are other methods for this: Creating specialized business classes for each form POST, using a custom model binder, setting each field to default… A Whitelist on the Bind is easy, but can get complex for large models, and you won’t get a Compiler Error if someone renames a field. Specialized business classes give you compiler errors if stuff changes, but mean 1 class + mapping code per Form POST action.

While this example seems trivial, it’s very easy to miss over-posting vulnerabilities on larger models, and while attackers can just try guessing common model property names like UserID, the more forms your application has the more data an attacker has as well to correctly guess property names.