XenForo 2.3.12 rejected a bad PKCE verifier. An empty verifier returned working tokens.
I found it in the OAuth token endpoint on build 2031270. After a normal S256 authorization, I sent code_verifier= during the exchange. The response contained fresh access and refresh tokens. The access token worked at /api/me.
Where the check disappeared
The endpoint first called assertRequiredApiInput(), which accepted any present parameter, including an empty string. Credential checks came later:
if ($input['client_secret']) { /* compare it */ }
if ($input['code_verifier']) { /* verify PKCE */ }
In PHP, both '' and '0' are false. Supplying either value skipped the corresponding block.
For a public client, the empty value removed PKCE from the code exchange. An attacker needed a live authorization code, and the returned tokens kept its approved scopes. A stolen code was enough to redeem the grant.
The same pattern covered client_secret in the authorization-code and refresh-token grants. A confidential client could send an empty secret and avoid that comparison too.
Test and fix
The PoC performs the empty-verifier exchange and uses the returned access token at /api/me.
XenForo fixed the endpoint in 2.3.13. Empty credentials now fail before grant processing, and PKCE grants always reach the verifier check.