> For the complete documentation index, see [llms.txt](https://8olidity.gitbook.io/qu-kuai-lian-bi-ji/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://8olidity.gitbook.io/qu-kuai-lian-bi-ji/lou-dong-fu-xian/fu-xian-cao-zong-yu-yan-ji-+-ti-an-gong-ji-fortress-loans.md).

# (复现)操纵预言机+提案攻击—Fortress Loans

### 参考:<https://learnblockchain.cn/article/4043>

###

###

### 黑客操作分析

黑客地址 <https://bscscan.com/address/0xa6af2872176320015f8ddb2ba013b38cb35d22ad>

**攻击流程**

```
创建提案(提案内容为设置fToken的抵押因子为700000000000000000)->给提案投票->使提案通过->创建exp合约->exp合约中执行提案->修改ftoken质押因子成功->exp调用了Chain合约的submit函数修改了其中的状态变量->修改了状态变量fcds最终修改了价格预言机中的价格。->套利离场
```

### 提案操作

创建提案时间为5月3日

<https://bscscan.com/tx/0x18dc1cafb1ca20989168f6b8a087f3cfe3356d9a1edd8f9d34b3809985203501>

提案合约0x0db3b68c482b04c49cd64728ad5d6d9a7b8e43e6

对提案的操作根据的日志分析

<https://bscscan.com/address/0xe79ecdb7fedd413e697f083982bac29e93d86b2e#events>

1.ProposalCreated 提案(攻击者为11号提案)

<https://bscscan.com/tx/0x12bea43496f35e7d92fb91bf2807b1c95fcc6fedb062d66678c0b5cfe07cc002#eventlog>

2.调用了两次VoteCast

给提案投票(投了119774334170940063343039) 都是( 5月6日)

<https://bscscan.com/tx/0x83a4f8f52b8f9e6ff1dd76546a772475824d9aa5b953808dbc34d1f39250f29d>

给提案投票,投了296193548055351919633063

<https://bscscan.com/tx/0xc368afb2afc499e7ebb575ba3e717497385ef962b1f1922561bcb13f85336252#eventlog>

3.ProposalQueued 将提案插入队列中(5月6日)

<https://bscscan.com/tx/0x647c6e89cd1239381dd49a43ca2f29a9fdeb6401d4e268aff1c18b86a7e932a0>

调用queue函数

```
    function queue(uint proposalId) public {
        require(state(proposalId) == ProposalState.Succeeded, "GovernorAlpha::queue: proposal can only be queued if it is succeeded");
        Proposal storage proposal = proposals[proposalId];
        uint eta = add256(block.timestamp, timelock.delay());
        for (uint i = 0; i < proposal.targets.length; i++) {
            _queueOrRevert(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], eta);
        }
        proposal.eta = eta;
        emit ProposalQueued(proposalId, eta);
    }
```

走到第一步,`state`函数中的`quorumVotes`中

```
....
} else if (proposal.forVotes <= proposal.againstVotes || proposal.forVotes < quorumVotes()) {
            return ProposalState.Defeated;
....
```

`quorumVotes`返回400000个FTS

```
function quorumVotes() public pure returns (uint) { return 400000e18; } // 400,000 = 4% of FTS
```

而这个`proposal.forVotes`是在前面Votecast中修改的

```
function _castVote(address voter, uint proposalId, bool support) internal {
        require(state(proposalId) == ProposalState.Active, "GovernorAlpha::_castVote: voting is closed");
        Proposal storage proposal = proposals[proposalId];
        Receipt storage receipt = proposal.receipts[voter];
        require(receipt.hasVoted == false, "GovernorAlpha::_castVote: voter already voted");
        uint96 votes = fts.getPriorVotes(voter, proposal.startBlock);

        if (support) {
            proposal.forVotes = add256(proposal.forVotes, votes);
        } else {
            proposal.againstVotes = add256(proposal.againstVotes, votes);
        }

        receipt.hasVoted = true;
        receipt.support = support;
        receipt.votes = votes;

        emit VoteCast(voter, proposalId, support, votes);
    }
```

由于两次投票，可以满足这个条件

4.ProposalExecuted 执行提案(5月8日)

<https://bscscan.com/tx/0x13d19809b19ac512da6d110764caee75e2157ea62cb70937c8d9471afcb061bf>

恶意合约里面来执行的提案

### 恶意合约(5月8日)

创建exp tx

<https://bscscan.com/tx/0x4800928c95db2fc877f8ba3e5a41e208231dc97812b0174e75e26cca38af5039>

exp合约

<https://bscscan.com/address/0xcd337b920678cf35143322ab31ab8977c3463a45>

攻击前黑客给exp转了一些钱(100FTS 3.020309536199074866MAHA)

```
0x6a04f47f839d6db81ba06b17b5abbc8b250b4c62e81f4a64aa6b04c0568dc501	Transfer	17634604	4 days 16 hrs ago	Fotress Protocol Exploiter	OUT	 MahaDAO: MAHA Token	0 BNB	0.00020393
0xd127c438bdac59e448810b812ffc8910bbefc3ebf280817bd2ed1e57705588a0	Transfer	17634599	4 days 16 hrs ago	Fotress Protocol Exploiter	OUT	 Fortress Protocol: FTS Token	0 BNB	0.00045206
```

攻击交易

<https://bscscan.com/tx/0x13d19809b19ac512da6d110764caee75e2157ea62cb70937c8d9471afcb061bf>

首先是调用`_setCollateralFactor`修改了`fToken`的`newCollateralFactorMantissa`。

这里可以看到日志确实被修改了

```
{
  "fToken": "0x854c266b06445794fa543b1d8f6137c35924c9eb",
  "oldCollateralFactorMantissa": "0",
  "newCollateralFactorMantissa": "700000000000000000"
}
```

重点是submit函数，之所以能够成功修改状态变量fcds，是因为submit函数中缺少了对signer本身的校验以及power的校验。

用一下SharkTeam的图![](https://img.learnblockchain.cn/attachments/2022/05/Z6KrhUnS627c65c5f35ae.gif)

```javascript
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);
  }
```

修改了预言机中的价格，导致后续获取价格的时候出错了(这里好像SharkTeam弄错了,这里调用的是getCurrentValue而不是getCurrentValues)

```
  function getCurrentValue(bytes32 _key) external view returns (uint256 value, uint256 timestamp) {
    FirstClassData storage numericFCD = fcds[_key];
    return (uint256(numericFCD.value), numericFCD.dataTimestamp);
  }
```

交易细节查看Exploit中的步骤。主要为套利

EXP:<https://github.com/8olidity/DeFiVulhub/tree/main/Fortress>
