创宇区块链|传统安全与 IPFS 间的安全性研究

IP归属:香港

前言

通信技术让世界具备了更多的连接,我们每个人都在这样的连接中被影响和受益着。同时这种连接也产生了更多对于监视需求的便利。许多人的隐私或自由可能会在不经意间受到影响,而这也催生了对于隐私保护的需求。通常,由于中心化服务器的存在,我们很难实现完整的隐私保护,而分布式的存储等技术,则让其成为了可能。

无数的开发者加入了 Web3 的开发实现中,陆续构建一个又一个伟大的 Dapp,他们在普通用户与区块链底层技术中扮演着重要的中间人角色。与此同时,对于普通人接触的最多的 web-ui 与 IPFS, 它们之间的安全也值得被探索。

知道创宇区块链安全实验室将对此进行详细解读。

Web-interface 与 IPFS

1. Web-interface 是什么

在 Web3.0 中,分布式的公链技术设施提供了各种接口供给使用者调用,但这些接口无法直接被普通用户直接去使用。对用户来说,Web-interface 是用户和运行在 Web 服务器上的软件之间的桥梁。用户使用浏览器连接 Web-interfacce 后进行展示与交互,同时通过钱包进行身份识别。对底层区块链基础设施来说,Web-interface 是公链/智能合约的一层封装,将其包装成为友好的页面可直接可用的功能展示给用户。其结构功能类似如下的图片:

2. IPFS 是什么

星际文件系统(IPFS)是分布式存储和共享文件的网络传输协议,它将现有的成功系统分布式哈希表、版本控制系统 Git、BitTorrent、自认证文件系统与区块链相结合。正是这些系统的综合优势,给 IPFS 带来了以下显著特性:

1.永久的、去中心化保存和共享文件

2.点对点超媒体:P2P 保存各种各样类型的数据

3.版本化:可追溯文件修改历史

4.内容可寻址:通过文件内容生成独立哈希值来标识文件,而不是通过文件保存位置来标识

当用户将文件添加到 IPFS 时,该文件会被拆分为更小的块,经过加密哈希处理并赋予内容标识符 CID 作为唯一指纹;当其他节点查找该文件时,节点会询问对等节点谁存储了该文件 CID 引用的内容,当查看、下载这份文件时,他们将缓存一份副本——同时成为该内容的另一个提供者,直到他们的缓存被清除。

IPFS使用实例

网站https://ipfs.io提供一个带 UI 界面的客户端,安装运行后会启动 IPFS 的服务,显示当前的节点 ID、网关和 API 地址:

我们导入想上传的文件,上传文件成功后会生成该文件的 CID 信息,通过QmHash(CID)我们也能查找到指定的文件:

由于 IPFS 是分布式存储和共享文件的网络传输协议,因此上传成功的文件被拷贝到其他节点上后,即使我们本地节点主动删除,依然可以在 IPFS 网络查询到该文件:

IPFS中的传统安全问题

根据使用实例,我们知道 IPFS 允许上传任意类型的文件,由于允许 Web 访问下载文件的特性,导致攻击者可以像传统安全一样使用 HTML 或 SVG 文件实现钓鱼:

https://IPFS.io网关为例,上传一个 Metamask 钓鱼网站,由于存储在受信域名里,受害者访问该文件很可能攻击成功:

但由于 IPFS 只能通过 CID 查询文件,使得钓鱼攻击的利用面很窄,没办法定向的实施攻击。既然 CID 是发起定向攻击的关键,那我们回头研究下 CID。

IPLD 是构建 IPFS 的数据层,它定义了默克尔链接(Merkle-Links)、默克尔有向无环图(Merkle-DAG) 和默克尔路径(Merkle-Paths)三种数据类型,通过 IPLD 发送到 IPFS 的数据保存在链上,使用者会收到一个 CID 来访问该数据。

CID 是一个由 Version、Codec和Multihash 三部分组成的字符串,目前分成 V0和V1两个版本。V0 版采用 Base58 编码生成 CID,V1 版包含表明内容的编号种类 Codec、哈希算法 MhType 和哈希长度 MhLength 共同构成:

`CID::=<multibase type><cid-version><multicodec><multihash>`

我们以 go-cid 生成一组 CID 测试:

package main

import (
	"fmt"
	mc "github.com/multiformats/go-multicodec"
	mh "github.com/multiformats/go-multihash"
	cid "github.com/ipfs/go-cid"
)

const (
	File = "./go.sum"
)

func main() {
	pref := cid.Prefix{
		Version:  0,
		Codec:    mc.Raw,
		MhType:   mh.Base58,
		MhLength: -1,
	}
	c, err := pref.Sum([]byte("CIDTest"))
	if err != nil {...}
	fmt.Println("CID: ", c)
}

可以看到在生成 CID 的过程中,无法实现结果的预测和更换,我们再往上分析上传文件的部分。将文件上传到IPFS,通过块的方式保存到本地 blockstore 的过程位于/go-ipfs-master/core/commands/add.go:

