If you’re building smart contracts with Solidity, you’re likely aware of the importance of security. However, even with the best intentions, malicious code can still find its way into your codebase. One way attackers can hide malicious code is by using external contracts. In this article we’ll discuss how to protect your smart contracts from attacks or exploits introduced in this manner.
What is an External Contract?
An external contract is Solidity code which is used by another contract—the caller—or, ultimately, an account. An external contract can be called through an address and a function signature. External contracts can be utilized for storing shared data or executing calculations.
How External Contracts Can Hide Malicious Code
Attackers can hide malicious code by either misleading users—pretending the contract is something else and providing false information about it—by obscuring its code so that users are not aware of possible attacks, or by deploying a seemingly innocent contract and later updating it with malicious code.
The caller may not be aware of malicious code or changes to the contract, allowing the attacker to potentially compromise the calling contract or account.
How to Protect Your Smart Contracts
To protect yourself from malicious external contracts, you can implement several strategies:
- Use trusted external contracts: only use external contracts from trusted sources. If possible, reputable security experts should audit and vet external contracts to ensure their security.
- Utilize immutable external contracts: utilize external contracts that remain immutable once deployed. This means that they are not behind a Proxy implementation. By using immutable external contracts, it is ensured that the contract cannot be modified with malicious code at any time in the future.
- Monitor external contracts: keep an eye on the external contracts that your smart contracts use. If you notice any unexpected changes, investigate immediately.
- Limit external contract access: only provide access to external contracts that your smart contract needs to function. Limiting access reduces the potential attack surface for malicious code.
Sample Code
Here’s an example of how you can use an external contract in Solidity:
pragma solidity 0.8.4;
contract ExternalContract {
uint256 public sharedData;
function setData(uint256 _data) public {
sharedData = _data;
}
}
contract MyContract {
ExternalContract externalContract;
constructor(address _externalContract) {
externalContract = ExternalContract(_externalContract);
}
function setData(uint256 _data) public {
externalContract.setData(_data);
}
}
In the code above, we define an external contract that has a public variable called sharedData
. We then define MyContract
, which can call an instance ExternalContract
via an address passed to the constructor. MyContract
has a function setData
that calls the setData
function in ExternalContract
, allowing it to store data that can be shared between multiple contracts.
Vulnerability
In Solidity, it is possible to cast an address into any arbitrarily-typed contract variable, even if the contract housed at that address is not of the same type as the one being casted.
Malicious actors can exploit this to conceal harmful code. Let’s see how.
pragma solidity 0.8.4;
import "hardhat/console.sol";
contract MyContract {
MyLibrary lib;
constructor(address _lib) {
lib = MyLibrary(_lib);
}
function callLib() public {
lib.utility();
}
}
contract MyLibrary {
function utility() public {
console.log("library utility has been called");
}
}
contract MaliciousLibrary {
function utility() public {
console.log("exploit has been carried out");
}
}
In this case, a calling contract of the type MyContract
expects an external contract of the type MyLibrary
to be set in its constructor and called at a later point through a utility function.
However, if a MaliciousLibrary
contract instance was provided instead of a MyLibrary
contract instance, an attack or exploit could take place. Why? Because it contains a function which mimics the signature of the regular library, and so the call can still be carried out regardless of the type declared for the lib
variable.
The code executed for the utility function might be completely unexpected for the caller, depending on whether or not they are aware of the code that resides in the address provided in the constructor at the moment of contract deployment.
Preventative Techniques
Naturally, making the address of external contract public so that its code can be reviewed is a sensible idea. With this objective in mind, using tools like Etherscan (which provide smart contract code verification) can help a caller judge whether or not they should invoke an external function.
From the side of the caller contract, having a setter function can provide more flexibility than setting the address exclusively at the deployment time through the constructor. In this case, if a reference is found out to be malicious or house problematic code, it can be updated to a different address. However, keep in mind that:
- The function to set or change the address of an external contract should be a privileged action, so that only an owner or admin can carry it out, instead of any caller.
- An attack could be executed in this manner, too. A deployed contract can initially point to a benign external contract but then later updated to a malicious one. As a result, a modifiable external address can reduce the trust users have in the caller contract.
Finally, the code in an external contract can change if its implementation is behind a proxy. Proxies are widely used, and therefore do not necessarily imply that an implementation is malicious. If your contract calls a external code deployed through a proxy, you should be on the lookout for events such as Upgraded
. Based on the emission of these events you can inspect whether or not the code you are calling is still safe to use, and disable the call (or change its referenced address) if necessary.
Conclusion
By using external contracts, you can improve the functionality and flexibility of your Solidity smart contracts. However, it’s important to be aware of the potential risks and take steps to protect your contracts from malicious code. By following the strategies outlined above and keeping an eye on the external contracts your smart contracts use, you can help ensure the security and reliability of your blockchain applications.
Posted in Blockchain, Smart Contract, Solidity, Technologies