摘要
Horizon3.ai的研究团队在流行的开源服务台系统osTicket中发现了一个受CTF启发、编号为CVE-2026-22200的安全漏洞。该漏洞允许匿名攻击者通过向工单中注入恶意的PHP过滤器链表达式,然后将工单导出为PDF文档,从而读取服务器上的任意文件。攻击者可以利用该漏洞窃取敏感文件(这些文件会以位图图像形式嵌入PDF中);若结合利用CVE-2024-2961(亦称CNEXT),甚至可实现远程代码执行。该问题已在osTicket 1.18.3 / 1.17.7版本中修复,研究团队强烈建议所有用户升级至最新版本。
背景
osTicket是一款广受欢迎的开源服务台系统,常被寻求轻量级、自托管支持解决方案的组织采用。Horizon3.ai的研究人员经常在SLED(州、地方及教育)部门以及其他中端市场至中小型企业环境中发现其部署。由于其数千个实例暴露在互联网上,且更多实例部署于内网,该系统的攻击面相当可观。
工单系统通常是攻击者的高价值目标,它们通常包含令牌或凭据等敏感信息,并可能成为攻击者横向移动进入内部网络的跳板。近期在野被利用的工单系统漏洞包括影响SolarWinds Web Help Desk的CVE-2024-28986/CVE-2024-28987和影响SysAid的CVE-2025-2775/CVE-2025-2776。
从架构上看,osTicket是一个老派的PHP应用程序,最初于2003年发布,已接受过包括SonarSource、Checkmarx在内的多方深入研究。尽管如此,研究人员在梳理代码库时,发现该应用对老旧第三方库的依赖值得进一步探索。结合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解码,然后才进行访问。
解码后的资源随后通过file_get_contents函数访问:
第二个障碍:HTML净化
即使有了针对mPDF的绕过方法,攻击载荷仍需穿过osTicket的输入验证层。工单中的所有富文本HTML内容都会被清理,并交由htmLawed第三方库处理,该库通过中和可疑标签和属性来净化输入。
htmLawed采用基于白名单的方法严格检查URI方案,并且足够智能,能够识别像%3a这样的URL编码冒号。因此,类似下面的输入URI:
<img src="php%3a//myurl" />
会被添加denied:前缀进行中和:
其他图像属性如srcset也遇到同样问题。style属性中的所有URI也会被完全阻止。例如,像这样的style属性:
<ul>
<li style="list-style-image:url(http://myurl.com)">listitem</li>
</ul>
同样会被添加denied:前缀中和:
绕过HTML净化
在测试style属性时,研究团队注意到htmLawed中存在一个微妙的解析差异。如果在url关键字和开括号之间包含空格,URI就能逃脱净化器的白名单检查。
例如,以下载荷就完全通过了htmLawed的净化:
<ul><li style="list-style-image:url (http://myurl.com)">listitem</li></ul>
然而,这并非意味着成功。mPDF遵循严格的CSS标准,期望的是url()格式,中间没有多余的空格。如果保留空格,漏洞利用会在汇点(mPDF)失败;如果去掉空格,htmLawed又会阻止该URI。
但在此测试过程中,他们注意到输出会被奇怪地截断:
<ul>
<li style="list-style-image:url (http">listitem</li>
</ul>
他们发现,osTicket向htmLawed注册了一个自定义的净化后回调函数__html_cleanup,该函数对style属性执行了额外的字符串操作。
这段代码处理的是htmLawed已经清理过的输出,因此十分危险。它执行了多种转换,其中最重要的是HTML实体解码和字符剥离。研究团队面临的挑战是构思一个有效载荷,使其既能通过htmLawed,又能经受住__html_cleanup中对关键字符(引号、分号、冒号等)的处理,并最终被转换为mPDF可以接受的格式。他们最终构造出的载荷如下:
<ul><li style="list-style-image:url"(php%3a//myurl)">listitem</li></ul>
这个载荷使用了代表双引号"的HTML实体"来绕过htmLawed。该实体随后在__html_cleanup中被解码并剥离。值得注意的是,实体不需要结尾的分号。事实上,使用"反而会破坏__html_cleanup中的解析逻辑。
该有效载荷从输入到最终生效的完整流程如下:
- 恶意输入:
url"(php%3a//myurl) - htmLawed输出(未改动):
url"(php%3a//myurl) __html_cleanup中的实体解码:url"(php%3a//myurl)__html_cleanup中的字符剥离:url(php%3a//myurl)- 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实体解码,而非一次。
为此,载荷必须被再次编码。这种情况下,载荷需要使用嵌套的实体序列4来替代",才能在双重解码过程中存活下来,并以正确格式到达mPDF汇点。其osticket_ticket_payload_gen脚本通过--reply标志来处理此问题。
从PDF中提取数据
一旦工单包含恶意HTML,攻击者导航至工单视图并选择“打印”为PDF。这会强制mPDF处理注入的list-style-image属性,解析PHP过滤器链,并渲染目标文件。
被窃取的数据以位图图像形式嵌入生成的PDF中。这些文件可通过从PDF的图像对象中剥离伪造的BMP文件头来提取。
文件读取的特殊性
在测试过程中,研究团队发现了几个影响数据窃取可靠性的细微之处:
- 大写字母敏感性: 他们观察到包含大写字母的文件路径有时会窃取失败。其
osticket_ticket_payload_gen脚本通过URL编码载荷中的大写字母来解决此问题。 - 编码变化: 标准过滤器链对文本文件有效,但二进制文件可能存在问题。他们发现,在BMP转换之前,将数据包装在Base64或zlib+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_在glibc的iconv()函数中发现了一个巧妙的基于堆的缓冲区溢出漏洞,编号为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编码。
将载荷作为回复添加到现有工单后,他们将工单打印为PDF并提取文件。
接下来,他们使用NT_GNU_BUILD_ID来识别部分获取的libc库,并使用pwntools库从https://libc.rip/下载完整的libc。
随后,利用完整的libc和/proc/self/maps文件,他们生成了一个CNEXT载荷,用于将Web Shell写入应用程序的Web根目录。
](https://horizon3.ai/wp-content/uploads/2026/01/Screenshot-2026-01-21-at-9.28.49-PM.png)
接着,他们将CNEXT载荷添加到现有工单中,并再次将其导出为PDF以触发漏洞。这将导致内部服务器错误并且连接被重置,但Web Shell将变得可用,而应用程序将继续正常运行。
AI辅助漏洞利用测试
端到端的漏洞利用序列较为复杂,虽然研究团队可以自行编写一键式利用脚本,但他们很好奇,配备Opus 4.5的Claude Code能否自行将这些步骤组合起来。他们针对运行默认配置的osTicket设置了一场CTF挑战,并在根目录放置了一个随机生成的标志文件。他们向Claude提供了漏洞描述和本报告中概述的步骤提示,并指示Claude仅在需要访问电子邮件时才寻求帮助。
不到10分钟,Claude就成功完成了挑战,仅在注册账户后需要确认邮件内容时才寻求了一次帮助。
修复建议
如果正在运行面向互联网的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端点的GET和POST请求,表明可能存在暴力破解工单访问权限的尝试,通常伴有类似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://osticket.com/osticket-v1-18-3-v1-17-7-available/
- https://github.com/horizon3ai/CVE-2026-22200
- https://blog.splitline.tw/hitcon-ctf-2022/#%F0%9F%93%83-web2pdf-web
- https://blog.lexfo.fr/iconv-cve-2024-2961-p1.html
- https://github.com/ambionics/cnext-exploits/blob/main/cnext-exploit.py
- https://nvd.nist.gov/vuln/detail/cve-2026-22200
- https://nvd.nist.gov/vuln/detail/cve-2024-2961
- https://www.cve.org/CVERecord?id=CVE-2025-26241



















