Welcome back. Today we are back to Syscoin. Previously we have published two submissions to the bounty related to the implementation of the Sysethereum bridge. Today we present another vulnerability that we found later in the Sysethereum bridge implementation, outside of the public bounty project. Sysethereum bridge project is still under development and hence also this vulnerability was never exploitable on the mainnet and the latest development version has this problem fixed.

Like Qtum, Syscoin project is rather welcoming to bug hunters and the payments are way above average in the industry, which is nice to see.

The proof of knowledge is provided at the end of the article as usual for already fixed vulnerabilities.

Note that the design of the Sysethereum bridge changed a lot since we found this vulnerability and a lot from this report does not apply to the latest version. As usual, everything in the report was valid at the time of writing the report and before the vulnerability was fixed.

Meanwhile NavCoin managed to shoot themselves in a foot by splitting their own network with a series of bugs in their code. Temporary solution was to politely ask users to stop using the buggy feature...

Unchecked Superblock's Midblock

Bug type: asset inflation, theft

Bug severity: 8/10

Scenario 1

Attacker cost: very low

The attacker constructs superblocks in a way that she can pass all challenges while the superblocks she submits are invalid. Honest agents are going to challenge those superblocks and because the attacker always wins all challenges, she will take all the deposits from all honest agents. Moreover, eventually the attacker succeeds in having her superblock approved, which will cause a complete takeover of the superblock chain. Once that happens, the attacker can create fake proofs of nonexisting burns on the Syscoin network. This means the attacker can increase her balance of arbitrary asset on Sysethereum, including SYSX asset. This allows the attacker to steal all the deposits of SYS made by users from Syscoin to Ethereum.

Description

SyscoinBattleManager is Ethereum contract that evaluates responses to superblock challenges. The submitter of a superblock should respond to every challenge by providing specific information about the structure of the superblock. If the submitter is able to respond to all challenges, the superblock may become approved. Specifically, a superblock becomes semi-approved if there are any challenges in its history and such a superblock may become approved if three semi-approved superblocks are submitted on the top of it. Alternatively, a superblock becomes semi-approved if it is put on the top of another semi-approved superblock. A superblock may skip the semi-approved state and become directly approved in case it is built on the top of an approved superblock and no one challenged it.

A superblock is a representation of 60 blocks on Syscoin blockchain that is announced to Ethereum smart contract in order to later allow proving existence of burn transactions on Syscoin blockchain to Ethereum smart contract. In the Ethereum smart contract, superblocks are represented by the following structure:

struct SuperblockInfo {

bytes32 blocksMerkleRoot;

uint accumulatedWork;

uint timestamp;

bytes32 lastHash;

bytes32 parentId;

address submitter;

bytes32 ancestors;

uint32 lastBits;

uint32 index;

uint32 height;

Status status;

}



There are three verification steps in case of a superblock challenge that have to be executed in order: QueryMerkleRootHashes, QueryLastBlockHeader, and VerifySuperblock.

When an honest agent detects a superblock which does not match with what the agent calculates locally, it challenges the superblock, which requires a proper response from the superblock submitter. The first challenge is QueryMerkleRootHashes and the submitter has to reply with respondMerkleRootHashes. In this response, the submitter simply provides all 60 hashes of the blocks that the superblock is supposed to represent. If the submitter does not provide this reply within 10 minutes, the challenger wins. If the response is submitted, SyscoinBattleManager verifies that the root of the Merkle tree built from the submitted 60 hashes equals to the value of blocksMerkleRoot in the superblock. It also checks that the last of the submitted block hashes is equal to the value of lastHash in the superblock. Notably, it also saves last two of the 60 provided block hashes for later use. If the response is successfully verified by the contract, the challenge continues.

The challenger continues by sending QueryLastBlockHeader challenge. The submitter has to reply with respondLastBlockHeader within 10 minutes again. In this response, the submitter provides a full block header of the last of the 60 blocks. SyscoinBattleManager calculates a hash of this block header and compares it to the stored hash of the last block from the previous step, which is equal to the value of lastHash in the superblock. Then SyscoinBattleManager verifies that the submitted block header has correct proof of work on it. If everything is okay, the challenge continues.

The challenger continues by executing the final step – VerifySuperblock. This consists of two steps. First, validateLastBlocks is called, in which SyscoinBattleManager should verify that the previous hash of the last block, which header was submitted in the previous step, is equal to the second from the last block hash submitted in the first step. The code is as follows:

bytes32 blockSha256Hash = session.blockHashes[session.blockHashes.length - 1];

BlockInfo storage blockInfo = session.blocksInfo;

if(session.blockHashes.length > 2){

bytes32 prevBlockSha256Hash = session.blockHashes[session.blockHashes.length - 2];

if(blockInfo.prevBlock != prevBlockSha256Hash){

return ERR_SUPERBLOCK_BAD_PREVBLOCK;

}

}

