package session import ( "fmt" "net" "net/http" "strings" "github.com/stashapp/stash/pkg/logger" ) type ExternalAccessError net.IP func (e ExternalAccessError) Error() string { return fmt.Sprintf("stash accessed from external IP %s", net.IP(e).String()) } func CheckAllowPublicWithoutAuth(c ExternalAccessConfig, r *http.Request) error { if !c.HasCredentials() && !c.GetDangerousAllowPublicWithoutAuth() && !c.IsNewSystem() { requestIPString, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { return fmt.Errorf("error parsing remote host (%s): %w", r.RemoteAddr, err) } // presence of scope ID in IPv6 addresses prevents parsing. Remove if present scopeIDIndex := strings.Index(requestIPString, "%") if scopeIDIndex != -1 { requestIPString = requestIPString[0:scopeIDIndex] } requestIP := net.ParseIP(requestIPString) if requestIP == nil { return fmt.Errorf("unable to parse remote host (%s)", requestIPString) } if r.Header.Get("X-FORWARDED-FOR") != "" { // Request was proxied proxyChain := strings.Split(r.Header.Get("X-FORWARDED-FOR"), ", ") // validate proxies against local network only if !isLocalIP(requestIP) { return ExternalAccessError(requestIP) } else { // Safe to validate X-Forwarded-For for i := range proxyChain { ip := net.ParseIP(proxyChain[i]) if !isLocalIP(ip) { return ExternalAccessError(ip) } } } } else if !isLocalIP(requestIP) { // request was not proxied return ExternalAccessError(requestIP) } } return nil } func CheckExternalAccessTripwire(c ExternalAccessConfig) *ExternalAccessError { if !c.HasCredentials() && !c.GetDangerousAllowPublicWithoutAuth() { if remoteIP := c.GetSecurityTripwireAccessedFromPublicInternet(); remoteIP != "" { err := ExternalAccessError(net.ParseIP(remoteIP)) return &err } } return nil } func isLocalIP(requestIP net.IP) bool { _, cgNatAddrSpace, _ := net.ParseCIDR("100.64.0.0/10") return requestIP.IsPrivate() || requestIP.IsLoopback() || requestIP.IsLinkLocalUnicast() || cgNatAddrSpace.Contains(requestIP) } func LogExternalAccessError(err ExternalAccessError) { logger.Errorf("Stash has been accessed from the internet (public IP %s), without authentication. \n"+ "This is extremely dangerous! The whole world can see your stash page and browse your files! \n"+ "You probably forwarded a port from your router. At the very least, add a password to stash in the settings. \n"+ "Stash will not serve requests until you edit config.yml, remove the security_tripwire_accessed_from_public_internet key and restart stash. \n"+ "This behaviour can be overridden (but not recommended) by setting dangerous_allow_public_without_auth to true in config.yml. \n"+ "More information is available at https://docs.stashapp.cc/networking/authentication-required-when-accessing-stash-from-the-internet \n"+ "Stash is not answering any other requests to protect your privacy.", net.IP(err).String()) }