T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/stake.mjs:96
- Finding
- Unvalidated Receiver Can Redirect Staking Assets<![CDATA[ ## Vulnerability Details **File Location**: `scripts/stake.mjs`, lines 5-6, 68-69, and 96-101 **Vulnerability Type**: Unvalidated asset receiver configuration **Risk Level**: High ### Vulnerable Code ```javascript const PRIVATE_KEY = process.env.ETH_PRIVATE_KEY || 'YOUR_PRIVATE_KEY'; const MY_ADDRESS = process.env.MY_ADDRESS || 'YOUR_ADDRESS'; // ... const provider = new ethers.JsonRpcProvider(RPC_URL); const wallet = new ethers.Wallet(PRIVATE_KEY, provider); // ... const tx = await vault.updateStateAndDeposit( MY_ADDRESS, deadline, harvestParams, { value: amountWei } ); ``` ### Technical Analysis The ETH used for staking is supplied and signed by the wallet derived from `ETH_PRIVATE_KEY`, while the receiver of the resulting staking position is independently supplied through `MY_ADDRESS`. The script does not: - Verify that `MY_ADDRESS` is a valid checksummed Ethereum address. - Bind the receiver to `wallet.address`. - Warn when the receiver differs from the signing wallet. - Require explicit confirmation before transferring value to a third-party receiver. Consequently, a poisoned, stale, or mistyped `MY_ADDRESS` can cause the signer to fund a staking transaction whose resulting assets are assigned to another address. This violates the principle of secure transaction construction for an operation involving irreversible transfers. ### Attack Path 1. An attacker modifies `MY_ADDRESS` in the victim's environment, shell profile, deployment configuration, or execution wrapper. 2. The victim retains control of their legitimate `ETH_PRIVATE_KEY`. 3. The victim runs the documented command, such as `node scripts/stake.mjs 0.1`. 4. The script constructs a transaction funded and signed by the victim's wallet. 5. `updateStateAndDeposit` receives the attacker-controlled `MY_ADDRESS` as the receiver. 6. The staking output is assigned to the unintended address, potentially making recovery impossible. The same outcome can occur without an attac ...[truncated 493 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Use the signing wallet as the default and preferred receiver: ```javascript const receiver = wallet.address; ``` 2. If third-party receivers are required, make that behavior explicit rather than relying on a general environment variable: ```javascript const receiver = process.env.STAKE_RECEIVER ? ethers.getAddress(process.env.STAKE_RECEIVER) : wallet.address; ``` 3. Require an explicit command-line option such as `--allow-third-party-receiver` whenever `receiver !== wallet.address`. 4. Before signing, display and confirm: - Ethereum chain ID - Signing wallet - Receiver - Vault contract - ETH amount - Estimated gas cost 5. Validate the active network and contract configuration: ```javascript const network = await provider.getNetwork(); if (network.chainId !== 1n) { throw new Error(`Unexpected chain ID: ${network.chainId}`); } ``` 6. Abort if the receiver is the zero address, malformed, or unexpectedly differs from the signer. 7. Prefer hardware-wallet or external-signer integration so raw private keys do not need to be placed in process environment variables. ]]>
