白帽故事 · 2026年1月23日

CVE-2026-22200:osTicket工单直通系统Shell的漏洞剖析

摘要

Horizon3.ai的研究团队在流行的开源服务台系统osTicket中发现了一个受CTF启发、编号为CVE-2026-22200的安全漏洞。该漏洞允许匿名攻击者通过向工单中注入恶意的PHP过滤器链表达式,然后将工单导出为PDF文档,从而读取服务器上的任意文件。攻击者可以利用该漏洞窃取敏感文件(这些文件会以位图图像形式嵌入PDF中);若结合利用CVE-2024-2961(亦称CNEXT),甚至可实现远程代码执行。该问题已在osTicket 1.18.3 / 1.17.7版本中修复,研究团队强烈建议所有用户升级至最新版本。

背景

osTicket界面

osTicket是一款广受欢迎的开源服务台系统,常被寻求轻量级、自托管支持解决方案的组织采用。Horizon3.ai的研究人员经常在SLED(州、地方及教育)部门以及其他中端市场至中小型企业环境中发现其部署。由于其数千个实例暴露在互联网上,且更多实例部署于内网,该系统的攻击面相当可观。

工单系统通常是攻击者的高价值目标,它们通常包含令牌或凭据等敏感信息,并可能成为攻击者横向移动进入内部网络的跳板。近期在野被利用的工单系统漏洞包括影响SolarWinds Web Help Desk的CVE-2024-28986/CVE-2024-28987和影响SysAid的CVE-2025-2775/CVE-2025-2776。

从架构上看,osTicket是一个老派的PHP应用程序,最初于2003年发布,已接受过包括SonarSourceCheckmarx在内的多方深入研究。尽管如此,研究人员在梳理代码库时,发现该应用对老旧第三方库的依赖值得进一步探索。结合PHP过滤器链利用技术的最新进展,他们决定从新的视角审视其安全性。

漏洞详解

从汇点入手:mPDF库

研究团队首先分析了mPDF,这是osTicket用于将支持工单生成为PDF文档的第三方PHP库。任何被授权查看工单的用户均可使用此功能,若服务台配置为允许访客访问工单(此为默认设置),则未认证的访客亦可使用。

PDF库因其需要衔接HTML/CSS与PDF这两种复杂格式而著称。将这些库集成到应用中时,一个常见的故障点是对外部资源(如图片、样式表)的处理。在将URL或本地文件路径传递给PDF生成器之前,调用应用需对其进行何种程度的净化往往不明确;且生成器本身也可能存在缺陷,这可能导致服务器端请求伪造或本地文件读取漏洞。

在研究mPDF时,研究人员偶然发现了一个来自HITCON CTF 2022、由@_splitline_提出的相关且有趣的CTF挑战web2pdf。该挑战探讨了如何利用mPDF读取任意本地文件,所用的HTML片段非常简单:

<img src="<malicious_url>" />

解决该挑战的技巧主要有两点:

  • 路径规范化绕过: mPDF试图将phar://php://等危险的URI方案列入黑名单。然而,该库的路径处理中存在一个错误,允许攻击者使用php:\\./php://等变体路径绕过此检查。问题在于,mPDF是在规范化之前依据流包装器黑名单检查URL的。此Bug在mPDF的最新版本中依然存在。
  • PHP过滤器魔法: 为了绕过mPDF的图像验证,攻击者需使用PHP过滤器链在任意文件内容前添加一个有效的位图(BMP)文件头。这使得mPDF误将任意文件(如/etc/passwd)作为有效图像渲染到PDF中。随后,敏感数据可以从PDF中的位图提取出来。

BMP技巧尤为实用,因为它允许攻击者单次高效地窃取文件内容,而无需采用速度较慢的基于错误的预言机方法。

第一个障碍:不同版本的mPDF

在尝试将CTF解决方案应用于osTicket时,研究团队遇到的第一个障碍是,osTicket使用的是2019年前后的一个非常旧的mPDF版本。web2pdf挑战中使用的规范化绕过方法,在该版本中并不存在。

然而,他们发现了另一种涉及URL编码的绕过方法。像php%3a//这样的URL编码流包装器可以绕过流包装器黑名单检查。这是因为旧版mPDF中的逻辑是:先依据流包装器黑名单检查本地资源,之后才对其进行URL解码,然后才进行访问。

mPDF旧版本中URL解码逻辑的位置

解码后的资源随后通过file_get_contents函数访问:

使用file_get_contents访问解码后的资源

第二个障碍:HTML净化

