“Bóng ma” bên trong Smart Contract

TH13🇻🇳
5 min readNov 14, 2023

--

Smart Contract được code bằng solidity là một class, bên trong là tập hợp những functions thể hiện chức năng và logic được cam kết trước. Theo thông lệ, gọi đến function không tồn tại trong smart contract là transaction bị revert. Thế nhưng, solidity đã hỗ trợ một chức năng gọi là “fallback”, mọi function call đến contract không tìm thấy tên function tương ứng sẽ được fallback “hứng” trọn. Đây là một chức năng nhưng vừa là một rủi ro tiềm tàng. Sau đây, cùng đi sâu vào phân tích tại sao cần thận trọng khi sử dụng fallback.

History of “fallback”

Trước khi v0.6.0 ra mắt, solidity còn sơ khai với syntax:

function() public payable{}

Function trên có ý nghĩa là đón nhận mọi “lời gọi” chuyển native token (ETH, BNB,…) đến contract. Đồng thời nó cũng là nơi nhận những function call không có trong contract. Có lẽ, đội ngũ Ethereum/sollidity đã nhìn ra vấn đế gì đó mà đã ra mắt Breaking Changes v0.6.0 với việc thay đổi syntax và tách thành 2 function có chức năng sau:

  • receive() external payable: Được trigger khi call data rỗng, có nhiệm vụ nhận native token gửi đến cho smart contract.
  • fallback() external payable: Trigger trong mọi trường hợp không tìm được function. Thay thế cho fallback cũ và có thể bỏ payable ra nếu không muốn nhận native token.

Risk of “fallback” & Case study

Fallback function là một tính năng rất hay, được sử dụng rộng rãi trong Proxy Pattern. Chỉ một vài trường hợp đặc biệt thì khi có fallback bên trong contract là rủi ro lớn, ảnh hưởng trực tiếp có thể làm mất tiền người dùng.

Cụ thể khi contract A có chứa fallback, một contract B của bên khác không liên quan có sử dụng đến contract A. Khi gọi một function không có được biết trước mà contract A không có, như vậy fallback trigger và transaction không bị revert. Như vậy, bằng một cách nào đó người gọi đã “bypass”
được logic nếu B gọi đến A với một function không tồn tại.

Nghe thì có vẻ cần rất nhiều điều kiện được thỏa, nhưng trên thực tế đã có vụ tấn công gây ra hậu quả nghiêm trọng. Hãy đến với 2 case study sau để biết chi tiết tại sao fallback nguy hiểm và security impact của nó như nào.

Multiswap (anyswap) — access control vulnerability

Bên trong Multiswap Router (trước V6), có những hàm gọi là permit có vai trò sử dụng “ủy quyền” người khác bridge hoặc swap token của họ. Ví dụ:

function anySwapOutUnderlyingWithPermit(
address from,
address token,
address to,
uint amount,
uint deadline,
uint8 v,
bytes32 r,
bytes32 s,
uint toChainID
) external {
address _underlying = AnyswapV1ERC20(token).underlying();
IERC20(_underlying).permit(from, address(this), amount, deadline, v, r, s);
TransferHelper.safeTransferFrom(_underlying, from, token, amount);
AnyswapV1ERC20(token).depositVault(amount, from);
_anySwapOut(from, token, to, amount, toChainID);
}

Function anySwapOutUnderlyingWithPermit trên gọi hàm permit của underlying token để from ủy quyền transfer cho contract. Sau đó contract gọi transferfrom để chuyển tiền và bên dưới thao tác bridge tiền sang chain khác.

Có thể thấy function trên là một function bình thường, NHƯNG sẽ không bình thường nếu contract của underlying token có chứa fallback. Một ví dụ tiêu biểu cho token luôn có fallback là wrap native token (WETH, WBNB, WAVAX,…). Kịch bản tấn công:

  • Victim Alice đã approve 10 WETH đến Router.
  • Attacker Bob thực hiện gọi anySwapOutUnderlyingWithPermit với amount là 10 WETH và v, r, s bất kỳ.
  • Contract sẽ thực thi gọi hàm permit với mục đích gọi để ủy quyền nhưng lúc này token là WETH có chứa fallback bên trong. Không hề có logic kiểm tra logic kiểm tra ủy quyền nào xảy ra và lệnh call không bị revert -> Contract tiếp tục thực hiện bridge 10 WETH của Alice đến cho Bob.

Cuối cùng, Bob đã trộm 10 WETH của Alice thành công tại vì contract Router đã không ràng buộc kỹ càng khi gọi một function ở một contract khác.

Vụ hack này đã làm cho rất nhiều users bị mất tiền, tổng thiệt hại lên đến 1.44M USD. Đây là một con số rất lớn và là bài học cho rất nhiều nhà phát triển dự án, lập trình viên và Security Auditor.

Arbitrary make a token loss of another contract via flashloan

Đây là một vulnerability được tìm thấy bởi các Auditor trong Code4rena. Mình sẽ reproduce lại vulnerable contract một cách đơn giản và kịch bản để làm cho contract khác mất tiền.

interface IFlashBorrower {
function onFlashLoan(uint256, bytes) external;
}
contract VulnerableContract {
IERC20 tokenX = 0x001234;
function flashloan(IFlashBorrower receiver, uint256 amount, bytes calldata data) public payable {
uint256 fee = amnount * 50 / 1_000;
tokenX.mint(address(receiver), amount);

receiver.onFlashLoan(shareAmount, data);

tokenX.burn(address(receiver), amount + fee);

emit Flashloaned(receiver, eusdAmount, burnShare);
}
}

Ở contract trên, người gọi flashloan có thể control hoàn toàn receiver là ai. Bên trong function, logic không hề kiểm tra tính hợp lệ mà đã mint và burn tokenX tính fee của receiver. Trong trường hợp receiver là một contract có chứa fallback function và không hề revert thì tokenX trong contract sẽ bị burn mất một lượng fee. Kịch bản tấn công:

  • Victim là một contract A được deployGnosisSafeProxy, có chứa fallback function và không hề revert nếu gọi đến một function không có trong contract. Người dùng lưu trong contract 100 tokenX.
  • Attacker gọi flashloan function với receiver là contract A.
  • Lúc này logic được thực hiện không gặp phải một cản trở nào dẫn đến revert. Sau khi transaction thành công, contract A bị burn mất 5 token được tính là fee trong lúc flashloan bởi A. Đây là vi phạm nghiêm trọng đến vấn đề bảo vệ người dùng khi tham gia vào sản phẩm.

Conclusion

Với 2 case study trên, có thể xem việc chứa fallback function bên trong contract như là một bóng ma có thể gây hại đến người dùng contract đó. Nguyên nhân trực tiếp dẫn đến mất tiền người dùng hoàn toàn không phải đến từ bản thân contract mà là những contract bên thứ 3 sử dụng. Nhưng với vai trò nhà phát triển, hãy luôn có trách nhiệm bảo vệ người dùngtoi61 đa với mọi trường hợp.

Đối với contract có chứa fallback hãy luôn kiểm tra logic và revert ngay khi có behavior trong data không hợp lệ.

Đối với 3rd party, khi sử dụng một contract khác bạn sẽ không giờ lường trước được logic của nó. Hãy luôn kiểm tra giá trị trả về và không được để người dùng control những data quan trọng truyền lên.

--

--