mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
Disallow access in publicly exposed services (#1761)
* Add security against publicly exposed services * Add trusted proxies setting, validate proxy chain against internet access * Validate chain on local proxies too * Move authentication handler to separate file * Add startup check and log if tripwire is active Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
dcf58b99a6
commit
f1da6cb1b2
117
pkg/session/authentication.go
Normal file
117
pkg/session/authentication.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
)
|
||||
|
||||
type ExternalAccessError net.IP
|
||||
|
||||
func (e ExternalAccessError) Error() string {
|
||||
return fmt.Sprintf("stash accessed from external IP %s", net.IP(e).String())
|
||||
}
|
||||
|
||||
type UntrustedProxyError net.IP
|
||||
|
||||
func (e UntrustedProxyError) Error() string {
|
||||
return fmt.Sprintf("untrusted proxy %s", net.IP(e).String())
|
||||
}
|
||||
|
||||
func CheckAllowPublicWithoutAuth(c *config.Instance, 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)
|
||||
}
|
||||
|
||||
requestIP := net.ParseIP(requestIPString)
|
||||
|
||||
if r.Header.Get("X-FORWARDED-FOR") != "" {
|
||||
// Request was proxied
|
||||
trustedProxies := c.GetTrustedProxies()
|
||||
proxyChain := strings.Split(r.Header.Get("X-FORWARDED-FOR"), ", ")
|
||||
|
||||
if trustedProxies == nil {
|
||||
// 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 {
|
||||
// validate proxies against trusted proxies list
|
||||
if isIPTrustedProxy(requestIP, trustedProxies) {
|
||||
// Safe to validate X-Forwarded-For
|
||||
// validate backwards, as only the last one is not attacker-controlled
|
||||
for i := len(proxyChain) - 1; i >= 0; i-- {
|
||||
ip := net.ParseIP(proxyChain[i])
|
||||
if i == 0 {
|
||||
// last entry is originating device, check if from the public internet
|
||||
if !isLocalIP(ip) {
|
||||
return ExternalAccessError(ip)
|
||||
}
|
||||
} else if !isIPTrustedProxy(ip, trustedProxies) {
|
||||
return UntrustedProxyError(ip)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Proxy not on safe proxy list
|
||||
return UntrustedProxyError(requestIP)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// request was not proxied
|
||||
if !isLocalIP(requestIP) {
|
||||
return ExternalAccessError(requestIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckExternalAccessTripwire(c *config.Instance) *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() || cgNatAddrSpace.Contains(requestIP)
|
||||
}
|
||||
|
||||
func isIPTrustedProxy(ip net.IP, trustedProxies []string) bool {
|
||||
for _, v := range trustedProxies {
|
||||
if ip.Equal(net.ParseIP(v)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
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://github.com/stashapp/stash/wiki/Authentication-Required-When-Accessing-Stash-From-the-Internet \n"+
|
||||
"Stash is not answering any other requests to protect your privacy.", net.IP(err).String())
|
||||
}
|
||||
Reference in New Issue
Block a user