Powder was a web service written in Go. It allowed users to chat with each other and also with bot users. The flags were stored encrypted in the address field of each profile.
After each login, the Server returned a token, which was sent back by the client in subsequent requests. The token was created using the username and was used to prove to the server that a former login using the given username had been successful. But how was the token generated?
The token consisted of three parts: hmac + IV + ciphertext
First, the IV was generated randomly and the ciphertext was created by encrypting the username with AES-CTR using the generated IV and a hardcoded key. Then the sha256-hmac was calculated of IV+ciphertext by using the hardcoded key again.
The obvious flaw here was the hardcoded key and therefore all other teams used the same key as we did, so we were able to forge arbitrary tokens using the key "DONT_FORGET_TO_CHANGE_IT". The obvious fix was to change the key to an unknown one.
When we went to the profile page using a forged token, the server decrypted the address field of the user specified by the token and we received the flag.
It was possible to obtain a list of recently registered users by accessing /api/v1/users. This provided for each user the fields login, fullname,picture,public and address. login was the username, fullname and picture were values that did not matter. public was a integer generated at signup and address held the flag, but encrypted. This list only returned only the 10 newest users by default but increasing this limit by providing the GET paramter limit with a higher value made exploits more effective.
The generation of public was done by multiplying three primes prime1, prime2 and prime3 where prime1 and prime2 were random 512 bit primes.
prime3 was generated by taking the value of prime2 and adding random 32 bit primes until the result formed again a prime. Up to then, public and the primes seemed to be useless until we found out how the flags were encrypted.
The encrypted address was created by encrypting the flag with the md5-hash of prime3, then the result was encrypted using the hash of prime2 and this result was then encrypted using the hash of prime1. The encryptions used again AES-CTR with randomly generated IVs prepended to each encryption output.
As the first vulnerability was fixed by most teams we hoped that we find a way to obtain the primes which would have allowed us to steal flags again by decrypting the public available address of the gameserver created users.
But there was this bot-feature which we ignored until then. If you were lucky and/or sent many messages the bot, which was usually activated for the gameserver created users, replied you a message containing prime1:
OK, I'll give what you want tonight. Let's meet here \xe2\x80\x94 11297249021808963085101011438644760426159242657540879787659093881930011073546755933906196347958709572600298992689406997351365078684715791671939409803794273
As public and prime1 were known and public = prime1 * prime2 * prime3 holds, we could calculate public / prime1 and factorize the result to learn prime2 and prime3. This worked well using factorint from the python sympy module.
Our fix was to add a constant salt to the hash, as an attacker is then not able to generate the decryption key using the primes:
The first exploit worked well. Sadly our second exploit was not really efficient, as we had to sent too many messages to get prime1 back. The challenge also taught me to not hardcode the address of our own vulnbox in the exploit script and the distinct difference between digest() and hexdigest(). I really liked about this challenge that both exploits can not be stolen using traffic analysis. Overall, Powder was fun! (As was the whole CTF itself).