security: fix CVE-2023-36456 (#6171)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L 2023-07-06 18:16:26 +02:00 committed by GitHub
parent 786a84640e
commit d22d147c8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 94 additions and 13 deletions

View File

@ -5,18 +5,25 @@ postgresql:
name: authentik name: authentik
user: authentik user: authentik
port: 5432 port: 5432
password: 'env://POSTGRES_PASSWORD' password: "env://POSTGRES_PASSWORD"
use_pgbouncer: false use_pgbouncer: false
listen: listen:
listen_http: 0.0.0.0:9000 listen_http: 0.0.0.0:9000
listen_https: 0.0.0.0:9443 listen_https: 0.0.0.0:9443
listen_metrics: 0.0.0.0:9300 listen_metrics: 0.0.0.0:9300
trusted_proxy_cidrs:
- 127.0.0.0/8
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
- fe80::/10
- ::1/128
redis: redis:
host: localhost host: localhost
port: 6379 port: 6379
password: '' password: ""
tls: false tls: false
tls_reqs: "none" tls_reqs: "none"
db: 0 db: 0

View File

@ -16,10 +16,12 @@ LOGGER = get_logger()
def _get_client_ip_from_meta(meta: dict[str, Any]) -> str: def _get_client_ip_from_meta(meta: dict[str, Any]) -> str:
"""Attempt to get the client's IP by checking common HTTP Headers. """Attempt to get the client's IP by checking common HTTP Headers.
Returns none if no IP Could be found""" Returns none if no IP Could be found
No additional validation is done here as requests are expected to only arrive here
via the go proxy, which deals with validating these headers for us"""
headers = ( headers = (
"HTTP_X_FORWARDED_FOR", "HTTP_X_FORWARDED_FOR",
"HTTP_X_REAL_IP",
"REMOTE_ADDR", "REMOTE_ADDR",
) )
for _header in headers: for _header in headers:

View File

@ -38,13 +38,14 @@ type RedisConfig struct {
} }
type ListenConfig struct { type ListenConfig struct {
HTTP string `yaml:"listen_http" env:"AUTHENTIK_LISTEN__HTTP"` HTTP string `yaml:"listen_http" env:"AUTHENTIK_LISTEN__HTTP"`
HTTPS string `yaml:"listen_https" env:"AUTHENTIK_LISTEN__HTTPS"` HTTPS string `yaml:"listen_https" env:"AUTHENTIK_LISTEN__HTTPS"`
LDAP string `yaml:"listen_ldap" env:"AUTHENTIK_LISTEN__LDAP"` LDAP string `yaml:"listen_ldap" env:"AUTHENTIK_LISTEN__LDAP"`
LDAPS string `yaml:"listen_ldaps" env:"AUTHENTIK_LISTEN__LDAPS"` LDAPS string `yaml:"listen_ldaps" env:"AUTHENTIK_LISTEN__LDAPS"`
Radius string `yaml:"listen_radius" env:"AUTHENTIK_LISTEN__RADIUS"` Radius string `yaml:"listen_radius" env:"AUTHENTIK_LISTEN__RADIUS"`
Metrics string `yaml:"listen_metrics" env:"AUTHENTIK_LISTEN__METRICS"` Metrics string `yaml:"listen_metrics" env:"AUTHENTIK_LISTEN__METRICS"`
Debug string `yaml:"listen_debug" env:"AUTHENTIK_LISTEN__DEBUG"` Debug string `yaml:"listen_debug" env:"AUTHENTIK_LISTEN__DEBUG"`
TrustedProxyCIDRs []string `yaml:"trusted_proxy_cidrs" env:"AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS"`
} }
type PathsConfig struct { type PathsConfig struct {

View File

@ -0,0 +1,44 @@
package web
import (
"net"
"net/http"
"github.com/gorilla/handlers"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/config"
)
// ProxyHeaders Set proxy headers like X-Forwarded-For and such, but only if the direct connection
// comes from a client that's in a list of trusted CIDRs
func ProxyHeaders() func(http.Handler) http.Handler {
nets := []*net.IPNet{}
for _, rn := range config.Get().Listen.TrustedProxyCIDRs {
_, cidr, err := net.ParseCIDR(rn)
if err != nil {
continue
}
nets = append(nets, cidr)
}
ph := handlers.ProxyHeaders
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err == nil {
// remoteAddr will be nil if the IP cannot be parsed
remoteAddr := net.ParseIP(host)
for _, allowedCidr := range nets {
if remoteAddr != nil && allowedCidr.Contains(remoteAddr) {
log.WithField("remoteAddr", remoteAddr).WithField("cidr", allowedCidr.String()).Trace("Setting proxy headers")
ph(h).ServeHTTP(w, r)
return
}
}
}
// Request is not directly coming from a CIDR we "trust"
// so set XFF to the direct host IP
r.Header.Set("X-Forwarded-For", host)
h.ServeHTTP(w, r)
})
}
}

