CVE-2022-0866 describes a subtle but impactful concurrency bug in JBoss EAP (7.1. and onward) and WildFly (11+), specifically when Elytron security is enabled. This bug can cause applications to return the wrong caller principal from session context, and incorrectly verify user roles, when multiple users use Enterprise JavaBeans (EJBs) with the RunAs principal configuration at the same time. That means, in high-load production systems, users could be accidentally granted the permission of others (or denied their own rights) without clear reason.
What is “RunAs Principal” in EJBs?
In Java EE's EJB architecture, the “RunAs” annotation lets a bean temporarily assume another security identity when calling other beans or components. This helps enforce separation of privileges between components, ensuring that only authorized resources are accessed.
Example
@Stateless
@RunAs("AdminRole")
public class AdminSessionBean {
public void performAdminTask() {
// Task runs with "AdminRole" privileges
}
}
How Does the Bug Happen?
The flaw is inside the implementation of org.jboss.as.ejb3.component.EJBComponent. This class handles EJB logic, and it stores the *current* identity to a field called incomingRunAsIdentity. This field is just a plain SecurityIdentity object.
public class EJBComponent {
protected SecurityIdentity incomingRunAsIdentity;
public Principal getCallerPrincipal() {
return incomingRunAsIdentity.getPrincipal();
}
}
There’s no synchronization or thread-local isolation around this field.
In a concurrent environment, when several users simultaneously invoke an EJB (each possibly with different identities or roles), the following happens:
- They all share the same incomingRunAsIdentity field at the object/class level.
Proof-of-Concept (Exploit Scenario)
Below is a simplified simulation to show the problem in code. Assume that two users—Alice and Bob—are calling an EJB at the same time that uses @RunAs.
Setup
@Stateless
@RunAs("AdminRole")
public class SensitiveBean {
@Resource
private SessionContext ctx;
public String whoAmI() {
return ctx.getCallerPrincipal().getName();
}
}
Test Client
ExecutorService threads = Executors.newFixedThreadPool(2);
Runnable user1 = () -> {
// Simulate call as 'alice'
String principal = sensitiveBean.whoAmI();
System.out.println("User 1 sees: " + principal);
};
Runnable user2 = () -> {
// Simulate call as 'bob'
String principal = sensitiveBean.whoAmI();
System.out.println("User 2 sees: " + principal);
};
threads.execute(user1);
threads.execute(user2);
Expected: User 1 sees “alice”, User 2 sees “bob”.
Actual (in affected version): User 1 and User 2 *might* both see “alice”, “bob”, or even swap values depending on thread timing—the result is unpredictable.
Why? (Under the hood)
Because both threads are writing and reading from the same shared field for identity, a race condition lets users “borrow” each other’s security.
The Key Interceptor
public final class RunAsPrincipalInterceptor extends Interceptor {
@Override
public Object processInvocation(InterceptorContext context) throws Exception {
SecurityIdentity previous = ejbComponent.incomingRunAsIdentity;
try {
ejbComponent.incomingRunAsIdentity = createRunAsIdentity();
return context.proceed();
} finally {
ejbComponent.incomingRunAsIdentity = previous;
}
}
}
Should be thread-local! But since incomingRunAsIdentity is not thread-local or synchronized, one thread can *borrow* another's value between the set and restore steps.
How to Fix It
The proper fix is to use a ThreadLocal<SecurityIdentity> variable, so each thread has its own copy of the identity.
Before fix
protected SecurityIdentity incomingRunAsIdentity; // Shared by all threads
After fix
protected final ThreadLocal<SecurityIdentity> incomingRunAsIdentity = new ThreadLocal<>();
Actions within the EJB now always see their own correct principal, no matter how many users are making requests in parallel.
Elytron security domain is enabled.
_Attackers don’t need any special privilege—they just need to flood the EJB with concurrent requests from different identities, and wait for mis-attribution of session principal._
Implications for Devs & Admins
- If you’re using JBoss EAP ≥ 7.1.+ or WildFly 11+ with Elytron: You must upgrade or apply the patch.
- Failing to do so: Any authenticated user could “borrow” the security permissions of random other users (undetected).
- Standard cluster or load-balanced deployments will also be affected —this is not just a development or stress test bug.
Mitigations and Fixes
- Upgrade to the latest patched version of JBoss EAP or WildFly (check official advisories—see below).
- If upgrading is slow, disable Elytron (if possible) or prevent use of untrusted concurrent EJB invocations.
References
- Red Hat Security Advisory for CVE-2022-0866
- WildFly Issue Tracker - WFLY-15835
- JFrog Security Research: CVE-2022-0866 write-up
- Elytron Project Documentation
Closing Thoughts
CVE-2022-0866 is a textbook example of what can go wrong with shared mutable state in concurrent Java applications, especially in frameworks as critical as JBoss and WildFly. The risk is silent, hard-to-test, and the fix is easy—but must be deployed urgently if you run affected versions.
Stay alert to the dangers of shared fields in your own code—and always apply security patches promptly!
*Read more exclusive posts on secure Java EE programming and catch the latest advisories with us. If you're affected, update now—don't let user identities leak between threads!*
Timeline
Published on: 05/10/2022 21:15:00 UTC
Last modified on: 05/18/2022 16:13:00 UTC