Ethan's Things

Stupid Chimp Slop

Stupid Chimp Slop (from now on abbreviated to SCS) is an absurd horror game about running around varying facilities to fend against or hide from bizarre and grotesque monsters

While it's not my favourite project, it is definitely one of my most notable ones due to the revenue and playerbase created from it

For SCS, I was partly a leader in the project up until its release, ensuring code quality maintained a sustainable structure and stayed fast due to the limitations of our platform (Meta Quest headset series)

The Security Pains

Due to SCS using a cloud networking solution known as Photon PUN (Formally Photon Unity Networking), we had to make a lot of creative decisions to prevent malicious users from abusing features in the game

During early deveolopment of SCS, XORWIRE used an open-source tool known as PhotonVR, and while I don't typically and probably never will endulge in the tool personally, it provided the best and friendliest start for the project

Colours

Users in SCS are able to assign the fur on their character a unique colour code, where they can choose 3 values representing RGB with those 3 values ranging from 0-9 (0-255 would be too impractical, doing it this way allowed us to keep interactions simple for both parties)

The problem?

Unity's Color struct has three floats (r, g, b) which range from 0f to 1f typically, however the RGB values accept values over 1f

In PhotonVR, if you want to change your client's colour, you call PhotonVRManager.SetColor(colorStructHere), PhotonVR will then automatically handle updating your local client's colour, and will synchronize your desired colour to remote players

PhotonVR did NOT sanitize the color being passed into the SetColor function, allowing players to synchronize incredibly bright colours by passing a colour struct with absurd RGB values, this resulted in our bloom post processing effect assuming the object really is that bright, and "flashbanging" viewers, bloom would bleed and cover the entire screen, letting players only see pure white

The solution?

Simple enough, with a networking solution like Photon PUN, you can NEVER trust ANY clients (counterintuitive when it's based on client trust I know, it makes more sense in development)

Do we clamp the value before our client synchronizes it? No, that's very easily bypassable

Instead, we let malicious clients send insane colour values, and when clients receive the value they personally sanitize it, this is unbreakable and can be shown as:

  • Client A sends really bad colour value (RGB values are in the thousands instead of 0-1)
  • Other clients receive this colour, and before applying the colour on their side, clamp it
  • Client A can send a bad colour, but can physically not ever make clients display it due to the client-sided approach we took

Cosmetics

THIS was a big problem for us and took a bit of planning to figure out

XORWIRE employees or users of noticeable status were to receive designated wearable in-game sweaters to prove their identity

This sounds fine in practice, however we run into another one of PhotonVR's flaws

The problem?

PhotonVR blindly takes the cosmetics a client says they're wearing and applies them without checking if they own the cosmetics. This is an understandable flaw, PhotonVR is backend agnostic and only relies on Photon

To us however, we couldn't have this happen, we could not have users running around in sessions pretending to be someone they're not

To prevent this, we modified PhotonVR to, again, locally sanitize! Our approach? Relatively simple

  • Client joins the session
  • All other clients inquire with our backend about what cosmetics this user owns (our backend being a trusted source of information obviously)
  • All other clients from now on will ONLY apply the cosmetics the server reported the joining client owning

So let's say ClientA, a malicious client, is joining, the flow internally is basically

  • Clients get notified ClientA joins
  • Clients fetch a list of cosmetics ClientA owns, in this case they own a blue sweater and a cool cap
  • ClientA tells everyone "hey, I'm actually wearing this super duper cool developer exclusive sweater"
  • Clients say "Okay sure, but I'm only going to put it on you if it's in the list of cosmetics you own"
  • ClientA doesn't own the super duper cool developer exclusive sweater, so the clients don't apply it, and instead leave that cosmetic slot empty
  • ClientA is now a character with no sweater for everybody else but themselves, they see the sweater on their side, but to others, they're wearing nothing :)

If clients also buy something mid session, they can tell all other clients to fetch the new data the server may have waiting for them

!! IN HINDSIGHT !!

I would've taken a different approach, this proved to be, not expensive, but well, not an appropriate level of bandwidth is spent in this approach

I would've used asymmetric signing, this would look something like

  • ClientA asks the server (can I get a list of my cosmetics, alongside a signature that represents proof of my cosmetics?)
  • Server goes "ok cool here" and hands both over
  • ClientA distributes the list of cosmetics alongside the signature to everyone
  • Everyone uses a public key to determine whether the signature and the list of cosmetics ClientA sends match

What does this change?

  • Only one client is asking our backend for proof of themselves, that's 1 person asking rather than 20
  • Clients can no longer maliciously scan another client's inventory since a client can only get proof of their cosmetics if they give proof that it's them asking
  • Since we're not rapidly inquiring with PlayFab about a big chunk of data (a user's inventory), our costs regarding PlayFab go down marginally
  • We have the same amount of security. If a client sends a cosmetic list that doesn't match the signature then they cant wear ANYTHING, if they send the correct data and try to equip something not in the list, then they still cant!