Hacking 10FastFingers

Gotta go fast.

Disclaimer: all of this works at the time of writing, which is Dec 17, 2019.

It all began with mechanical keyboards.

A couple of us had Topres, some of us had Cherry MX, and we created a slack channel at work (#skills-wpm) to compare how fast we could type with MKs and work laptop keyboards.

It was fun comparing our scores (and for some reason, I got my fastest score on my work laptop's crappy butterfly switches - https://10fastfingers.com/share-badge/1_EN).

The above score is legit, and I can consistently get above 130 WPM, but that actually leads to a point that I'm trying to make - how do you know that this is legit?

The link looks legit enough, but what if you could cheat at the game?

Now, obviously every game can be cheated given enough time, BUT I really wanted to highlight how untrustworthy these WPM results are.

I first started looking at the HTML inspector to try to see if I could automatically detect the "current" word and type it into the input box to save me the trouble of typing.

3 minutes into scripting, I was like "surely someone must've done this already" and just looked up the answer online. There's actually a surprisingly helpful thread on GitHub about cheating on 10FastFingers (along with a whole slew of other typing sites, but for today we're going to focus on 10FF):

BOT for 10fastfingers, automatic typing with adjustable speed
BOT for 10fastfingers, automatic typing with adjustable speed - 10fastfinger_bot.js

In fact, you can just copy and paste in the very first snippet into your browser console, and it will just type for you!

And, ta-dah! You get a inhumanely fast WPM, with a proof (so you can flex on your coworkers on the slack channel)!

But being a software engineer, my first reaction was: how much can I push this?

Seeing that there was a spot on the snippet to set your speed, I set it to something absurd like 420 WPM (= 420 words/1 min = 420 words/60,000 ms = 1 word/143 ms) and ran it.

And it seemed like it worked:

Even though it errored out partway through (it seemed like 10FastFingers just stopped loading words around the 50 second mark because it ran out of words!), it still gave me a crazy score that seemed legit:

The image clearly says 360 WPM here

Except when I went to share that sweet, sweet link with the channel, I saw that the link said "0 WPM":

I can type 360 words per minute. Are you faster?
Test your typing speed and compare the result with your friends.
(see how the image says 0 WPM even though the title says 360)

I thought I got banned or something for cheating.

Turns out, no.

I noticed that the link - https://10fastfingers.com/share-badge/1_MW - was in a particular format - https://10fastfingers.com/share-badge/${1-digit number}_${1-2-digit code}.

From first glance, I could guess that the number probably had to do with languages (it does), and that the 2-digit code was what indicated the speed.

It wasn't hex code - the lowest I could go was this:

I can type 1 words per minute. Are you faster?
Test your typing speed and compare the result with your friends.
SELECT * FROM WPM WHERE Code='B'

But this did confirm something - that the server generates images based on the information clients send them.

Oh dear.

So I started fuzzing the 1-2-digit code, going as high as I could. In the end, I hit my limit at 255 WPM:

I can type 255 words per minute. Are you faster?
Test your typing speed and compare the result with your friends.
SELECT * FROM WPM WHERE Code='IW'

In fact, if you go even a single WPM higher, it will return 0, so that's why the badge displayed a score of 0 when I tried to view it.

I can type 256 words per minute. Are you faster?
Test your typing speed and compare the result with your friends.
SELECT * FROM WPM WHERE Code='IV'

So from this I could tell a couple of things:

  1. you could generate whatever WPM you wanted (up to 255)
  2. they were probably storing WPMs as an unsigned byte (255=2^8-1)
  3. 10FastFingers is broken.

Of course, it's all meant to be fun, but ignoring any cheating at the typing end (the snippet copy-pasting I did at the beginning), the very fact that the link trusts the information the client sends them (in form of links) and generates results based on it makes any of your 10FastFingers results completely useless and unverifiable.

So what's the takeaway from all this?

Well, if you're a backend dev, NEVER EVER trust any information that the frontend sends you. If it can be sent to you, it can and will be spoofed!

And if you're a 10FastFingers dev... well, you might want to take a look at some strategies for preventing cheating, including but not limited to:

  • IP banning for people who are blatantly cheating
  • Some sort of check on the typing results (perhaps using ML) to ensure that an actual human was typing it, not a script
  • Not generating results based on a link template!

On that last point, I would generate the results as follows:

  • On every typing test result, generate a random URL with a hash (or hell, you could even use UUIDs, but ideally you would want something non-guessable), something like 10fastfingers.com/results/XYZ
  • To prevent having to generate an image for every single result (which is why I'm guessing 10FastFingers even went with this asinine approach of embedding the WPM in the link itself), in that link (10fastfingers.com/results/XYZ), hardcode a link to an image with the appropriate WPM (e.g. 10fastfingers.com/images/WPM/125.jpg). That way, you can't change the displayed WPM by fuzzing with the link - for one, you can't guess the result hash, and for another, you can't change the hardcoded image link in the results HTML. Not to mention, by sharing the image links you could cache the image once it's generated and prevent having to generate the image on every single result!

Happy typing!