Create an account


Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tut] DelegateCall or Storage Collision Attack on Smart Contracts

#1
DelegateCall or Storage Collision Attack on Smart Contracts

<div><div class="kk-star-ratings kksr-valign-top kksr-align-left " data-payload="{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;385265&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;top&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;count&quot;:&quot;1&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;5&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;5&quot;,&quot;greet&quot;:&quot;Rate this post&quot;,&quot;legend&quot;:&quot;5\/5 - (1 vote)&quot;,&quot;size&quot;:&quot;24&quot;,&quot;width&quot;:&quot;142.5&quot;,&quot;_legend&quot;:&quot;{score}\/{best} - ({count} {votes})&quot;}">
<div class="kksr-stars">
<div class="kksr-stars-inactive">
<div class="kksr-star" data-star="1" style="padding-right: 5px">
<div class="kksr-icon" style="width: 24px; height: 24px;"></div>
</p></div>
<div class="kksr-star" data-star="2" style="padding-right: 5px">
<div class="kksr-icon" style="width: 24px; height: 24px;"></div>
</p></div>
<div class="kksr-star" data-star="3" style="padding-right: 5px">
<div class="kksr-icon" style="width: 24px; height: 24px;"></div>
</p></div>
<div class="kksr-star" data-star="4" style="padding-right: 5px">
<div class="kksr-icon" style="width: 24px; height: 24px;"></div>
</p></div>
<div class="kksr-star" data-star="5" style="padding-right: 5px">
<div class="kksr-icon" style="width: 24px; height: 24px;"></div>
</p></div>
</p></div>
<div class="kksr-stars-active" style="width: 142.5px;">
<div class="kksr-star" style="padding-right: 5px">
<div class="kksr-icon" style="width: 24px; height: 24px;"></div>
</p></div>
<div class="kksr-star" style="padding-right: 5px">
<div class="kksr-icon" style="width: 24px; height: 24px;"></div>
</p></div>
<div class="kksr-star" style="padding-right: 5px">
<div class="kksr-icon" style="width: 24px; height: 24px;"></div>
</p></div>
<div class="kksr-star" style="padding-right: 5px">
<div class="kksr-icon" style="width: 24px; height: 24px;"></div>
</p></div>
<div class="kksr-star" style="padding-right: 5px">
<div class="kksr-icon" style="width: 24px; height: 24px;"></div>
</p></div>
</p></div>
</div>
<div class="kksr-legend"> 5/5 – (1 vote) </div>
</div>
<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio">
<div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="Storage Collision Attack on Smart Contracts - DelegateCall" width="780" height="439" src="https://www.youtube.com/embed/Qcb37lr_c1c?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
</figure>
<p>The post is part 6 and continues our <strong>Smart Contract Security Series</strong>:</p>
<ul>
<li><a rel="noreferrer noopener" href="https://blog.finxter.com/smart-contract-security-series-part-1-ownership-exploit/" target="_blank">Ownership Exploit</a></li>
<li><a rel="noreferrer noopener" href="https://blog.finxter.com/private-exploit-smart-contract-security-series-part-2/" target="_blank">Private Variable Exploit</a></li>
<li><a rel="noreferrer noopener" href="https://blog.finxter.com/reentrancy-attack-hacking-smart-contract-security-series-part-3/" target="_blank">Reentrancy Attack</a></li>
<li><a rel="noreferrer noopener" href="https://blog.finxter.com/tx-origin-phishing-attack-smart-contract-security-series-part-4/" target="_blank">tx.origin Phishing Attack</a></li>
<li><a rel="noreferrer noopener" href="https://blog.finxter.com/?p=318951" target="_blank">Denial of Service Attack</a></li>
<li><a href="https://blog.finxter.com/delegatecall-or-storage-collision-attack-on-smart-contracts/" data-type="URL" data-id="https://blog.finxter.com/delegatecall-or-storage-collision-attack-on-smart-contracts/" target="_blank" rel="noreferrer noopener">Storage Collision Attack</a></li>
</ul>
<p>The <code>DelegateCall</code> attack or storage collision is expounded in this post. </p>
<p>Before you can grasp this exploit, you must first understand how <a href="https://academy.finxter.com/university/solidity-basics/" target="_blank" rel="noreferrer noopener">Solidity</a> saves state variables as explained <a href="https://blog.finxter.com/private-exploit-smart-contract-security-series-part-2/" target="_blank" rel="noreferrer noopener">here</a>.  </p>
<p>We start with the differences between <code>call</code> and <code>delegatecall</code> in Solidity, followed by exploiting the vulnerability of the <code>delegatecall</code> using the proxy contracts (mostly in smart contract upgrades), and then a solution for the attack. </p>
<p>Let’s start the journey!</p>
<h2><a></a>Call VS DelegateCall</h2>
<p>Solidity supports two low-level interfaces for interaction or sending messages to the contract functions. </p>
<p>These interfaces operate on addresses rather than contract instances (using <code>this</code> keyword). The key differences are highlighted with an example.</p>
<h3><a></a>Call</h3>
<p>It allows you to call the code of the callee contract from the caller with the storage context of the callee.</p>
<p>In order to understand this confusing sentence, let’s consider two contracts <code>A</code>, and <code>CallA</code>, with the naming convention as below:</p>
<ul>
<li><code>A</code> is the <strong><em>callee,</em></strong></li>
<li><code>CallA</code> is the <strong><em>caller</em></strong></li>
</ul>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// Callee
contract A
{ uint256 public x; function foo(uint256 _x) public { x = _x; }
} // Caller
contract CallA
{ uint256 public x; function callfoo(address _a) public { (bool success,) = _a.call(abi.encodeWithSignature("foo(uint256)", 15)); require(success, "Call was not successful"); }
}</pre>
<p>To test, deploy the contracts on <a rel="noreferrer noopener" href="https://blog.finxter.com/top-10-solidity-tutorials/" data-type="post" data-id="262867" target="_blank">Remix</a>, and when you execute the caller (<code>CallA -> callfoo</code>), you can verify that <code>foo()</code> gets called, and the value of ‘<code>x</code>‘ in the <code>callee(A ->x)</code> is set to <code>15</code>.</p>
<p class="has-global-color-8-background-color has-background"><img src="https://s.w.org/images/core/emoji/13.1.0/72x72/1f4a1.png" alt="?" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Note</strong>: It is also possible to send <a rel="noreferrer noopener" href="https://blog.finxter.com/introduction-to-ethereums-gas-in-solidity-development/" data-type="post" data-id="37644" target="_blank">Ether and gas</a> as part of the call using <code>value</code> and <code>gas</code> as params.</p>
<p>The above scenario is described in the figure as shown.</p>
<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" width="541" height="121" src="https://blog.finxter.com/wp-content/uploads/2022/05/image-266.png" alt="" class="wp-image-385277" srcset="https://blog.finxter.com/wp-content/uploads/2022/05/image-266.png 541w, https://blog.finxter.com/wp-content/uplo...300x67.png 300w" sizes="(max-width: 541px) 100vw, 541px" /><figcaption><strong>Fig</strong>: call flow</figcaption></figure>
</div>
<h3><a></a>DelegateCall</h3>
<p>It allows you to call the code of the callee contract from the caller with the storage context of the caller. </p>
<p>As previously mentioned, let’s consider two contracts <code>A</code> and <code>DelegateCallA</code>, with the naming convention as below:</p>
<ul>
<li><code>A</code> is the <strong><em>callee,</em></strong></li>
<li><code>DelegateCallA</code> is the <strong><em>caller</em></strong></li>
</ul>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">contract A
{ uint256 public x; function foo(uint256 _x) public { x = _x; }
} contract DelegateCallA
{ uint256 public x; function callfoo(address _a) public { (bool success,) = _a.delegatecall(abi.encodeWithSignature("foo(uint256)", 15)); require(success, "Delegate Call was not successful"); }
}</pre>
<p>To test, deploy the contracts on Remix, and when you execute the caller (<code>DelegateCallA -> ‘callfoo’</code>), you can verify that <code>foo()</code> gets called and the value of <code>‘x‘</code> in the <code>callee(A ->x)</code> is still 0, while the value of <code>x</code> in the caller (<code>DelegateCallA -> x</code>) is <code>15</code>.</p>
<p>Equipped with the above examples, it is evident that the <code>delegatecall</code><em>,</em> executes in the caller’s context, while the <code>call</code> executes in the callee context.</p>
<p>A picture speaks a thousand words. The above scenario is in the below figure.</p>
<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" width="491" height="141" src="https://blog.finxter.com/wp-content/uploads/2022/05/image-267.png" alt="" class="wp-image-385284" srcset="https://blog.finxter.com/wp-content/uploads/2022/05/image-267.png 491w, https://blog.finxter.com/wp-content/uplo...300x86.png 300w" sizes="(max-width: 491px) 100vw, 491px" /><figcaption><strong>Fig</strong>: <code>delegatecall</code> flow</figcaption></figure>
</div>
<p>One use case of <code>call</code> is the transfer of <a rel="noreferrer noopener" href="https://blog.finxter.com/ethereum-top-10-articles-to-get-started/" target="_blank">Ether</a> to a contract, and it passes all the gas to the receiving function, while the use cases of the <code>delegatecall</code> are when a contract invokes a library with public functions or uses a proxy contract to write smart upgradeable contracts.</p>
<h2><a></a>Exploit with delegatecall</h2>
<p>The most widely adopted technique to upgrade contracts is utilizing a <strong>proxy contract</strong>. </p>
<p>A proxy interposes the actual logical contract and the <a rel="noreferrer noopener" href="https://blog.finxter.com/smart-contracts-and-evm/" data-type="post" data-id="92507" target="_blank">dapp</a> interface. To update the logical contract with a new version (say V2), only the new deployed address of the logical contract is passed to the proxy. </p>
<p>This helps achieve minimal or no changes in the dapp/web3 interface, saving a lot of development time.</p>
<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" width="548" height="251" src="https://blog.finxter.com/wp-content/uploads/2022/05/image-268.png" alt="" class="wp-image-385288" srcset="https://blog.finxter.com/wp-content/uploads/2022/05/image-268.png 548w, https://blog.finxter.com/wp-content/uplo...00x137.png 300w" sizes="(max-width: 548px) 100vw, 548px" /><figcaption><strong>Fig</strong>: Contract upgrade with proxy</figcaption></figure>
</div>
<p>Let us write a quick and short proxy, and a logical contract (say V1). For the same, create a file <code>DelegateCall.sol</code> with Proxy and V1 contracts as below.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">contract Proxy
{ uint256 public x; address public owner; address public logicalAddr; constructor(address _Addr) { logicalAddr = _Addr; owner = msg.sender; } function upgrade(address _newAddr) public { logicalAddr = _newAddr; } // To call any function of the logical contract fallback() external payable { (bool success, ) = logicalAddr.delegatecall(msg.data); require(success , " Error calling logical contract"); }
}</pre>
<p><strong>V1, </strong>This represents version 1 of the logical contract.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">contract V1
{ uint256 public x; // abi.encodedWithSignature("increment_X()") = 0xeaf2926e function increment_X() public { x += 1; }
}</pre>
<p>Compile, deploy and run the contracts in Remix with the constructor param in proxy as the address of the V1 contract. </p>
<p>You can observe that, when the <code><strong>abi.encodedWithSignature("increment_X()"))</strong></code><em>  </em>is passed as <code>calldata</code> to Proxy (<code>fallback()</code> is triggered), the function <code>increment_X()</code> in V1 is called. </p>
<p>Abi encoding is calculated using the tool,</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$ npm install web3-eth-abi</pre>
<p>and then</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">const Web3EthAbi = require('web3-eth-abi'); > Web3EthAbi.encodeFunctionSignature("increment_X()") '0xeaf2926e'</pre>
<p>As discussed above in <code>delegatecall</code><em>,</em> the storage context of the caller (i.e., Proxy) is used, and the value of <code>x</code> in Proxy is incremented by 1. </p>
<p>So far, this is all good.</p>
<p>At some point in the future, it is decided to upgrade the V1 contract with new functionality, let’s call it V2. </p>
<p>Create a new contract<strong> V2</strong></p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">contract V2
{ uint256 public x; uint256 public y; function increment_X() public { x += 1; } // abi.encodedWithSignature("set_Y(uint256)", 10) //0x1675b4f5000000000000000000000000000000000000000000000000000000000000000a function set_Y(uint256 _y) public { y = _y; }
}</pre>
<p>Compile and deploy V2. </p>
<p>Pass the address of V2, to <code>upgrade()</code><em> </em>in Proxy as V2 is the new contract we need. </p>
<p>When <code><strong>abi.encodedWithSignature("set_Y(uint256)", 10))</strong></code><strong><em> </em></strong>is passed as <code>calldata</code> to <code>proxy</code>, the function <code>increment_Y()</code> in V2 is called. </p>
<p>The value of <code>y</code> is 10, but wait a minute, surprise, surprise!</p>
<p>As there is no <code>y</code> in the <code>Proxy</code> contract, and as the storage context of Proxy is used, it has overwritten the second param in Proxy (i.e., owner) with 10 (or <code>0x000000000000000000000000000000000000000A</code>).</p>
<p>With the owner address changed, the attacker is now in complete control of all the contracts.</p>
<h2>How to Prevent the Attack</h2>
<p>The <code><strong>delegatecall</strong></code> is tricky to use, and erroneous usage might have disastrous consequences. </p>
<p>For example, possible solutions to the above problem can be</p>
<ol type="1">
<li>If possible, avoid using additional storage variables or go stateless in the upgraded contract – V2.</li>
<li>Mirror the storage layout in V2, in other words, the contract calling <code>delegatecall</code> and the contract being called must have the same storage layout.</li>
<li>By implementing unstructured storage in proxy with the help of assembly code as in <a href="https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies#unstructured-storage-proxies">O</a><a href="https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies#unstructured-storage-proxies" target="_blank" rel="noreferrer noopener">p</a><a href="https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies#unstructured-storage-proxies">enZeppelins proxy</a> and not having any storage variables in proxy apart from the logical contract address.</li>
</ol>
<h2><a></a>Outro</h2>
<p>In this tutorial, we saw how <code>delegatecall</code> can lead to disastrous results with an incorrect understanding or usage. </p>
<p>While using <code>delegatecall</code><em>,</em> it is vital to keep it in our minds that <code>delegatecall</code> keeps context intact (<a href="https://blog.finxter.com/smart-contracts-and-evm/" target="_blank" rel="noreferrer noopener">storage</a>, caller, etc…Wink. </p>
<p>Even though there are certain problems associated with <code>delegatecall</code>, it is very often used in many contracts such as OpenZeppelin, Solidity libraries, EIP2535 diamonds, and many more.</p>
<p>To conclude, use <code>delegatecall</code><em>,</em> but with care!</p>
<hr class="wp-block-separator"/>
<div class="wp-block-image">
<figure class="aligncenter size-full"><a href="https://academy.finxter.com/university/creating-an-nft-marketplace/" target="_blank" rel="noopener"><img loading="lazy" width="363" height="650" src="https://blog.finxter.com/wp-content/uploads/2022/02/image-21.png" alt="" class="wp-image-170334" srcset="https://blog.finxter.com/wp-content/uploads/2022/02/image-21.png 363w, https://blog.finxter.com/wp-content/uplo...68x300.png 168w" sizes="(max-width: 363px) 100vw, 363px" /></a></figure>
</div>
</div>


https://www.sickgaming.net/blog/2022/05/...contracts/
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

Forum software by © MyBB Theme © iAndrew 2016