diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..60fa6f9 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/foomo/gofoomo + +go 1.14 + +require ( + github.com/abbot/go-http-auth v0.4.0 + github.com/bgentry/speakeasy v0.1.0 + github.com/foomo/htpasswd v0.0.0-20200116085101-e3a90e78da9c + github.com/foomo/tlsconfig v0.0.0-20180418120404-b67861b076c9 + gopkg.in/yaml.v2 v2.3.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..dc98f9e --- /dev/null +++ b/go.sum @@ -0,0 +1,22 @@ +github.com/GehirnInc/crypt v0.0.0-20190301055215-6c0105aabd46 h1:rs0kDBt2zF4/CM9rO5/iH+U22jnTygPlqWgX55Ufcxg= +github.com/GehirnInc/crypt v0.0.0-20190301055215-6c0105aabd46/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo= +github.com/abbot/go-http-auth v0.4.0 h1:QjmvZ5gSC7jm3Zg54DqWE/T5m1t2AfDu6QlXJT0EVT0= +github.com/abbot/go-http-auth v0.4.0/go.mod h1:Cz6ARTIzApMJDzh5bRMSUou6UMSp0IEXg9km/ci7TJM= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/foomo/htpasswd v0.0.0-20200116085101-e3a90e78da9c h1:DBGU7zCwrrPPDsD6+gqKG8UfMxenWg9BOJE/Nmfph+4= +github.com/foomo/htpasswd v0.0.0-20200116085101-e3a90e78da9c/go.mod h1:SHawtolbB0ZOFoRWgDwakX5WpwuIWAK88bUXVZqK0Ss= +github.com/foomo/tlsconfig v0.0.0-20180418120404-b67861b076c9 h1:RPOsDNbnDUFaSt/3bCxUsaGCJsKqA6dGubevl20nE9g= +github.com/foomo/tlsconfig v0.0.0-20180418120404-b67861b076c9/go.mod h1:OdiGKKgTAfMv7x9Hh9qYFueue77tr09LUAxwy2+M8wY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d h1:2+ZP7EfsZV7Vvmx3TIqSlSzATMkTAKqM14YGFPoSKjI= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/proxy/handler/staticfiles.go b/proxy/handler/staticfiles.go index 681c3a7..80398a9 100644 --- a/proxy/handler/staticfiles.go +++ b/proxy/handler/staticfiles.go @@ -4,6 +4,7 @@ import ( "mime" "net/http" "os" + "path/filepath" "strings" "time" @@ -49,15 +50,7 @@ func fileExists(filename string) bool { return err == nil } -func panicOnErr(err error) { - if err != nil { - panic(err) - } - -} - func (files *StaticFiles) ServeHTTP(w http.ResponseWriter, incomingRequest *http.Request) { - parts := strings.Split(incomingRequest.URL.Path, "/") path := strings.Join(parts[4:], "/") moduleNameParts := strings.Split(parts[3], "-") @@ -68,14 +61,39 @@ func (files *StaticFiles) ServeHTTP(w http.ResponseWriter, incomingRequest *http } else { moduleDir = files.foomo.GetModuleHtdocsVarDir(moduleName) } - f, err := os.Open(moduleDir + "/" + path) - panicOnErr(err) + fullName := filepath.Join(moduleDir, path) + // validate path + absPath, errAbs := filepath.Abs(fullName) + if errAbs != nil { + http.Error(w, "forbidden", http.StatusForbidden) + return + } + if !strings.HasPrefix(absPath, moduleDir) { + http.Error(w, "forbidden", http.StatusForbidden) + return + } + // check file + fileInfo, errStat := os.Stat(fullName) + if errStat != nil { + switch true { + case os.IsNotExist(errStat): + http.Error(w, "not found", http.StatusNotFound) + case os.IsPermission(errStat): + http.Error(w, "forbidden", http.StatusForbidden) + default: + http.Error(w, "internal server error", http.StatusInternalServerError) + } + return + } + // open it + f, errOpen := os.Open(fullName) defer f.Close() + if errOpen != nil { + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + // compression support _, compress := getContentType(path) - //w.Header().Set("Content-Type", mime) - fileInfo, err := f.Stat() - panicOnErr(err) - //const TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT" w.Header().Set("Expires", time.Now().Add(time.Hour*24*365).Format(http.TimeFormat)) if compress && strings.Contains(incomingRequest.Header.Get("Accept-Encoding"), "gzip") { w.Header().Set("Content-Encoding", "gzip") @@ -83,6 +101,7 @@ func (files *StaticFiles) ServeHTTP(w http.ResponseWriter, incomingRequest *http defer crw.Close() w = crw } + // passing it on to std library http.ServeContent(w, incomingRequest, f.Name(), fileInfo.ModTime(), f) } diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go index 817cbe3..c36d571 100644 --- a/proxy/proxy_test.go +++ b/proxy/proxy_test.go @@ -65,6 +65,21 @@ func TestSNI(t *testing.T) { if responseErr != nil { t.Fatal("failed to get", responseErr) } + + response404, response404Err := c.Get("https://localhost:8443/foomo/modulesVar/Foomo.JS/test-is-not-here") + if response404Err != nil { + t.Fatal("failed to get", response404Err) + } + if response404.StatusCode != http.StatusNotFound { + t.Fatal("unexpected status code:", response404.StatusCode) + } + responsePathAttack, responsePathAttackErr := c.Get("https://localhost:8443/foomo/modulesVar/Foomo.JS/../../../../etc/passwd") + if responsePathAttackErr != nil { + t.Fatal("failed to get", responsePathAttackErr) + } + if responsePathAttack.StatusCode != http.StatusForbidden { + t.Fatal("unexpected status code:", response404.StatusCode) + } if response.TLS.PeerCertificates[0].Subject.CommonName != "localhost" { t.Fatal("SNI Fail, unexpected common name in first peer certificate common name:", response.TLS.PeerCertificates[0].Subject.CommonName) }