133728e barerepo 1mo
1
package e2e
133728e barerepo 1mo
3
import (
133728e barerepo 1mo
4
"io"
133728e barerepo 1mo
5
"net/http"
133728e barerepo 1mo
6
"os/exec"
133728e barerepo 1mo
7
"strings"
133728e barerepo 1mo
8
"testing"
133728e barerepo 1mo
11
// Chapter 45.4's attacks, kept permanently, and these need a real push or request.
133728e barerepo 1mo
12
func TestSecurityList(t *testing.T) {
133728e barerepo 1mo
13
if _, err := exec.LookPath("git"); err != nil {
133728e barerepo 1mo
14
t.Skip("git is not installed")
133728e barerepo 1mo
16
in := newInstance(t)
133728e barerepo 1mo
17
john := in.account("john")
133728e barerepo 1mo
18
seed(t, in, john, "john", "johnbot")
133728e barerepo 1mo
20
t.Run("a branch that is really an argument", func(t *testing.T) {
133728e barerepo 1mo
21
c := clone(t, in, john, "/john/johnbot")
133728e barerepo 1mo
22
for _, ref := range []string{
133728e barerepo 1mo
23
"refs/heads/--upload-pack=/bin/sh",
133728e barerepo 1mo
24
"refs/heads/-x",
133728e barerepo 1mo
25
"refs/heads/..",
133728e barerepo 1mo
26
} {
133728e barerepo 1mo
27
out, err := try(t, c, "git", "push", in.url(john, "/john/johnbot"), "HEAD:"+ref)
133728e barerepo 1mo
28
if err == nil {
133728e barerepo 1mo
29
t.Errorf("%s was accepted:\n%s", ref, out)
133728e barerepo 1mo
32
})
133728e barerepo 1mo
34
t.Run("a repository named ../../etc", func(t *testing.T) {
133728e barerepo 1mo
35
c := clone(t, in, john, "/john/johnbot")
133728e barerepo 1mo
36
for _, path := range []string{"/john/..%2f..%2fetc", "/john/../../etc", "/../../etc/passwd"} {
133728e barerepo 1mo
37
out, err := try(t, c, "git", "push", in.url(john, path), "master")
133728e barerepo 1mo
38
if err == nil {
133728e barerepo 1mo
39
t.Errorf("a push to %s was accepted:\n%s", path, out)
133728e barerepo 1mo
42
})
133728e barerepo 1mo
44
t.Run("evil.html fetched raw", func(t *testing.T) {
133728e barerepo 1mo
45
// A file with a script in it, served from this origin, is the reader's session. 42.3.
133728e barerepo 1mo
46
c := clone(t, in, john, "/john/johnbot")
133728e barerepo 1mo
47
write(t, c, "evil.html", "<script>alert(document.cookie)</script>\n")
133728e barerepo 1mo
48
run(t, c, "git", "add", "-A")
133728e barerepo 1mo
49
run(t, c, "git", "commit", "-qm", "add evil.html")
133728e barerepo 1mo
50
run(t, c, "git", "push", "-q", in.url(john, "/john/johnbot"), "master")
133728e barerepo 1mo
52
resp, err := http.Get(in.http.URL + "/john/johnbot/raw/master/evil.html")
133728e barerepo 1mo
53
if err != nil {
133728e barerepo 1mo
54
t.Fatal(err)
133728e barerepo 1mo
56
defer resp.Body.Close()
133728e barerepo 1mo
57
body, _ := io.ReadAll(resp.Body)
133728e barerepo 1mo
58
if !strings.Contains(string(body), "alert(document.cookie)") {
133728e barerepo 1mo
59
t.Fatalf("the file did not come back at all: %s", body)
133728e barerepo 1mo
61
want := map[string]string{
133728e barerepo 1mo
62
"Content-Type": "text/plain; charset=utf-8",
133728e barerepo 1mo
63
"Content-Disposition": "attachment",
133728e barerepo 1mo
64
"X-Content-Type-Options": "nosniff",
133728e barerepo 1mo
65
"Content-Security-Policy": "default-src 'none'; sandbox",
133728e barerepo 1mo
67
for header, value := range want {
133728e barerepo 1mo
68
if got := resp.Header.Get(header); got != value {
133728e barerepo 1mo
69
t.Errorf("%s = %q, want %q: the browser would run this file", header, got, value)
133728e barerepo 1mo
72
})
133728e barerepo 1mo
74
t.Run("a private repository over git", func(t *testing.T) {
133728e barerepo 1mo
75
// No .barerepo/config in the content, or the file decides and the default never applies.
133728e barerepo 1mo
76
c := t.TempDir()
133728e barerepo 1mo
77
run(t, c, "git", "init", "-q", "-b", "master")
133728e barerepo 1mo
78
write(t, c, "secret.txt", "do not read\n")
133728e barerepo 1mo
79
run(t, c, "git", "add", "-A")
133728e barerepo 1mo
80
run(t, c, "git", "commit", "-qm", "private things")
133728e barerepo 1mo
81
run(t, c, "git", "push", "-q", in.url(john, "/john/hidden"), "master")
133728e barerepo 1mo
83
lisa := in.account("lisa")
133728e barerepo 1mo
84
if out, err := try(t, "", "git", "ls-remote", in.url(lisa, "/john/hidden")); err == nil {
133728e barerepo 1mo
85
t.Errorf("another account listed a private repository:\n%s", out)
133728e barerepo 1mo
87
if out, err := try(t, "", "git", "ls-remote", in.http.URL+"/john/hidden"); err == nil {
133728e barerepo 1mo
88
t.Errorf("an anonymous client listed a private repository:\n%s", out)
133728e barerepo 1mo
90
})
133728e barerepo 1mo
92
t.Run("a config that says public makes it public", func(t *testing.T) {
133728e barerepo 1mo
93
// A pushed visibility = "public" is chapter 14 working, not the default failing.
133728e barerepo 1mo
94
c := clone(t, in, john, "/john/johnbot")
133728e barerepo 1mo
95
run(t, c, "git", "push", "-q", in.url(john, "/john/open"), "master")
133728e barerepo 1mo
96
if _, err := try(t, "", "git", "ls-remote", in.http.URL+"/john/open"); err != nil {
133728e barerepo 1mo
97
t.Error("a repository whose config says public was not readable")
133728e barerepo 1mo
99
})
133728e barerepo 1mo
101
t.Run("a token is not a session", func(t *testing.T) {
133728e barerepo 1mo
102
// A git token must not open the interface, or one in a build script is a login.
133728e barerepo 1mo
103
req, _ := http.NewRequest(http.MethodGet, in.http.URL+"/keys", nil)
133728e barerepo 1mo
104
req.AddCookie(&http.Cookie{Name: "barerepo_session", Value: john})
133728e barerepo 1mo
105
resp, err := http.DefaultTransport.RoundTrip(req)
133728e barerepo 1mo
106
if err != nil {
133728e barerepo 1mo
107
t.Fatal(err)
133728e barerepo 1mo
108
}
133728e barerepo 1mo
109
defer resp.Body.Close()
133728e barerepo 1mo
110
if resp.StatusCode == http.StatusOK {
133728e barerepo 1mo
111
t.Error("a git token worked as a session cookie")
133728e barerepo 1mo
112
}
133728e barerepo 1mo
113
})