type AddEvent struct {
	Name  string
	Hash  string `json:",omitempty"`
	Bytes int64  `json:",omitempty"`
	Size  string `json:",omitempty"`
}

const (
	quietOptionName       = "quiet"
	quieterOptionName     = "quieter"
	silentOptionName      = "silent"
	progressOptionName    = "progress"
	trickleOptionName     = "trickle"
	wrapOptionName        = "wrap-with-directory"
	onlyHashOptionName    = "only-hash"
	chunkerOptionName     = "chunker"
	pinOptionName         = "pin"
	rawLeavesOptionName   = "raw-leaves"
	noCopyOptionName      = "nocopy"
	fstoreCacheOptionName = "fscache"
	cidVersionOptionName  = "cid-version"
	hashOptionName        = "hash"
	inlineOptionName      = "inline"
	inlineLimitOptionName = "inline-limit"
)

把上传文件信息保存到 AddEvent 对象中,再通过 /go-ipfs-master/core/coreunix/add.go 里的 addALLAndPin 和 fileAdder.AddFile 方法遍历文件路径,读取文件内容,将数据送入块中:

func (adder *Adder) AddAllAndPin(ctx context.Context, file files.Node) (ipld.Node, error) {
	ctx, span := tracing.Span(ctx, "CoreUnix.Adder", "AddAllAndPin")
	defer span.End()

	if adder.Pin {		//knownsec 如果被锁定
		adder.unlocker = adder.gcLocker.PinLock(ctx)
	}
	defer func() {
		if adder.unlocker != nil {
			adder.unlocker.Unlock(ctx)
		}
	}()

	if err := adder.addFileNode(ctx, "", file, true); err != nil {
		return nil, err
	}

	mr, err := adder.mfsRoot()
	if err != nil {
		return nil, err
	}
	var root mfs.FSNode
	rootdir := mr.GetDirectory()		//knownsec 获取路径
	root = rootdir

	err = root.Flush()
	if err != nil {
		return nil, err
	}

	_, dir := file.(files.Directory)
	var name string
	if !dir {
		children, err := rootdir.ListNames(adder.ctx)		//knownsec 展示当前路径文件名
		if err != nil {
			return nil, err
		}

		if len(children) == 0 {
			return nil, fmt.Errorf("expected at least one child dir, got none")
		}

		name = children[0]
		root, err = rootdir.Child(name)
		if err != nil {
			return nil, err
		}
	}

	err = mr.Close()
	if err != nil {
		return nil, err
	}

	nd, err := root.GetNode()
	if err != nil {
		return nil, err
	}

	err = adder.outputDirs(name, root)
	if err != nil {
		return nil, err
	}

	if asyncDagService, ok := adder.dagService.(syncer); ok {
		err = asyncDagService.Sync()
		if err != nil {
			return nil, err
		}
	}

	if !adder.Pin {
		return nd, nil
	}
	return nd, adder.PinRoot(ctx, nd)
}

最后再利用 addFile 函数完成文件的上传:

func (adder *Adder) addFile(path string, file files.File) error {
	var reader io.Reader = file
	if adder.Progress {
		rdr := &progressReader{file: reader, path: path, out: adder.Out}		//knonwsec 按字节读取文件
		if fi, ok := file.(files.FileInfo); ok {
			reader = &progressReader2{rdr, fi}
		} else {
			reader = rdr
		}
	}

	dagnode, err := adder.add(reader)		//knownsec 添加上传文件
	if err != nil {
		return err
	}
	return adder.addNode(dagnode, path)
}

分析代码发现,IPFS 在打包文件上传返回 CID 的整个过程,都没实现劫持的可能,而成功上传的文件无法实现修改其内容,同样无法实现篡改:

后记

Web3 建立在区块链技术之上,无需中央机构即可维护。其允许用户在互联网上保护他们的数据,并允许网络平台的去中心化。而 IPFS 技术对他来说就如同一台电脑的硬盘,web-ui 就如同主机的显示器一样不可或缺,其间亦存在着复杂而多样的安全风险可能给予不法分子可乘之机,理解其风险并避免发生问题是每一位 Web3 从业人员的责任与义务。

本文来源:陀螺科技 文章作者:创宇区块链安全实验室
收藏
举报
创宇区块链安全实验室
累计发布内容34篇 累计总热度10万+

陀螺科技现已开放专栏入驻,详情请见入驻指南: https://www.tuoluo.cn/article/detail-27547.html

创宇区块链安全实验室专栏: https://www.tuoluo.cn/columns/author1876980/

本文网址: https://www.tuoluo.cn/article/detail-10098399.html

免责声明:
1、本文版权归原作者所有,仅代表作者本人观点,不代表陀螺科技观点或立场。
2、如发现文章、图片等侵权行为,侵权责任将由作者本人承担。

相关文章