Skip to content

Latest commit

 

History

History
593 lines (439 loc) · 13.2 KB

File metadata and controls

593 lines (439 loc) · 13.2 KB

DIAP ERC-4337 安全审计报告

概述

本文档详细分析了 DIAP ERC-4337 集成的安全性,包括已实施的安全措施、潜在漏洞和缓解策略。

安全等级评估

组件 风险等级 状态
DIAPAccount 🟡 中等 需要审计
DIAPAccountFactory 🟢 低 相对安全
DIAPPaymaster 🟡 中等 需要审计
DIAPAgentNetwork 集成 🟢 低 最小改动

已实施的安全措施

1. 重入攻击防护

实施: ✅ 所有关键函数使用 ReentrancyGuard

contract DIAPAccount is BaseAccount, Initializable {
    // 继承自 BaseAccount,使用 Checks-Effects-Interactions 模式
    
    function transferToken(address token, address to, uint256 amount) 
        external onlyEntryPointOrOwner notFrozen 
    {
        // 先检查权限
        // 再执行转账(外部调用)
        IERC20(token).transfer(to, amount);
    }
}

防护级别: 🟢 高

2. 访问控制

实施: ✅ 多层权限控制

// Level 1: Owner(完全控制)
modifier onlyOwner() {
    if (msg.sender != _owner) revert OnlyOwner();
    _;
}

// Level 2: EntryPoint 或 Owner
modifier onlyEntryPointOrOwner() {
    if (msg.sender != address(ENTRY_POINT) && msg.sender != owner()) {
        revert NotFromEntryPointOrOwner();
    }
    _;
}

// Level 3: 冻结检查
modifier notFrozen() {
    if (_frozen) revert AccountFrozen();
    _;
}

防护级别: 🟢 高

3. 整数溢出防护

实施: ✅ Solidity 0.8+ 自动检查

// 自动溢出检查
sessionKey.spentToday += amount;

// 关键操作额外验证
if (sessionKey.spentToday + amount > sessionKey.dailyLimit) {
    revert DailyLimitExceeded();
}

防护级别: 🟢 高

4. 签名验证

实施: ✅ ECDSA + EIP-1271

function _validateSignature(
    IEntryPoint.UserOperation calldata userOp,
    bytes32 userOpHash
) internal view override returns (uint256 validationData) {
    bytes32 hash = userOpHash.toEthSignedMessageHash();
    address signer = hash.recover(userOp.signature);
    
    // 验证 owner 或 Session Key
    if (signer == _owner) {
        return 0;
    }
    
    SessionKeyData storage sessionKey = sessionKeys[signer];
    if (!sessionKey.isActive || block.timestamp > sessionKey.validUntil) {
        return 1;
    }
    
    return 0;
}

防护级别: 🟢 高

5. 时间锁和限额

实施: ✅ 多重限制

struct SessionKeyData {
    uint256 validUntil;       // 时间限制
    uint256 dailyLimit;       // 每日限额
    uint256 perTxLimit;       // 单笔限额
    uint256 spentToday;       // 已花费
    uint256 lastResetTime;    // 重置时间
    bool isActive;            // 激活状态
}

防护级别: 🟢 高

潜在漏洞分析

🔴 高风险漏洞

1. Session Key 重放攻击

描述: Session Key 签名可能被重放

当前状态: ⚠️ 部分防护

漏洞代码:

// DIAPAccount.sol
function _validateSignature(...) internal view override returns (uint256) {
    // 只验证签名者,没有 nonce 检查
    address signer = hash.recover(userOp.signature);
    if (signer == _owner) return 0;
    // ...
}

攻击场景:

  1. 攻击者截获一个有效的 UserOperation
  2. 在 Session Key 过期前重放该操作
  3. 可能导致重复转账或操作

缓解方案:

// 添加 nonce 管理
mapping(address => uint256) public sessionKeyNonces;

function _validateSignature(...) internal returns (uint256) {
    address signer = hash.recover(userOp.signature);
    
    if (sessionKeys[signer].isActive) {
        // 验证并增加 nonce
        uint256 expectedNonce = sessionKeyNonces[signer];
        require(userOp.nonce == expectedNonce, "Invalid nonce");
        sessionKeyNonces[signer]++;
    }
    
    return 0;
}

优先级: 🔴 高 - 需要立即修复


2. 前端运行攻击(Frontrunning)

描述: 攻击者可以抢先执行交易

当前状态: ⚠️ 无防护

攻击场景:

  1. 用户提交 addSessionKey 交易
  2. 攻击者看到交易,抢先提交更高 Gas 价格的交易
  3. 攻击者的 Session Key 被添加

缓解方案:

// 添加提交-揭示模式
mapping(bytes32 => uint256) public commitments;

