VYPR
Medium severity6.8NVD Advisory· Published Jun 12, 2026

CVE-2026-53523

CVE-2026-53523

Description

Nezha Monitoring before 2.2.0 constructs OAuth2 callback URLs from the unvalidated Host header, allowing an attacker to steal authorization codes.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Nezha Monitoring before 2.2.0 constructs OAuth2 callback URLs from the unvalidated Host header, allowing an attacker to steal authorization codes.

Vulnerability

Nezha Monitoring versions 1.0.0 up to (but not including) 2.2.0 contain a host header injection vulnerability in the OAuth2 callback URL construction. The getRedirectURL function in cmd/dashboard/controller/oauth2.go (lines 22-29) builds the redirect URL by concatenating the HTTP Host header with a fixed path (/api/v1/oauth2/callback) without any validation. This URL is passed to the OAuth2 provider as the callback endpoint. The affected code is reachable whenever the OAuth2 login flow is used. The X-Forwarded-Proto header and Referer header are also used to determine the scheme, and these can be attacker-controlled in certain configurations, enabling a fully controllable https:// redirect. [1]

Exploitation

An attacker must be able to inject a malicious Host header into a request that reaches the oauth2redirect handler. This is possible if the attacker can perform a man-in-the-middle attack, or if a misconfigured reverse proxy passes through the Host header unchanged. In the described attack scenario, the attacker sends an OAuth2 authorization request with Host: evil.com. The getRedirectURL function returns https://evil.com/api/v1/oauth2/callback. The OAuth2 provider (e.g., GitHub) then redirects the victim user's browser—along with the authorization code—to evil.com. No authentication or write access is required. [1]

Impact

On successful exploitation, the attacker's server at evil.com captures the victim's OAuth2 authorization code. The attacker can then exchange this code for an access token, effectively binding the victim's OAuth identity (and thus access in the monitoring dashboard) to the attacker's own dashboard account. This leads to account takeover and unauthorized access to the monitoring data and capabilities of the victim's account. The impact includes confidentiality and integrity compromise of dashboard-managed resources. [1]

Mitigation

The vulnerability has been patched in Nezha Monitoring version 2.2.0, released on 2026-06-12. Users should upgrade to version 2.2.0 or later. For environments where an immediate upgrade is not possible, administrators should ensure that reverse proxies or load balancers are configured to validate and sanitize the Host header, or restrict OAuth2 configuration to trusted providers with strict callback validation. No KEV listing is currently available. [1]

AI Insight generated on Jun 12, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

1
ba2d1a9355eb

fix(oauth2): validate Host header against allowlist for callback URL

