CVE-2026-27199(Werkzeug)研究

admin 2026-03-03 08:07:52 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文剖析CVE-2026-27199漏洞,针对Werkzeug库safe_join函数在Windows平台的设备名绕过缺陷。影响版本小于3.1.6,攻击者利用嵌套路径可致数据静默丢失或拒绝服务。文章详述漏洞原理与危害,提供Python检测脚本及攻击场景演示,建议升级至3.1.6修复,具备较高实战价值。 综合评分: 91 文章分类: 漏洞分析,漏洞POC,WEB安全,安全建设


cover_image

CVE-2026-27199(Werkzeug)研究

sec0nd安全

2026年2月21日 20:12 河北

以下文章来源于MicroPest ,作者MicroPest

MicroPest .

个人开发的小工具

从Github-CVE监测平台中发现了这个漏洞。着手看了下,发现危害面挺广,危害性也挺大。

WerkZeug是什么?

Werkzeug 是一个用于 Python 的 WSGI(Web Server Gateway Interface)工具库,它是构建 Web 应用程序的基础工具包。

核心特点

与 Flask 的关系

Werkzeug 最著名的应用是作为 Flask 框架的底层基础

  • Flask 构建在 Werkzeug 和 Jinja2 之上
  • Werkzeug 处理 HTTP 协议层面的细节
  • Flask 提供更高级的 Web 框架功能

现在很多的web都是基于Flask的 ,所以,这个漏洞的危害性还是很大的。

我们来验证一下。

一、漏洞情况

这个漏洞涉及 Werkzeug 的 safe_join() 函数:

  • 漏洞类型:Windows 设备名称绕过
  • 影响版本< 3.1.6
  • 修复版本3.1.6
  • 问题:在 Windows 系统上,safe_join() 函数未能正确拦截嵌套路径中的设备名称(如 NULCONPRN 等)

1、攻击原理

safe_join(base, ‘NUL’)        → 被正确拦截(返回 None)

safe_join(base, ‘subdir/NUL’) → 绕过检查(返回危险路径)

攻击者可以利用这个漏洞在 Windows 系统上写入特殊设备文件,导致数据被静默丢弃或其他未预期的行为。

总结:Werkzeug 是 Python Web 开发生态系统中非常重要的基础库,为 Flask 等众多框架提供底层 HTTP 处理能力。这个 CVE 漏洞提醒我们即使成熟的库也可能存在平台特定的安全问题,及时更新依赖非常重要。

2、漏洞的危害性?

它的危害性主要不在“读到别人的文件/目录穿越”,而在于:应用以为拿到的是一个普通磁盘路径,实际却可能指向 Windows 的保留设备对象,导致业务逻辑被破坏。

  • 静默数据丢失(完整性破坏) :例如上传/导出/生成报表时把文件写到 subdir\NUL ,写入会“成功”,但内容被系统直接丢弃;应用可能仍然返回成功、写入计数正常,后续读取却发现文件为空/不存在,引发数据一致性问题。

  • 拒绝服务(可卡死请求/线程) :读取或通过 send_from_directory() 之类接口返回静态文件时,如果路径落到 CON 等设备,可能发生阻塞等待输入或异常行为,造成单请求卡死、线程池耗尽、服务不可用(典型 DoS)。

  • 绕过业务约束/审计(安全边界错判) :很多程序把 safe_join() 当作“只会返回安全文件路径”的信任边界;一旦返回了设备路径,后续的权限控制、文件类型校验、大小限制、审计记录都可能失真(比如记录了“写入某文件成功”,实际没落盘)。

  • 影响面取决于使用场景 :最容易出问题的是“把用户提供的文件名/路径拼到某个目录下再读写”的功能,例如文件上传落盘、附件下载、模板/缓存文件读写、静态文件服务等;且 仅在 Windows 上成立。

二、检测代码

1、https://github.com/alimezar/CVE-2026-27199-werkzeug-safe-join-bypass-PoC,给出了一个poc验证代码;

2、或 为了通俗易懂,我们编写了检测代码(一段检测 Werkzeug 版本并判断是否存在 CVE-2026-27199 漏洞的 Python 代码),并在我的windows上检测:

check_werkzeug.py

!/usr/bin/env python3

“””Werkzeug 版本检测工具

检测 CVE-2026-27199 漏洞(Windows safe_join 绕过)”””

import sys

import os

