I’ll start this off by saying: I heckin love Omega Strikers. It’s a great game that’s unfortunately no longer being heavily worked on as the studio Odyssey Interactive has chosen to work on other projects instead. I’ll also say: I heckin love Odyssey. Heck, I even tried to work there at one point! Every interaction I’ve had with the people at that company has been great, and I can’t wait to see what they make next. That being said, unfortunately this write up is going to get a bit critical of some of their recent work.
If you’ve played Omega Strikers in the last month or so, you’ve probably noticed they added Discord integration to the game. By linking your Discord account to your Omega Strikers account, you’re given the option to join a voice call with your teammates whenever you join a match. You’re also able to use the in-game chat system to send messages to your in-game friends as well as any of your Discord friends. This all sounds rad on paper, but imagine my surprise when I received a message via the in-game chat system from my wife, who does not play Omega Strikers, which she had sent over Discord. At first I thought “Neat!”, but then I got curious and thought “How are they doing this?”. Then I started turning over rocks and revealing some interesting things. This write up shall cover what I found.
WARNING
If you have linked your Discord to your Omega Strikers account, you should be aware of the following:
- Anyone with access to your Omega Strikers account can send/receive Discord Direct Messages as you. This includes any Odyssey Interactive employees with “Admin” access.
- There is absolutely no way for you to know if your DMs are being monitored and it DOES NOT require you to have the game open
- Additionally, while the game IS open, all DMs sent/received by your Discord account are forwarded to Odyssey’s servers, meaning they are likely being logged somewhere
HOWEVER
It is very unlikely that Odyssey Interactive is doing anything malicious. I trust them more than I trust most companies. However that does not mean I trust them with full access to my Discord DMs, and I think their design is unacceptable.
Additionally, if you linked your Discord account, you explicitly authorized them to do this:
Write Up
Now to get into the technical details of what exactly is going on! That screenshot above with the permissions is a great place to start. When you Authorize your Discord account in that manner, you’re entrusting whoever is on the other side of that transaction with some form of access to your account. Essentially, you’re handing them everything they need to authenticate to Discord as you and act on your behalf. What exactly they’re able to do as you though is limited. Fortunately, developers are able to scope out the exact permissions they need for their application, meaning the users don’t have to give up full control.
I’ll cover how I found them later on, but these are the permissions Omega Strikers gains when you authorize it:
- activities.read
- activities.write
- relationships.read
- relationships.write
- dm_channels.read
- dm_channels.messages.read
- dm_channels.messages.write
- account.global_name.update
- gateway.connect
- identify
- openid
Descriptions on what most of these permissions do can be found in the official docs at: https://discord.com/developers/docs/topics/oauth2?embed=
These are all interesting in their own way, but let’s just do a quick overview of what can be done with our acccounts:
- Activities can be read/written. These are related to Now Playing/Recently Played and activity status
- Relationships (Friends) can be read/written. Meaning it can list/add/remove Discord friends
- Discord DM Channels can be accessed and messages can be written/read
- Your global_name (display name) can possibly be changed. I haven’t managed to get it to work yet, but the permission is there!
Note that gaining accessed to channel history is a separate permission, meaning that while DMs can be monitored and tinkered with, it is not possible to dump chat history with these permissions. Also note that there is no access to Guilds (servers).
After authorizing access to your account, the developers gains the ability to generate Tokens for your account. These Tokens are only valid for a short amount of time, and act as authorization to the Discord APIs. Like a short-lived API Key or Cookie. Because you handed over the ability to do so, as long as you keep the Omega Strikers Discord authorization, these tokens can be generated at any time in the future without additional consent or notice.
So that all sounds a little spooky, but what really drives the point home is seeing it in action. First off, it’s relatively trivial to get a Token for your own account from the Odyssey APIs. When your game first loads up, the game authenticates via Steam, then reaches out to an API to get data about your account. The account data includes all sorts of stuff like your Username, title/nameplate, and matchmaking server locations. It also contains a Discord Token that was generated on Odyssey’s systems that is then sent over to your client to use for a Discord connection.
That Token can be immediately used to access the Discord APIs to authenticate as the connected Discord account:
So while the REST API is cool, it’s not capable of capturing dynamic messages in flight. As I mentioned before, there’s no history permission, so only messages actively being monitored for can be seen. This requires using WebSockets, which fortunately Discord has documented out pretty well: https://discord.com/developers/docs/events/gateway
Basically, by opening a websocket to the Discord Gateway and authenticating with the Token, we start getting access to the good stuff. I’ll just handwave the minor hurdles I hit at this point, and say I hastily built a python script to do exactly that: https://gist.github.com/reznok/4b0f5a145886d49145be19548ac43c67
After successfully authenticating to the websocket API, the server sends a message with a ton of Discord information, including the scopes listed out above:
Here’s a clip of the monitoring script running (right) showing me having a totally normal conversation with a good friend of mine. Note that it can see everything about the message: sender, recipient, and content. This monitoring script could be running on any machine anywhere in the world, it doesn’t have to be on my machine, and I would have no idea it’s running.
It’s important to note that at this point, while the Token was supplied by Odyssey’s API, all of the Discord interaction that’s actually happening is happening only on my local machine directly to Discord. It’s my Client connecting to Discord, and my messages are not going through Odyssey’s servers at all. Sending my Discord messages directly to them would be wild design.
Bonus Write Up: Messages Get Sent To Odyssey
OK this one still blows my mind. I actually found this issue day 1 of their new system and raised some concerns about it, so I do have some context on what’s going on.
Up until this point, while I don’t think it’s the most secure design overall, I’d at least say the design made some sense. Ignoring for a moment that Odyssey can make their own tokens and gain access to our accounts, they did put some thought into security. They limited the scope to not be all inclusive, and they’re not just proxying all of the messages through their servers, they’re trying to keep it isolated to the clients.
So why… WHY… is it the case that if the game is open, all Discord messages, both sent and received, including Discord messages not in-game, are sent directly to Odyssey’s APIs?
As seen in that mess of a screenshot, I sent a message to my buddy Mark who has never even heard of Omega Strikers. Those message contents made their way directly to “*.odysseyinteractive.gg/api/…”.
Well the answer to “Why?” is actually pretty easy: it’s sanitization. Basically, everything listed in the write up above happens: Discord messages shoot over websockets using the Token. But then those messages need to be displayed in the game’s UI. There is currently a process where between the websocket message and the message’s content being displayed, those contents are shipped up to Odyssey’s API for content filtering and sanitization.
Now a keen eye will notice that there is only the message contents, no identifying information. However, if you look in the Request headers, there is a JWT in the Authorization Header that I assure you has your Omega Strikers username inside it (go ahead and type out my expired one if you want proof (: ). They may not be able to tell exactly who you’re talking to, but they can definitely tie you to the message.
Additionally, Odyssey is pretty public about being an AWS shop. It’s almost guaranteed that this sanitization is being run in lambdas that are being logged into AWS CloudTrail, and could be retrieved. Unless they aren’t logging. Honestly I don’t know which situation would be worse.
What the big head scratcher here though is why isn’t the filtering/sanitization just happening in the client? Why does it need to be shipped off to another remote service when the content filtering rules could just be included locally? My only guess here is that Odyssey wants to make another Nintendo supported game and Nintendo won’t let them include a list of naughty words in their game for filtering.
Conclusion
The fact that this feature got released in this state is a bit wild to me. As a professional in the cyber security industry that works with developers frequently, I pride myself on helping developers find the balance between an application being secure and an application being usable. I fully embrace that sometimes you have to give up a little bit of one for the other to make a functional product. However, this design crosses my personal comfort line in the laxness of security. I wouldn’t give my neighbor, my friends, or even my wife access to monitor all of my Discord messages, so why would I give that level of access to Odyssey?