主页 > imtoken最新下载 > go-ethereum security audit 以太坊链审计报告

go-ethereum security audit 以太坊链审计报告

imtoken最新下载 2024-01-26 05:09:37

1.2.4. Javascript 引擎和 API

伪随机数生成器的弱随机种子

1.2.5. EVM 实现

滥用 intPool 导致廉价的内存消耗

开采区块中的弱负值保护

1.2.6. 各种各样的

挖掘代码中的竞争条件

许多第三方依赖

1.3. 结果详情 1.3.1。 P2P和网络

TrueSec审计了p2p和网络部分的代码,主要关注:

Secure channel implementation - 握手和共享秘密的实现

安全通道属性 - 机密性和完整性

消息序列化

节点发现

针对 DOS 的保护:超时和消息大小限制

TrueSec 还通过 go-fuzz 对 RLP 解码进行了模糊处理,没有发现节点崩溃。

1.3.1.1. 已知的问题

虽然在加密握手中更好地实现了共享秘密,但由于对称加密算法中的二次填充缺陷,通道缺乏机密性。 这是一个已知的问题。 (有关详细信息,请参阅 []() 和 []())。 由于通道现在只传输公链数据,所以这个问题暂时不需要解决。

另一个持续存在的问题是在安全通道级别缺乏重放保护(以太坊开发讨论中提到了默认的基于时间的重放保护机制)。 TrueSec 提议下一版本的协议通过控制消息的数量来实现重放保护。

1.3.1.2。 内存分配太大

在 rlpx.go 中,TrueSec 发现了两个用户可控的超大内存分配。 TrueSec 没有发现可利用的 DOS 场景,但建议正确验证它们。

读取协议消息时,可以分配16.8MB内存:

