The following is presented by our semi-new team member: SI. He explains how it was possible to steal challenge submissions of vol9 round 1 from other players. How and why? All of them were deployed without the “FirstSolveOnly” setting that was meant to protect against similar issues. We chose not to use it to see what happens in such a wargame and he found it (SI was focusing on challenge creation, not the smart contract code and he found it!). Before he became our team member, he was the only one who actually reported that he found a flag hidden…. well somewhere else hidden in plain sight!
Lets see the detailed vulnerability hidden in vol9 rnd1 smart contract. All below is written by SI:
CCTF (CryptoCurrency (is) The Flag / Crypto Capture The Flag) is a (recurring) CTF contest. For season 9 of the contest, a smart-contract was deployed²: contestants had to register and submit flags by making appropriate calls to the smart-contract, which would automatically validate submissions and give scores in a transparent way. One goal of such a smart-contract is to serve as a transparent accreditation mechanism regarding cyber-security and cryptology skills for pseudonymous entities (with the ultimate goal of disrupting bureaucratic bullshit in the cyber-security certification market).
There was a set of challenges, and each challenge was independently solvable by multiple contestants (each contestant progresses independently). Obviously, some care was taken to not reveal a flag by storing it plainly in the smart-contract, as well as to prevent leaking a flag during computation when validating a submission. Basically, each flag was a private key, and a valid submission consisted of a digital signature using the correct flag. Furthermore, re-use of a digital signature was denied.
The smart-contract is vulnerable to signature forgery, leading to the possibility of flag submission forgery. A sneaky contestant can effectively “copy” a solution submitted by another contestant by constructing an alternative signature.
The flag submission function of the smart-contract takes a challenge number, a message hash and an ECDSA signature. The function takes the cryptocurrency address — almost like the public key — for the message-hash-and-signature pair, and compares it to the correct one for the challenge (stored in the smart-contract). ECDSA has the property that the public key can be calculated this way.
ElGamal-type digital signatures, ECDSA included, have a property that signatures, for any given public key, can be easily forged, although these signatures correspond to unclean messages, which are practically unmatchable if they are to be hashes of actual messages. However, the smart-contract takes message hashes as-is, making forgery possible.
Borrowing the way Bitcoin and Ethereum check for coin ownership, the smart-contract compares only cryptocurrency addresses. A cryptocurrency addresses is a cryptographic hash of the public key, so the public key (for a given challenge) is not publicly known until it is first used (unless the challenge authors are fished). This means that forgery for a given challenge cannot be made until at least one correct solution is claimed for that challenge.
There was an option in the smart-contract to reward only the first solver of a given challenge, which would have made this attack useless for specific challenges, but the option was not set for any challenge.
Schematically, a valid ElGamal-type digital signature consists of a pair (r, s) of integers such that
r = F(gᵐᵗyʳᵗ) where t = s⁻¹
where g is a generator of a (multiplicative) group, m is the message (or, rather, its hash), y is the public key (y = gˣ, where x is the private key), and F is some kind of a function. For ECDSA, the group is a set of points on an elliptic curve, and F is the x-coordinate of such a point.
One standard class of existential forgeries is as follows: let a and b be arbitrary; then
r = F(gᵃyᵇ)
s = rb⁻¹
is a valid signature for m = sa.
The possibility of flag submission forgery was conjectured (by the author) already during the contest, held approximately between 2022-08-25 and 2022-08-26.
On 2022-08-30, six — the operator of the contest — was asked not to disclose solutions to the challenges (pending deeper investigation into the conjecture).
Later, the vulnerability was confirmed, and an exploitation tool was developed (by the author).
Responsible™ disclosure: On 2022-09-05, this advisory was released to the public as a surprise, for maximum shaming and embarrassment — thou shalt not write buggy/vulnerable smart-contracts ever again! More awesome would have been to keep the vulnerability secret and exploit it during the next CCTF event. See also: ³.
All contestants that reached a place on the podium are hereby requested to recall and disclose the text/source they used for each message hash, to prove that each submission wasn’t forged. In practice, for each challenge, only those who submitted a flag subsequenty need to provide evidence. In theory, note that once a valid flag submission transaction is released to the network, it can be readily exploited even before any finalization on the blockchain; however, if a considerable time had passed between the first two valid submissions, the first one can be considered clear.
If, however, one such “subsequent” contestant does not provide the said evidence, it does not imply that that contestant has cheated, as contestants were not required to log/memorize the said texts/sources. As a fallback, such a contestant could reproduce the flags and/or describe the paths to the solutions, which would provide a bit of support for having solved some challenges. In any case, due to the vulnerability, the smart-contract, in itself, does not provide reliable proof that such a contestant independently solved the relevant challenges.
The deployment of the smart-contract does not need to be corrected, because it was stopped upon the end of the contest.
Putting aside the potentially uncorrectable issues described in the Mitigation section, the next version of the smart-contract can be cleared of this vulnerability.
Proof of Concept
See the attached <forge.py> file, which implements the core forging functionality; see <forge1.py> for example source code using the former (the latter contains real input from actual submissions recorded on the blockchain). These depend on the “web3” PyPI package.
The most epic parameterization is the default, which produces a signature on an all-zero hash.
In general, consult a cryptologist. Now, where can one find those? :} —> note from six: we found SI, our main cryptologist!
² at address 0x36a1424da63a50627863d8f65c0669da7347814a on the Polygon network
³: see the original paste
forge.py –> https://gist.github.com/sixTheDave/038463c232c5cf644860ab038a1c9fde
forge1.py –> https://gist.github.com/sixTheDave/f1864a9d3a3eca9d7aa6edca8eed226c