View File

@ -35,7 +35,7 @@ type WebServer struct {
func NewWebServer(g *gounicorn.GoUnicorn) *WebServer { func NewWebServer(g *gounicorn.GoUnicorn) *WebServer {
l := log.WithField("logger", "authentik.router") l := log.WithField("logger", "authentik.router")
mainHandler := mux.NewRouter() mainHandler := mux.NewRouter()
mainHandler.Use(handlers.ProxyHeaders) mainHandler.Use(web.ProxyHeaders())
mainHandler.Use(handlers.CompressHandler) mainHandler.Use(handlers.CompressHandler)
loggingHandler := mainHandler.NewRoute().Subrouter() loggingHandler := mainHandler.NewRoute().Subrouter()
loggingHandler.Use(web.NewLoggingHandler(l, nil)) loggingHandler.Use(web.NewLoggingHandler(l, nil))

View File

@ -59,6 +59,11 @@ kubectl exec -it deployment/authentik-worker -c authentik -- ak dump_config
- `AUTHENTIK_LISTEN__LDAPS`: Listening address:port (e.g. `0.0.0.0:6636`) for LDAPS (LDAP outpost) - `AUTHENTIK_LISTEN__LDAPS`: Listening address:port (e.g. `0.0.0.0:6636`) for LDAPS (LDAP outpost)
- `AUTHENTIK_LISTEN__METRICS`: Listening address:port (e.g. `0.0.0.0:9300`) for Prometheus metrics (All) - `AUTHENTIK_LISTEN__METRICS`: Listening address:port (e.g. `0.0.0.0:9300`) for Prometheus metrics (All)
- `AUTHENTIK_LISTEN__DEBUG`: Listening address:port (e.g. `0.0.0.0:9900`) for Go Debugging metrics (All) - `AUTHENTIK_LISTEN__DEBUG`: Listening address:port (e.g. `0.0.0.0:9900`) for Go Debugging metrics (All)
- `AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS`: List of CIDRs that proxy headers should be accepted from (Server)
Defaults to `127.0.0.0/8`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `fe80::/10`, `::1/128`.
Requests directly coming from one an address within a CIDR specified here are able to set proxy headers, such as `X-Forwarded-For`. Requests coming from other addresses will not be able to set these headers.
## authentik Settings ## authentik Settings

View File

@ -0,0 +1,21 @@
# CVE-2023-36456
_Reported by [@thijsa](https://github.com/thijsa)_
## Lack of Proxy IP headers validation
### Summary
authentik does not verify the source of the X-Forwarded-For and X-Real-IP headers, both in the Python code and the go code.
### Impact
Only authentik setups that are directly accessible by users without a reverse proxy are susceptible to this. Possible spoofing of IP addresses in logs, downstream applications proxied by (built in) outpost, IP bypassing in custom flows if used.
### Details
This poses a possible security risk when you have flows or policies that check the user's IP address, e.g. when you want to ignore the user's 2 factor authentication when the user is connected to the company network.
Another security risk is that the IP addresses in the logfiles and user sessions is not reliable anymore, anybody can spoof this address and you cannot verify that the user has logged in from the IP address that is in their account's log.
And the third risk is that this header is passed on to the proxied application behind an outpost. The application may do any kind of verification, logging, blocking or rate limiting based on the IP address, and this IP address can be overridden by anybody that want to.

View File

@ -321,11 +321,12 @@ module.exports = {
}, },
items: [ items: [
"security/policy", "security/policy",
"security/CVE-2023-36456",
"security/2023-06-cure53", "security/2023-06-cure53",
"security/CVE-2023-26481",
"security/CVE-2022-23555", "security/CVE-2022-23555",
"security/CVE-2022-46145", "security/CVE-2022-46145",
"security/CVE-2022-46172", "security/CVE-2022-46172",
"security/CVE-2023-26481",
], ],
}, },
], ],