func (rw *rlpxFrameRW) ReadMsg() (msg Msg, err error) {    ...    fsize := readInt24(headbuf)    // ignore protocol type for now    // read the frame content    var rsize = fsize // frame size rounded up to 16 byte boundary    if padding := fsize % 16; padding > 0 {    rsize += 16 - padding    }    // TRUESEC: user-controlled allocation of 16.8MB:    framebuf := make([]byte, rsize)    ...}

由于以太坊协议中将最大消息大小定义为 10MB,因此 TrueSec 推荐的内存分配也定义为相同大小。

在加密握手过程中,可以分配65KB内存给握手信息。

func readHandshakeMsg(msg plainDecoder, plainSize int,                      prv *ecdsa.PrivateKey, r io.Reader) ([]byte, error) {    ...    // Could be EIP-8 format, try that.    prefix := buf[:2]    size := binary.BigEndian.Uint16(prefix)    if size < uint16(plainSize) {        return buf, fmt.Errorf("size underflow, need at least ...")    }    // TRUESEC: user-controlled allocation of 65KB:    buf = append(buf, make([]byte, size-uint16(plainSize)+2)...)    ...}

除非握手消息确实包含 65KB 的数据,否则 TrueSec 建议限制握手消息的大小。

1.3.2. 事务和块处理

TrueSec 对交易、区块下载和区块处理进行了代码审计,重点关注:

内存分配、goroutine 泄漏和 IO 操作导致的拒绝服务

同步问题

1.3.2.1。 除以零风险

在 Go 中,被零除会导致恐慌。 在downloader.go的qosReduceConfidence方法中,是否发生除零取决于调用者是否正确调用:

func (d *Downloader) qosReduceConfidence() {    peers := uint64(d.peers.Len())    ...    // TRUESEC: no zero-check of peers here    conf := atomic.LoadUint64(&d.rttConfidence) * (peers - 1) / peers    ...}

TrueSec 没有发现可导致节点崩溃的漏洞,但仅依靠调用者来确保 d.peers.Len() 不为零是不安全的。 TrueSec 建议在执行除法之前应检查所有非常数股息。

1.3.2.2。 代码复杂度

TrueSec 发现交易和区块处理代码部分比其他部分代码更复杂,更难阅读和审计。 这部分方法比较大,在fetcher.go、downloader.go和blockchain.go中有200多行代码。 同步的实现有时会结合互斥锁和通道消息。 例如,结构体Downloader的定义需要60行代码,包括3个mutexes和11个channel。

难以阅读和理解的代码是安全问题的温床。 特别是,eth 包中有一些代码量很大的方法、结构、接口以及扩展的互斥锁和通道。 TrueSec 建议花一些时间重构和简化代码,以防止将来出现安全问题。

1.3.3. IPC 和 RPC 接口

TrueSec 审核 IPC 和 RPC(HTTP 和 Websocket)接口,重点关注潜在的访问控制问题,从公共 API 权限升级到私有 API(管理、调试等)。

1.3.3.1. CORS:允许默认 HTTP RPC 中的所有域

可以使用 geth 的 --rpc 参数启用 HTTP RPC 接口。 这将启动一个 Web 服务器,监听端口 8545 上的 HTTP 请求,任何人都可以访问它。 由于可能暴露端口(例如连接到不受信任的网络),默认情况下只有公共 API 允许 HTTP RPC 接口。

同源策略和默认跨源资源共享 (CORS) 配置限制 Web 浏览器访问并限制通过 XSS 攻击 RPC API 的可能性。 允许的来源可以通过--rpccorsdomain "domain"配置,也可以配置多个域名,用逗号隔开--rpccorsdomain "domain1, domain2", 或者--rpccorsdomain "*",这样所有的域都可以通过标准网络浏览器访问。 如果未配置,则不会设置 CORS 标头 - 并且浏览器将不允许跨域请求:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8545/. (Reason: CORS header 'Access-Control-Allow-Origin missing').

由于缺少 CORS 标头,Firefox 禁止跨域请求。

然而,在 commit 5e29f4b(自 2017 年 4 月 12 日起)中 - 可以绕过同源策略并且可以从 Web 浏览器访问 RPC。

HTTP RPC 的 CORS 配置已更改为处理允许来源的字符数组 - 而不是作为单引号分隔的字符串在内部传输。

在此之前,在实例化 cors 中间件之前,以逗号分隔的字符串被拆分成一个数组(参见清单 1)。 使用默认值(如果用户未明确配置任何设置,例如使用 --rpccorsdomain )空字符串,这会导致字符数组包含空字符串。

在提交 5e29f4b 之后,默认值为一个空数组,它被传递给位于 newCorsHandler 的中间件 cors(参见清单 2)。

然后 cors 中间件检查允许的 origins 数组的长度(参见清单 3)。 如果长度为 0,在本例中表示空数组,则 cors 中间件将成为默认值并允许所有域。

可以通过运行 geth -rpc 而不指定任何允许的来源来重现此问题,并在提交 5e29f4b 之前和之后使用 OPTION 请求检查 CORS 标头。 第二个输出的 Access-Control-Allow-Origin 值得注意。

请注意,即使在更改之前也是如此。 如果不是因为字符串拆分,则输入值不会在 cors(包含空字符串的数组)中解释为空。

此问题可以通过以下 JavaScript 代码加以利用,从任何域执行(甚至是本地文件系统以太坊挖矿d池,即无效或空源)

var xhr = new XMLHttpRequest();xhr.open("POST", "http://localhost:8545", true);xhr.setRequestHeader("Content-Type", "application/json");xhr.onreadystatechange = function() {    if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {    console.log("Modules: " + xhr.responseText);    }}xhr.send('{"jsonrpc":"2.0","method":"rpc_modules","params":[],"id":67}')

TrueSec 建议明确限制 CORS 的默认配置(例如将允许的来源设置为本地主机,或者根本不设置 CORS 标头),而不是依赖外界选择正常(安全)的默认设置

165 func newCorsHandler(srv *Server, corsString string) http.Handler {166     var allowedOrigins []string167     for _, domain := range strings.Split(corsString, ",") {168         allowedOrigins = append(allowedOrigins, strings.TrimSpace(domain))169     }170     c := cors.New(cors.Options{171         AllowedOrigins: allowedOrigins,172         AllowedMethods: []string{"POST", "GET"},173         MaxAge: 600,174         AllowedHeaders: []string{"*"},175     })176     return c.Handler(srv)177 }

清单 1:rpc/http.go,在提交 5e29f4be935ff227bbf07a0c6e80e8809f5e0202 之前

164 func newCorsHandler(srv *Server, allowedOrigins []string) http.Handler {165     c := cors.New(cors.Options{166         AllowedOrigins: allowedOrigins,167         AllowedMethods: []string{"POST", "GET"},168         MaxAge: 600,169         AllowedHeaders: []string{"*"},170     })171     return c.Handler(srv)172 }

清单 2:rpc/http.go,在提交 5e29f4be935ff227bbf07a0c6e80e8809f5e0202 之后

113     // Allowed Origins114     if len(options.AllowedOrigins) == 0 {115     // Default is all origins116     c.allowedOriginsAll = true117     }

清单 3:vendor/github.com/rs/cors/cors.go

$ curl -i -X OPTIONS    -H "Access-Control-Request-Method: POST"    -H "Access-Control-Request-Headers: content-type"    -H "Origin: foobar" http://localhost:8545HTTP/1.1 200 OKVary: OriginVary: Access-Control-Request-MethodVary: Access-Control-Request-HeadersDate: Tue, 25 Apr 2017 08:49:10 GMTContent-Length: 0Content-Type: text/plain; charset=utf-8

清单 4:提交 5e29f4b 之前的 CORS 标头

$ curl -i -X OPTIONS    -H "Access-Control-Request-Method: POST"    -H "Access-Control-Request-Headers: content-type"    -H "Origin: foobar" http://localhost:8545HTTP/1.1 200 OKAccess-Control-Allow-Headers: Content-TypeAccess-Control-Allow-Methods: POSTAccess-Control-Allow-Origin: foobarAccess-Control-Max-Age: 600Vary: OriginVary: Access-Control-Request-MethodVary: Access-Control-Request-HeadersDate: Tue, 25 Apr 2017 08:47:24 GMTContent-Length: 0Content-Type: text/plain; charset=utf-8

清单 5:提交 5e29f4b 后的 CORS 标头

1.3.4. JavaScript 引擎和 API

JavaScript引擎otto是Go Ethereum中的CLI脚本接口,IPC/RPC接口的终端交互解释器,私有调试API的一部分。 考虑到它的代码有限,审计优先级比较低。

1.3.4.1。 用于伪随机数生成的弱随机数种子

在jsre中初始化伪随机数生成器时,如果crypto/rand(crypto/rand返回一个密码安全的伪随机数)方法失败,随机数种子将取决于当时的UNIX时间。 在清单 6 中,这个弱随机数种子将用于初始化 math/rand 实例。

此 PRNG 未用于任何敏感信息,显然不应该用作加密安全 RNG,但由于用户可以通过命令行运行脚本来使用 PRNG,使其失败而不是生成弱随机数种子显然更安全。 从 crypto/rand 获取错误意味着其他地方也可能存在问题。 即使使用安全的随机数种子,也应该在文档中指出 PRNG 不是加密安全的。

84 // randomSource returns a pseudo random value generator.85 func randomSource() *rand.Rand {86     bytes := make([]byte, 8)87     seed := time.Now().UnixNano() // 不是完全随机88     if _, err := crand.Read(bytes); err == nil {89         seed = int64(binary.LittleEndian.Uint64(bytes))90     }9192     src := rand.NewSource(seed)93     return rand.New(src)94 }

清单 6:internal/jsre/jsre.go

1.3.5. 以太坊虚拟机(EVM)的实现

TrueSec审计了以太坊虚拟机(EVM)部分的代码,重点关注滥用内存分配和IO操作导致的拒绝服务。 EVM解释器(runtime/fuzz.go)有一个go-fuzz入口点,使用成功。 TrueSec 确认了其功能,但在模糊测试过程中未发现有影响的漏洞。

1.3.5.1。 滥用intPool导致的廉价内存消耗

出于性能原因,在EVM执行过程中,使用大整数会进入整数池intPool(intpool.go)。 由于对整数池大小没有限制,使用特定的操作码组合将导致意想不到的廉价内存使用。

0 JUMPDEST      // 1 gas1 COINBASE      // 2 gas2 ORIGIN        // 2 gas3 EQ            // 3 gas, puts 20 + 20 bytes on the intpool4 JUMP          // 8 gas, puts 4-8 bytes on the intpool

例如,合约代码会消耗3.33e9单位的gas(当时约合3300美元),分配10G的内存给intPool。 在以太坊虚拟机中分配 10GB 内存的预期 gas 成本为 1.95e14(约 195,000,000 美元)

当intPool产生内存不足恐慌时,会引起拒绝服务攻击。 但是共识算法限制了gas limit,可以防止拒绝服务攻击的发生。 但是,考虑到攻击者可能会找到更有效的方式来填充 intPool,或者 gaslimit 目标增长过快以太坊挖矿d池,TrueSec 仍然建议限制 intPool 的大小。

1.3.5.2。 开采区块中易受攻击的负值保护

以太币在账户之间的转账是通过core/evm.go中的Transfer方法完成的。

func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {    db.SubBalance(sender, amount)    db.AddBalance(recipient, amount)}

输入金额是指向有符号类型的指针,可能具有负参考值。 负数会将 ETH 从接收方转移到转账方,允许转账方从接收方窃取 ETH。

当收到一个解包的交易时,会验证交易的价值是否为正。 如tx_pool.go、validateTx():

if tx.Value().Sign() < 0 {    return ErrNegativeValue}

但是在块处理过程中没有这种显式验证; 具有负值的交易只会被 p2p 序列化格式(RLP)隐式阻止,它不能解码负值。 假设一个邪恶的矿工为了非法获得以太坊而发布带有负交易的区块,依靠特定的序列化格式来保护似乎有点脆弱。 TrueSec 还建议在块处理期间明确检查交易值。 或者使用无符号类型强制指定事务的值为正。

1.3.6. 各种各样的

1.3.6.1。 挖矿代码中的条件竞争

TrueSec使用“-race”来构建flags,利用Go语言内置的条件竞争检测特性来寻找条件竞争。 在 ethash/ethash.go 中发现了与挖矿时使用的 ethash 数据集的时间戳相关的竞争条件。

func (ethash *Ethash) dataset(block uint64) []uint32 {    epoch := block / epochLength    // If we have a PoW for that epoch, use that    ethash.lock.Lock()    ...    current.used = time.Now() // TRUESEC: race    ethash.lock.Unlock()    // Wait for generation finish, bump the timestamp and finalize the cache    current.generate(ethash.dagdir, ethash.dagsondisk, ethash.tester)    current.lock.Lock()    current.used = time.Now()    current.lock.Unlock()...}

要消除竞争条件,可以使用 current.lock 互斥锁来保护第一个 current.used 设置。 TrueSec 没有研究条件竞争是否会影响节点的挖矿。

1.3.6.2。 第三方依赖太多

Go Ethereum依赖71个第三方包(由govendor list +vend列出)

由于每个依赖项都可能引入新的攻击向量,并且需要花费时间和精力来监控安全漏洞,因此 TrueSec 始终建议将第三方包的数量保持在最低限度。

71 个依赖项对于任何项目来说都是很多的。 TrueSec 建议以太坊开发人员调查是否所有依赖项实际上都是必需的,或者其中一些是否可以用代码替换。