Openfire is a real-time collaboration (RTC) server licensed under the Open Source Apache License. It is written in Java and uses the XMPP protocol for instant messaging. During our work, Hack the Box was able to identify two improper access control issues affecting the application.
Openfire 是在开源 Apache 许可证下获得许可的实时协作 (RTC) 服务器。它是用 Java 编写的,并使用 XMPP 协议进行即时消息传递。在我们的工作过程中,Hack the Box 能够识别出影响应用程序的两个不正确的访问控制问题。
What are CVE-2024-25420 & CVE-2024-25421?
什么是 CVE-2024-25420 和 CVE-2024-25421?
The first issue lies in the way the application is managing deleted administrative users. When an administrative user is created, his admin privileges are saved in a system property called admin.authorizedJIDs and the key used is the account’s username. If the administrative user is deleted, his username is not deleted from the admin.authorizedJIDs system property.
第一个问题在于应用程序管理已删除管理用户的方式。创建管理用户时,其管理员权限将保存在名为 admin.authorizedJIDs 的系统属性中,使用的密钥是帐户的用户名。如果删除了管理用户,则不会从 admin.authorizedJIDs 系统属性中删除其用户名。
This way, if a new user is created with the same username, the new user is automatically an administrator. The new user does not need to be created from the administrative panel of the application. It can be created through an XMPP registration. This issue is registered as CVE-2024-25420.
这样,如果使用相同的用户名创建新用户,则新用户将自动成为管理员。不需要从应用程序的管理面板创建新用户。它可以通过 XMPP 注册创建。此问题已注册为 CVE-2024-25420。
The second issue is similar, but the underlying cause is different.
第二个问题相似,但根本原因不同。
If a user is added to a group chat, his affiliation along with other related data are stored in the cache.
如果将用户添加到群聊中,则其隶属关系以及其他相关数据将存储在缓存中。
When this user is deleted, the cache is not updated to reflect the new state of the group, allowing a new user with the same username to gain access to the group chat with the same level of privileges as the previously deleted user. This issue is registered as CVE-2024-25421.
删除此用户时,缓存不会更新以反映组的新状态,从而允许具有相同用户名的新用户以与之前删除的用户相同的权限级别访问群聊。此问题已注册为 CVE-2024-25421。
Steps to exploit CVE-2024-25420
利用 CVE-2024-25420 的步骤
To replicate this vulnerability, take the following steps:
若要复制此漏洞,请执行以下步骤:
1. Create an admin user.
1. 创建管理员用户。
2. Delete the admin user.
2. 删除管理员用户。
3. Create a new user with the same username as the deleted admin user. We created the user through XMPP using pidgin for this example.
3. 创建一个与已删除管理员用户同名的新用户。在此示例中,我们使用 pidgin 通过 XMPP 创建了用户。
4. Log in with the new account through the web application.
4. 通过 Web 应用程序使用新帐户登录。
Steps to exploit CVE-2024-25421
利用 CVE-2024-25421 的步骤
To replicate CVE-2024-25421, take the following steps:
若要复制 CVE-2024-25421,请执行以下步骤:
1. Create a new user and add him to a group chat (under any affiliation – for this example, Admin).
1. 创建一个新用户并将其添加到群聊中(在任何隶属关系下 – 在本例中为管理员)。
2. Delete the user.
2. 删除用户。
3. Notice that the user is still in the admin list of the group chat.
3. 请注意,该用户仍在群聊的管理员列表中。
4. Create a new user with the same username.
4. 创建具有相同用户名的新用户。
5. Search through the group list and notice the user has access to the group chat.
5. 搜索群组列表,并注意到用户可以访问群聊。
6. Notice that the user can see previous messages and perform the actions that correspond to his role (for example, if he was an owner he could do run/config).
6. 请注意,用户可以看到以前的消息并执行与其角色相对应的操作(例如,如果他是所有者,他可以执行运行/配置)。
Analyzing CVE-2024-25420 分析 CVE-2024-25420
During user creation, when the admin checkbox is ticked, the isUserAdmin() function of the AdminManager class is called.
在用户创建过程中,当勾选 admin 复选框时,将调用 AdminManager 类的 isUserAdmin() 函数。
public boolean isUserAdmin(String username, boolean allowAdminIfEmpty) {
if (adminList == null {
loadAdminList() ;
}
if (allowAdminIfEmpty && adminList.isEmpty()) {
return "admin".equals(username) ;
}
JID userJID = XMPPServer.getInstance().createJID(username, null);
return adminlist.contains(userJID);
}
The function initially returns false, since the username is not present in the adminList variable, which contains the values of the admin.authorizedJIDs system property. If this function returns false, the addAdminAccount function of the AdminManager class is called, which adds the user to the adminList.
该函数最初返回 false,因为 adminList 变量中不存在用户名,该变量包含 admin.authorizedJIDs 系统属性的值。如果此函数返回 false,则调用 AdminManager 类的 addAdminAccount 函数,这会将用户添加到 adminList。
public void addAdminAccount(String username) {
if (adminList == null) {
loadAdminList();
}
JID userJID = XMPPServer.getInstance().createJID(username, null);
if (adminList.contains(userJID)) {
// Already have them.
return;
}
// Add new admin to cache.
adminList.add(userJID);
// Store updated list of admins with provider.
provider.setAdmins(adminList);
}
The function essentially leads to the setValue() function of the SystemProperty class which sets the property through the JiveGlobals.setProperty() function.
该函数实质上导致 SystemProperty 类的 setValue() 函数,该函数通过 JiveGlobals.setProperty() 函数设置属性。
public void setValue(final T value) {
JiveGlobals.setProperty(key, TO_STRING.get(getConverterClass()).apply(value, this), isEncrypted());
}
Although on user creation the admin.authorizedJIDs system property is edited accordingly, the same operation is not performed during user deletion. On user deletion, the deleteUser() function from the UserManager class is called.
尽管在创建用户时会相应地编辑 admin.authorizedJIDs 系统属性,但在用户删除期间不会执行相同的操作。在用户删除时,将调用 UserManager 类中的 deleteUser() 函数。
public void deleteUser(final User user) {
if (provider.isReadOnly()) {
throw new UnsupportedOperationException("User provider is read-only.");
}
final String username = user.getUsername();
// Make sure that the username is valid.
try {
/*username =*/ Stringprep.nodeprep(username);
}
catch (final StringprepException se) {
throw new IllegalArgumentException("Invalid username: " + username, se);
}
// Fire event.
final Map<String,Object> params = Collections.emptyMap();
UserEventDispatcher.dispatchEvent(user, UserEventDispatcher.EventType.user_deleting, params);
provider.deleteUser(user.getUsername());
// Remove the user from cache.
userCache.remove(user.getUsername());
}
This is the base function that fires off a series of events (especially through the dispatchEvent() function) that ensures proper user deletion.
这是触发一系列事件(尤其是通过 dispatchEvent() 函数)的基本函数,以确保正确删除用户。
At no point during these operations or the ones that come after, should you perform the same kind of operation (setProperty) on the system property as the create user operations did. The function for deletion of the username from the system property exists:
在这些操作或之后的操作中,您都不应该对系统属性执行与创建用户操作相同的操作 (setProperty)。存在从系统属性中删除用户名的功能:
public void removeAdminAccount(String username) {
if (adminList == null) {
loadAdminList();
}
JID userJID = XMPPServer.getInstance().createJID(username, null);
if (!adminList.contains(userJID)) {
return;
}
// Remove user from admin list cache.
adminList.remove(userJID);
// Store updated list of admins with provider.
provider.setAdmins(adminList);
}
(removeAdminAccount() on AdminManager class) but it is never called. The result is, that the username remains in the admin.authorizedJIDs system property even if the user is deleted.
(removeAdminAccount() on AdminManager 类),但它从未被调用。结果是,即使删除了用户,用户名仍保留在 admin.authorizedJIDs 系统属性中。
This way, if another user creates a new user with the same username as the deleted user, the isUserAdmin() function will return true, granting access to the administration panel.
这样,如果另一个用户使用与已删除用户相同的用户名创建新用户,则 isUserAdmin() 函数将返回 true,授予对管理面板的访问权限。
Analyzing CVE-2024-25421 分析 CVE-2024-25421
When the group chat management page is visited, one of the functions that is being called is the getChatRoom() function from the MultiUserChatService class.
访问群聊管理页面时,调用的函数之一是 MultiUserChatService 类中的 getChatRoom() 函数。
This function tries to load the room properties from the ROOM_CACHE, but if the room cache is not initialized, it initializes it from the database (which is where the room’s original properties are stored for consistency) and stores it in the ROOM_CACHE at the end of the if condition.
此函数尝试从ROOM_CACHE加载房间属性,但如果未初始化房间缓存,则会从数据库(存储房间的原始属性以实现一致性的位置)对其进行初始化,并将其存储在 if 条件末尾的ROOM_CACHE中。
public MUCRoom getChatRoom(@Nonnull final String roomName, @Nonnull final JID userjid) throws NotAllowedE
MUCRoom room;
boolean loaded = false;
boolean created = false;
final Lock lock = localMUCRoomManager.getLock(roomName);
lock.lock();
try {
room = localMUCRoomManager.get(roomName); // Get from ROOM_CACHE
if (room == null) {
room = new MUCRoom(this, roomName);
// If the room is persistent Load the configuration values from the DB
try {
// Try to load the room's configuration from the database (if the room is
// persistent but was added to the DB after the server was started up or the
// room may be an old room that was not present in memory)
MUCPersistenceManager.loadFromDB(room); // Load from DB if not found
loaded = true;
}
}
localMUCRoomManager.add(room);
This ROOM_CACHE value will not change until the web server is restarted, for two reasons:
在重新启动 Web 服务器之前,此ROOM_CACHE值不会更改,原因有两个:
1. The expiry date is set to never expire.
1. 到期日期设置为永不过期。
LocalMUCRoomManager(@Nonnull final MultiUserChatService service)
{
this.serviceName = service.getServiceName();
Log.debug("Instantiating for service '{}':, serviceName);
ROOM_CACHE = CacheFactory.createCache("MUC Service '" + serviceName + "' Rooms");
ROOM_CACHE.setMaxLifetime(-1);
ROOM_CACHE.setMaxCacheSize(-1L);
ROOM_CACHE_STATS = CacheFactory.createCache("MUC Service '" + serviceName + "' Room Statistics");
ROOM_CACHE_STATS.setMaxLifetime(-1);
ROOM_CACHE_STATS.setMaxCacheSize(-1L);
}
2. At no point in the communication between the client and the server, the ROOM_CACHE is re-evaluated (not even after user deletion which is what we are after here).
2.在客户端和服务器之间的通信中,ROOM_CACHE不会被重新评估(即使在用户删除之后也不会重新评估,这就是我们在这里所追求的)。
Since the ROOM_CACHE will continue to include the deleted user in its properties, and it is not refreshed after user deletion, a user that creates a new user with the same username, will have access to the group chat with the corresponding affiliation, until the ROOM_CACHE is refreshed (which in this case, until the server restarts, since the expiry is to never expire).
由于ROOM_CACHE将继续在其属性中包含已删除的用户,并且在用户删除后不会刷新,因此创建具有相同用户名的新用户的用户将有权访问具有相应隶属关系的群聊,直到刷新ROOM_CACHE(在本例中,直到服务器重新启动, 因为到期日永远不会过期)。
Impact of Openfire CVEs Openfire CVE 的影响
CVE-2024-25420
Users can bruteforce known commonly used administrator usernames during registration, in hopes of finding a deleted admin account to gain access to the admin panel. Deletion of the default “admin” user is common, making this attack easier to perform.
用户可以在注册过程中暴力破解已知常用的管理员用户名,希望找到已删除的管理员帐户以访问管理面板。删除默认的“管理员”用户很常见,这使得这种攻击更容易执行。
This attack vector is even more prevalent if the attacker has some internal knowledge of the organization (for example if they already have gained access to the company’s internal network) and tries to bruteforce known usernames.
如果攻击者对组织有一定的内部了解(例如,如果他们已经获得了对公司内部网络的访问权限)并试图暴力破解已知用户名,则这种攻击媒介更加普遍。
Even from the public internet, attackers can figure out a wordlist of possible usernames through OSINT sources (e.g. LinkedIn), especially of former employees, to use against the service.
即使来自公共互联网,攻击者也可以通过OSINT来源(例如LinkedIn)找出可能的用户名的单词列表,尤其是前雇员,以用于该服务。
CVE-2024-25421
Since websites are generally aiming for a constant workflow and don’t restart often, the impact of the specific bug can be quite significant.
由于网站通常以恒定的工作流程为目标,并且不经常重新启动,因此特定错误的影响可能非常显着。
An attacker can brute force registration of OSINT gathered usernames and then try to refresh the group chat list of each account they registered in hopes of gaining access to the group chats that a now-deleted user had.
攻击者可以暴力破解 OSINT 收集的用户名注册,然后尝试刷新他们注册的每个帐户的群聊列表,以期获得对现已删除用户的群聊的访问权限。
This becomes even more prevalent in internal environments, where the attacker could gather information about users that have been deleted/left the organization etc.
这在内部环境中变得更加普遍,攻击者可以收集有关已删除/离开组织的用户的信息等。
原文始发于hackthebox:Openfire CVEs explained (CVE-2024-25420 & CVE-2024-25421)
转载请注明:Openfire CVEs explained (CVE-2024-25420 & CVE-2024-25421) | CTF导航