即使有了针对mPDF的绕过方法,攻击载荷仍需穿过osTicket的输入验证层。工单中的所有富文本HTML内容都会被清理,并交由htmLawed第三方库处理,该库通过中和可疑标签和属性来净化输入。

htmLawed采用基于白名单的方法严格检查URI方案,并且足够智能,能够识别像%3a这样的URL编码冒号。因此,类似下面的输入URI:

<img src="php%3a//myurl" />

会被添加denied:前缀进行中和:

htmLawed对URL编码的URI进行中和

其他图像属性如srcset也遇到同样问题。style属性中的所有URI也会被完全阻止。例如,像这样的style属性:

<ul>
  <li style="list-style-image:url(http://myurl.com)">listitem</li>
</ul>

同样会被添加denied:前缀中和:

htmLawed阻止style属性中的URI

绕过HTML净化

在测试style属性时,研究团队注意到htmLawed中存在一个微妙的解析差异。如果在url关键字和开括号之间包含空格,URI就能逃脱净化器的白名单检查。

例如,以下载荷就完全通过了htmLawed的净化:
<ul><li style="list-style-image:url (http://myurl.com)">listitem</li></ul>

带空格的URL绕过htmLawed检查

然而,这并非意味着成功。mPDF遵循严格的CSS标准,期望的是url()格式,中间没有多余的空格。如果保留空格,漏洞利用会在汇点(mPDF)失败;如果去掉空格,htmLawed又会阻止该URI。

但在此测试过程中,他们注意到输出会被奇怪地截断:

<ul>
  <li style="list-style-image:url (http">listitem</li>
</ul>

他们发现,osTicket向htmLawed注册了一个自定义的净化后回调函数__html_cleanup,该函数对style属性执行了额外的字符串操作。

osTicket中危险的__html_cleanup回调函数代码

这段代码处理的是htmLawed已经清理过的输出,因此十分危险。它执行了多种转换,其中最重要的是HTML实体解码和字符剥离。研究团队面临的挑战是构思一个有效载荷,使其既能通过htmLawed,又能经受住__html_cleanup中对关键字符(引号、分号、冒号等)的处理,并最终被转换为mPDF可以接受的格式。他们最终构造出的载荷如下:

<ul><li style="list-style-image:url&#34(php%3a//myurl)">listitem</li></ul>

这个载荷使用了代表双引号"的HTML实体&#34来绕过htmLawed。该实体随后在__html_cleanup中被解码并剥离。值得注意的是,实体不需要结尾的分号。事实上,使用"反而会破坏__html_cleanup中的解析逻辑。

利用HTML实体构建的最终有效载荷

