# openvpn-auth-oauth2 returns FUNC_SUCCESS on client-deny, allowing unauthenticated VPN access

openvpn-auth-oauth2 plugin mode returned FUNC_SUCCESS on client-deny, so non-WebAuth clients bypassed OIDC auth and got full VPN access.

- Payout: Research
- Severity: critical
- Program: openvpn-auth-oauth2
- Found by: kkalev
- Disclosed: 2026-04-22
- CVE: CVE-2026-41070
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-246w-jgmq-88fg
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-246w-jgmq-88fg
- Tags: area:devices domain:network domain:oss impact:ato impact:internal-access kind:research platform:ghsa sev:critical tech:go technique:source-review vuln:access-control vuln:auth-bypass

**Target:** OpenVPN plugin (shared library) auth callback
**Root cause:** ClientAuthDeny branch wrote "0" to auth_control_file but returned OPENVPN_PLUGIN_FUNC_SUCCESS; OpenVPN only reads auth_control_file on FUNC_DEFERRED, treating synchronous FUNC_SUCCESS as approval.
**Steps:**
1. Deploy openvpn-auth-oauth2 1.26.3–1.27.2 in experimental plugin mode via `plugin` directive.
2. Connect with a client that does not advertise `IV_SSO=webauth` (e.g. Linux `openvpn` CLI).
3. Plugin issues `client-deny` via management interface but returns status=0 to OpenVPN.
4. OpenVPN interprets status=0 as auth passed and admits the client to the internal network.
**How found:** Reporter traced the ClientAuthDeny branch in lib/openvpn-auth-oauth2/openvpn/handle.go and noticed the return value was FUNC_SUCCESS despite writing deny to auth_control_file, then checked OpenVPN plugin semantics where auth_control_file is only honored on FUNC_DEFERRED.
**Exploitation:** Attacker connects with any standard OpenVPN client lacking webauth support; no OIDC credentials needed. Plugin's deny is ignored due to FUNC_SUCCESS return, granting full VPN network access.
**Insight:** When integrating with OpenVPN plugins, the return code is the real auth decision: auth_control_file is only consulted on FUNC_DEFERRED, so any synchronous FUNC_SUCCESS silently approves regardless of file contents. Audit every deny path's return value, not just the file write.

## Proof of concept (copied exactly from the original report)

**Vulnerable ClientAuthDeny branch returning FUNC_SUCCESS**

```
case management.ClientAuthDeny:
    // ... writes "0" to auth_control_file ...
    if err := openVPNClient.WriteToAuthFile("0"); err != nil {
        // only returned ERROR on write failure
        return c.OpenVPNPluginFuncError
    }
    return c.OpenVPNPluginFuncSuccess  // ← BUG: OpenVPN sees this as "auth passed"
```

