请教solidity高手一个“如何在一个excel函数应用500例体内调用自己”的技术点。

Solidity安全之delegatecall 的滥用问题Solidity安全之delegatecall 的滥用问题链得得APP百家号Soildity 作为编写智能合约的语言,已经被广泛的应用。但同时,开发者和用户也得到了惨痛的教训,智能合约的安全问题层出不穷。因此,我们总结了一些常见的Solidity安全问题。前车之鉴,后车之师,希望后来者能有所警惕。delegatecall 「滥用」问题在 Solidity 中提供了delegatecall函数来实现合约之间相互调用及交互。各种调用,导致了delegatecall函数被合约开发者“滥用”,甚至“肆无忌惮”提供任意调用“功能”,导致了各种安全漏洞及风险。Delegatecall是一种常用的调用函数,它的特点是调用后内置变量 msg 的值不会修改为调用者,但执行环境为调用者的运行环境。代码分析在智能合约的开发过程中,合约的相互调用是经常发生的。开发者为了实现某些功能会调用另一个合约的函数。比如下面的例子,调用一个合约 A 的 test() 函数,这是一个正常安全的调用。但是在实际开发过程中,开发者为了兼顾代码的灵活性,往往会有下面这种写法: 这将引起任意 public 函数调用的问题:合约中的 delegatecall 的调用地址和调用的字符序列都由用户传入,那么完全可以调用任意地址的函数。相关事件7 月 19 日,Parity发布安全警报,警告其钱包软件1. 5 版本及之后的版本存在一个漏洞。据该公司的报告,确认有153,000ETH(大约价值 3000 万美元)被盗。因为initWallet函数是公开函数( public function) , 攻击者调用initWallet,重新初始化钱包会把之前合约钱包所有者覆盖, 即可改变钱包所有者。 漏洞代码:攻击过程技术分析还原:第一步:成为合约的owner通过往这个合约地址转账一个value = 0 ,msg.data.length & 0 的交易, 执行到_walletLibrary.delegatecall的分支,该函数能无条件的调用合约内的任何一个函数,黑客调用了一个叫做 initWallet的函数:这个函数再次调用initMultiowned函数:不幸的是,initWallet没有检查以防止攻击者在合同初始化后调用到initMultiowned, 这个函数使得这个合约的所有者被改为攻击者,相当于从unix中获得了root权限。第二步: 转账, 剩下的事情就很清晰了,通过调用execute函数转账到黑客的地址:第一个参数: address to= 0xbd6f121e79c32a65829cd1ddb4d32, 转账额度e4140000是为以Wei为单位的的eth,即 ,可以通过如下的代码获得具体数值。作为已经有过被利用先例的漏洞,开发者在智能合约在部署前必须通过严格的审计和测试,在编写时使用delegatecall要更加小心。本文由百家号作者上传并发布,百家号仅提供信息发布平台。文章仅代表作者个人观点,不代表百度立场。未经作者许可,不得转载。链得得APP百家号最近更新:简介:做负责任的区块链媒体。作者最新文章相关文章智能合约如何使用签名验证调用函数者的身份 - 简书
智能合约如何使用签名验证调用函数者的身份
在公开的区块链系统上部署智能合约,相当于将合约暴露给所有人,因此为了保证合约的执行限制在可控范围之内,可以对合约的函数调用进行权限控制。
如果一个函数的执行改变了合约状态,则必须通过eth_sendTransaction进行调用,对交易签名的验证保证了msg.sender不可伪造。但如果一个函数是由view或constant修饰的函数,只读取合约状态而不改变合约状态,而你又想限制只有特定的人能调用该函数,这时候又该怎样验证msg.sender的身份呢?
对于这种只读函数,使用者通过eth_call就可调用,使用eth_call时不需要调用者的签名,调用者可使用任意的msg.sender,如果通过判断msg.sender的方式进行权限控制,显然是形同虚设的,那么就需要使用签名来验证调用者身份。
比如一个只读函数为read(),调用者需要用自己的私钥对read函数的函数名进行签名。所谓函数名就是被调用函数的calldata的前四个字节,即msg.data。
获得函数定义部分的散列值的前4个字节:bytes4(keccak256("read()")。这里是0xbcbb0181。
将其补齐为32字节(64位16进制数)得到hash。因为验证签名的函数ecrecover接受的散列值是32字节的。这里是0xbcbb。
调用者用自己的地址对应的私钥对这个散列值进行签名得到(r,v,s)三元组。
通过ecrecover验证签名。ecrecover是solidity的内置函数,可以根据签名v,r,s和签名对象hash得到签名者的地址,来证明msg.sender身份。
方法一:使用web3.js进行数据签名
以太坊提供了web3.eth.sign方法来对数据生成数字签名。
//初始化基本对象
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);
在上面的代码中,我们先将要签名的数据abc生成哈希串,使用web3.sha3("abc")。接着我们使用当前连接节点的第一个默认帐户进行签名。
由于我使用的是EtherumJS TestRPC,它默认打开了帐户。否则,你还需要web3.personal.unlockAccount("0x..", "&passs&", 1000)来打开数据签名所使用帐户。需要注意的是,当你打开你的帐户时,可能有安全风险。因为其它程序也可以通过访问节点进行类似的sign,这意味着,他们可以伪造你的数据,包括以你的名义发起交易,转走你的钱。
运行的结果:
$ node test.js
account: 0xbcd194ad8cac0bee1e331
sha3(message): 0x4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45
Signed data: 0xf4128988cbe7df8315440adde412aff9a27fbdfbd3fe33fc8e581f442acbcf6ab800
方法二:使用 geth客户端进行数据签名
但是需要注意的是geth客户端签名比较特殊,下面是:
sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message)))
从上述代码可以看出,geth会为要签名的消息加上"\x19Ethereum Signed Message:\n" + len(message)前缀,所以实际上真正的被签名的数据并不是message 的哈希值,而是message的哈希值加上前缀后再次哈希的值。我们要非常注意这一点,否则后期验证签名会不成功。
验证签名:
ecrecover函数是由以太坊提供的一个全局函数,用于签名数据的校验。与上面所陈述的方式略有不同的是,这个函数返回的是签名者的公匙地址。如果返回结果是签名者的公匙地址,那么说明数据是正确的。
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)
ecrecover函数需要四个参数,需要被签名数据的结果值hash,r,s,v三个值。通过前面的说明,我们知道r,s,v是分别来自签名结果串。
r = signature[0:64]
s = signature[64:128]
v = signature[128:130]
其中v取出来的值或者是00或01。要使用时,我们先要将其转为整型,再加上27,所以我们将得到27或28。在调用函数时v将填入27或28。
验证签名的完整例子:
pragma solidity ^0.4.4;
contract Decode{
//公匙:0xbcd194ad8cac0bee1e331
//sha3(msg): 0x4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45 (web3.sha3("abc");)
//签名后的数据:0xf4128988cbe7df8315440adde412aff9a27fbdfbd3fe33fc8e581f442acbcf6ab800
//验签数据入口函数
function decode() returns (address){
bytes memory signedString =hex"f4128988cbe7df8315440adde412aff9a27fbdfbd3fe33fc8e581f442acbcf6ab800";
r = bytesToBytes32(slice(signedString, 0, 32));
s = bytesToBytes32(slice(signedString, 32, 32));
v = slice(signedString, 64, 1)[0];
return ecrecoverDecode(r, s, v);
//将原始数据按段切割出来指定长度
function slice(bytes memory data, uint start, uint len) returns (bytes){
bytes memory b = new bytes(len);
for(uint i = 0; i & i++){
b[i] = data[i + start];
//使用ecrecover恢复公匙
function ecrecoverDecode(bytes32 r, bytes32 s, byte v1) returns (address addr){
uint8 v = uint8(v1) + 27;
addr = ecrecover(hex"4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45", v, r, s);
//bytes转换为bytes32
function bytesToBytes32(bytes memory source) returns (bytes32 result) {
assembly {
result := mload(add(source, 32))
上述代码使用临时写的slice()函数把数据签名中的r,s,v切割出来;由于返回的仍是一个bytes类型,所以我们使用bytesToBytes32()进行一下类型转换;另外需要注意的是ecrecoverDecode()根据前面的说明,我们需要对v值,加上27后再进行调用。最后调用decode()函数,我们将会得到公匙0xbcd194ad8cac0bee1e331。
椭圆曲线DSA(ECDSA)简介:
假如Alice要对消息m加上数字签名,而Bob需要验证该签名。
生成数字签名
Alice根据随机数r和基点G求出点rG = (x, y);
Alicce根据随机数r、消息m的散列值h、和私匙a计算
最后,Alice将消息m、点rG = (x, y)和s发送给Bob,其中点rG和s就是数字签名。
验证数字签名
Bob接收到消息m、点rG = (x, y)和s。
Bob根据消息求出散列值h。
最后,Bob根据上述信息,用Alice的公匙进行以下计算。
最后让上述计算结果与rG进行比较看是否相等。
如果签名结果正确,则计算结果应如下所示。
大体上来说,对于要签名的数据m,使用它的哈希后的结果h,会生成签名。签名结果分为r,s,v三段值。其中r,s为32字节。v为一个字节,如果要用ecrecover()算法来验签,需对v值加27来组成27,28这两个值中的一个。
这篇文档旨在为Solidity开发人员提供一些智能合约的安全准则(security baseline)。当然也包括智能合约的安全开发理念、bug赏金计划指南、文档例程以及工具。对该文档提出修改或增补建议,请点击“阅读原文”。 1 基本理念 以太坊和其他复杂的区块链项目都处于...
pdf下载地址:Java面试宝典 第一章内容介绍 20 第二章JavaSE基础 21 一、Java面向对象 21 1. 面向对象都有哪些特性以及你对这些特性的理解 21 2. 访问权限修饰符public、private、protected, 以及不写(默认)时的区别(201...
手把手带你走上智能合约编程之路 译注:原文首发于ConsenSys开发者博客,原作者为Eva以及ConsenSys的开发团队。如果您想要获取更多及时信息,可以访问ConsenSys首页点击左下角Newsletter订阅邮件。本文的翻译获得了ConsenSys创始人Lubin...
安全问题: 函数可重入性 超出调用栈深度 跨函数的竞态条件 交易顺序依赖与非法预先交易导致的漏洞 时间戳依赖 整数的上溢和下溢导致的漏洞 存储操作中的深度下溢 利用交易失败,促使意外恢复 利用区块燃料上限引发漏洞 强行给智能合约中加入以太币,引发程序逻辑漏洞 安全问题 漏洞...
Solidity 合约类似于面向对象语言中的类。合约中有用于数据持久化的状态变量,和可以修改状态变量的函数。 调用另一个合约实例的函数时,会执行一个 EVM 函数调用,这个操作会切换执行时的上下文,这样,前一个合约的状态变量就不能访问了。 1 创建合约 可以通过以太坊交易 ...
小时候我并不喜欢画画。长大了一直当自己是个画渣。然而今年以来却谜一般的迷上了画画。尤其是彩铅。虽然画得实在不好,但每天都痴迷的要画个不停。 没有任何技巧的画着,大多数时候是临摹。 不论如何,就是喜欢。
春色满园关不住,一枝红杏出墙来。 前段时间的文章中,我阐述了世间充满太多诱惑,让我们各自遵循自我。今天,就收到了网友的来信咨询。 网友: 流离孤云,你好。我是你的读者,前些天看到你那篇《心乱了,就什么都乱了》的文章,觉得给我的启发很大。现实的生活中,我能经得住诱惑,但是我女...
所有复杂的共同进化系统都需要“冒出”。系统一旦冒出,它的独立、自稳定程度也就越高,不再需要初创时的人为扶持。也就是说,一个系统达到了稳定水平,就不会轻易地趋向倒退,仿佛这个系统被新的复杂性带来的凝聚力所“吸引”。人类组织,比如团队、企业,也会显示“冒出”的特征。
有的人不会看
沂蒙雨中荷叶
我为什么参加21天文案训练营?因为那里的老师长的好看啊!颜值高,很温柔,肯定很有才,这逻辑没毛病吧?有不服的吗?不服找我们老师去,让他们教到你服为止!还有一个原因是因为我们营的人是一群特别积极,特别有上进心的人,最主要的是我在...在 SegmentFault,学习技能、解决问题
每个月,我们帮助 1000 万的开发者解决各种各样的技术问题。并助力他们在技术能力、职业生涯、影响力上获得提升。
点击阅读原文
【Solidity入门系列】函数的可见性与访问权限控制
3月7日 发布,来源:
在之前的文章中我们介绍了Solidity语言函数的一些基本语法。下面来继续介绍作为一个分布式网络语言所特有的internal和external这两种不同的函数调用方式,以及Solidity提供的对函数调用时的可见性控制语法。
一、 调用方式
Solidity封装了两种函数的调用方式internal和external。
internal调用,实现时转为简单的EVM跳转,所以它能直接使用上下文环境中的数据,对于引用传递时将会变得非常高效(不用拷贝数据)。
在当前的代码单元内,如对合约内函数,引入的库函数,以及父类合约中的函数直接使用即是以internal方式的调用。我们来看个简单的例子:
pragma solidity ^0.4.0;
contract Test {
function f(){}
//以`internal`的方式调用
function callInternally(){
在上述代码中,callInternally()以internal的方式对f()函数进行了调用。
external调用,实现为合约的外部消息调用。所以在合约初始化时不能external的方式调用自身函数,因为合约还未初始化完成。下面来看一个以external方式调用的例子:
pragma solidity ^0.4.0;
contract A{
function f(){}
contract B{
//以`external`的方式调用另一合约中的函数
function callExternal(A a){
虽然当前合约A和B的代码放在一起,但部署到网络上后,它们是两个完全独立的合约,它们之间的方法调用是通过消息调用。上述代码中,在合约B中的callExternal()以external的方式调用了合约A的f()。
external调用时,实际是向目标合约发送一个消息调用。消息中的函数定义部分是一个24字节大小的消息体,20字节为地址,4字节为函数签名。
我们可以在合约的调用函数前加this.来强制以external方式的调用。需要注意的是这里的this的用法与大多数语言的都不一致。
pragma solidity ^0.4.0;
contract A{
function f() internal{}
function callInternally(){
//以`external`的方式调用
//f()只能以`internal`的方式调用
//Untitled3:7:9: Error: Member &f& not found or not visible after argument-dependent lookup in contract A
function callExternally(){
//this.f();
调用方式说明
上面所提到的internal和external指的函数调用方式,请不要与后面的函数可见性声明的external,public,internal,private弄混。声明只是意味着这个函数需要使用相对应的调用方式去调用。后续说明中会用以某某方式调用,来强调是对调用方式的阐述以加以区分。
二、函数的可见性
Solidity为函数提供了四种可见性,external,public,internal,private。
声明为external的可以从其它合约或通过Transaction进行调用,所以声明为external的函数是合约对外接口的一部分。
不能以internal的方式进行调用。
有时在接收大的数据数组时性能更好。
pragma solidity ^0.4.5;
contract FuntionTest{
function externalFunc() external{}
function callFunc(){
//以`internal`的方式调用函数报错
//Error: Undeclared identifier.
//externalFunc();
//以`external`的方式调用函数
this.externalFunc();
声明为external的externalFunc()只能以external的方式进行调用,以internal的方式调用会报Error: Undeclared identifier.。
函数默认声明为public。
public的函数既允许以internal的方式调用,也允许以external的方式调用。
public的函数由于被外部合约访问,是合约对外接口的一部分。
pragma solidity ^0.4.5;
contract FuntionTest{
//默认是public函数
function publicFunc(){}
function callFunc(){
//以`internal`的方式调用函数
publicFunc();
//以`external`的方式调用函数
this.publicFunc();
我们可以看到声明为public的publicFunc()允许两种调用方式。
在当前的合约或继承的合约中,只允许以internal的方式调用。
pragma solidity ^0.4.5;
contract A{
//默认是public函数
function internalFunc() internal{}
function callFunc(){
//以`internal`的方式调用函数
internalFunc();
contract B is A{
//子合约中调用
function callFunc(){
internalFunc();
上述例子中声明为internal的internalFunc()在定义合约,和子合约中均只能以internal的方式可以进行调用。
只能在当前合约中被访问(不可在被继承的合约中访问)。
即使声明为private,仍能被所有人查看到里面的数据。访问权限只是阻止了其它合约访问函数或修改数据。
pragma solidity ^0.4.5;
contract A{
//默认是public函数
function privateFunc() private{}
function callFunc(){
//以`internal`的方式调用函数
privateFunc();
contract B is A{
//不可调用`private`
function callFunc(){
//privateFunc();
上述例子中,声明为private的privateFunc()只能在定义的合约中以internal的方式进行调用。
专注基于以太坊(Ethereum)的相关区块链(Blockchain)技术,了解以太坊,Solidity,Truffle,web3.js。
个人博客: Solidity中文翻译:
感谢您的支持
处于某些特定的环境下,可以看到评论框,欢迎留言交流^_^。
我要该,理由是:
关闭理由:
删除理由:
忽略理由:
推广(招聘、广告、SEO 等)方面的内容
与已有问题重复(请编辑该提问指向已有相同问题)
答非所问,不符合答题要求
宜作评论而非答案
带有人身攻击、辱骂、仇恨等违反条款的内容
无法获得确切结果的问题
非开发直接相关的问题
非技术提问的讨论型问题
其他原因(请补充说明)从 solidity 语言特性深度解读以太坊智能合约漏洞原理和攻击利用
随着区块链、以太坊技术的兴起和不断成熟,安全问题也随之而来,今年智能合约漏洞已经让多个区块链项目价值瞬间归零。智能合约的开发语言、设计模式、运行机制都与传统应用有较大差异,它既有传统的安全风险(如整数溢出等),又有独特的新型风险(如私有变量不“私有”和特殊类型变量覆盖等)。研发人员如果不能深刻理解这些核心原理,则很容易编写出存在漏洞的智能合约;恶意合约也可以通过这种方法留下隐蔽漏洞,欺骗合约投资人并暗地里收割。本文以WCTF2018的一道智能合约漏洞赛题[1]为例,从solidity语言特性出发,深度解读以太坊智能合约漏洞原理和攻击利用。
2 漏洞合约分析
该合约是一个银行类合约,用户可以存入eth到该合约,并在存入到期之后取出。原题对该合约描述如下:
该合约中存在漏洞,攻击者利用漏洞可以盗取合约中的所有余额。漏洞涉及到整数溢出、变量覆盖以及由变量覆盖导致的变量相互影响。
合约源码如下:
要提取合约的全部合约余额,confiscate 函数是关键,但该函数调用成功必须满足:
msg.sender == owner
secret == _secret
now &= balances[account].deposit_term + 1 years
攻击者可以通过合约存储访问、整数溢出和变量覆盖来依次构造上述条件。
2.1 solidity全局变量存储
在BelluminarBank合约中,一共有4个全局变量,分别是balances、head、owner、secrete。它们的默认访问属性是private,看上去只有合约自己能够访问这些变量。事实上,合约的所有变量数据都是公开存储在链上的区块中,任何人都可以通过访问存储数据来获得这些变量的值[2]。在solidity语言中,全局变量都存储在storage中,根据solidity的变量存储规则,定长的变量在storage中是顺序存储的,数组变量在storage中其索引位置存放的是其数组长度(参见[3])。该合约storage中的变量存储布局如下:
对于在公链部署的合约,可通过以太坊web3接口web3.eth.getStorageAt(co ntractAddress, index)获取某个合约指定storage索引的数据。
因此,secrete并不是一个不可获取的私有数据,攻击者只需要访问该合约storage中的数据就可以构造confiscate 函数的secret == _secret条件。
2.2 solidity全局变量覆盖
BelluminarBank合约中的confiscate函数要求调用者必须是合约拥有者才可以进行余额提取操作,看上去攻击者是无法提取的。然而,由于solidity语言的局部变量存储特性,导致本合约的owner变量可以被修改,覆盖问题出现在 invest 函数中。
首先来看solidity局部变量覆盖全局storage的问题。solidity语言的变量存储有一个特性,即数组、映射、结构体类型的局部变量默认是引用合约的storage [4],而全局变量默认存储在storage中。因此,如果这些局部变量未被初始化,则它们将直接指向storage,修改这些变量就是在修改全局变量。
以如下的简单合约test为例,函数test1中定义了一个局部结构体变量x,但是没有对其进行初始化。根据solidity的变量存储规则,这时候x是存储在storage中的,而且是从索引0开始,那么对其成员变量x,y赋值之后,刚好覆盖了全局变量a和b。有兴趣可以在 remix 中在线对本合约进行调试。
pragma solidity 0.4.24;
contract test {
struct aa{
uint public a = 4;
uint public b = 6;
function test1() returns (uint){
在invest函数的else分支中,使用了一个局部结构变量investment。该局部变量在当前执行分支中并没有被初始化,默认指向合约的storage。执行中对该变量的成员赋值就会直接覆盖全局变量,覆盖关系为:
同时,在变量覆盖之前必须满足如下条件,即存款期限是最末一个存款记录的期限后一年:deposit_term &= balances[balances.length - 1].deposit_term + 1 years。由于deposit_term是用户提供的,轻松就可以满足。
所以,通过精心构造invest函数的参数就可以覆盖stroage中的sender,从而改变该合约的拥有者为攻击者,突破confiscate 函数的msg.sender == owner限制。
2.3 整数溢出
在BelluminarBank合约源码的confiscate函数还有另外一个如下的时间限制,即必须在存款满一年后才能提取,now &= balances[account].deposit_term + 1 years。
上一节用于全局变量覆盖的存款操作使得balances中最末一个存储记录的期限已经是1年后,即攻击者至少在2年后才能调用confiscate函数进行提款。与此同时,deposit_term在赋值给局部变量的时候会把全局变量head覆盖为超大的数,这也使得后续的for (uint256 i = i &= i++)循环处理无法提取全部的存款,因为head不为0。
显然,必须把head覆盖为0才能提取全部的存款,即invest函数的deposit_term参数必须为0。但如果该参数为0,又无法满足invest函数的全局变量覆盖执行的条件deposit_term &= balances[balances.length - 1].deposit_term + 1 years。
仔细分析可发现,如果balances[balances.length - 1].deposit_term+ 1 years恰好等于0,则上述的条件恒为真。显然,balances[balances.length - 1].deposit_term只要取值为(uint256_max – 1 years + 1),就会导致相加后的值为uint256_max+1。这个结果会超过uint256的表达空间,产生溢出导致最后的值为0。
因此,攻击者先做第一次存款,把balances最后一项的deposit_term设置为特殊值;然后做第二次存款,deposit_term传入0值,就能触发整数溢出,绕过变量覆盖条件限制并修改head为0值。
2.4 “变量纠缠”的副作用
在全局变量覆盖中,很容易产生“变量纠缠”现象,从而触发一些容易被忽视的副作用。这里以一个简单合约test为例,函数testArray中依然存在结构体局部变量a覆盖全局变量x的情况。但由于x是数组变量,其直接索引的storage存储位置仅存储其数组长度,也就是a.x只会覆盖x的数据长度,而a.y将覆盖变量num。
在testArray函数中,赋值操作a.x = 5时,因为x.length与变量a.x处于同一存储位置,赋值后数组x的长度变成了5。接下来,赋值a.y,并将变量a加入到数组x。所以变量a实际上加入到了数组x索引为5的位置。如果调试testArray函数执行,会发现在函数执行完毕之后,x[5].x = 6, x[5].y = 7。
这是为什么呢?明明代码中赋值写的是 a.x = 5,a.y = 7。这就是全局变量x和局部变量a形成了“纠缠”,首先是局部变量a修改导致全局变量x改变,然后是全局变量x修改导致了局部变量修改,最后把修改后的局部变量又存储到修改后的全局变量。这里即是,赋值操作a.x = 5时,把数组x的长度变成了5;
接下来x.push操作,实际上是先将该数组x的长度加1,此时a.x = 6; 最后再把a.x = 6, a.y=7加入到x[5]。所以,存入数据的x就是新数组的长度6。
pragma solidity 0.4.24;
contract test {
struct aa{
uint public num = 4;
function testArray() returns (uint){
x.push(a);
3 漏洞利用方式
在第2节中对合约 BelluminarBank存在的几个漏洞进行了分析,下面将说明如何利用这个漏洞提取合约的全部余额,这里在Remix在线编译环境中部署该合约,并演示其利用方式。
首先部署合约,在部署参数中设置secrete 为“0x01”,deposit_term为1000,msg.value为 31337 wei。
部署合约后,合约的全局变量如下图所示:
这样,合约目前的余额是 31337 wei,合约拥有者的地址为:0xca35b7d 0ade6068dfe2f44e8fa733c。
下面开始需要构造条件使得攻击者可以成功调用confiscate函数。
覆盖owner并构造整数溢出条件
要想转走合约余额,首先必须修改合约的owner。利用局部结构体 investment 修改合约owner,需满足条件:
account & head or account &= balances.length
deposit_term &= balances[balances.length – 1].deposit_term + 1 years
设置攻击者(0xC)的invest调用参数如下:
msg.value = 1 wei (因为在合约初始化时owner已经存入一笔金额,所以此时balances数组长度为1,为了不改变balances数组长度,这里依然将其设置为1 we i
depositsit_term = 2^256 - 1 years = 103936 (在步骤2中需要利用这个数值构造溢出,同时这个值可以使源码中 require 条件得到满足)
account = 1 (满足条件 account &= balances.length)
调用之后,新的存款记录数据将存放在balances数组索引为1的位置。此时的balances数组情况和全局storage变量情况如下图所示。
可以发现,owner已经修改为攻击者地址,同时head被传入的deposit_term覆盖为一个超大值。
而提取余额是从balances数组中head索引开始的存款记录开始计算数额的。显然,为了提取到合约owner的余额,即balances[0]账户的余额,head必须被覆盖为0。因此,需要进行第二次storage变量覆盖,修改head。
恢复head并绕过deposit_term限制
继续设置攻击者调用invest的参数:
msg.value = 2wei (同样保证balances的长度覆盖后不出现错误)
deposit_term = 0: 恢复head
account = 2 (满足条件 account &= balances.length 即可)
因为在步骤 1 中,已经将balances[1].deposit_term 设置为 2^256 -1 years,因此在第二次调用 invest 函数时,由于balances[balances.length - 1].deposit_term + 1 years”溢出为0满足了require条件,所以可以成功进行第二次覆盖。
这样即满足了调用confiscate函数的条件msg.sender == owner,通过读取storage很容易获得secrete,条件secret == _secret 也可以满足,同时还重新覆盖了head使之变为0 。
覆盖之后全局storage变量和balances数组如下图所示:
可以发现head已经修改为0了。
现在来看看第三个条件:
now &= balances[account].deposit_term + 1 years
account是传入的数据,目前合约中account数量为3。在前面的invest调用后, balances[2].deposit_term = 0。 显然条件 now &= balances[2].deposit_term + 1 years 成立,所以在恢复head数据的同时,也绕过了confiscate函数中对于存款期限的判定。接下来只要调用函数confiscate时,设置account 为 2,便可使时间判断条件满足,同时也能提取所有账户的余额。
增加合约余额
经过步骤1和步骤2,仿佛攻击者已经可以调用confiscate函数提取所有余额了,然而实际上是不行的。交易会发生回滚,这是为什么呢?
仔细分析前面的数据就会发现,步骤1中msg.value为 1 wei,但是最后balances数组中的balances[1].amount 却变成了 2 wei。这是因为变量覆盖过程中产生了“纠缠”副作用,由于msg.value覆盖balances数组的长度,balances更新前增加了数组长度,数组长度又改变了msg.value,最后导致存入的amount变成了新的数组长度,即2。
所以,每次调用invest函数进行变量覆盖,存款记录的账目金额都比调用者实际支付的msg.value大。下图是两次调用invest之后的balances数组情况。
从图中可以看出,存款记录中的账面值会比实际交易的msg.value多 1 wei。通过confiscate函数计算得到的所有账户总额为31342 wei,而实际的合约账户总余额为 31340 wei。
为了能够将合约中所有余额提取出来,需要增加合约的真实余额,使其同存款记录中的余额相等。然而,通过invest方式增加的余额都会被计入账面余额,那么怎么在不通过invest函数的情况下增加合约的真实余额呢?
答案是selfdestruct函数。
selfdestruct函数会将该合约的余额转到指定账户,然后从区块链中销毁该合约的代码和storage。该函数的官方文档说明[5]如下:
因此,可以构造一个合约,然后在合约中调用selfdestruct函数将合约的余额转给BelluminarBank合约。为此,构造如下合约:
contract donar{
function donar() public payable{
selfdestruct(contractAddr);
该合约创建后马上销毁,同时将自己的余额转给银行合约。
在 remix 中 编译该合约,同时将 contractAddr替换为银行合约地址。然后 在deploy该合约时,设置 msg.value 为2 wei。当合约创建又销毁之后,其余额(2wei)将转给银行账户,使银行合约的账面余额和实际余额一致,这样confiscate函数调用就能够正确执行。
Donar合约部署设置如下:
合约部署完之后,BelluminarBank 合约余额如下图:
步骤4:调用confiscate提取合约余额
经过上面的操作之后,设置confiscate函数的参数为[2,“0x01”]即可将合约的全部余额转走。
启明星辰积极防御实验室(ADLab)
ADLab成立于1999年,是中国安全行业最早成立的攻防技术研究实验室之一,微软MAPP计划核心成员。截止目前,ADLab通过CVE发布Windows、Linux、Unix等操作系统安全或软件漏洞近400个,持续保持国际网络安全领域一流水准。实验室研究方向涵盖操作系统与应用系统安全研究、移动智能终端安全研究、物联网智能设备安全研究、Web安全研究、工控系统安全研究、云安全研究。研究成果应用于产品核心技术研究、国家重点科技项目攻关、专业安全服务等。
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:}

我要回帖

更多关于 excel函数应用500例 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信