Soliditips est une série d’articles qui viennent mettre en lumière certains fonctionnements particuliers de Solidity, le langage des smart contracts d’Ethereum.

Pour ce premier article, nous verrons que tout ce qui est privé dans les smart contracts n’est pas si privé que ça. Explications …

La situation 

Voici notre exemple : 

pragma solidity 0.6.0;

contract Vault {
    
    uint8 private privateCode;
    
    constructor(uint8 _code) payable {
        privateCode = _code;
    }
    
    function withdraw(uint8 _code) public {
        if(privateCode == _code)  
            payable(msg.sender).transfer(address(this).balance);
    }
}

Nous venons de déclarer un contrat Vault, que le propriétaire va déployer dans une transaction en lui transférant dans le même temps des Ethers.

Le contrat sera alors le propriétaire de ces Ethers. Le créateur du contrat souhaite pouvoir les récupérer plus tard. Il va alors protéger cette opération par un code secret, connu de lui seul, pour ne pas que d’autres personnes les récupèrent à sa place.

On crée une variable privée pour stocker ce code, parce qu’on ne souhaite pas qu’elle soit lue en dehors du contrat.

Le créateur définit ce code secret en constructeur du contrat (via le champ privateCode, entier sur 8 bits).

L’attaque qui échoue 

Un contrat Attacker qui va essayer de voler les Ethers détenus par Vault. Il est créé en lui passant en paramètre l’adresse du contrat Vault à attaquer.  

contract Attacker { 

    address private vaultAddress; 

    constructor(address _vaultAddress) public { 
        vaultAddress = _vaultAddress; 
    } 

    function attack() public pure { 
        Vault vault = Vault(vaultAddress); 
        vault.withdraw(vault.privateCode); 
    } 
} 

Dans un premier temps, Attacker instancie le contrat Vault ciblé et essaiera d’accéder au champ « privateCode » pour récupérer le code secret. 

Et le compilateur de répondre, comme attendu : 

browser/Attacker.sol:28:24: TypeError: Member "privateCode" not found or not visible after argument-dependent lookup in contract Vault. 
vault.withdraw(vault.privateCode); 
^---------------^ 

On voit bien que cet exemple n’ira pas plus loin que la compilation. Le champ est privé, il ne peut donc pas être lu par un autre contrat. 

Jusque là, tout va bien !

Mais … 

Les smart contracts sont stockés dans l’EVM (Ethereum Virtual Machine) de chaque noeud du réseau. Chaque contrat dispose également d’une zone de stockage pour ses données. Cette zone de mémoire est organisée en emplacements et est parfaitement accessible publiquement. Ainsi, le privateCode de notre Vault, en tant que premier champ déclaré, est stocké dans le premier emplacement de la zone de stockage du contrat.

Pour y accéder, on peut par exemple utiliser Web3.js dans la console javascript d’un navigateur web3, via getStorageAt qui permet d’accéder en lecture aux slots mémoire d’un contrat : 

web3.eth.getStorageAt('<adresse du contrat déployé>', 0).then(result => { 
  console.log(web3.utils.hexToNumber(result)); 
}); 

On peut donc maintenant soit modifier notre fonction attack() pour qu’elle prenne en paramètre le code à utiliser pour l’attaque. On l’appellera avec le code récupéré grâce à l’instruction ci-dessus. Ou alors, au plus simple, appeler directement withdraw(code) de Vault.

Cet exemple montre bien qu’il peut être très dangereux de compter sur les champs privés pour cacher des secrets. Pour sécuriser des éléments, le mieux est encore de se baser sur l’adresse de l’émetteur (pattern Owner, on enregistre l’adresse qui crée le contrat et on la compare avec les adresses qui appellent les méthodes) étant donné que lui seul, en possession de sa clé privée, pourra envoyer des transactions avec son adresse. 

Notre contrat deviendra alors :

contract Vault {

	address private owner;

	constructor() public payable {
		owner = msg.sender;
	}

	function withdraw(uint8 code) public {
		if(msg.sender == owner)  
			payable(msg.sender).transfer(address(this).balance);
	}
}

Et on peut même passer l’adresse en publique, le contrat ne sera pas moins sécurisé pour autant.

Voilà pour ce premier « tips » Solidity qui vous aura appris qu’il faut, comme dans tous les domaines liés à la sécurité, redoubler de vigilance !