Coding Range

(Fixed) CS:GO server takeover vulnerability

February 22nd, 2013

Counter-Strike: Global Offensive yesterday updated to version 1.22.2.3 which fixes a nasty bug that could have been used to take over a random remote server.

CS:GO 1.22.2.2 (14/02/2013) introduced the console variable sv_workshop_allow_other_maps. The default setting was true. When a server was idling empty, the matchmaking server could tell it to download and run any map on CS:GO’s Steam Workshop. This sounded great - it provided a way to players to be able to try out new maps while providing another way for server admins to fill up their empty servers.

Unfortunately, CS:GO also includes a map entity that’s been in the Source engine the entire time - point_servercommand. This entity lets the map run any command on the server. Adjust gravity, kick players, change the remote console password, shut down the server, etc. I put together a test map a couple of nights ago that consisted of 6 walls, 8 spawn points, a handful of lights, a logic_auto (to run when the map loaded) and a point_servercommand. The logic_auto entity triggered the point_servercommand with two server commands:

  • rcon_password "testing" set the server’s remote console password to “testing”.
  • sv_gravity 10 changed the gravity to 1/8th of it’s normal value, to prove that the point_servercommand had run. Possibly the server had some hyper-paranoid security plugins, and I wanted to make sure that the map had actually tried to execute the commands.

After testing the map on a local server, I uploaded the test map to Steam Workshop and asked Matchmaking to put me in a server running that map. Unfortunately for GamePlanet New Zealand, Steam picked one of their servers, and surprise surprise, I suddenly had full remote console / admin access.

I also tried embedding a Valve Server Plugin (VSP, a .so or .dll file that exposes a plugin to Source Dedicated Server), but fortunately Valve were smart enough not to let the game load a binary plugin from a map.

Monday night I mentioned within virtual hearing range of a Valve employee that this might be possible. Tuesday night I verified it. Thursday it was patched, though it wasn’t mentioned in the update notes. Only later on the HLDS mailing list was it pointed out that:

point_servercommand was amended to only function when a map is played offline.
It was deemed a server security vulnerability.

Great work, Valve.

Here's what you need to know to write good SteamID code

February 6th, 2013

One of the first data types every programmer learns is an integer. It’s a number. Cool. Then you get into different types, number of bits or bytes in an integer, and fun things that happen when you overflow (e.g. set a ‘byte’ field to 256 and see what happens).

It’s a pretty basic concept in most programming languages, the difference between byte, short, int, long and long long. And depending on your language and/or compiler, short, int and long can be of different lengths. So I find it quite surprising when people blindly refer to numbers in a particular format as the format itself. Mostly with Steam IDs.

A Steam ID uniquely identifies most things on Steam. A user, a gameserver, a group, a group chat and so on. They come in three major formats:

  1. STEAM_0:1:23456 is an older textual format. The ‘0’, which should really be a ‘1’ (and is 1 in more recent games) specifies the Universe of Steam. In all cases where you don’t work for Valve, this is Universe 1 (Public). The next section is the lowest bit of the user’s account ID, and the last section is your account ID bitshifted 1 to the right (or just divided by two).

  2. [U:1:46913] is the more modern textual format. The U signifies that the account is a User or Individiaul. The 1 is the Universe, and the last section is the user’s account ID.

  3. 76561197960312641 is the 64-bit integer representation and the more commonly used representation. This is used in almost all APIs, both Steamworks and Steam Web APIs.

All three SteamIDs above refer to the exact same account. (It’s not my SteamID, I just picked it for the numerical sequence in the first representation.) It’s not a mysterous format, it’s actually documented quite well in the Steamworks and Source SDK header files. (Well, not the textual bit):

// 64 bits total
union SteamID_t
{
    struct SteamIDComponent_t
    {
        uint32              m_unAccountID : 32;         // unique account identifier
        unsigned int        m_unAccountInstance : 20;   // dynamic instance ID (used for multiseat type accounts only)
        unsigned int        m_EAccountType : 4;         // type of account - can't show as EAccountType, due to signed / unsigned difference
        EUniverse           m_EUniverse : 8;    // universe this account belongs to
    } m_comp;

    uint64 m_unAll64Bits;
} m_steamid;

64 bits, made up of four components: the ID, the instance, the type and the universe. For a normal user, the following generally holds true:

  • The universe field is 1, k_EUniversePublic.
  • The account type field is also 1, k_EAccountTypeIndividual
  • The instance field is usually 1. This was 2 for users logged in from a PS3, but I’m not sure if that still holds or not.

    Assert( ! ( ( k_EAccountTypeIndividual == eAccountType ) && ( 1 != unAccountInstance ) ) ); // enforce that for individual accounts, instance is always 1

Because of these effective constants, people have come up with really crappy ways to convert Steam IDs between the 64-bit representation and just the account ID representation (as used in some APIs such as IDota2Match). Looking at this, it’s pretty obvious:

  • The account ID is the lowest 32 bits of the 64-bit ID.
  • The 64-bit ID is the account ID binary OR’ed with either 0x110000100000000 or 0b100010000000000000000000100000000000000000000000000000000

Unfortunately, bad code spreads like a disease. Especially security-related code. Some people have the right idea but only one-way, other use the magic number 76561197960265728 without understanding what it means, and some just don’t cover all cases.

So please, the next time you copy and paste some code from a forum, ask yourself what it is that the code is actually doing. Because I can guarantee that I can supply most developers with a valid Steam ID that will break their code.

And pretty please stop calling them “64-bit Steam IDs” and “32-bit Steam IDs”. The latter is just incorrect.

That's a whole lot of hats 

February 2nd, 2013

The first two weeks that we did this we actually broke Paypal because they didn’t have - I don’t know what they’re worried about, maybe drug dealing - they’re, “like nothing generates cash to our userbase other than selling drugs”. We actually had to work something out with them and said “no … they’re making hats.” Gabe Newell

The Outside 

January 16th, 2013

What you’re seeing through the window is Outside. Now, I’m not a massive fan of Outside, generally speaking - I’m of the belief that we coped with Outside for a long time, until we finally invented Inside, and then breathed a huge sigh of relief - but it can really clear your head and restore some perspective. I really do need to get outside more.

1.7 Gigadeliciousnesses 

January 9th, 2013

Ultimately though, as one can’t gauge how delicious a meal is simply by the ingredients that go into it, the technical specs of such a device are secondary to how it actually feels to use.

Information

December 23rd, 2012

If you don’t read the newspaper, you’re uninformed. If you read the newspaper, you’re mis-informed. Mark Twain