Smart contracts are self-executing codes that form the backbone of the Web3 ecosystem. Smart contracts serve as the foundational threads of the Web3 ecosystem, delicately balancing billions on an open network. Today, we'll discuss the vulnerabilities auditors should look for floating pragma, phishing with tx.origin and block timestamp manipulation. This is a great place to start if you want to learn about Solidity and how to audit smart contracts. This is one article in a series on auditing Solidity smart contracts. The series will cover vulnerabilities and resources that smart contract auditors use.
Floating Pragma
When you use a floating pragma in Solidity, the compiler can use any version of Solidity that is equal to or newer than the specified version. However, this flexibility comes with risks. Your smart contracts may be compiled using an outdated or incompatible compiler version, which can lead to bugs or vulnerabilities.
To provide a clearer understanding, the `pragma solidity ^0.7.0;` statement is a floating pragma. The caret (`^`) symbol plays a crucial role here. It allows the use of any Solidity compiler version that is compatible with `0.7.0` but less than `0.8.0`. This means that future minor versions (e.g., `0.7.1`, `0.7.2`, etc.) can be used, while any breaking changes in major versions (e.g., `0.9.0`) are effectively avoided, providing a sense of stability to your code.
However, there's a situation where it's okay to use a floating pragma, and that's when you're dealing with libraries or packages. In other cases, if you don't set a specific version, other developers would need to manually adjust the code to make it work correctly on their computers.
Phishing With Tx.Origin
In Solidity, there is a global variable known as "tx.origin." This variable provides the initiator's address or account that triggered the transaction. However, it's essential to exercise caution when utilizing "tx.origin" as a determinant for granting authorization.
Consider the following scenario: An individual manipulates a smart contract that relies on "tx.origin" to wrongly ascertain their entitlement to perform an action, even if their entitlement is invalid. This arises because "tx.origin" reveals the originator of the transaction process, not the current entity attempting the action.
Consequently, if one heavily depends on "tx.origin" for authorization, unintended access might be granted to an unauthorized entity. This analogy resembles allowing entry to someone based solely on their proximity to another person who possesses a key, which inherently lacks security.
Victim Smart Contract
In the above contracts, "Wallet" is designed to manage fund storage and withdrawals, and "Attack" is a malicious actor's creation aiming to exploit the initial contract. It's worth noting that the authorization mechanism for the transfer function relies on the utilization of "tx.origin."
In the above contracts, "Wallet" is designed to manage fund storage and withdrawals, and "Attack" is a malicious actor's creation aiming to exploit the initial contract. It's worth noting that the authorization mechanism for the transfer function relies on the utilization of "tx.origin."
Attacker Smart Contract
Now, here's the intricacy: When the possessor of the Wallet contract directs a transaction with sufficient gas to the Attack contract's address, it triggers the invocation of the fallback function. This, in turn, prompts the execution of the transfer function from the Wallet contract, utilizing "attacker" as a parameter. Consequently, all the financial resources housed within the Wallet contract will be extracted and transferred to the attacker's designated address. This occurs because the identity that kickstarted the function call process is that of the target, which is the Wallet contract's owner.
Hence, the value of "tx.origin" will align with the owner's identity, satisfying the "require" condition and permitting the operation to proceed as intended.
Recommendation
It is recommended to use msg.sender instead of tx.origin.
Block Timestamp Manipulation
Each block in a blockchain system has a header that includes a timestamp field, a crucial piece of information that enables the network to establish chronological order and track the sequence of events. Interestingly, this timestamp field is not an immutable element; it is set by the miner responsible for generating the block.
The miner can manipulate the timestamp field to a certain extent, although with certain restrictions. This capability allows the miner to influence the system within specific time constraints.
To set a block timestamp, the miner must emerge victorious in mining the next block in the blockchain. As a responsible validator of transactions, the miner must adhere to two primary conditions:
- The next timestamp must follow chronologically after the timestamp of the preceding block. The blockchain maintains its integrity and continuity by logically progressing through time.
- The timestamp cannot be set too far into the future. This limitation ensures the network remains synchronized, avoiding potential disruptions from blocks set too far ahead of real-time.
As the miner successfully mines a new block and becomes the block producer, they can slightly adjust the block timestamp. This presents a unique advantage for the miner, who can leverage this control to subtly influence the sequence of events in the blockchain.
Vulnerable contract
The provided roulette contract is a game in which players can win the entire Ether balance in the contract if they submit a transaction at a specific time. Players can participate by calling the "spin" function and sending 5 Ethers to the contract. Winning is based on whether the block timestamp is divisible by 12, and if the condition is met, the player receives the entire Ether amount stored in the contract.
Malicious miners can attack this contract by calling it the "spin" function and submitting 5 Ethers. Then, manipulate the block timestamp for the upcoming block to ensure it is divisible by 12. If successful in mining the next block with the manipulated timestamp, the attacker will claim the entire Ether balance stored in the smart contract as their reward.
Recommendation
Do not use block.timestamp as a source of entropy and random number. Integrating a more secure and unbiased source of entropy like Chainlink VRF (Verifiable Random Function) is more secure.
In Conclusion
When developing Solidity smart contracts, it's essential to adhere to best practices to enhance security and reliability. Avoid floating pragma to reduce the risk of bugs or vulnerabilities caused by outdated or incompatible compiler versions. Instead of relying on `tx.origin` for transaction origin verification, use `msg.sender` for enhanced security. For cryptographic randomness, avoid using `block.timestamp`, which can be manipulated by miners; opt for Chainlink VRF (Verifiable Random Function) for secure randomness generation. Smart contract audits, bug bounties, and reviews are crucial in every stage of development. They increase the number of eyes scouting for vulnerabilities and decrease the chance of critical vulnerabilities slipping through.
Stay safe.
Join AuditOne as an Auditor: https://www.auditone.io/auditors
Book A Smart Contract Audit: https://calendar.app.google/cgZHGcPmpFP1mPZ18
Related Articles:
Auditing A Solidity Contract: Episode 1 - Re-entrancy Attack
Auditing A Solidity Contract: Episode 2 - Delegatecall
Auditing A Solidity Contract: Episode 4 - Testing
Auditing A Solidity Contract: Episode 5 - Automated Testing Tools
Auditing A Solidity Contract: Episode 6 - Frontrunning
Auditing A Solidity Contract: Episode 7 - Documentation and Reporting
Auditing A Solidity Contract: Episode 8 - Benefits of Auditing