function commitSessionKey(bytes32 commitment) external onlyOwner {
    commitments[commitment] = block.timestamp;
}

function revealSessionKey(
    address key,
    uint256 validUntil,
    uint256 dailyLimit,
    uint256 perTxLimit,
    bytes32 salt
) external onlyOwner {
    bytes32 commitment = keccak256(abi.encodePacked(
        key, validUntil, dailyLimit, perTxLimit, salt
    ));
    
    require(commitments[commitment] > 0, "Not committed");
    require(block.timestamp >= commitments[commitment] + 1 minutes, "Too early");
    
    // 添加 Session Key
    _addSessionKey(key, validUntil, dailyLimit, perTxLimit);
    delete commitments[commitment];
}

优先级: 🟡 中 - 建议实施


🟡 中风险漏洞

3. 每日限额重置时间操纵

描述: 限额重置逻辑可能被利用

当前代码:

// 重置每日限额(如果需要)
if (block.timestamp >= sessionKey.lastResetTime + 1 days) {
    sessionKey.spentToday = 0;
    sessionKey.lastResetTime = block.timestamp;
}

攻击场景:

  1. 攻击者在 23:59 花费接近限额
  2. 在 00:01 限额重置
  3. 再次花费接近限额
  4. 在短时间内花费 2 倍限额

缓解方案:

// 使用固定的 UTC 时间重置
function _getDayStart(uint256 timestamp) internal pure returns (uint256) {
    return (timestamp / 1 days) * 1 days;
}

function _checkAndUpdateSessionKeyLimit(address key, uint256 amount) internal {
    SessionKeyData storage sessionKey = sessionKeys[key];
    
    uint256 currentDayStart = _getDayStart(block.timestamp);
    uint256 lastDayStart = _getDayStart(sessionKey.lastResetTime);
    
    if (currentDayStart > lastDayStart) {
        sessionKey.spentToday = 0;
        sessionKey.lastResetTime = currentDayStart;
    }
    
    // 检查限额...
}

优先级: 🟡 中 - 建议修复


4. Paymaster DoS 攻击

描述: 恶意用户可能耗尽 Paymaster 资金

当前代码:

function validatePaymasterUserOp(...) external onlyEntryPoint {
    // 检查配额
    if (quota.usedToday + maxCost > quota.dailyQuota) {
        revert QuotaExceeded();
    }
    
    // 检查存款
    uint256 deposit = ENTRY_POINT.balanceOf(address(this));
    if (deposit < maxCost) {
        revert InsufficientDeposit();
    }
    
    // 批准赞助
    return (context, 0);
}

攻击场景:

  1. 攻击者创建多个账户
  2. 每个账户消耗最大配额
  3. Paymaster 资金快速耗尽

缓解方案:

// 添加全局限制
uint256 public globalDailyLimit;
uint256 public globalUsedToday;
uint256 public globalLastResetTime;

function validatePaymasterUserOp(...) external onlyEntryPoint {
    // 检查全局限额
    if (block.timestamp >= globalLastResetTime + 1 days) {
        globalUsedToday = 0;
        globalLastResetTime = block.timestamp;
    }
    
    if (globalUsedToday + maxCost > globalDailyLimit) {
        revert GlobalQuotaExceeded();
    }
    
    // 其他检查...
    globalUsedToday += maxCost;
}

优先级: 🟡 中 - 建议实施


5. 白名单绕过

描述: 白名单检查可能被绕过

当前代码:

function executeWithSessionKey(
    address dest,
    uint256 value,
    bytes calldata func
) external notFrozen {
    if (msg.sender != address(ENTRY_POINT) && msg.sender != _owner) {
        revert NotFromEntryPointOrOwner();
    }
    
    if (msg.sender != _owner) {
        if (!whitelist[dest]) revert NotWhitelisted();
    }
    
    _call(dest, value, func);
}

攻击场景:

  1. 攻击者调用白名单合约
  2. 白名单合约再调用非白名单合约
  3. 绕过白名单限制

缓解方案:

// 添加调用深度检查
uint256 private callDepth;

function executeWithSessionKey(...) external notFrozen {
    require(callDepth == 0, "No nested calls");
    callDepth++;
    
    // 执行调用
    _call(dest, value, func);
    
    callDepth--;
}

// 或者:禁止 delegatecall
function _call(address target, uint256 value, bytes memory data) internal {
    (bool success, bytes memory result) = target.call{value: value}(data);
    // 不允许 delegatecall
    require(success, "Call failed");
}

优先级: 🟡 中 - 建议修复


🟢 低风险问题

6. Gas 限制 DoS

描述: 批量操作可能因 Gas 限制失败

当前代码:

