CVE-2018-12976: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: In Go Doc Dot Org (gddo) through 2018-06-27, an attacker could use specially crafted <go-import> tags in packages being fetched by gddo to cause a directory traversal and remote code execution.
CWE description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
CVSS v3 base score: 9.8
Published: 2018-07-05T18:29Z
Repository: https://github.com/golang/gddo
Language: Go
Files changed: gosrc/gosrc.go, gosrc/gosrc_test.go
Commit date: 2018-07-03 17:44:36 +0000
Commit message: gosrc: validate repo from meta

Fixes CVE-2018-12976.

Change-Id: I6b87ab692915d46ba4f668ab848473de9b054c8a
Reviewed-on: https://go-review.googlesource.com/121358
Reviewed-by: Filippo Valsorda <filippo@golang.org>

Diff: diff --git a/gosrc/gosrc.go b/gosrc/gosrc.go
index 9a9e44b..b5173d3 100644
--- a/gosrc/gosrc.go
+++ b/gosrc/gosrc.go
@@ -376,10 +376,13 @@ func getDynamic(ctx context.Context, client *http.Client, importPath, etag strin
 		return nil, NotFoundError{Message: "bad repo URL: " + im.repo}
 	}
 	proto := im.repo[:i]
 	clonePath := im.repo[i+len("://"):]
 	repo := strings.TrimSuffix(clonePath, "."+im.vcs)
+	if !IsValidRemotePath(repo) {
+		return nil, fmt.Errorf("bad path from meta: %s", repo)
+	}
 	dirName := importPath[len(im.projectRoot):]
 
 	resolvedPath := repo + dirName
 	dir, err := getStatic(ctx, client, resolvedPath, etag)
 	if err == errNoMatch {
diff --git a/gosrc/gosrc_test.go b/gosrc/gosrc_test.go
index be4acde..d90e798 100644
--- a/gosrc/gosrc_test.go
+++ b/gosrc/gosrc_test.go
@@ -92,10 +92,13 @@ var testWeb = map[string]string{
 		`<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>` +
 		`<meta name="go-import" content="myitcv.io/blah2 git https://github.com/myitcv/x">` +
 		`<meta name="go-import" content="myitcv.io/blah2 mod https://raw.githubusercontent.com/myitcv/pubx/master">` +
 		`<meta name="go-source" content="myitcv.io https://github.com/myitcv/x/wiki https://github.com/myitcv/x/tree/master{/dir} https://github.com/myitcv/x/blob/master{/dir}/{file}#L{line}">` +
 		`</head>`,
+
+	// The repo element of go-import includes "../"
+	"http://my.host/pkg": `<head> <meta name="go-import" content="my.host/pkg git http://vcs.net/myhost/../../tmp/pkg.git"></head>`,
 }
 
 var getDynamicTests = []struct {
 	importPath string
 	dir        *Directory
@@ -219,10 +222,11 @@ var getDynamicTests = []struct {
 		ProjectURL:   "http://myitcv.io/blah2",
 		ResolvedPath: "github.com/myitcv/x",
 		VCS:          "git",
 		Files:        []*File{{Name: "main.go", BrowseURL: "https://github.com/myitcv/x/blob/master/main.go"}},
 	}},
+	{"my.host/pkg", nil},
 }
 
 type testTransport map[string]string
 
 func (t testTransport) RoundTrip(req *http.Request) (*http.Response, error) {
@@ -291,17 +295,17 @@ func TestGetDynamic(t *testing.T) {
 	for _, tt := range getDynamicTests {
 		dir, err := getDynamic(context.Background(), client, tt.importPath, "")
 
 		if tt.dir == nil {
 			if err == nil {
-				t.Errorf("getDynamic(client, %q, etag) did not return expected error", tt.importPath)
+				t.Errorf("getDynamic(ctx, client, %q, etag) did not return expected error", tt.importPath)
 			}
 			continue
 		}
 
 		if err != nil {
-			t.Errorf("getDynamic(client, %q, etag) return unexpected error: %v", tt.importPath, err)
+			t.Errorf("getDynamic(ctx, client, %q, etag) return unexpected error: %v", tt.importPath, err)
 			continue
 		}
 
 		if !cmp.Equal(dir, tt.dir) {
 			t.Errorf("getDynamic(client, %q, etag) =\n     %+v,\nwant %+v", tt.importPath, dir, tt.dir)

Vulnerable code: 				t.Errorf("getDynamic(client, %q, etag) did not return expected error", tt.importPath)
			t.Errorf("getDynamic(client, %q, etag) return unexpected error: %v", tt.importPath, err)
Fixed code: 	if !IsValidRemotePath(repo) {
		return nil, fmt.Errorf("bad path from meta: %s", repo)
	}

	// The repo element of go-import includes "../"
	"http://my.host/pkg": `<head> <meta name="go-import" content="my.host/pkg git http://vcs.net/myhost/../../tmp/pkg.git"></head>`,
	{"my.host/pkg", nil},
				t.Errorf("getDynamic(ctx, client, %q, etag) did not return expected error", tt.importPath)
			t.Errorf("getDynamic(ctx, client, %q, etag) return unexpected error: %v", tt.importPath, err)