Service | Inlook |
---|---|
Authors | Lorenzo Demeio <@Devrar>, Francesco Felet <@PhiQuadro>, Matteo Protopapa <@matpro> |
Stores | 2 |
Categories | crypto, misc |
Port | TCP 1337 |
FlagIds | store1: [address, email_id], store2: [mailinglist] |
Checker | store1, store2 |
This is an encrypted mail service. Users can send/receive emails and ask the server for arbitrary ciphertexts.
Users can also create/subscribe to mailing lists (which members are supposed to be "private" in some sense), send emails to mailing lists and "invite" another user to an existing mailing list, making them join automatically.
At registration, the server generates an asymmetric key pair which is sent back to the client at each login. The public key is used to encrypt (server side) the body of an email, using the Okamoto-Uchiyama cryptosystem on each block of plaintext, much like what one would do with a symmetric block cipher in ECB mode.
Ciphertexts are also bundled with an ElGamal-like signature (which is also computed server side) and the corresponding public key.
Flags are either the body of some encrypted email, or users of some mailing lists.
The scheme is supposed to encrypt only plaintexts that are strictly less than
Luckily, the service implements a "confirmation of receipt" feature which allows an attacker to do just that: when a user sends an email, the server reflects it back to the sender after encrypting and decrypting it with the public (resp. private) key of the recipient; it's sufficient to send b'\xff'*32
to a victim to be able to break their key.
(There are actually some details to pay attention to if one wants the attack to be fully reliable, mainly due to block alignment / presence of padding. The interested reader can infer those from the exploit code.)
Fix: reduce by 1 the block length.
The aforementioned signature being sent when we ask the server for a ciphertext is computed via an algorithm resembling the ElGamal signature scheme, but this time performed modulo
The order of the multiplicative group of integers modulo
Therefore, if we look at the equation modulo
We can thus factor
Fix: sample
Since the check is performed on the file read as a string, users who own a mailing list which name contains the name of another list, result as owners of that list too.
From user.py
:
def check_mailinglist(self, name):
with open(f"{self.user_dir}/mailinglists.json") as rf:
mailinglists = rf.read()
return name in mailinglists
Fix: parse the JSON structure in mailinglists.json
and look for a string in a list rather than for a substring in a string.
Sanitization is performed after check_email_is_invite
has already been called, so an attacker can send an email that does not result as an invite at first, but does after the sanitization. E.g. an invite that starts as `=====MAILING LIST INVITE=====
.
From mail.py
:
def send_email(sender_email: str, address: str, content: bytes, subject: str):
...
is_invite = check_email_is_invite(content)
... # send email
def send_email_to_user(sender_email: str, recipient: User, content: bytes, subject: str, email_id=None, send_answer=True):
...
content = sanitize_email(content)
...
if check_email_is_invite(content):
...
Fix: sanitize the email content as the first action, and specifically before the check_email_is_invite
.
Store | Exploit |
---|---|
1 | Inlook-1-block-length.py |
1 | Inlook-1-signature-flaw.py |
2 | Inlook-2-mailinglist-ownership.py |
2 | Inlook-2-mailinglist-invite.py |