Skip to content

Ethereum Dapp 签名验签中的安全问题

2023 年 11 月 19 日 · 更新

因为我们最近在做 ERC4337 账户抽象相关的东西,根据 EntryPoint 校验签名的几个条件补充一下,除了 地址、次数、链 这些维度之外还有 时间维度,如果一个有个硬分叉的链,只有旧数据没有新数据,恶意攻击者会拿着新的签名去旧数据中使用。

针对时间维度的防攻击就是加上 validUntil、validAfter

2021 年 03 月 22 日 · 更新

在一些审计报告和 Uniswap 中学到其实可以设置一个 deadline(block.timestamp)来取代下文 nonce 字段。因为使用 nonce 字段你需要记录和知道当前 nonce 到哪里了。

关键点

钱包的签名可以

  • 任何地址 D 用户拿着 C 用户的签名去验证
  • 任意次数 重复调用
  • 任何网络(主网、测试网)跨链攻击
  • 调用合约 给 A 合约的签名被拿去给 B 使用
  • 任何时间 一个签名一旦签出,这个签名在任何时间都是合法签名

来使用。

举例

拿一个 Solidity 官方文档中的例子来说:

// recipient is the address that should be paid.
// amount, in wei, specifies how much ether should be sent.
// nonce can be any unique number to prevent replay attacks
// contractAddress is used to prevent cross-contract replay attacks
function signPayment(recipient, amount, nonce, contractAddress, callback) {
    var hash = "0x" + abi.soliditySHA3(
        ["address", "uint256", "uint256", "address"],
        [recipient, amount, nonce, contractAddress]
    ).toString("hex");

    web3.eth.personal.sign(hash, web3.eth.defaultAccount, callback);
}

其实这个例子不算特别好,这是一个支付相关的签名示例,包含了下面的字段:

  • recipient 收款地址,验签固定收款的钱包
    延伸解释:B 对「B 支付给 A 3 ETH」这个数据进行签名,然后 C 偷盗了签名数据拿去调用,这样 ETH 也不会划转给 C 地址。
  • amount 付款金额,验签固定付款金额
  • nonce 唯一 ID,防止签名被多次使用
    延伸解释:B 对「B 支付给 A 3 ETH」这个数据进行签名,然后 A 去使用签名多次领取,这样 B 就付给了 A 多次 ETH,如果使用了 唯一 ID,即可限制领取一次
  • contractAddress 当前合约的地址(加一个合约地址可以有效防止签名被用在其他合约中)
    延伸解释:B 对「B 支付给 A 3 ETH」这个数据进行签名,原本是在二手手机中介那里记的帐,A 已经领取了 ETH,结果 A 转头又去二手车中介那里说 B 应该付款 3 ETH 又收了 B 的钱。

所以通过这几个参数,我们发现

  • recipient 对应 关键点里面的 「任意地址」
  • nonce 对应 「任意次数」
  • contractAddress 对应 「任意合约」

唯独漏了一点「任意网络」,这样的话你在测试网上面给 「向 A 转账 3 ETH」,A 可以拿到 mainnet 上面相同 contractAddress 的合约里面调用来领取 3 ETH。

所以这个例子应该改为:

// recipient is the address that should be paid.
// amount, in wei, specifies how much ether should be sent.
// nonce can be any unique number to prevent replay attacks
// chainId is used to prevent cross-contract replay attacks
// contractAddress is also used to prevent cross-contract replay attacks
function signPayment(recipient, amount, nonce, chainId, contractAddress, callback) {
    var hash = "0x" + abi.soliditySHA3(
        ["address", "uint256", "uint256", "uint256", "address"],
        [recipient, amount, nonce, chainId, contractAddress]
    ).toString("hex");

    web3.eth.personal.sign(hash, web3.eth.defaultAccount, callback);
}

Comments

Commenting as a guest. A nickname is required; email is optional. Only new comments posted after verifying your email and signing in will appear on your profile. Existing guest comments will not be linked automatically.

Share article

Ethereum Dapp 签名验签中的安全问题

Email X

View original ↗