CVE Explained

8 min read

Openfire CVEs explained (CVE-2024-25420 & CVE-2024-25421)

An in-depth look at Openfire CVEs (CVE-2024-25420 & CVE-2024-25421): featuring two improper access control issues affecting the application.

xRogue avatar

xRogue,
Mar 26
2024

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.

What are 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. 

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.

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.

Steps to exploit CVE-2024-25420

To replicate this vulnerability, take the following steps:

1. Create an admin user.

2. Delete the admin user.

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.

4. Log in with the new account through the web application.

Steps to exploit CVE-2024-25421

To replicate CVE-2024-25421, take the following steps:

1. Create a new user and add him to a group chat (under any affiliation – for this example, Admin).

2. Delete the user.

3. Notice that the user is still in the admin list of the group chat.

4. Create a new user with the same username.

5. Search through the group list and notice the user has access to the group chat.

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).

Analyzing CVE-2024-25420

During user creation, when the admin checkbox is ticked, the isUserAdmin() function of the AdminManager class is called.

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.

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.

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.

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. 

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:

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. 

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.

Analyzing 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. 

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.

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:

1. The expiry date is set to never expire.

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). 

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).

Impact of Openfire CVEs

 

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.

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. 

This becomes even more prevalent in internal environments, where the attacker could gather information about users that have been deleted/left the organization etc.

Stay ahead of threats with Hack The Box

At HTB, we release new content every month based on recent threats and vulnerabilities. Keep your team up to speed on the latest CVEs using HTB Enterprise Platform:

 

Author bio: Stavros Manis (xRogue), Content Engineer, Hack The Box

Stavros Manis works as a Content Engineer at Hack the Box, where he ensures the Machines on the platform are high-quality and realistic.

He has experience as a penetration tester, working on security projects for big companies in finance, health, and construction. He also has experience in cybersecurity defense, having worked as a SOC analyst in Greece to help protect companies' users and assets.

You can find him on LinkedIn and Twitter.

Hack The Blog

The latest news and updates, direct from Hack The Box