security: fix CVE-2023-36456 (#6171)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
parent
786a84640e
commit
d22d147c8e
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
|
@ -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",
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
Reference in New Issue