Service | APT Finals |
---|---|
Authors | Lorenzo Demenio <@Devrar>, Gianluca Altomani <@devgianlu> |
Stores | 2 |
Categories | crypto, misc |
Port | HTTP 8080 |
FlagIds | store1: [match_id], store2: [username] |
Checkers | store1, store2 |
The service implements a tennis game simulator in Go. A client is provided that allows all the functionalities intended by the server: a user can register, login, create and play matches against a bot and add friends.
At the moment of registration the user also inserts a secret, which will be visible only to friends, which can be added by sending and accepting invitations.
During the tennis matches the client interacts with the server through a web socket. The server decides the destination of the ball and checks that every shot is possible.
When creating a match it is possible to type in a prize for who wins and also the level of difficutly. Since the matches are played randomly, the level of difficulty only decides how far from the center you are able to throw the ball.
The first flag store is in the prize of the matches created by the checker. These matches are created with difficulty 3, which isn't possible to win playing in the intended way.
Every match has a secret key that is used to derive a point
Every time a player hits the ball sends two random numbers
The server computes
It is important to notice that the curve
In level 3 of difficulty, the gaussian distribution
The curve
thus without knowing
Such points can be obtained by sending
Fix: check that
When the server returns us the coordinates of where the ball is launched to, we can actually recover
Let
At this point we can win the match as if we know the secret key.
Fix: There are two possible fixes. Either we check that the point
Users inside the service can become friends with each other. The friendship is obtained by creating an invite token and by sharing it another user. Friends can see their respective secret which ultimately contains the flag. Friend requests are considered pending as long as there's an invite.
An invite is created by encrypting the invitee's username with a randomly generated key that is stored inside the
friend_invites
table. Simultaneously the username pair is added to the friends
table.
When an user accepts an invite, the server fetches the key for that username pair, deciphers the invite and checks that the plaintext is equal to the logged in username. When the key is fetched it is also immediately removed from the database. If any error occurs the friends entry is also removed.
To exploit this vulnerability, multiple bugs must be chained together:
-
When fetching the decryption key from the database the select is erroneously performed both for
user_a = $1 and user_b = $2
oruser_a = $2 and user_b = $1
which is not required. This way it is possible to trigger the decryption of the invite by providing the victim's username. This won't ever succeed because the final check after the decryption will always fail. -
The only way not to call the
RemoveFriend
function is to cause a panic after the decryption key has been removed from the database. -
By looking inside the
crypto.DecryptInvite
function we can see that multiple checks are performed on the invite length before decoding and decryption. -
The panic can be triggered in the
CryptBlocks
function implementation of the CBC decrypter:func (x *cbcDecrypter) CryptBlocks(dst, src []byte) { if len(src)%x.blockSize != 0 { panic("crypto/cipher: input not full blocks") } if len(dst) < len(src) { panic("crypto/cipher: output smaller than input") } if alias.InexactOverlap(dst[:len(src)], src) { panic("crypto/cipher: invalid buffer overlap") } if len(src) == 0 { return } ... }
Specifically, we can see that a panic will occur if the input is not multiple of the block size.
-
However, the check performed before decoding the invite seems to ensure that the Base64 output will be a multiple of the block size and therefore prevent the panic.
-
In order to have the decode output length mismatch it is needed for the Base64 decode to violate the usual 3/4 ratio. This is possible because
DecodeString
will strip all newlines, even in the middle of the string. Those at the start and end are trimmed before enteringDecryptInvite
. -
Therefore it is enough to use an invite like
YWFhYWFhYWFhYW\n\nFhYWFh
to cause a panic. The panic will be handled by the HTTP server and will not crash the service. -
After this the connection will be closed, but the invite will result accepted. By opening a new connection and listing the friends you'll be able to see the flag.
During the competition, teams exploit a simpler variation of the vulnerability by exploiting the race condition between removing the invite key from the database and removing the friendship when the decryption fails.
It is possible to make the race longer by providing a very long invite that will take a while to decrypt. Considering the very low latency between the vulnboxes some teams managed to exploit with less noticeable amounts of data.
Store | Exploit |
---|---|
1 | 4tor |
1 | SmallSubgroups |
2 | exploit2 |