function batchTransferToken(
    address token,
    address[] calldata recipients,
    uint256[] calldata amounts
) external onlyEntryPointOrOwner notFrozen {
    require(recipients.length == amounts.length, "Length mismatch");
    for (uint256 i = 0; i < recipients.length; i++) {
        IERC20(token).transfer(recipients[i], amounts[i]);
    }
}

问题: 数组过大可能导致 Gas 不足

缓解方案:

uint256 public constant MAX_BATCH_SIZE = 50;

function batchTransferToken(...) external {
    require(recipients.length <= MAX_BATCH_SIZE, "Batch too large");
    // ...
}

优先级: 🟢 低 - 可选优化


7. 事件缺失

描述: 某些关键操作缺少事件

建议添加:

event SessionKeyLimitUpdated(address indexed key, uint256 newDailyLimit, uint256 newPerTxLimit);
event SessionKeyUsageRecorded(address indexed key, uint256 amount, uint256 remaining);
event EmergencyAction(address indexed actor, string action, uint256 timestamp);

优先级: 🟢 低 - 建议添加


安全检查清单

部署前检查

  • 所有合约通过 Slither 静态分析
  • 所有合约通过 Mythril 符号执行
  • 进行完整的单元测试(覆盖率 > 90%)
  • 进行集成测试
  • 进行模糊测试(Echidna)
  • 第三方安全审计
  • 测试网部署和验证
  • Bug 赏金计划

运行时监控

  • 监控异常交易模式
  • 监控 Paymaster 余额
  • 监控 Session Key 使用情况
  • 设置告警阈值
  • 定期安全审查

应急响应

  • 准备暂停机制
  • 准备升级方案
  • 准备资金恢复流程
  • 建立事件响应团队
  • 准备公告模板

推荐的安全工具

1. 静态分析

# Slither
pip install slither-analyzer
slither contracts/aa/

# Mythril
pip install mythril
myth analyze contracts/aa/DIAPAccount.sol

2. 模糊测试

# Echidna
echidna-test contracts/aa/DIAPAccount.sol --contract DIAPAccount

3. 形式化验证

# Certora Prover
certoraRun contracts/aa/DIAPAccount.sol --verify DIAPAccount:specs/DIAPAccount.spec

修复优先级

立即修复(1-2 天)

  1. 🔴 Session Key 重放攻击防护
  2. 🔴 添加 nonce 管理

短期修复(1 周)

  1. 🟡 每日限额重置逻辑
  2. 🟡 Paymaster 全局限制
  3. 🟡 白名单绕过防护

中期优化(2-4 周)

  1. 🟡 前端运行防护(提交-揭示)
  2. 🟢 批量操作限制
  3. 🟢 添加完整事件日志

长期改进(持续)

  1. 第三方安全审计
  2. Bug 赏金计划
  3. 形式化验证
  4. 持续监控和改进

安全最佳实践

1. 最小权限原则

// ✅ 好:Session Key 只有有限权限
sessionKey.dailyLimit = 1000 * 10**18;  // 限制每日花费

// ❌ 坏:Session Key 无限权限
sessionKey.dailyLimit = type(uint256).max;

2. 深度防御

// 多层检查
function transferToken(...) external {
    // 1. 权限检查
    require(msg.sender == owner || isValidSessionKey(msg.sender));
    
    // 2. 限额检查
    require(amount <= dailyLimit);
    
    // 3. 白名单检查
    require(whitelist[token]);
    
    // 4. 冻结检查
    require(!frozen);
    
    // 执行转账
}

3. 失败安全

// ✅ 好:默认拒绝
function isWhitelisted(address target) public view returns (bool) {
    return whitelist[target];  // 默认 false
}

// ❌ 坏:默认允许
function isBlacklisted(address target) public view returns (bool) {
    return blacklist[target];  // 默认 false,允许所有
}

4. 可审计性

// 记录所有关键操作
event SessionKeyUsed(address indexed key, uint256 amount, uint256 timestamp);
event LimitExceeded(address indexed key, uint256 attempted, uint256 limit);
event EmergencyFreeze(address indexed actor, uint256 timestamp);

总结

当前安全状态

  • ✅ 基础安全措施完善
  • ⚠️ 存在中等风险漏洞
  • 🔴 需要修复高风险问题

建议行动

  1. 立即: 修复 Session Key 重放攻击
  2. 短期: 实施所有中风险缓解措施
  3. 中期: 完成第三方审计
  4. 长期: 建立持续安全监控

风险评估

风险类别 当前状态 目标状态
重入攻击 🟢 低 🟢 低
权限控制 🟢 低 🟢 低
签名验证 🟡 中 🟢 低
DoS 攻击 🟡 中 🟢 低
前端运行 🟡 中 🟢 低
总体 🟡 中 🟢 低

完成所有建议修复后,系统安全性将达到生产级别。