一行代码蒸发了¥6,447,277,680 人民币!
现在进入你还是先行者,最后观望者进场才是韭菜。
美图董事长蔡文胜曾在三点钟群,高调的说出了这句话,随即被大众疯传。
在他发表完言论没多久,2月美链(BEC)上交易所会暴涨4000%,后又暴跌。尽管他多次否认,聪明的网友早已扒出,他与BEC千丝万缕的关系。
庄家坐庄操控币价,美图的股价随之暴涨,蔡文胜顺利完成了他的韭菜收割大计。
但在币圈,割人者,人恒割之。
随着BEC智能合约的漏洞的爆出,被黑客利用,瞬间套现抛售大额BEC,6亿在瞬间归零。
而这一切,竟然是因为一个简单至极的程序Bug。
背景
今天有人在群里说,Beauty Chain 美蜜 代码里面有bug,已经有人利用该bug获得了 57,896,044,618,658,100,000,000,000,000,000,000,000,000,000,000,000,000,000,000.792003956564819968 个 BEC
那笔操作记录是 0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f
下面我来带大家看看,黑客是如何实现的!
我们可以看到执行的方法是 batchTransfer
那这个方法是干嘛的呢?(给指定的几个地址,发送相同数量的代币)
整体逻辑是
你传几个地址给我(_receivers),然后再传给我你要给每个人多少代币(_value)
然后你要发送的总金额 = 发送的人数* 发送的金额
然后 要求你当前的余额大于 发送的总金额
然后扣掉你发送的总金额
然后 给_receivers 里面的每个人发送 指定的金额(_value)
从逻辑上看,这边是没有任何问题的,你想给别人发送代币,那么你本身的余额一定要大于发送的总金额的!
但是这段代码却犯了一个很傻的错!
代码解释
这个方法会传入两个参数
- _receivers
- _value
_receivers 的值是个列表,里面有两个地址
0x0e823ffe018727585eaf5bc769fa80472f76c3d7
0xb4d30cac5124b46c2df0cf3e3e1be05f42119033
_value 的值是 8000000000000000000000000000000000000000000000000000000000000000
我们再查看代码(如下图)
我们一行一行的来解释
uint cnt = _receivers.length;
是获取 _receivers 里面有几个地址,我们从上面可以看到 参数里面只有两个地址,所以 cnt=2,也就是 给两个地址发送代币
uint256 amount = uint256(cnt) * _value;
uint256
首先uint256(cnt)
是把cnt 转成了 uint256类型
那么,什么是uint256类型?或者说uint256类型的取值范围是多少…
uintx 类型的取值范围是 0 到 2的x次方 -1
也就是 假如是 uint8的话
则 uint8的取值范围是 0 到 2的8次方 -1
也就是 0 到255
那么uint256 的取值范围是
0 - 2的256次方-1 也就是 0 到115792089237316195423570985008687907853269984665640564039457584007913129639935
python 算 2的256次方是多少
那么假如说 设置的值超过了 取值范围怎么办?这种情况称为溢出
举个例子来说明
因为uint256的取值太大了,所以用uint8来 举例。。。
从上面我们已经知道了 uint8 最小是0,最大是255
那么当我 255 + 1 的时候,结果是啥呢?结果会变成0
那么当我 255 + 2 的时候,结果是啥呢?结果会变成1
那么当我 0 - 1 的时候,结果是啥呢?结果会变成255
那么当我 0 - 2 的时候,结果是啥呢?结果会变成255
那么 我们回到上面的代码中,
amount = uint256(cnt) * _value
则 amount = 2* _value
但是此时 _value 是16进制的,我们把他转成 10进制
(python 16进制转10进制)
可以看到 _value = 57896044618658097711785492504343953926634992332820282019728792003956564819968
那么amount = _value*2 = 115792089237316195423570985008687907853269984665640564039457584007913129639936
可以在查看上面看到 uint256取值范围最大为 115792089237316195423570985008687907853269984665640564039457584007913129639935
此时,amout已经超过了最大值,溢出 则amount = 0
下一行代码 require(cnt > 0 && cnt <= 20);
require 语句是表示该语句一定要是正确的,也就是 cnt 必须大于0 且 小于等于20
我们的cnt等于2,通过!
require(_value > 0 && balances[msg.sender] >= amount);
这句要求 _value 大于0,我们的_value是大于0 的 且,当前用户拥有的代币余额大于等于 amount,因为amount等于0,所以 就算你一个代币没有,也是满足的!
balances[msg.sender] = balances[msg.sender].sub(amount);
这句是当前用户的余额 - amount
当前amount 是0,所以当前用户代币的余额没有变动
for (uint i = 0; i < cnt; i++) {
balances[_receivers[i]] = balances[_receivers[i]].add(_value);
Transfer(msg.sender, _receivers[i], _value);
}
这句是遍历 _receivers中的地址, 对每个地址做以下操作
balances[_receivers[i]] = balances[_receivers[i]].add(_value);
_receivers中的地址 的余额 = 原本余额+value
所以 _receivers 中地址的余额 则加了57896044618658097711785492504343953926634992332820282019728792003956564819968 个代币!!!
Transfer(msg.sender, _receivers[i], _value); }
这句则只是把赠送代币的记录存下来!!!
总结
就一个简单的溢出漏洞,导致BEC代币的市值接近归0
那么,开发者有没有考虑到溢出问题呢?
其实他考虑了,
可以看如上截图
除了amount的计算外, 其他的给用户转钱 都用了safeMath 的方法(sub,add)
那么 为啥就偏偏这一句没有用safeMath的方法呢。。。
这就要用写代码的人了。。。
啥是safeMath
safeMath 是为了计算安全 而写的一个library
我们看看他干了啥?为啥能保证计算安全.
function mul(uint256 a, uint256 b) internal constant returns (uint256) {
uint256 c = a * b;
assert(a == 0 || c / a == b);
return c;
}
如上面的乘法. 他在计算后,用assert 验证了下结果是否正确!
如果在上面计算 amount的时候,用了 mul的话, 则 c / a == b
也就是 验证 amount / cnt == _value
这句会执行报错的,因为 0 / cnt 不等于 _value
所以程序会报错!
也就不会发生溢出了…
那么 还有一个小问题,这里的assert
好 require
好像是干的同一件事
都是为了验证 某条语句是否正确!
那么他俩有啥区别呢?
用了assert的话,则程序的gas limit 会消耗完毕
而require的话,则只是消耗掉当前执行的gas
总结
那么 我们如何避免这种问题呢?
我个人看法是
- 只要涉及到计算,一定要用safeMath
- 代码一定要测试!
- 代码一定要review!
- 必要时,要请专门做代码审计的公司来 测试代码
这件事后需要如何处理呢?
目前,该方法已经暂停了(还好可以暂停)所以看过文章的朋友 不要去测试了…
不过已经发生了的事情咋办呢?
我的想法是,快照在漏洞之前,所有用户的余额情况
然后发行新的token,给之前的用户 发送等额的代币…
h
2. 代码一定要测试!
3. 代码一定要review!
4. 必要时,要请专门做代码审计的公司来 测试代码
这件事后需要如何处理呢?
目前,该方法已经暂停了(还好可以暂停)所以看过文章的朋友 不要去测试了…
不过已经发生了的事情咋办呢?
我的想法是,快照在漏洞之前,所有用户的余额情况
然后发行新的token,给之前的用户 发送等额的代币…
接下来我将给各位同学划分一张学习计划表!
学习计划
那么问题又来了,作为萌新小白,我应该先学什么,再学什么?
既然你都问的这么直白了,我就告诉你,零基础应该从什么开始学起:
阶段一:初级网络安全工程师
接下来我将给大家安排一个为期1个月的网络安全初级计划,当你学完后,你基本可以从事一份网络安全相关的工作,比如渗透测试、Web渗透、安全服务、安全分析等岗位;其中,如果你等保模块学的好,还可以从事等保工程师。
综合薪资区间6k~15k
1、网络安全理论知识(2天)
①了解行业相关背景,前景,确定发展方向。
②学习网络安全相关法律法规。
③网络安全运营的概念。
④等保简介、等保规定、流程和规范。(非常重要)
2、渗透测试基础(1周)
①渗透测试的流程、分类、标准
②信息收集技术:主动/被动信息搜集、Nmap工具、Google Hacking
③漏洞扫描、漏洞利用、原理,利用方法、工具(MSF)、绕过IDS和反病毒侦察
④主机攻防演练:MS17-010、MS08-067、MS10-046、MS12-20等
3、操作系统基础(1周)
①Windows系统常见功能和命令
②Kali Linux系统常见功能和命令
③操作系统安全(系统入侵排查/系统加固基础)
4、计算机网络基础(1周)
①计算机网络基础、协议和架构
②网络通信原理、OSI模型、数据转发流程
③常见协议解析(HTTP、TCP/IP、ARP等)
④网络攻击技术与网络安全防御技术
⑤Web漏洞原理与防御:主动/被动攻击、DDOS攻击、CVE漏洞复现
5、数据库基础操作(2天)
①数据库基础
②SQL语言基础
③数据库安全加固
6、Web渗透(1周)
①HTML、CSS和JavaScript简介
②OWASP Top10
③Web漏洞扫描工具
④Web渗透工具:Nmap、BurpSuite、SQLMap、其他(菜刀、漏扫等)
那么,到此为止,已经耗时1个月左右。你已经成功成为了一名“脚本小子”。那么你还想接着往下探索吗?
阶段二:中级or高级网络安全工程师(看自己能力)
综合薪资区间15k~30k
7、脚本编程学习(4周)
在网络安全领域。是否具备编程能力是“脚本小子”和真正网络安全工程师的本质区别。在实际的渗透测试过程中,面对复杂多变的网络环境,当常用工具不能满足实际需求的时候,往往需要对现有工具进行扩展,或者编写符合我们要求的工具、自动化脚本,这个时候就需要具备一定的编程能力。在分秒必争的CTF竞赛中,想要高效地使用自制的脚本工具来实现各种目的,更是需要拥有编程能力。
零基础入门的同学,我建议选择脚本语言Python/PHP/Go/Java中的一种,对常用库进行编程学习
搭建开发环境和选择IDE,PHP环境推荐Wamp和XAMPP,IDE强烈推荐Sublime;
Python编程学习,学习内容包含:语法、正则、文件、 网络、多线程等常用库,推荐《Python核心编程》,没必要看完
用Python编写漏洞的exp,然后写一个简单的网络爬虫
PHP基本语法学习并书写一个简单的博客系统
熟悉MVC架构,并试着学习一个PHP框架或者Python框架 (可选)
了解Bootstrap的布局或者CSS。
阶段三:顶级网络安全工程师
如果你对网络安全入门感兴趣,那么你需要的话可以点击这里*👉*网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!
学习资料分享
当然,只给予计划不给予学习资料的行为无异于耍流氓,
这里给大家整理了一份【282G】的网络安全工程师从入门到精通的学习资料包,可点击下方二维码链接领取哦。