该有效载荷从输入到最终生效的完整流程如下:

  1. 恶意输入: url&#34(php%3a//myurl)
  2. htmLawed输出(未改动): url&#34(php%3a//myurl)
  3. __html_cleanup中的实体解码: url"(php%3a//myurl)
  4. __html_cleanup中的字符剥离: url(php%3a//myurl)
  5. mPDF中的URL解码: url(php://myurl)

组合利用:利用PDF生成功能读取文件

在有了可工作的有效载荷后,该团队接下来展示了端到端的漏洞利用流程。他们假设的场景是:在Ubuntu上运行默认配置的osTicket 1.18.2版本,且电子邮件功能已配置。所有相关的利用脚本都托管在https://github.com/horizon3ai/CVE-2026-22200

获取工单访问权限

要触发PDF导出功能,攻击者首先必须能够查看已提交的工单。在默认的osTicket配置下,匿名攻击者有两种路径:

  • 自助注册: 如果启用(默认),攻击者可注册账户、登录并创建工单。
  • 暴力破解: 如果自助注册被禁用,攻击者可以访客身份提交工单,然后通过“检查工单状态”表单暴力破解访问权限。

暴力破解路径借助了以下条件:

  • 检查工单状态预言机: “检查工单状态”表单充当了一个预言机,当电子邮箱和工单号码组合有效时会予以确认。若有效,系统会将工单访问链接发送到用户的电子邮箱。
  • 小的工单号码空间: 默认情况下,工单号码空间为6位数字,从100000到999999。
  • 绕过速率限制: 每次请求前开启一个新会话,即可绕过针对每个用户的速率限制保护。这也规避了暴力破解尝试的日志记录。
  • 创建多个工单: 攻击者可创建多个工单(例如100个),由于工单号码在6位数字空间中随机分布,此举可极大加速暴力破解过程。

在团队的测试中,通过标准互联网连接进行暴力破解,通常可在不到一小时内轻松完成。

% python osticket_access_bruteforce.py http://osticket.example.com '[email protected]' --threads 20

======================================================================
osTicket 工单访问链接枚举脚本
======================================================================

目标:        http://osticket.example.com/
邮箱:         [email protected]
工单范围:  100000 - 999999
延迟:         0.5 秒
线程数:       20

[*] 扫描开始于: 2026-01-21 14:47:16

[i] 进度: 100/900000 已测试, 0 个有效发现
[i] 进度: 200/900000 已测试, 0 个有效发现
[i] 进度: 300/900000 已测试, 0 个有效发现
>>已截断<<
[i] 进度: 27000/900000 已测试, 0 个有效发现
[i] 进度: 27100/900000 已测试, 0 个有效发现
[i] 进度: 27200/900000 已测试, 0 个有效发现
[+] 有效: 工单 #127227 - 访问链接已发送(需要邮箱验证)
[i] 进度: 27300/900000 已测试, 1 个有效发现
[i] 进度: 27400/900000 已测试, 1 个有效发现
[i] 进度: 27500/900000 已测试, 1 个有效发现
>>已截断<<

某些非默认但较为常见的设置使获取工单访问权限变得更加容易:

  • 自动回复器: 若启用了“新工单:工单所有者”自动回复器,系统会立即向提交新工单的任何人发送工单访问链接的邮件。
  • 用户界面自定义: 若工单提交模板被修改为直接显示工单号码,则根本无需暴力破解。

将有效载荷注入工单

获取工单访问权限后,攻击者便可向工单中注入针对服务器上特定文件的有效载荷。在下例中,他们生成了一个用于窃取/etc/passwd和敏感文件include/ost-config.php的载荷。此恶意字符串被直接放置于工单的富文本HTML内容中。

将文件读取载荷注入工单

处理工单回复

如果攻击者想在工单已开放的情况下针对更多文件,可通过回复工单来注入更多载荷。然而,系统处理工单回复的方式与创建工单略有不同:它执行了两次HTML实体解码,而非一次。

为此,载荷必须被再次编码。这种情况下,载荷需要使用嵌套的实体序列&#3&#52来替代&#34,才能在双重解码过程中存活下来,并以正确格式到达mPDF汇点。其osticket_ticket_payload_gen脚本通过--reply标志来处理此问题。

从PDF中提取数据

一旦工单包含恶意HTML,攻击者导航至工单视图并选择“打印”为PDF。这会强制mPDF处理注入的list-style-image属性,解析PHP过滤器链,并渲染目标文件。

打印工单为PDF以触发漏洞利用

被窃取的数据以位图图像形式嵌入生成的PDF中。这些文件可通过从PDF的图像对象中剥离伪造的BMP文件头来提取。

从PDF中提取被窃取的数据

文件读取的特殊性

在测试过程中,研究团队发现了几个影响数据窃取可靠性的细微之处:

  • 大写字母敏感性: 他们观察到包含大写字母的文件路径有时会窃取失败。其osticket_ticket_payload_gen脚本通过URL编码载荷中的大写字母来解决此问题。
  • 编码变化: 标准过滤器链对文本文件有效,但二进制文件可能存在问题。他们发现,在BMP转换之前,将数据包装在Base64zlib+Base64过滤器中,对二进制文件能产生稳定结果。其osticket_ticket_payload_gen脚本提供了这些编码选项。
  • 大小限制: 被窃取的图像通常截断在大约45KB处。这对于捕获配置文件和凭据来说绰绰有余,但可能会限制对二进制文件、数据库文件和大日志文件的窃取。

任意文件读取的影响

攻击者在osTicket中利用任意文件读取能做些什么?除了像/etc/passwd这样的标准系统文件外,主要目标是位于应用程序Web根目录下的配置文件include/ost-config.php

# 加密/解密密钥 - 安装过程中随机生成。

define('SECRET_SALT','SEFDaIg1UP=Rh0xHE=Ij6Lew8u49L=Tt');

# 默认管理员邮箱。仅用于数据库连接问题及相关警报。

define('ADMIN_EMAIL','[email protected]');

# 数据库选项

# ====================================================

# Mysql 登录信息

#

define('DBTYPE','mysql');

# DBHOST 可以包含逗号分隔的主机 (例如 db1:6033,db2:6033)

define('DBHOST','localhost');
define('DBNAME','osticket');
define('DBUSER','osticket');
define('DBPASS','XXXXXXXX');

该文件包含访问osTicket数据库的凭据,以及一个用于加密操作的SECRET_SALT值。如果数据库暴露在外,攻击者即可访问并转储所有工单数据。此外,数据库密码本身也是对其他组织账户进行凭据填充攻击的潜在目标。

SECRET_SALT是一个主密钥,用于加密/解密数据库中的敏感配置,如LDAP凭据、SMTP凭据和AWS访问密钥。需要注意的是,即使数据库未暴露,在osTicket 1.18.2之前的版本中,存在一个重大的SQL注入漏洞CVE-2025-26241,该漏洞能使任何经过身份验证的用户(包括自助注册用户)转储osTicket数据库的内容。当结合利用此CVE-2026-22200漏洞(可获取SECRET_SALT)时,攻击者便能完全读取数据库内容。

SECRET_SALT还用于生成访问令牌的链接。

在Windows安装环境中,影响可能更大;攻击者很可能能够访问已加入域的计算机上的远程SMB共享文件,并通过强制身份验证尝试,泄露运行osTicket的服务账户的NTLM哈希值。

伪造工单访问权限

SECRET_SALT值还允许攻击者绕过身份验证来获取工单访问权限。

osTicket默认允许访客用户使用访问链接直接查看工单,无需登录。生成此访问链接有两种方法,其展示了一种较旧但仍有效、且更容易伪造的方法。

该过时方法生成的访问链接基于四个组件构建:

  • 内部工单ID(一个自增的标识符)
  • 外部工单号码(默认为6位数字)
  • 用户电子邮箱
  • SECRET_SALT

除SECRET_SALT外,工单访问链接的其他组件均可进行无速率限制的暴力破解。

若启用了用户自助注册(默认),用户注册端点将充当预言机,泄露某个用户邮箱是否已注册,从而实现用户名枚举。osticket_registered_user_enum.py脚本演示了如何实现这一点。

如前所述,用户及其关联的外部工单号码可使用“检查工单访问权限”预言机和osticket_access_bruteforce.py脚本进行高效暴力破解。

内部工单ID是自增标识符,从1开始。综上所述,攻击者可按如下方式伪造一个访问链接:

% python3 osticket_forge_access_link.py 637963 140 '[email protected]' 'SEFDaIg1UP=Rh0xHE=Ij6Lew8u49L=Tt' http://osticket.example.com
[*] 为 ID: 140, 邮箱: [email protected] 计算哈希...
[*] 计算出的哈希 (a): a32056617064315cae1b4d98a8c95772
[*] 向链接发送 GET 请求: http://osticket.example.com/view.php
[*] 请求参数: {'t': '637963', 'e': '[email protected]', 'a': 'a32056617064315cae1b4d98a8c95772'}
[+] 请求成功发送。分析响应中...
--------------------------------------------------
发送的完整 URL: http://osticket.example.com/tickets.php?id=140

状态码: 200

结合CNEXT将文件读取升级为远程代码执行

2024年,@cfreal_glibciconv()函数中发现了一个巧妙的基于堆的缓冲区溢出漏洞,编号为CVE-2024-2961(亦称CNEXT)。该漏洞的核心在于,任何PHP文件读取原语都可被转化为远程代码执行。据报道,该漏洞已于2024年与影响Adobe Magento的未认证XML外部实体注入漏洞CVE-2024-34102野结合利用。研究团队证明,同样的远程代码执行链对CVE-2026-22200也是可行的。

原始利用程序在高级层面上需要知晓PHP进程的内存布局(可从/proc/self/maps文件获取)以及目标上的完整libc.so.6二进制文件,以准确计算偏移量。但如前文“文件读取的特殊性”部分所述,osTicket的PDF生成器将每个“图像”的窃取限制在大约45KB。因此,他们修改了利用程序以适应osTicket。

首先,他们利用文件读取原语读取目标上的/proc/self/maps和部分libc.so.6文件,采用zlib+base64编码。

获取内存布局和libc信息的载荷

将载荷作为回复添加到现有工单后,他们将工单打印为PDF并提取文件。

提取/proc/self/maps和libc信息

接下来,他们使用NT_GNU_BUILD_ID来识别部分获取的libc库,并使用pwntools库从https://libc.rip/下载完整的libc

根据Build ID下载完整libc

随后,利用完整的libc/proc/self/maps文件,他们生成了一个CNEXT载荷,用于将Web Shell写入应用程序的Web根目录。

生成CNEXT载荷以写入Web Shell](https://horizon3.ai/wp-content/uploads/2026/01/Screenshot-2026-01-21-at-9.28.49-PM.png)

接着,他们将CNEXT载荷添加到现有工单中,并再次将其导出为PDF以触发漏洞。这将导致内部服务器错误并且连接被重置,但Web Shell将变得可用,而应用程序将继续正常运行。

触发CNEXT载荷后得到Web Shell

AI辅助漏洞利用测试

端到端的漏洞利用序列较为复杂,虽然研究团队可以自行编写一键式利用脚本,但他们很好奇,配备Opus 4.5的Claude Code能否自行将这些步骤组合起来。他们针对运行默认配置的osTicket设置了一场CTF挑战,并在根目录放置了一个随机生成的标志文件。他们向Claude提供了漏洞描述和本报告中概述的步骤提示,并指示Claude仅在需要访问电子邮件时才寻求帮助。

向AI(Claude)提供漏洞利用提示

不到10分钟,Claude就成功完成了挑战,仅在注册账户后需要确认邮件内容时才寻求了一次帮助。

AI成功执行漏洞利用链

修复建议

如果正在运行面向互联网的osTicket实例,应立即更新至最新的osTicket版本 1.18.3 / 1.17.7或更高版本。该补丁通过在调用mPDF之前禁用PHP流包装器来解决CVE-2026-22200。如果在Linux服务器上运行osTicket,还建议检查服务器是否存在CVE-2024-2961并进行修补,该漏洞影响glibc版本 <= 2.39。

如果无法立即打补丁,以下缓解措施有助于阻止匿名攻击者的利用:

  • 实施网络或主机防火墙规则,限制对osTicket服务器的访问。
  • 管理员面板 -> 用户选项卡中更新osTicket配置,禁用公共用户的自助注册。
  • 管理员面板 -> 用户选项卡中更新osTicket配置,要求用户注册和登录才能提交工单。
  • 管理员面板 -> 系统选项卡中更新osTicket配置,禁用线程条目和电子邮件通信中的HTML。

漏洞检测

研究团队提供了一个检测脚本check.py,可用于判断是否在运行过时的osTicket版本。该脚本不直接测试漏洞利用,而是检查1.18.3 / 1.17.7更新中包含的其他变化。

运行漏洞检测检查脚本

入侵指标

以下迹象表明系统可能已遭到利用:

  • Web服务器访问日志中包含大量对/login.php端点的GETPOST请求,表明可能存在暴力破解工单访问权限的尝试,通常伴有类似python-requests的可疑用户代理。
  • 创建工单或注册账户的数量异常高于正常水平。
  • Web服务器访问日志条目中包含大量用于将工单打印为PDF的GET请求,例如GET /tickets.php?a=print&id=140
  • Web服务器访问日志条目中包含路径很长、并带有PHP过滤器载荷的GET请求,载荷中含有php%3a//和convert.iconv等字符串,通常导致“414 URI过长”错误。
  • osTicket应用程序的Web根目录中存在Web Shell PHP脚本。

披露时间线

  • 2025年8月28日: Horizon3.ai通过邮件向EnhanceSoft报告PDF文件读取问题,并附带关于90天披露政策的声明。
  • 2025年8月29日: EnhanceSoft确认收到报告。
  • 2025年9月3日: Horizon3.ai报告PDF文件读取问题在与CNEXT结合利用时可导致远程代码执行。同时披露了其他中/低严重性问题(存储型跨站脚本、服务器端请求伪造等)。
  • 2025年9月4日: EnhanceSoft确认收到额外信息。
  • 2025年9月至12月: Horizon3.ai与EnhanceSoft就补丁状态进行了多次沟通。
  • 2025年1月12日: 在向供应商初次披露130多天后,Horizon3.ai公开披露CVE-2026-22200,并通知EnhanceSoft。
  • 2025年1月12日至15日: EnhanceSoft确认漏洞,并与Horizon3.ai合作验证修复程序。
  • 2025年1月15日: EnhanceSoft发布修补版本1.18.3 / 1.17.7。
  • 2025年1月22日: 本研究报告发布。

团队像处理其他零日漏洞一样,作为其快速响应计划的一部分,通知了所有已知的受影响客户,并在NodeZero产品中添加了该漏洞的检测覆盖。

致谢与参考

感谢@_splitline_提出的web2pdf CTF挑战和巧妙的位图图像技巧,以及@cfreal_对CNEXT利用链的突破性发现。

原文:https://horizon3.ai/attack-research/attack-blogs/ticket-to-shell-exploiting-php-filters-and-cnext-in-osticket-cve-2026-22200/