As we can see, there is a minor error which prevents this comparison to occur. session.blockHashes.length is actually 2, not greater than 2. SyscoinBattleManager also verifies that previously submitted block header's timestamp and hash are equal to values of the superblock's timestamp and lastHash values. Finally, the timestamp of the superblock must not be lower than the timestamp of the previous superblock.

The second step of VerifySuperblock is validateProofOfWork. Here it is verified that the previously submitted block header's bits value (encoded target) is equal to the superblock's lastBits value. It is then checked that accumulated work value accumulatedWork of the superblock is bigger than the one of its parent. Then SyscoinBattleManager calculates a range of valid values for the block difficulty target in the last block of the superblock and compares that to the value of lastBits of the superblock. Finally, the accumulated work is calculated from the difficulty target value and compared to the claimed accumulatedWork value of the superblock. If all checks pass, the superblock submitter wins the challenge and can take challenger's deposit. Otherwise, if the challenger wins, it can take the submitter's deposit.

The available challenges do not prevent the attacker to submit an invalid superblock. It is possible to create a superblock with valid last block taken from the real chain (the last block of the attacker's superblock would be set to the last block of a valid superblock), possibly together with the second from the last block, if the minor mistake mentioned above was fixed, and this would allow us to fill any or all of other 58 blocks with fake blocks. Subsequently, the attacker can use these fake blocks to create fake proofs of burn transactions and thus credit her balance on token contracts arbitrarily. Such invalid superblocks are going to be challenged, but the attacker can pass the first challenge by submitting all 60 hashes. The contract will then calculate the Merkle tree root and will compare it to the claimed one. It is no problem for the attacker to provide a correct root value here for her 60 hashes. Then, the attacker can pass the second challenge by submitting the correct last block from the Syscoin main chain. All checks done by the contract will pass just as if they were done on the valid superblock that has the same last block. And finally, the last step would also be passed by the invalid superblock because all calculations are again done on the last block. This means there is no checking of the internal blocks at all.

Such an invalid superblock would be semi-approved because there would be challenges. The attacker just needs to submit more superblocks on the top of it. These will compete with the alternative chain of honest submitters, but due to the current implementation of honest agents, which come with certain delays before the honest superblocks are submitted, the attacker has a great chance of submitting first and thus win the battle for the best superblock chain. Once the attacker manages to have one of her superblocks to be approved, honest agents will stop submitting honest superblocks and the attacker completely takes over the superblock chain. Moreover, even when that happens, honest agents are still going to try to challenge attacker's superblocks and they will fail and thus give their deposits to the attacker. In case the attacker fails the race and an honest superblock is approved before her superblock, the attacker just forgets about her superblocks and tries again. Eventually, she succeeds.

For the above-mentioned competition between the attacker's superblock chain and the honest superblock chain, the attacker needs to invest money to challenge honest superblocks. She is going to lose those challenges, but she will get much more from honest agents for defending her superblocks against honest challenges.

Proof of Knowledge

As usual in case of already fixed bugs, we should present a proof that we were aware of the bug before it was fixed. We do that with the help of OpenTimestamps. Our timestamp data is the following string:

art_of_bug - Syscoin - SyscoinBattleManager's challenges do not properly verify superblocks. Specifically, it is possible to construct superblock with an invalid block in the middle, provided that the last block is valid, and still respond to all challenges properly and thus not only drain wallets of honest challengers, but also get an invalid superblock approved. This can be used to steal money from users who deposited assets to Ethereum token contracts.

The OTS file proving our knowledge converted to hex looks as follows:

004f70656e54696d657374616d7073000050726f6f6600bf89e2e884e892940108c408cd2b811392aa4b9ceb2c1bc52ff946570de1f8670c5ecf722414d879a749f010ed0dc3a3460d9645df2f698f629e5c0b08fff01059c876bccf2f8e6749fd3e58a54c7fa508f1045d45b6f1f008a352f0d504d9453d0083dfe30d2ef90c8e2e2d68747470733a2f2f616c6963652e6274632e63616c656e6461722e6f70656e74696d657374616d70732e6f7267fff0103a2ad50a2d088634028b9cbe5a4a07f708f1045d45b6f1f008370a2144ae47a49a0083dfe30d2ef90c8e2c2b68747470733a2f2f626f622e6274632e63616c656e6461722e6f70656e74696d657374616d70732e6f7267f010ee43b6f22e67d79ee80f29cd612cd43508f1045d45b6f2f008a50fd76b07f445ac0083dfe30d2ef90c8e292868747470733a2f2f66696e6e65792e63616c656e6461722e657465726e69747977616c6c2e636f6d

If you run OpenTimestamps client correctly, you should see something like this:

This proves that we created the record on 3rd August 2019, well before the fix was implemented on 11th September.