CVE-2026-41070
Description
openvpn-auth-oauth2 is a plugin/management interface client for OpenVPN server to handle an OIDC based single sign-on (SSO) auth flows. From version 1.26.3 to before version 1.27.3, when openvpn-auth-oauth2 is deployed in the experimental plugin mode (shared library loaded by OpenVPN via the plugin directive), clients that do not support WebAuth/SSO (e.g., the openvpn CLI on Linux) are incorrectly admitted to the VPN despite being denied by the authentication logic. The default management-interface mode is not affected because it does not use the OpenVPN plugin return-code mechanism. This issue has been patched in version 1.27.3.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/jkroepke/openvpn-auth-oauth2Go | >= 1.26.3, < 1.27.3 | 1.27.3 |
Affected products
1- Range: >= 1.26.3, < 1.27.3
Patches
136f69a6c67c1fix: plugin returns FUNC_SUCCESS on client-deny, allowing unauthenticated connections (#829)
2 files changed · +134 −3
lib/openvpn-auth-oauth2/openvpn/handle.go+1 −3 modified@@ -144,11 +144,9 @@ func (p *PluginHandle) handleAuthUserPassVerify(clientEnvList **c.Char, perClien logger.ErrorContext(p.ctx, "write to auth file", slog.Any("err", err), ) - - return c.OpenVPNPluginFuncError } - return c.OpenVPNPluginFuncSuccess + return c.OpenVPNPluginFuncError case management.ClientAuthPending: pendingRespCh, err := p.managementClient.RegisterPendingPoller(currentClientID) if err != nil {
lib/openvpn-auth-oauth2/openvpn/plugin_test.go+133 −0 modified@@ -271,6 +271,139 @@ func TestPlugin(t *testing.T) { } } +// TestPluginDenyNonWebAuthClient verifies that a client not supporting webauth +// receives OPENVPN_PLUGIN_FUNC_ERROR so that OpenVPN rejects the connection. +// Previously the plugin returned OPENVPN_PLUGIN_FUNC_SUCCESS on denial, which +// caused OpenVPN to let the client in despite the management-interface deny. +func TestPluginDenyNonWebAuthClient(t *testing.T) { + t.Parallel() + + unixSocket, err := nettest.LocalPath() + require.NoError(t, err) + + passwordFile, err := os.CreateTemp(t.TempDir(), "openvpn-auth-oauth2-test-") + require.NoError(t, err) + t.Cleanup(func() { + _ = passwordFile.Close() + }) + + _, err = passwordFile.WriteString("password") + require.NoError(t, err) + + argv, cStrings := testutil.CreateCStringArray([]string{"openvpn-auth-oauth2", "unix://" + unixSocket, passwordFile.Name()}) + + t.Cleanup(func() { + testutil.FreeCStringArray(argv, cStrings) + }) + + openArgs := &c.OpenVPNPluginArgsOpenIn{ + Callbacks: testutil.Callbacks(), + Argv: argv, + } + openRet := &c.OpenVPNPluginArgsOpenReturn{} + + status := PluginOpenV3(PluginStructVerMin, openArgs, openRet) + require.Equal(t, c.OpenVPNPluginFuncSuccess, status) + + t.Cleanup(func() { + PluginCloseV1(openRet.Handle) + }) + + args := &c.OpenVPNPluginArgsFuncIn{ + Handle: openRet.Handle, + Argv: argv, + Type: c.OpenVPNPluginUp, + } + ret := &c.OpenVPNPluginArgsFuncReturn{} + + status = PluginFuncV3(PluginStructVerMin, args, ret) + require.Equal(t, c.OpenVPNPluginFuncSuccess, status) + + logger := testsuite.NewTestLogger() + + // clientListener must not be closed, because it is used by the httpClientListener. + clientListener, err := nettest.NewLocalListener("tcp") + require.NoError(t, err) + + _, resourceServerURL, clientCredentials, err := testutils.SetupResourceServer(t, clientListener, logger.Logger, nil) + require.NoError(t, err) + + conf := config.Defaults + conf.OpenVPN.Addr = types.URL{URL: &url.URL{Scheme: "unix", Path: unixSocket}} + conf.OpenVPN.Password = "password" + conf.OAuth2.OpenVPNUsernameClaim = testsuite.SubjectClaim + conf.HTTP.BaseURL = types.URL{URL: &url.URL{Scheme: "http", Host: clientListener.Addr().String()}} + conf.HTTP.Secret = testsuite.Secret + conf.OAuth2.Issuer = resourceServerURL + conf.OAuth2.Nonce = true + conf.OAuth2.RefreshNonce = config.OAuth2RefreshNonceEmpty + conf.OAuth2.Client.ID = clientCredentials.ID + conf.OAuth2.Client.Secret = clientCredentials.Secret + + tokenStorage := tokenstorage.NewInMemory(testsuite.Secret, time.Hour) + _, openVPNClient := testutils.SetupOpenVPNOAuth2Clients(t.Context(), t, conf, logger.Logger, http.DefaultClient, tokenStorage) + + errOpenVPNClientCh := make(chan error, 1) + + go func(errCh chan<- error) { + errCh <- openVPNClient.Connect(t.Context()) + }(errOpenVPNClientCh) + + select { + case err := <-errOpenVPNClientCh: + require.NoError(t, err) + default: + } + + time.Sleep(50 * time.Millisecond) + + clientContextPtr := PluginClientConstructorV1(openRet.Handle) + require.NotNil(t, clientContextPtr) + + authControlFile, err := os.CreateTemp(t.TempDir(), "auth_control_file") + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, authControlFile.Close()) + }) + + // Client without webauth support (no IV_SSO=webauth) + envp, envCStrings := testutil.CreateCStringArray([]string{ + "n_clients=0", + "password=", + "session_id=SESSIONID", + "untrusted_port=17016", + "untrusted_ip=192.168.65.1", + "common_name=user@example.com", + "username=", + "session_state=Initial", + "auth_control_file=" + authControlFile.Name(), + }) + + t.Cleanup(func() { + testutil.FreeCStringArray(envp, envCStrings) + }) + + args = &c.OpenVPNPluginArgsFuncIn{ + Handle: openRet.Handle, + Argv: argv, + Envp: envp, + Type: c.OpenVPNPluginAuthUserPassVerify, + PerClientContext: clientContextPtr, + } + ret = &c.OpenVPNPluginArgsFuncReturn{} + + // A client without webauth support must be denied: the plugin must return ERROR. + status = PluginFuncV3(PluginStructVerMin, args, ret) + require.Equal(t, c.OpenVPNPluginFuncError, status, logger.String()) + + // The auth control file must reflect the denial. + data, err := os.ReadFile(authControlFile.Name()) + require.NoError(t, err) + require.Equal(t, "0", string(data)) + + PluginClientDestructorV1(args.Handle, clientContextPtr) +} + func TestPluginOpenV3_InvalidArgs(t *testing.T) { t.Parallel()
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-246w-jgmq-88fgghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-41070ghsaADVISORY
- github.com/OpenVPN/openvpn/blob/master/include/openvpn-plugin.h.inghsaWEB
- github.com/OpenVPN/openvpn3/blob/master/doc/webauth.mdghsaWEB
- github.com/jkroepke/openvpn-auth-oauth2/commit/36f69a6c67c1054da7cbfa04ced3f0555127c8f2nvdWEB
- github.com/jkroepke/openvpn-auth-oauth2/pull/829ghsaWEB
- github.com/jkroepke/openvpn-auth-oauth2/releases/tag/v1.27.3ghsaWEB
- github.com/jkroepke/openvpn-auth-oauth2/security/advisories/GHSA-246w-jgmq-88fgnvdWEB
News mentions
0No linked articles in our index yet.