https://github.com/nezhahq/nezhanaibaJun 5, 2026Fixed in 2.2.0via llm-release-walk
2 files changed · +83 1
  • cmd/dashboard/controller/oauth2.go+13 1 modified
    @@ -19,13 +19,25 @@ import (
     	"github.com/nezhahq/nezha/service/singleton"
     )
     
    +// GHSA-9rc6-8cjv-rcvx: the OAuth2 callback URL is sent to the identity
    +// provider and is where the authorization code lands. Deriving it from the
    +// raw Host header lets an attacker who can reach this handler with a forged
    +// Host (or a provider with loose redirect-URI matching) divert a victim's
    +// code to their own origin and bind the victim's identity. Only trust the
    +// request Host when it is an operator-declared dashboard host (the same
    +// allowlist that guards NAT routing); otherwise fall back to the configured
    +// InstallHost so a forged Host cannot steer the redirect.
     func getRedirectURL(c *gin.Context) string {
     	scheme := "http://"
     	referer := c.Request.Referer()
     	if forwardedProto := c.Request.Header.Get("X-Forwarded-Proto"); forwardedProto == "https" || strings.HasPrefix(referer, "https://") {
     		scheme = "https://"
     	}
    -	return scheme + c.Request.Host + "/api/v1/oauth2/callback"
    +	host := c.Request.Host
    +	if !singleton.IsReservedDashboardHost(host) && singleton.Conf != nil && singleton.Conf.InstallHost != "" {
    +		host = singleton.Conf.InstallHost
    +	}
    +	return scheme + host + "/api/v1/oauth2/callback"
     }
     
     // @Summary Get Oauth2 Redirect URL
    
  • cmd/dashboard/controller/oauth2_test.go+70 0 modified
    @@ -112,6 +112,76 @@ func TestOAuth2_VerifyState_HappyPath(t *testing.T) {
     	require.Equal(t, model.RTypeBind, st.Action)
     }
     
    +// GHSA-9rc6-8cjv-rcvx: getRedirectURL must not echo an attacker-controlled
    +// Host header into the OAuth2 callback URL. It may only trust a Host that the
    +// operator declared (InstallHost / ListenHost / ReservedHosts); any other Host
    +// must fall back to the configured InstallHost so a forged value cannot divert
    +// the victim's authorization code.
    +
    +func setRedirectHostConf(t *testing.T, installHost, reservedHosts string) {
    +	t.Helper()
    +	prev := singleton.Conf
    +	singleton.Conf = &singleton.ConfigClass{Config: &model.Config{
    +		ConfigDashboard: model.ConfigDashboard{
    +			InstallHost:   installHost,
    +			ReservedHosts: reservedHosts,
    +		},
    +	}}
    +	t.Cleanup(func() { singleton.Conf = prev })
    +}
    +
    +func TestGetRedirectURL_RejectsForgedHostFallsBackToInstallHost(t *testing.T) {
    +	setRedirectHostConf(t, "panel.example.com", "")
    +	c, _ := newOAuth2Ctx(t)
    +	c.Request.Host = "evil.attacker.test"
    +
    +	got := getRedirectURL(c)
    +	require.Equal(t, "http://panel.example.com/api/v1/oauth2/callback", got,
    +		"a forged Host must be ignored in favour of the configured InstallHost")
    +}
    +
    +func TestGetRedirectURL_TrustsInstallHost(t *testing.T) {
    +	setRedirectHostConf(t, "panel.example.com", "")
    +	c, _ := newOAuth2Ctx(t)
    +	c.Request.Host = "panel.example.com"
    +
    +	got := getRedirectURL(c)
    +	require.Equal(t, "http://panel.example.com/api/v1/oauth2/callback", got,
    +		"the declared InstallHost must be trusted verbatim")
    +}
    +
    +func TestGetRedirectURL_TrustsReservedHostForMultiDomain(t *testing.T) {
    +	setRedirectHostConf(t, "panel.example.com", "alt.example.com,panel2.example.com")
    +	c, _ := newOAuth2Ctx(t)
    +	c.Request.Host = "panel2.example.com"
    +
    +	got := getRedirectURL(c)
    +	require.Equal(t, "http://panel2.example.com/api/v1/oauth2/callback", got,
    +		"a Host listed in ReservedHosts must be trusted so multi-domain deployments keep working")
    +}
    +
    +func TestGetRedirectURL_HonoursForwardedProtoOnTrustedHost(t *testing.T) {
    +	setRedirectHostConf(t, "panel.example.com", "")
    +	c, _ := newOAuth2Ctx(t)
    +	c.Request.Host = "panel.example.com"
    +	c.Request.Header.Set("X-Forwarded-Proto", "https")
    +
    +	got := getRedirectURL(c)
    +	require.Equal(t, "https://panel.example.com/api/v1/oauth2/callback", got,
    +		"https scheme must still be derived for reverse-proxy TLS termination")
    +}
    +
    +func TestGetRedirectURL_ForgedHostCannotForceHTTPSOrigin(t *testing.T) {
    +	setRedirectHostConf(t, "panel.example.com", "")
    +	c, _ := newOAuth2Ctx(t)
    +	c.Request.Host = "evil.attacker.test"
    +	c.Request.Header.Set("X-Forwarded-Proto", "https")
    +
    +	got := getRedirectURL(c)
    +	require.Equal(t, "https://panel.example.com/api/v1/oauth2/callback", got,
    +		"even with an https hint the host must collapse to InstallHost, never the forged origin")
    +}
    +
     func TestOAuth2_Unbind_UnknownProviderRejected(t *testing.T) {
     	defer setupOAuth2Test(t)()
     
    

Vulnerability mechanics

Root cause

"Missing validation of the HTTP Host header in getRedirectURL allows an attacker to control the OAuth2 callback URL."

Attack vector

An attacker crafts a specially formed HTTP request to the dashboard's OAuth2 login endpoint (e.g., `/api/v1/oauth2/github`) with a forged `Host` header pointing to an attacker-controlled domain. The `getRedirectURL` function [ref_id=1] echoes this attacker-controlled host verbatim into the OAuth2 callback URL, which is sent to the OAuth2 provider as the `redirect_uri`. After the victim authenticates, the provider redirects the victim's browser—along with the authorization code—to the attacker's domain, where the attacker captures the code and exchanges it for an access token at the real dashboard, binding the victim's OAuth identity to the attacker's account [ref_id=1]. Full exploitation requires the victim to click the attacker's crafted link and the OAuth2 provider to accept the attacker's domain as a valid redirect URI.

Affected code

The vulnerable code is the `getRedirectURL` function in `cmd/dashboard/controller/oauth2.go:22-29`, which constructs the OAuth2 callback URL by concatenating the request's `Host` header (`c.Request.Host`) with a fixed path and performs no validation on that header. This function is called by `oauth2redirect()` at line 53 of the same file, and the resulting `redirectURL` is passed to `o2confRaw.Setup(redirectURL)` in `oauth2config.go:22-33` to set the OAuth2 `Config.RedirectURL` field. The patch modifies `cmd/dashboard/controller/oauth2.go` and adds comprehensive tests in `cmd/dashboard/controller/oauth2_test.go`.

What the fix does

The patch [patch_id=5752439] replaces the unconditional use of `c.Request.Host` with a validation step: `host` is now set to `c.Request.Host` only when `singleton.IsReservedDashboardHost(host)` returns true (meaning the host is in the operator-declared allowlist). Otherwise, the function falls back to `singleton.Conf.InstallHost`, the configured dashboard hostname. This ensures that an attacker-controlled `Host` header cannot divert the OAuth2 redirect URL, while multi-domain deployments that use reserved hosts continue to work. New unit tests confirm that forged hosts are rejected, trusted hosts (InstallHost and ReservedHosts) are accepted, and the `X-Forwarded-Proto` scheme derivation still functions correctly on trusted hosts.

Preconditions

  • configThe OAuth2 provider must accept the attacker's domain as a valid redirect URI (e.g., wildcard subdomain matching).
  • inputThe victim must click the attacker's crafted link to begin the OAuth2 flow.
  • networkThe forged Host header must reach the dashboard's handler unmodified (bypassing any reverse proxy normalization).

Reproduction

The advisory [ref_id=1] includes a conceptual PoC with request:

``` GET /api/v1/oauth2/github HTTP/1.1 Host: attacker-controlled.com X-Forwarded-Proto: https ```

The dashboard responds with a redirect to the OAuth2 provider where `redirect_uri` contains the attacker's domain. After the victim authenticates, the provider redirects the auth code to `https://attacker-controlled.com/api/v1/oauth2/callback?code=AUTH_CODE&state=...`, which the attacker captures from their server logs and exchanges at the real dashboard.

Generated on Jun 12, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

1

News mentions

0

No linked articles in our index yet.