本文作者:小驹[1] 在合约代码中,最常用的是使用 msg.sender 来检查授权,但有时由于有些程序员不熟悉 tx.origin[2] 和 msg.sender 的区别,如果使用了 tx.origin 黑客最典型的攻击场景是利用tx.origin的代码问题常与钓鱼攻击相结合的组合拳的方式进行攻击。 tx.origin 是 Solidity 中的一个全局变量,它返回发送交易的账户地址。 通过调用 tx.origin 来检查授权可能会导致合约受到攻击,因为 tx.origin 返回交易的原始发送者,因为攻击的调用链可能是原始发送者->攻击合约-> 受攻击合约。 那么 在 C 合约中,msg.sender 就是 B 合约的地址,tx.origin 为 A 地址。 在 B 合约中,msg.sender 是 A 地址,tx.origin 也为 A 地址。 在这里使用的是tx.origin==owner进行检查。
简介 tx.origin是Solidity的一个全局变量,它遍历整个调用栈并返回最初发送调用(或事务)的帐户的地址。在智能合约中使用此变量进行身份验证会使合约容易受到类似网络钓鱼的攻击。 但针对tx.origin的使用并不用谈虎色变,正确的使用还是有它的应用场景的。 漏洞详解 漏洞合约 在如下合约中使用到了tx.origin的判断。 其中在转账方法transferTo中进行了owner的判断,这里用到了tx.origin。 因为tx.origin是最初发起交易的地址,也就是合约拥有者的地址。然后,地址里面的ether便被转到攻击者地址中。 使用提醒 tx.origin不应该用于智能合约的授权。 但它也有自己使用的场景,比如想要拒绝外部合约调用当前合约则可使用require(tx.origin ==msg.sender)来进行实现。
ps1:if(msg.sender == tx.origin)如果调用者是一个账户,上面的条件永远是 True。 )) == false); require(num <= baseMapping[tx.origin]); baseMapping[address(tx.origin)] = baseMapping[address(tx.origin)].sub(num); nestContract.transfer(address(tx.origin), num); (address(tx.origin), address(this)) >= num); require(nestContract.transferFrom(address(tx.origin ),address(this),num)); baseMapping[address(tx.origin)] = baseMapping[address(tx.origin)].add(
= tx.origin); _;//可以部署一个中间合约来调用绕过 } modifier gateTwo() { require(msg.gas.mod(8191) == 0); = uint64(_gateKey)); require(uint32(_gateKey) == uint16(tx.origin)); _; } function enter( bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) { entrant = tx.origin; = uint64(_gateKey)); require(uint32(_gateKey) == uint16(tx.origin)); _; } 先看最后一个判断 tx.origin = tx.origin); _; } modifier gateTwo() { uint x; assembly { x := extcodesize(caller) }
function destroy() onlyOwner public onlyOwner{ selfdestruct(owner); } 9、用户鉴权问题 合约中不要使用tx.origin 做鉴权 tx.origin代表最初始的地址,如果用户a通过合约b调用了合约c,对于合约c来说,tx.origin就是用户a,而msg.sender才是合约b,对于鉴权来说,这是十分危险的,这代表着可能导致的钓鱼攻击 owner = msg.sender; } function transferTo(address dest, uint amount) public { require(tx.origin 其中存在数据可靠问题的合约共2732个, 存在int型变量gas优化问题的合约共18285个, 存在string型变量gas优化问题的合约共194个, 存在Owner权限过大或合约后门的合约共1194个, 存在tx.origin 5、tx.origin 鉴权问题 截止2018年10月31日,我们发现了52个存在tx.origin 鉴权问题,其中交易量最高的10个合约情况如下: ?
因此设计者必须考虑到这一点, 可以通过限定最大循环次数方式, 来避免发生对智能合约的某次调用不能在Gas限制之内执行完毕的情况. tx.origin和msg.sender Solidity提供两两个方式来获取调用者的身份 : tx.origin和msg.sender. 那么对于智能合约A来说, msg.sender和tx.origin都是P的地址, 而对于智能合约B来说, msg.sender是A的地址, tx.origin是P的地址. 通过这个匿名函数判断tx.origin确实是TxUserWallet合约的owner, 虽然owner没有直接调用transferTo函数, 但是owner在TxUserWallet中的钱都转到了hack 因此msg.sender和tx.origin的使用应该结合具体场景, 开发者应该在充分理解二者的区别的基础上考虑合约的安全性.
tx.origin 切勿使用tx.origin进行授权。 owner = msg.sender; } function transferTo(address dest, uint amount) public { require(tx.origin 但通过检查tx.origin,它会得到启动交易的原始地址,该地址仍是所有者地址。 攻击钱包立即消耗您的所有资金。
Tx.origin鉴权 简单介绍 tx.origin是Solidity的一个全局变量,它遍历整个调用栈并返回最初发送调用(或事务)的帐户的地址,在智能合约中使用此变量进行身份验证可能会使合约受到类似网络钓鱼的攻击 payable {} // collect ether function withdrawAll(address _recipient) public { require(tx.origin 进行判断,如果tx.origin是owner,则将合约地址所拥有的ether发送到_recipient中 现在攻击者创建了以下合约: pragma solidity ^0.4.22; //设置原合约接口 是最初发起交易的地址,即原合约的owner,require(tx.origin == owner);条件满足,_recipient.transfer(this.balance);可以执行,即将原合约地址里的 防御措施 tx.origin不应该用于智能合约的授权,这并不是说永远不应该使用tx.origin变量,它在智能合约中确实有一些合法的用例,例如,如果想要拒绝外部合约调用当前合约,他们可以通过require
= uint64(_gateKey)); require(uint32(_gateKey) == uint16(tx.origin)); _; } function enter (bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) { entrant = tx.origin 3、gateThree() 也比较简单,将 tx.origin 倒数三四字节换成 0000 即可。bytes8(tx.origin) & 0xFFFFFFFF0000FFFF 即可满足条件。 = uint64(_gateKey)); require(uint32(_gateKey) == uint16(tx.origin)); _; } function enter (bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) { entrant = tx.origin
owner = msg.sender; }//构造函数,部署的人是合约的所有者 function changeOwner(address _owner) public { if (tx.origin = msg.sender) { owner = _owner; }//最初调用合约的人与调用者不一样的话,就把合约的所有者改成_owner } } 画个图了解一下 tx.origin 很明显,想要让 tx.origin 跟 msg.sender 不同,我们只需要部署一个合约,通过这个合约去调用题目合约的 changeOwner 就可以啦 首先 await contract.owner owner = msg.sender; }//构造函数,部署的人是合约的所有者 function changeOwner(address _owner) public { if (tx.origin function pwn{ hacked.changeOwner(msg.sender); //这个参数msg.sender是调用pwn函数的调用者,也就是我们的地址,也就是tx.origin
pledgeToken function pledgeToken() public payable{ require(address(msg.sender) == address(tx.origin 1.4 收益提取函数takeProfit function takeProfit() public { require(address(msg.sender) == address(tx.origin takeToken function takeToken(uint256 amount) public { require(address(msg.sender) == address(tx.origin
collection //守护者节点的发币 */ function nodeGet() public { require(address(msg.sender) == address(tx.origin erc20Amount, address erc20Address) public payable { require(address(msg.sender) == address(tx.origin function turnOut(address contractAddress) public { require(address(msg.sender) == address(tx.origin uint256 other = miningAmount.mul(otherAmount).div(100); nestToken.transfer(address(tx.origin tranTokenAmount, address tranTokenAddress) public payable { require(address(msg.sender) == address(tx.origin
避免使用tx.origin 永远不用使用tx.origin做身份验证,授权用户使用 tx.origin 变量的合约通常容易受到网络钓鱼攻击的攻击,这可能会诱骗用户在有漏洞的合约上执行身份验证操作。 更多信息详见:Solidity 文档[39] 警告: 除了身份验证问题,tx.origin未来可能从以太坊协议中删除,如果使用tx.origin可能会造成未来的不兼容。 Vitalik: 'Do NOT assume that tx.origin will continue to be usable or meaningful. '[40] 还需要注意的是,不能使用tx.origin来限制合约之间的互操作,因为使用tx.origin的合约不能被另一个合约使用。 还有一种替代方法是检查(tx.origin == msg.sender)的值,这种方式也有自己的缺点[53]。在其他情况下,extcodesize检查可以满足需求。
质押函数pledgeTokenfunction pledgeToken() public payable{require(address(msg.sender) == address(tx.origin
依赖 tx.origin 智能合约不应依赖于tx.origin进行身份验证,因为恶意合约可能会进行中间人攻击,耗尽所有资金。 建议改用msg.sender: function transferTo(address dest, uint amount) { require(tx.origin == owner) { 简单的说,tx.origin始终是合约调用链中的最初的发起者帐户,而msg.sender则表示直接调用者。 如果链中的最后一个 合约依赖于tx.origin进行身份验证,那么调用链中间环节的合约将能够榨干被调用合约的资金,因为身份验证没有检查究竟是谁(msg.sender)进行了调用。 6.
Telephone() public { owner = msg.sender; } function changeOwner(address _owner) public { if (tx.origin owner = _owner; } } } 合约分析 前面是个构造函数,把owner赋给了合约的创建者,照例看了一下这是不是真的构造函数,确定没有问题,下面一个changeOwner函数则检查tx.origin 这里涉及到了tx.origin和msg.sender的区别,前者表示交易的发送者,后者则表示消息的发送者,如果情景是在一个合约下的调用,那么这两者是木有区别的,但是如果是在多个合约的情况下,比如用户通过 A合约来调用B合约,那么对于B合约来说,msg.sender就代表合约A,而tx.origin就代表用户,知道了这些那么就很简单了,和上一个题目一样,我们这里需要另外部署一个合约来调用这儿的changeOwner Telephone() public { owner = msg.sender; } function changeOwner(address _owner) public { if (tx.origin
解题流程 让msg.sender与tx.origin不相同即可,使用合约就可以实现。 tx.origin 是交易的发送方。 msg.sender 是消息的发送方。 pragma solidity >=0.4.18 <0.6.0; import ". tx.origin的理解 解题过程 gateOne和之前的一样,这里就不赘述了。用合约来就能完成这个任务。 编译器版本:v0.4.18+commit.9cf6e910 ? 我以tx.origin=0x566f6e07f13ed2b092fc2fbe95aaf5e7f558efbf 为例 为了满足uint32(_gateKey) == uint16(tx.origin);。 uint16(tx.origin)的值最后4个字节,即0xefbf bytes8 相当于uint64,bytes应该是为了兼容Utf-8吧,采用的是宽字节。
Transfer(address indexed _from, address indexed _to, uint256 _value); function MyToken() { balances[tx.origin Transfer(address indexed _from, address indexed _to, uint256 _value); function MyToken() { balances[tx.origin
= _value.length) { Error(7, tx.origin, msg.sender); return false; } ElcoinDb db = _db(); if (db.getBalance(msg.sender) < totalToSend) { Error(8, tx.origin
= tx.origin require caller % 4096 == 4095 if bool(stor3[caller]) == 1: stor3[caller] = 0 = tx.origin require caller % 4096 == 4095 require not unknown35983396[caller] require not balanceOf = tx.origin require caller % 4096 == 4095 require not unknown35983396[caller] require not balanceOf require call.value == 1 balanceOf[caller] = 100 unknown35983396[caller] = 1 return 1 函数调用需满足tx.origin = tx.origin require caller % 4096 == 4095 if bool(stor3[caller]) == 1: stor3[caller] = 0