OpenMeetings 是一款网络会议应用程序,可用于视频通话、演示和协作工作。其官方docker 镜像已被下载超过 50,000 次,OpenMeetings 还可以作为 Jira、Confluence 或 Drupal 等应用程序的插件进行部署。它的广泛采用以及它可能用于敏感讨论、会议和协作的事实使其成为对攻击者有吸引力的目标。
在本文中,我们将向您展示我们在 Apache OpenMeetings 中发现的一个有趣问题,该问题是由意外的应用程序状态引起的。攻击者可以将此问题与我们发现的其他代码漏洞结合起来,劫持 OpenMeetings 实例并在底层服务器上执行命令。他们所需要的只是一个可以在默认配置中自行创建的帐户。
我们在 Apache OpenMeetings 中发现以下漏洞:
CVE-2023-28936:弱哈希比较
CVE-2023-29032:通过邀请哈希进行无限制访问
CVE-2023-29246:空字节注入
由于逻辑缺陷和弱哈希比较的结合,帐户被接管是可能的。攻击者可以以意外的顺序触发某些操作,以创建没有分配房间的房间邀请。这会导致不受限制地邀请访问任何用户帐户。通过使用通配符,攻击者可以自己兑换此邀请并获得管理员权限。
由于对可配置项的验证不充分,攻击者可以利用获得的管理员权限在其中一个二进制路径中注入空字节。可以利用它来运行任意二进制文件,从而导致远程代码执行。
所有漏洞均已通过 Apache OpenMeetings 7.1.0修复。
在本节中,我们将解释 OpenMeetings 的会议室邀请是如何工作的,并深入探讨帐户接管和空字节注入的技术细节。
OpenMeetings 允许用户将事件添加到他们的日历中。添加新活动时,会创建一个单独的房间,可以在活动期间使用:
房间内的用户可以向另一个用户发送邀请:
这样的邀请被表示为一个Invitation
类。当创建此类的对象时,将设置被邀请者,并使用随机 UUID 作为哈希:
openmeetings-web/src/main/java/org/apache/openmeetings/web/common/InvitationForm.java
protected Invitation create(User u) { Invitation i = new Invitation(getModelObject()); // ... i.setInvitee(u); i.setHash(randomUUID().toString());
此外,Invitation
通过调用将对象绑定到特定房间setRoom
:
openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/RoomInvitationForm.java
public void updateModel(AjaxRequestTarget target) { super.updateModel(target); Invitation i = getModelObject(); i.setRoom(roomDao.get(roomId));
用户提交邀请后,受邀者会收到一封包含邀请链接的电子邮件。该链接指向/openmeetings/hash
并包含查询参数中生成的哈希值invitation
,例如:
https://example.com/openmeetings/hash?invitation=52e2f294-cc34-13...
然后使用此邀请哈希Invitation
通过调用来检索相应的对象getByHash
:
openmeetings-web/src/main/java/org/apache/openmeetings/web/app/WebSession.java
public void checkHashes(StringValue secure, StringValue inviteStr) { // ... invitation = inviteDao.getByHash(inviteStr.toString(), false); // ...
总结一下:邀请绑定到特定的房间和用户。它可以用随机生成的哈希值进行兑换。
第一个漏洞存在于该getByHash
方法中。此方法使用以下命名查询Invitation
从用户提供的哈希标识的数据库中检索对象:
openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/room/Invitation.java
@NamedQuery(name = "getInvitationByHashCode", query = "SELECT i FROM Invitation i where i.hash LIKE :hashCode AND i.deleted = false")
使用运算符来比较哈希值LIKE
。与使用等号 ( =
) 的严格比较相反,该LIKE
运算符允许使用通配符。默认数据库 H2 要求通配符前至少有一个字符。因此,例如,当传递哈希值时"5%"
,Invitation
将返回哈希值以 5 开头的所有对象。这样,攻击者就可以轻松枚举有效的邀请哈希值并兑换它们(UUID 的字符集仅限于[0-9a-f]
)。
由于邀请绑定到特定房间,因此这只允许攻击者代表受邀用户访问该房间。不可能与应用程序进行其他交互。但让我们看看如何兑换邀请。
该方法检索到邀请后checkHashes
,该方法会继续声明一个名为的集合hrights
,并尝试确定邀请的房间:
openmeetings-web/src/main/java/org/apache/openmeetings/web/app/WebSession.java
// ... Room r = null; if (invitation != null && invitation.isAllowEntry()) { // initialize hrights set Set<Right> hrights = new HashSet<>(); // try to determine room associated to invitation if (invitation.getRoom() != null) { r = invitation.getRoom(); } else if (invitation.getAppointment() != null && invitation.getAppointment().getRoom() != null) { r = invitation.getAppointment().getRoom(); } // ...
如果成功识别房间,则将该常量Right.ROOM
添加到hrights
集合中。最后,setUser
被调用,传递被邀请者和hrights
作为参数:
openmeetings-web/src/main/java/org/apache/openmeetings/web/app/WebSession.java
if (r != null) { // room was identified redirectHash(r, () -> inviteDao.markUsed(invitation)); hrights.add(Right.ROOM); roomId = r.getId(); } // set session user to invited user setUser(invitation.getInvitee(), hrights);
请注意,如果无法识别房间,则该hrights
集合在传递到 时为空setUser
。在这种情况下,新设置的用户的权限不受限制,而是源自用户本身:
openmeetings-web/src/main/java/org/apache/openmeetings/web/app/WebSession.java
private void setUser(User u, Set<Right> rights) { userId = u.getId(); if (rights == null || rights.isEmpty()) { // rights empty? derive rights from user Set<Right> r = new HashSet<>(u.getRights()); // ... this.rights = Collections.unmodifiableSet(r); } else { // rights not empty? only apply these this.rights = Collections.unmodifiableSet(rights); }}
这意味着兑换没有附加房间的邀请会导致受邀用户上下文中的不受限制的会话。
尽管通常的操作顺序可以防止这种情况发生,但攻击者可以通过将应用程序置于意外状态来规避这种情况。首先,攻击者可以创建一个事件 ( 1
) 并加入关联的房间 ( 2
):
3
现在,攻击者在房间中仍然存在的情况下删除了事件 ( )。尽管删除相关事件时房间也会被删除,但攻击者在房间中的存在使其成为僵尸房间。接下来,攻击者为管理员用户创建此房间的邀请 ( 4
)。这会产生一个没有附加空间的邀请:
最后,攻击者可以利用弱哈希比较,通过使用通配符(5
)来兑换邀请。尽管在兑换此类邀请的哈希值时会引发错误,但会为受邀者创建具有该用户完全权限的有效 Web 会话。可以使用服务器响应 ( 6
) 中的会话 cookie 来访问此 Web 会话:
获得的管理员权限允许攻击者更改 OpenMeetings 实例的配置。这包括添加和删除用户和组、更改房间设置以及终止已连接用户的会话。尽管这已经相当强大,但我们一直在寻找一种方法来获得代码执行,以不仅控制 OpenMeetings,而且还控制底层服务器。
OpenMeetings 允许管理员配置 ImageMagick、FFMPEG 等可执行文件的路径。例如,通过使用配置密钥和二进制文件名称 ( )convert
调用来检索二进制文件的路径:getPath
CONFIG_PATH_IMAGEMAGIC
convert
openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/BaseConverter.java
protected String getPathToConvert() { return getPath(CONFIG_PATH_IMAGEMAGIC, "convert");}
该getPath
方法将文件分隔符添加到配置的路径(如果尚不存在)并附加二进制文件的名称:
openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/BaseConverter.java
public abstract class BaseConverter { // ... private String getPath(String key, String app) { final String cfg = cfgDao.getString(key, ""); StringBuilder path = new StringBuilder(cfg); if (!Strings.isEmpty(path) && !cfg.endsWith(File.separator)) { path.append(File.separator); } path.append(app).append(EXEC_EXT); return path.toString(); }
由于配置的路径总是以文件分隔符(例如斜杠)结尾并且可执行文件名称是固定的(例如,convert
),因此似乎不可能运行具有不同名称的可执行文件。但是,当在配置的路径中注入空字节时,空字节后面的每个字符都将被忽略。尽管ProcessBuilder
用于执行命令的类在 Java 领域中带有空字节,但命令的实际执行的实现是特定于操作系统的,并且是在本机 C 中实现的。而在 Java 中,字符串的长度是单独存储的,允许它包含空字节,在 C 中,单个空字节指定字符串的末尾,有效地忽略附加在空字节之后的每个字符。
这允许具有管理员权限的攻击者通过将 ImageMagic 路径更改为"/bin/sh%00x"
(需要空字节后的单个字符以防止它首先被忽略)来获得代码执行。/bin/sh
现在,当上传包含有效图像标头和任意 shell 命令的假图像时,会生成第一个参数为假图像的转换,从而有效执行其中的每个命令。
与帐户接管相结合,该漏洞允许自我注册的攻击者在底层服务器上获得远程代码执行。
Copyright © 2022 All Rights Reserved. 地址:上海市浦东新区崮山路538号808 苏ICP123456 XML地图