# (复现)操纵预言机+提案攻击—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>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET 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?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