def get_werkzeug_version():

    “””    获取 Werkzeug 版本(兼容不同版本)    “””

    try:

        import werkzeug

     # 尝试多种方式获取版本

        # 方式1: __version__ (旧版本)

        if hasattr(werkzeug, ‘__version__’):

            return werkzeug.__version__

        # 方式2: version (新版本)

        if hasattr(werkzeug, ‘version’):

            return werkzeug.version

        # 方式3: 从 metadata 获取 (Python 3.8+)

        try:

            from importlib.metadata import version

            return version(‘werkzeug’)

        except ImportError:

            try:

                from importlib_metadata import version

                return version(‘werkzeug’)

            except ImportError:

                pass

        # 方式4: 从 pkg_resources 获取

        try:

            import pkg_resources

            return pkg_resources.get_distribution(‘werkzeug’).version

        except Exception:

            pass

        return None

    except ImportError:

        return None

def check_werkzeug_version():

    “””    检测 Werkzeug 版本及漏洞状态    “””

    print(“=” * 60)

    print(“Werkzeug 版本检测工具”)

    print(“CVE-2026-27199 漏洞检测”)

    print(“=” * 60)

    # 检测操作系统

    print(f”\n[*] 操作系统: {os.name}”)

    if os.name == ‘nt’:

        print(“[*] 检测到 Windows 系统(漏洞影响平台)”)

    else:

        print(“[*] 非 Windows 系统(漏洞不影响)”)

    # 获取 Werkzeug 版本

    version = get_werkzeug_version()

    if version is None:

        print(“\n[-] Werkzeug 未安装”)

        print(“[+] 系统不存在 CVE-2026-27199 漏洞风险”)

        return False

    print(f”\n[+] Werkzeug 已安装”)

    print(f”[*] 当前版本: {version}”)

    # 解析版本号

    try:

        version_parts = version.split(‘.’)

        major = int(version_parts[0])

        minor = int(version_parts[1])

        patch = int(version_parts[2]) if len(version_parts) > 2 else 0

        print(f”[*] 版本解析: {major}.{minor}.{patch}”)

    except (ValueError, IndexError):

        print(f”[!] 无法解析版本号: {version}”)

        return None

    # 判断漏洞状态

    # CVE-2026-27199 影响版本: < 3.1.6

    # 修复版本: >= 3.1.6

    is_vulnerable = False

    if major < 3:

        is_vulnerable = True

    elif major == 3:

        if minor < 1:

            is_vulnerable = True

        elif minor == 1:

            if patch < 6:

                is_vulnerable = True

    print(“\n” + “-” * 60)

    if is_vulnerable:

        print(“[!] 漏洞状态: 存在 CVE-2026-27199 漏洞”)

        print(f”[!] 受影响版本: {version} (< 3.1.6)”)

        print(“[!] 修复版本: >= 3.1.6”)

        if os.name == ‘nt’:

            print(“\n[!] 风险提示: Windows 系统上可被利用”)

            print(”    攻击者可通过嵌套路径绕过 safe_join() 限制”)

            print(”    示例: ‘subdir/NUL’ 会被错误地视为安全路径”)

        else:

            print(“\n[*] 注意: 非 Windows 系统不受此漏洞影响”)

        print(“\n[>] 修复命令:”)

        print(”    pip install \”werkzeug>=3.1.6\””)

        return True

    else:

        print(“[+] 漏洞状态: 已修复”)

        print(f”[+] 当前版本 {version} 不受 CVE-2026-27199 影响”)

        return False

def test_safe_join():

    “””    实际测试 safe_join 是否存在漏洞    “””

    print(“\n” + “=” * 60)

    print(“safe_join() 功能测试”)

    print(“=” * 60)

