How we hacked a retrospective board and what we learned along the way

Recently in our organization we participated in an online retrospective using a well-known online board.

A retrospectibe board was splitted into two columns: what went well and what could be improved. Each of the team members could write his own cards and did not see the content of the cards posted by other participants.

We found out that the cards of other participants were blurred by CSS.

After removing blur in the browser we noticed that uncovered text contains special characters and diacritics.

It became clear for us that we see a result of a substitution cipher.

We tried to enter the whole alphabet and see how it will be ciphered.

Input:
a b c d e f g h i j k l m n o p r s t u w x y z

Output:
x g c z y g k h z j k l f c w l b f g h w x y z

So we discovered a substitution, and the fact that it is lossy:

x -> a, x
g -> b, f, t
c -> c, n
z -> d, i, z
y -> e, y
k -> g, k
h -> h, u
l -> l, p
f -> m, s
w -> o, w
b -> r

Having this knowledge we wrote a deciphering code. First off we trimmed out non-letters and split the input into separate words.

We realized that one word translates to many possible variants since one letter can map to many word variants.

So for example taking as an input wcy we have 8 different output permutations.

Let’s see how they are made - letter by letter:

o
w

oc
on
wc
wn

oce
one
wce
wne
ocy
ony
wcy
wny

So we came up with an idea to filter out multiple variants through the dictionary. We have encountered one issue there. Our first implementation took the entire dictionary inside a list. Checking if the word exists in the dictionary (ArrayList) took n/2 on average (where n is the dictionary length). Some longer words have even hundreds of permutations. To filter out correct ones it took m*n/2 (where m is the number of permutations). That was pretty slow. Changing data structure to HashSet solved the problem as finding a word in such structure costs on average O(1). That’s another time in our life when knowledge about data structures helps us a lot.

So for our example: wcy, we would have a single valid output: one

With this optimization, we were able to efficiently decipher cards made by other people.

You may wonder if deciphering the cards of other people is a real security issue?

By the end of the retrospective all cards will be revealed anyway, right?

Consider the following situation:

  1. Facilitator creates a board
  2. Facilitator shares a link to the board with participants
  3. People start to put their cards
  4. Link to the board gets leaked (visible on zoom, screenshot, etc.)
  5. Facilitator wants to pull off: either by deleting cards or deleting the whole board
  6. Whoops, it’s too late as hidden cards have been already decrypted

In our organization the board lasted for a week until the cards got revealed.

We contacted the company that developed the board and hopefully weak obfuscation was fixed :)

Conclusions

If you want to hide something - do it well, a trivial obfuscation can backfire.

Few small security issues combined can cascade and turn into real problems - weak obfuscation is not a big problem, but combined with the leaked link may lead to a catastrophe.

If you would like to practice coding simple ciphers you can take part in one of the many initiatives like Advent of Code. Furthermore, you can try to figure out if your app is used as planned. Good business metrics could help a lot for that.

Hope you enjoyed reading this article as much as we enjoyed hacking the boards. Keep on coding & hacking!