CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-0969
8.80.07%

Markdown Madness: Turning Blog Posts into Shells with CVE-2026-0969

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 13, 2026·6 min read·16 visits

PoC Available

Executive Summary (TL;DR)

If you use `next-mdx-remote` versions 4.3.0 through 5.0.0 to render user-provided content, your server is wide open. The library fails to sanitize JavaScript expressions inside MDX files. An attacker can submit a post containing `{process.exit()}` or worse, and your server will execute it during rendering. Upgrade to v6.0.0 immediately to disable inline JS execution.

A critical Remote Code Execution (RCE) vulnerability in `next-mdx-remote` allows attackers to execute arbitrary commands on the server by injecting malicious JavaScript expressions into MDX content. This flaw exploits the library's default behavior of evaluating code within Markdown during the serialization process, effectively turning a harmless blog post into a weaponized payload.

The Hook: When Text Becomes Terror

We all love Markdown. It's the lingua franca of the developer web—simple, clean, and safe. Or so we thought. Enter MDX, the cool cousin that lets you import React components directly into your Markdown files. It’s a brilliant idea for developer blogs and documentation sites. But there's a catch: to make that magic happen, the text has to be compiled into code. And whenever you turn user input into code, you are walking a razor's edge over a pit of vipers.

CVE-2026-0969 is what happens when that compilation process is too trusting. next-mdx-remote, a massively popular library for Next.js applications, facilitates loading MDX content from anywhere (database, CMS, local files). The problem? In versions prior to 6.0.0, it didn't just render the Markdown; it eagerly evaluated any JavaScript expression tucked inside a pair of curly braces {}.

Imagine a scenario where you have a comment section or a guest post feature that supports MDX. You think you're just rendering bold text and lists. The attacker, however, sees a direct line to your server's runtime. This isn't just Cross-Site Scripting (XSS)—this is full-blown Remote Code Execution (RCE) if the rendering happens on the server (SSR), which is exactly what Next.js is famous for.

The Flaw: A Compiler Without a Conscience

The vulnerability lies in the serialize and compileMDX functions. These are the workhorses of the library, responsible for taking a raw string of MDX and transforming it into something React can render. In affected versions (4.3.0 to 5.0.0), the serialization process treats JavaScript expressions embedded in the Markdown as first-class citizens.

Technically, this is CWE-94: Improper Control of Generation of Code. The library assumes that the content passed to it is trusted. In the Jamstack era, this assumption often breaks down. Developers frequently fetch content from Headless CMS platforms where permissions might be lax, or worse, allow public submissions.

When next-mdx-remote parses the input, it uses a bundler (esbuild) internally to prepare the code. The flaw is that it didn't apply a default sandbox or strip out dangerous globals. It simply said, "Oh, you want to run Math.random()? Sure! Oh, you want to run process.kill()? Why not!" It failed to distinguish between benign logic (like formatting a date) and system-destroying logic.

The Code: Anatomy of a Disaster

Let's look at the "Smoking Gun". A typical implementation of next-mdx-remote looks innocent enough. You fetch a string from a database and pass it to serialize.

// Vulnerable Implementation
import { serialize } from 'next-mdx-remote/serialize';
 
export async function getStaticProps({ params }) {
  // source could be from a file, a DB, or an API
  const source = await getPostContent(params.slug);
  
  // THE KILL ZONE: 
  // The compiler evaluates expressions inside 'source' here.
  const mdxSource = await serialize(source);
  
  return { props: { source: mdxSource } };
}

The fix introduced in version 6.0.0 is a paradigm shift. Instead of allowing JS by default, they flipped the switch. Now, you have to explicitly opt-in to danger.

// Patched Implementation (v6.0.0+)
import { serialize } from 'next-mdx-remote/serialize';
 
const mdxSource = await serialize(source, {
  parseFrontmatter: true,
  mdxOptions: {
    // New default behavior blocks JS execution
    // If you manually set this to false, you are re-enabling the vulnerability
    // unless you know exactly what you are doing.
    blockJS: true 
  }
});

