459 words
2 minutes
World of Warcraft Armory XSS

Introduction#

An XSS vulnerability was present in a fork of Shadez/wowarmory used by Tauri, the #1 Hungarian WoW private server.

The World of Warcraft Armory is a vast searchable database of information for World of Warcraft - taken straight from the real servers and presented in a user-friendly interface.

--- Shadez

If you are not familiar with the game, let me introduce you to the concept of guilds. World of Warcraft is based on teamwork, therefore players are able to join small communities called guilds. Every member of a guild has an associated value, called rank, which is set by the Guild Master. The rank is used to give extended privileges to members like access to restricted items, gold, chat etc.

By default, there are 5 different ranks which can be customized.

default-guild-rank-names

Attack surface#

The name of a rank can be changed to an arbitrary string, but the game client enforces a 15-character limit, which makes this attack surface very limited.

an-abnormal-guild-rank-name

Long story short, it turned out that the 15-character limit set by the client is only a soft limit and the server does not really care about the length of the name - it can be anything up to 255 characters (limited only by the type of the database field).

database-structure-of-guild-rank-table

// https://github.com/mangostwo/server/blob/7cc4e36cf52994bf3b10827b93ae0e66492d59ff/src/game/Object/Guild.cpp#L762
void Guild::SetRankName(uint32 rankId, std::string name_)
{
    if (rankId >= m_Ranks.size())
        return;

    m_Ranks[rankId].Name = name_;

    CharacterDatabase.escape_string(name_);
    CharacterDatabase.PExecute("UPDATE guild_rank SET rname='%s' WHERE rid='%u' AND guildid='%u'", name_.c_str(), rankId, m_Id);
}

The missing server-side validation allowed an attacker to enter a valid <script src=”..”></script> tag as the name of the rank, and the web interface was more than happy to display the name without any HTML sanitization.

Proof of concept#

Let us set the name of the rank for a given guild member to the following:

<script>alert(document.cookie)</script>

Then visit the home page of the guild on the web interface:

session-hijacking-poc

Finally, we obtained the value of the PHPSESSID cookie which can be used to impersonate the given user on the server’s website.

Lessons learned#

  • If the length of a database field has a well-defined upper bound then it should be properly limited instead of using a default range
  • The Guild::SetRankName function of the game server should have verified the length of the rank name
  • The Guild::SetRankName function of the game server should have validated the name by using regular expressions
  • If you assume that a string is safe for SQL it does not automatically mean that the string is also safe for HTML
  • The variables displayed to the user in the web browser should have been properly escaped
  • The session cookie should have been set to HTTP(S)-only

As you can see, the XSS could have been prevented at multiple layers, but in this case, none of the validations were present.

Mitigation#

This vulnerability was reported to Tauri and was fixed immediately. The lack of length verification was resolved in TrinityCore, which is the successor of MaNGOS.