try:

        from werkzeug.security import safe_join

        import tempfile

    except ImportError as e:

        print(f”[-] 无法导入所需模块: {e}”)

        return None

    with tempfile.TemporaryDirectory() as tmpdir:

        print(f”\n[*] 测试目录: {tmpdir}”)

        test_cases = [

            (‘NUL’, ‘根目录设备名’),

            (‘subdir/NUL’, ‘嵌套路径设备名’),

            (‘a/b/COM1’, ‘深层嵌套 COM 设备’),

            (‘uploads/CON.txt’, ‘带扩展名的 CON 设备’),

            (‘normal/path/file.txt’, ‘正常路径’),

        ]

        vulnerable = False

       # for payload, desc in test_cases:

            try:

                result = safe_join(tmpdir, payload)

                if result is None:

                    status = “[+] 已拦截”

                else:

                    status = “[!] 已绕过”

                    # 检查是否包含设备名

                    upper_payload = payload.upper()

                    devices = [‘NUL’, ‘CON’, ‘PRN’, ‘AUX’, ‘COM1’, ‘COM2’, ‘COM3’,

                              ‘COM4’, ‘COM5’, ‘COM6’, ‘COM7’, ‘COM8’, ‘COM9’,

                              ‘LPT1’, ‘LPT2’, ‘LPT3’, ‘LPT4’, ‘LPT5’, ‘LPT6’,

                              ‘LPT7’, ‘LPT8’, ‘LPT9’]

                    if any(device in upper_payload for device in devices):

                        vulnerable = True

                    print(f”    {status} {desc}: ‘{payload}'”)

                if result:

                    print(f”        -> {result}”)

                except Exception as e:

                print(f”    [!] 测试异常 {desc}: {e}”)

        print(“\n” + “-” * 60)

        if vulnerable:

            print(“[!] 测试结果: safe_join() 存在绕过漏洞”)

        else:

            print(“[+] 测试结果: safe_join() 工作正常”)

        return vulnerable

def main():

    “””    主函数    “””

    version_vulnerable = check_werkzeug_version()

    func_vulnerable = test_safe_join()

    print(“\n” + “=” * 60)

    print(“检测总结”)

    print(“=” * 60)

    if version_vulnerable is True or func_vulnerable is True:

        print(“\n[!] 综合结论: 系统存在 CVE-2026-27199 漏洞”)

        print(“[!] 建议立即升级: pip install \”werkzeug>=3.1.6\””)

        sys.exit(1)

    elif version_vulnerable is False and func_vulnerable is False:

        print(“\n[+] 综合结论: 系统安全,不存在 CVE-2026-27199 漏洞”)

        sys.exit(0)

    else:

        print(“\n[*] 综合结论: 无法完全确定漏洞状态,建议手动检查”)

        sys.exit(2)

if __name__ == “__main__”:

    main()

检测结果如图:

3、或 运行以下命令检查是否有代码使用 safe_join

在项目目录中搜索

grep -r “safe_join” –include=”*.py” .

或 在 Windows PowerShell:

Get-ChildItem -Recurse -Filter *.py | Select-String -Pattern “safe_join”

如果搜索结果为空,说明您的应用未直接使用该函数,漏洞风险较低(但依赖的框架如 Flask 可能在内部使用)。

三、攻击的一些研究

1、我们展示一下攻击面的“想象”:

攻击面长什么样(高层)

  • 前提:目标运行在 Windows,且使用 werkzeug < 3.1.6 。

  • 关键点:应用把“用户可控的路径/文件名”交给 safe_join(base, user_input) ,然后把返回值继续用于 open() 、 send_from_directory() 、保存上传文件等。

  • 滥用方式:攻击者让 user_input 的 某个子路径段 变成 Windows 设备名(例如 NUL/CON/PRN/AUX/COM1… ),在旧版本里可能被 safe_join 误判为安全路径,从而触发设备对象语义(写入丢弃、读取阻塞等)。

会造成的具体效果(从攻击者视角的“目标”)

  • 让服务端“写文件成功但数据不落盘”(破坏完整性/业务混乱)。

  • 让某些读取/静态文件返回路径触发阻塞或异常(DoS)。

  • 让审计/日志与真实落盘行为不一致(误导运维排查)。

2、典型攻击场景

场景一:文件上传功能攻击

存在漏洞的典型代码(Flask应用)

from werkzeug.security import safe_join

from flask import Flask, request

app = Flask(__name__)

@app.route(‘/upload’, methods=[‘POST’])

def upload_file():

    filename = request.form[‘filename’]  # 用户可控!

    upload_dir = ‘/var/www/uploads’

    # 看似安全的代码,实际存在漏洞

    safe_path = safe_join(upload_dir, filename)

    if safe_path is None:

        return “非法路径”, 403

    # 保存文件

    with open(safe_path, ‘wb’) as f:

        f.write(request.files[‘file’].read())

    return “上传成功”

攻击者利用:

filename = “avatar/NUL” 或 “2024/CON”

结果:数据被写入 NUL 设备,永久丢失

攻击步骤:

(1). 正常上传尝试(被拦截)

curl -X POST -F “filename=NUL” -F “[email protected]” http://target/upload

结果:403 Forbidden

(2). 绕过尝试(成功)

curl -X POST -F “filename=photos/NUL” -F “[email protected]” http://target/upload

结果:200 OK,但文件消失!

#

场景二:日志/数据写入攻击

应用日志记录功能

def write_log(user_input, log_data):

    log_dir = ‘/app/logs’

    safe_path = safe_join(log_dir, user_input)

    if safe_path:

        with open(safe_path, ‘a’) as f:

            f.write(log_data)

        return True

    return False

攻击者利用:让所有日志写入 NUL(黑洞)

write_log(“2024/NUL”, “用户登录记录…”)

结果:所有审计日志被静默丢弃!

#

场景三:配置文件覆盖攻击(结合路径遍历)

如果应用还有其他路径处理问题

filename = “../../../Windows/System32/drivers/etc/hosts”

先经过 safe_join 检查…

但利用设备名可以干扰文件操作

filename = “config/COM1”

可能导致服务异常或配置写入失败

#

3、利用代码结果演示,如图:

#

四、升级前后的代码比较

1、找到当前版本的 safe_join 源码extract_safe_join.py

!/usr/bin/env python3

“””查找并导出 Werkzeug safe_join 源码

用于升级前后的代码对比”””

import os

import inspect

import shutil

from pathlib import Path

def find_safe_join_source():

    “””    定位 safe_join 函数的源码文件    “””

    try:

        from werkzeug.security import safe_join

        import werkzeug

        # 获取 Werkzeug 安装路径

        werkzeug_path = os.path.dirname(werkzeug.__file__)

        print(f”[+] Werkzeug 安装路径: {werkzeug_path}”)

        print(f”[*] 当前版本: {getattr(werkzeug, ‘__version__’, ‘unknown’)}”)

        # 找到 security.py 文件

        security_file = os.path.join(werkzeug_path, ‘security.py’)

        if os.path.exists(security_file):

            print(f”[+] 找到源码文件: {security_file}”)

            return security_file

        else:

            print(f”[-] 未找到 security.py,尝试搜索…”)

            # 递归搜索

            for root, dirs, files in os.walk(werkzeug_path):

                if ‘security.py’ in files:

                    found = os.path.join(root, ‘security.py’)

                    print(f”[+] 找到: {found}”)

                    return found

            return None

    except ImportError as e:

        print(f”[-] 导入错误: {e}”)

        return None

def extract_safe_join_function(file_path):

    “””

    从 security.py 中提取 safe_join 函数的源码

    “””

    print(f”\n[*] 正在提取 safe_join 函数…”)

    with open(file_path, ‘r’, encoding=’utf-8′) as f:

        content = f.read()

    # 找到 safe_join 函数定义

    lines = content.split(‘\n’)

    func_lines = []

    in_function = False

    indent_level = None

    for i, line in enumerate(lines):

        # 找到函数定义

        if line.strip().startswith(‘def safe_join(‘):

            in_function = True

            indent_level = len(line) – len(line.lstrip())

            func_lines.append(line)

            print(f”[+] 找到函数定义在第 {i+1} 行”)

            continue

        if in_function:

            # 检查是否是函数结束(遇到相同或更少缩进的非空行)

            if line.strip() and not line.strip().startswith(‘#’):

                current_indent = len(line) – len(line.lstrip())

                if current_indent <= indent_level and line.strip():

                    break

            func_lines.append(line)

    return ‘\n’.join(func_lines)

def save_source_backup(source_code, version):

    “””    保存源码备份    “””

    backup_dir = Path.home() / ‘werkzeug_source_backups’

    backup_dir.mkdir(exist_ok=True)

    # 保存完整文件

    source_file = find_safe_join_source()

    if source_file:

        shutil.copy2(source_file, backup_dir / f’security_{version}_before.py’)

        print(f”[+] 完整文件已保存: {backup_dir / f’security_{version}_before.py’}”)

    # 保存提取的函数

    func_file = backup_dir / f’safe_join_{version}_before.py’

    with open(func_file, ‘w’, encoding=’utf-8′) as f:

        f.write(f”# Werkzeug {version} – safe_join function\n”)

        f.write(“# Extracted before upgrade\n”)

        f.write(“=” * 60 + “\n\n”)

        f.write(source_code)

    print(f”[+] 函数源码已保存: {func_file}”)

    return backup_dir

def main():

    print(“=” * 60)

    print(“Werkzeug safe_join 源码提取工具”)

    print(“=” * 60)

    # 1. 找到源码

    source_file = find_safe_join_source()

    if not source_file:

        print(“[-] 无法找到源码文件”)

        return

    # 2. 提取函数

    func_code = extract_safe_join_function(source_file)

    if not func_code:

        print(“[-] 无法提取函数源码”)

        return

    print(f”\n{‘=’*60}”)

    print(“提取的 safe_join 源码:”)

    print(f”{‘=’*60}”)

    print(func_code)

    # 3. 获取版本号

    try:

        import werkzeug

        version = getattr(werkzeug, ‘__version__’, ‘unknown’)

    except:

        version = ‘unknown’

    # 4. 保存备份

    backup_dir = save_source_backup(func_code, version)

    print(f”\n{‘=’*60}”)

    print(“后续步骤:”)

    print(f”{‘=’*60}”)

    print(f”””

  1. 源码已备份到: {backup_dir}

  2. 升级 Werkzeug:

   pip install “werkzeug>=3.1.6”

  1. 升级后再次运行此脚本,保存新版本源码

  2. 对比两个版本:

   – 使用 diff 工具

   – 或手动比较 security_X.X.X_before.py 文件

  1. 重点关注:

   – 设备名检查逻辑的变化

   – 路径分割方式的变化

   – 新增的安全检查条件

    “””)

    # 同时输出到控制台方便查看

    print(f”\n{‘=’*60}”)

    print(“完整提取的函数代码:”)

    print(f”{‘=’*60}”)

    print(func_code)

if __name__ == “__main__”:

    main()

2、升级前后对比脚本compare_safe_join.py

!/usr/bin/env python3

“””比较 Werkzeug safe_join 升级前后的代码差异

修复版 – 处理 unknown 版本号情况”””

import os

import difflib

from pathlib import Path

def find_backup_files():

    “””    查找备份的源码文件(支持 unknown 版本号)    “””

    backup_dir = Path.home() / ‘werkzeug_source_backups’

    if not backup_dir.exists():

        print(f”[-] 备份目录不存在: {backup_dir}”)

        return None, None

    print(f”[*] 搜索目录: {backup_dir}”)

    # 列出所有文件

    all_files = list(backup_dir.glob(‘*.py’))

    print(f”[*] 找到 {len(all_files)} 个文件:”)

    for f in all_files:

        print(f”    – {f.name}”)

    # 查找 security_*.py 文件

    security_files = list(backup_dir.glob(‘security_*.py’))

    if len(security_files) >= 2:

        # 按修改时间排序,最新的两个

        security_files.sort(key=lambda x: x.stat().st_mtime, reverse=True)

        old_file = str(security_files[1])

        new_file = str(security_files[0])

        print(f”\n[+] 找到旧版本: {security_files[1].name}”)

        print(f”[+] 找到新版本: {security_files[0].name}”)

        return old_file, new_file

    elif len(security_files) == 1:

        print(f”\n[*] 只找到一个版本: {security_files[0].name}”)

        print(“[*] 将提取当前版本进行对比”)

        return str(security_files[0]), None

    else:

        print(“[-] 未找到任何备份文件”)

        return None, None

def extract_current_version():

    “””    提取当前安装的版本    “””

    try:

        from werkzeug.security import safe_join

        import werkzeug

        # 尝试多种方式获取版本

        version = ‘unknown’

        if hasattr(werkzeug, ‘__version__’):

            version = werkzeug.__version__

        elif hasattr(werkzeug, ‘version’):

            version = werkzeug.version

        import inspect

        source = inspect.getsource(safe_join)

        # 保存

        backup_dir = Path.home() / ‘werkzeug_source_backups’

        backup_dir.mkdir(exist_ok=True)

        new_file = backup_dir / f’security_{version}_after.py’

        with open(new_file, ‘w’, encoding=’utf-8′) as f:

            f.write(f”# Werkzeug {version} – safe_join function\n”)

            f.write(“# Extracted after upgrade\n”)

            f.write(“=” * 60 + “\n\n”)

            f.write(source)

        print(f”[+] 当前版本 ({version}) 源码已保存: {new_file}”)

        return str(new_file)

    except Exception as e:

        print(f”[-] 提取失败: {e}”)

        import traceback

        traceback.print_exc()

        return None

def compare_versions(old_file, new_file):

    “””    比较两个版本的源码差异    “””

    print(f”\n{‘=’*60}”)

    print(“开始对比”)

    print(f”{‘=’*60}”)

    print(f”旧版本: {old_file}”)

    print(f”新版本: {new_file}”)

    with open(old_file, ‘r’, encoding=’utf-8′) as f:

        old_lines = f.readlines()

    with open(new_file, ‘r’, encoding=’utf-8′) as f:

        new_lines = f.readlines()

    # 生成 unified diff

    diff = difflib.unified_diff(

        old_lines,

        new_lines,

        fromfile=’before_upgrade’,

        tofile=’after_upgrade’,

        lineterm=”

    )

    print(f”\n{‘=’*60}”)

    print(“代码差异 (Unified Diff):”)

    print(f”{‘=’*60}”)

    diff_text = list(diff)

    if diff_text:

        for line in diff_text:

            print(line)

    else:

        print(“[+] 两个版本完全相同”)

    # 生成 HTML 报告

    html_diff = difflib.HtmlDiff().make_file(old_lines, new_lines, ‘Before Upgrade’,   ‘After Upgrade’)

    report_path = Path.home() / ‘werkzeug_source_backups’ / ‘diff_report.html’

    with open(report_path, ‘w’, encoding=’utf-8′) as f:

        f.write(html_diff)

    print(f”\n[+] HTML 对比报告已保存: {report_path}”)

    # 关键变化分析

    print(f”\n{‘=’*60}”)

    print(“关键变化分析:”)

    print(f”{‘=’*60}”)

    analyze_changes(old_lines, new_lines)

def analyze_changes(old_lines, new_lines):

    “””    分析关键安全变化    “””

    old_code = ”.join(old_lines)

    new_code = ”.join(new_lines)

    checks = [

        (‘设备名检查’, [‘NUL’, ‘CON’, ‘PRN’, ‘AUX’, ‘COM’, ‘LPT’]),

        (‘路径分割’, [‘split’, ‘os.sep’, ‘replace’]),

        (‘大小写处理’, [‘upper()’, ‘lower()’, ‘case’]),

        (‘路径规范化’, [‘normpath’, ‘abspath’, ‘realpath’]),

        (‘循环检查’, [‘for ‘, ‘while ‘, ‘in parts’]),

        (‘多参数支持’, [‘*pathnames’, ‘*filenames’]),

    ]

    for name, keywords in checks:

        old_has = any(k in old_code for k in keywords)

        new_has = any(k in new_code for k in keywords)

        if old_has and new_has:

            print(f”[*] {name}: 两个版本都有”)

        elif not old_has and new_has:

            print(f”[+] {name}: 新版本新增!(关键修复)”)

        elif old_has and not new_has:

            print(f”[-] {name}: 新版本移除”)

        else:

            print(f”[ ] {name}: 两个版本都没有”)

def main():

    print(“=” * 60)

    print(“Werkzeug safe_join 版本对比工具 (修复版)”)

    print(“=” * 60)

    # 查找已有备份

    old_file, new_file = find_backup_files()

    if not old_file:

        print(“[-] 未找到任何备份,请先运行提取工具”)

        return

    # 如果没有新版本,提取当前版本

    if not new_file:

        print(“\n[*] 正在提取当前安装的版本…”)

        new_file = extract_current_version()

        if not new_file:

            print(“[-] 无法提取当前版本”)

            return

    # 确保不是同一个文件

    if os.path.samefile(old_file, new_file):

        print(“[-] 错误:新旧版本是同一个文件”)

        print(“[*] 请先升级 Werkzeug,然后重新运行本工具”)

        return

    # 比较

    compare_versions(old_file, new_file)

    print(f”\n{‘=’*60}”)

    print(“对比完成!”)

    print(f”{‘=’*60}”)

    print(f”””

文件位置:

  • 旧版本: {old_file}

  • 新版本: {new_file}

  • HTML报告: {Path.home() / ‘werkzeug_source_backups’ / ‘diff_report.html’}

建议:

  1. 用浏览器打开 diff_report.html 查看可视化对比

  2. 关注标记为 [+] 的新增安全特性

  3. 理解修复逻辑以应用到自己的代码中

    “””)

if __name__ == “__main__”:

    main()

3、使用步骤

第一步:升级前提取源码

运行提取工具

python extract_safe_join.py

第二步:升级 Werkzeug

pip install “werkzeug>=3.1.6”

第三步:升级后对比

运行对比工具

python compare_safe_join.py

#


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:sec0nd安全 《CVE-2026-27199(Werkzeug)研究》

评论:0   参与:  0