Service | Uber Pendragon |
---|---|
Author(s) | Francesco Felet <@PhiQuadro>, Vittorio Mignini <@M1gnus>, Matteo Protopapa <@matpro> |
Store(s) | 1 |
Category(ies) | crypto |
Port(s) | 80, 5000 |
FlagId(s) | username of the flag holder |
Checker(s) | store1 |
Uber Pendragon is a free transport service that allows you to book a ride, possibly shared with other users, on a dragon. The ride should start from one of the five available dragon stations and finish at one of the wonderful points of interest or the home of one of the passengers. Upon arrival the respective welcome message is displayed - in the case of a private residence, it is the doorbell of the owner.
In addition, the service allows you to monitor flight traffic through its frontend.
The service implements a basic client-server architecture. Users are supposed to use the client.py
to interact with the backend.
During registration, the user's password is processed locally to produce a public key (by default, on the secp256k1
curve), which is stored by the server alongside the username, address and doorbell details.
There isn't a proper login: authentication is guaranteed by the ability to sign a message composed of travel details, through a signature scheme resembling musig. This allows the aggregation of signatures of the same message, hence it is possible for multiple users to book the same ride with only one valid aggregate signature.
The flags of this service are stored as the name on the displayed doorbell.
The function Hagg
, which is meant to be a random oracle used to build a coefficient meant to disrupt rogue key attacks in the plain public key model, is not a hash function. In particular, it is defined as $$ H*{agg}(L, X*{i}) = sum(L + X*{i}) $$ where
This defies completely the purpose of adding those coefficients into the equation, once again allowing for a (slightly more sophisticated) rogue key attack.
Let
Writing down the equation representing the resulting aggregate key, with
Clearly, we want to somehow cancel out
Let
Therefore, if we manage to find (possibly repeated) integers
we can forge a valid signature for
Players cannot change Hagg
with a proper hash function, because the scheme is bound to the client, which is used by the checker.
The situation resembles a possible real world scenario in which a vulnerable client has been widely distributed and a hotfix needs to be applied server side. In this case, players need to build an "intrusion detection system" to reject signatures which could be attacks.
In particular, it is not wise to attempt to fingerprint attackers with external observations (for example rejecting every request with a duplicate public key: it may be that two different users share the same password). Therefore, players need to properly check if a given set of public keys is hiding a forgery: this happens for example when there is a subset of public keys for which the corresponding aggregate key "nullifies" (i.e. is the identity element of the group being used in the scheme). This gives a check with complexity
To avoid possible DoS, optimizations can be introduced: for example we need to check only $$ \sum_{i=1}^{\lfloor \frac{n}{2} \rfloor} \binom{n}{i} $$ subsets and for each one of those if it is either equal to the full aggregate public key or the identity element.
One can optimize even further with dynamic programming techniques or trying to delegate the whole check to a dedicated compiled binary.
Nevertheless, our testing patch (including only the first mentioned optimization) written in Python with fastecdsa
performed checks under plausible load in approximately one second at most, which should be fairly safe given that purposeful DoS is prohibited by the competition rules.
store | exploit |
---|---|
1 | exploit.py |