5月8日 fortress.loans 受到攻击损失 总计1,048.1 ETH 和 400,000 DAI
攻击交易:
https://bscscan.com/tx/0x13d19809b19ac512da6d110764caee75e2157ea62cb70937c8d9471afcb061bf
合约代码:
Chain:https://bscscan.com/address/0xc11b687cd6061a6516e23769e4657b6efa25d78e#code
FortressPriceOracle:https://bscscan.com/address/0x00fcf33bfa9e3ff791b2b819ab2446861a318285#code
合约Chain中,函数submit
没有权限验证(票数验证),导致任何人可以提交并修改任意代币价格,
function submit(
uint32 _dataTimestamp,
bytes32 _root,
bytes32[] memory _keys,
uint256[] memory _values,
uint8[] memory _v,
bytes32[] memory _r,
bytes32[] memory _s
) public { // it could be external, but for external we got stack too deep
uint32 lastBlockId = getLatestBlockId();
uint32 dataTimestamp = squashedRoots[lastBlockId].extractTimestamp();
require(dataTimestamp + padding < block.timestamp, "do not spam");
require(dataTimestamp < _dataTimestamp, "can NOT submit older data");
// we can't expect minter will have exactly the same timestamp
// but for sure we can demand not to be off by a lot, that's why +3sec
// temporary remove this condition, because recently on ropsten we see cases when minter/node
// can be even 100sec behind
// require(_dataTimestamp <= block.timestamp + 3,
// string(abi.encodePacked("oh, so you can predict the future:", _dataTimestamp - block.timestamp + 48)));
require(_keys.length == _values.length, "numbers of keys and values not the same");
bytes memory testimony = abi.encodePacked(_dataTimestamp, _root);
for (uint256 i = 0; i < _keys.length; i++) {
require(uint224(_values[i]) == _values[i], "FCD overflow");
fcds[_keys[i]] = FirstClassData(uint224(_values[i]), _dataTimestamp);
testimony = abi.encodePacked(testimony, _keys[i], _values[i]);
}
bytes32 affidavit = keccak256(testimony);
uint256 power = 0;
uint256 staked = stakingBank.totalSupply();
address prevSigner = address(0x0);
uint256 i = 0;
for (; i < _v.length; i++) {
address signer = recoverSigner(affidavit, _v[i], _r[i], _s[i]);
uint256 balance = stakingBank.balanceOf(signer);
require(prevSigner < signer, "validator included more than once");
prevSigner = signer;
if (balance == 0) continue;
emit LogVoter(lastBlockId + 1, signer, balance);
power += balance; // no need for safe math, if we overflow then we will not have enough power
}
require(i >= requiredSignatures, "not enough signatures");
*********************************👇************
// we turn on power once we have proper DPoS
// require(power * 100 / staked >= 66, "not enough power was gathered");
*********************************👆*************
squashedRoots[lastBlockId + 1] = _root.makeSquashedRoot(_dataTimestamp);
blocksCount++;
emit LogMint(msg.sender, lastBlockId + 1, staked, power);
}
同时借贷需要根据FortressPriceOracle . getUnderlyingPrice
计算价格
function getPrice(address underlying) internal view returns (uint) {
if (prices[underlying] != 0) {
// return v1 price.
return prices[address(underlying)];
********************👇
} else if (underlying == FTS_ADDRESS) {
// Handle Umbrella supported tokens.
return getUmbrellaPrice(ftsKey);
********************👆
} else if (areLPs[underlying]) {
// Handle LP tokens.
return getLPFairPrice(underlying);
} else {
// Handle Chainlink supported tokens.
return getChainlinkPrice(getFeed(underlying));
}
}
function getUmbrellaPrice(bytes32 _key) public view returns (uint256) {
(uint256 value, uint256 timestamp) = _chain().getCurrentValue(_key);
require(timestamp > 0, "value does not exists");
return value;
}
如果满足目标代币为FTS_ADDRESS
则直接调用 _chain().getCurrentValue
获得目标价格,
通过调用Chain中函数submit
设置FTS代币价格,并质押即可借取大量其他代币,完成攻击.