The patch also introduces blockDangerousJS, a heuristic-based blocker that tries to intercept calls to eval, Function, and process. It's a safety net, but as any exploit dev knows, heuristics are meant to be bypassed. The real fix is disabling JS entirely.

The Exploit: Escaping the Sandbox

Exploiting this is trivially easy if you control the input string. Because next-mdx-remote uses eval-like mechanisms to handle the compiled output, we can access the Node.js process global if the code runs on the server.

Here is what a weaponized payload looks like. It uses process.mainModule.require to bypass standard require restrictions if they exist, and then calls child_process to execute shell commands.

The Payload:

# My Innocent Blog Post
 
Here is a list of reasons why I love MDX:
 
1. It is flexible.
2. It allows components.
3. {process.mainModule.require('child_process').execSync('cat /etc/passwd').toString()}

The Execution Flow:

  1. Injection: The attacker submits the markdown above to a CMS or comment form.
  2. Retrieval: The Next.js server fetches this content during getStaticProps (build time) or getServerSideProps (request time).
  3. ** detonation**: The server calls serialize(). The bundler sees the code inside {} and executes it to resolve the value.
  4. Impact: The result of cat /etc/passwd is embedded directly into the HTML of the page, or the command executes silently (e.g., a reverse shell) before the page even renders.

> [!WARNING] > If this vulnerability is triggered in getStaticProps, the RCE happens on the build server (often CI/CD pipelines). If triggered in getServerSideProps, it happens on the production web server.

The Mitigation: Stop the Bleeding

The immediate fix is to upgrade to next-mdx-remote v6.0.0. This version changes the default behavior to blockJS: true. This effectively kills the attack vector by treating content inside curly braces as text rather than code, unless explicitly configured otherwise.

If you cannot upgrade, you must sanitize your inputs. However, sanitizing code is notoriously difficult. A simple regex looking for process or require is easily bypassed (e.g., global['pro' + 'cess']).

Defensive Strategy:

  1. Upgrade: Move to v6.0.0.
  2. Verify Configuration: Ensure you haven't accidentally set blockJS: false in your config.
  3. Network Segmentation: Why is your front-end server able to reach sensitive internal services? Ensure your Next.js containers are running with the least privilege possible (non-root user, read-only filesystem where possible).
  4. WAF: Configure your WAF to block requests containing patterns like process.env, child_process, or execSync in the body, although this is a weak layer of defense against a determined attacker.

Official Patches

npmVersion 6.0.0 Release on NPM

Technical Appendix

CVSS Score
8.8/ 10
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
EPSS Probability
0.07%
Top 78% most exploited

Affected Systems

Next.js applications using next-mdx-remote < 6.0.0Node.js SSR environments rendering untrusted MDX

Affected Versions Detail

Product
Affected Versions
Fixed Version
next-mdx-remote
HashiCorp / Open Source
>= 4.3.0 < 6.0.06.0.0
AttributeDetail
CWE IDCWE-94
CVSS Score8.8 (High)
Attack VectorNetwork
Exploit MaturityProof of Concept (High Probability)
Privileges RequiredLow (User Input)
Confidentiality ImpactHigh
Integrity ImpactHigh
Availability ImpactHigh

MITRE ATT&CK Mapping

T1059Command and Scripting Interpreter
Execution
T1190Exploit Public-Facing Application
Initial Access
CWE-94
Improper Control of Generation of Code ('Code Injection')

The software constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.

Known Exploits & Detection

Internal ResearchExploitation involves passing {process.mainModule.require('child_process')} into the MDX source during serialization.

Vulnerability Timeline

Vulnerability Disclosed by HashiCorp (HCSEC-2026-01)
2026-02-11
Patch Released (v6.0.0)
2026-02-11
CVE Published
2026-02-12

References & Sources

  • [1]next-mdx-remote GitHub Repository
  • [2]NVD CVE-2026-0969 Detail

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.