Environment
Jmix version: 2.8.5, 3.0.2
Description
SystemAuthenticator.begin()/end() (and therefore withSystem, runWithSystem, withUser, runWithUser) are documented to change the authentication of the current thread only. In fact they affect all threads of the same HTTP session: UI request threads, background tasks started from a view, and push requests.
As a result, while a withSystem block runs in a background task, UI requests of the same session run as system: full permissions, row-level policies bypassed, audit attributes recorded as system. If two begin()/end() pairs overlap on two threads of the same session, the session stays authenticated as system until logout.
Other sessions are not affected.
Reported at https://forum.jmix.io/t/systemauthenticator-begin-end-can-result-in-priviledge-escalation/7651
Steps to reproduce
In a view of a project with a logged-in admin:
@Autowired private UiAsyncTasks uiAsyncTasks;
@Autowired private SystemAuthenticator systemAuthenticator;
@Autowired private CurrentAuthentication currentAuthentication;
@Autowired private Notifications notifications;
@Subscribe(id = "longSystemTaskBtn", subject = "clickListener")
public void onLongSystemTaskBtnClick(ClickEvent<JmixButton> event) {
uiAsyncTasks.runnableConfigurer(() ->
systemAuthenticator.runWithSystem(() -> sleep(10_000)))
.runAsync();
}
@Subscribe(id = "overlappingTasksBtn", subject = "clickListener")
public void onOverlappingTasksBtnClick(ClickEvent<JmixButton> event) {
uiAsyncTasks.runnableConfigurer(() ->
systemAuthenticator.runWithSystem(() -> sleep(3_000)))
.runAsync();
uiAsyncTasks.runnableConfigurer(() -> {
sleep(1_000);
systemAuthenticator.runWithSystem(() -> sleep(5_000));
})
.runAsync();
}
@Subscribe(id = "whoAmIBtn", subject = "clickListener")
public void onWhoAmIBtnClick(ClickEvent<JmixButton> event) {
notifications.show(currentAuthentication.getAuthentication().getClass().getSimpleName()
+ " / " + currentAuthentication.getUser().getUsername());
}
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
- Log in as
admin and click whoAmI.
- Click longSystemTask, then click whoAmI within 10 seconds.
- Wait 10 seconds and click whoAmI again.
- Click overlappingTasks, wait 10 seconds, then click whoAmI and open a view that
admin is not allowed to open.
Actual behavior
- Step 1 shows
UsernamePasswordAuthenticationToken / admin.
- Step 2 shows
SystemAuthenticationToken / system — the UI request runs as system.
- Step 3 shows
admin again.
- Step 4 still shows
SystemAuthenticationToken / system after both tasks have finished, and the restricted view opens. The session stays system until logout.
Expected behavior
begin()/end() affect only the current thread. Other threads of the same session always see the user's own authentication, and overlapping begin()/end() pairs never leave the session with another authentication.
Environment
Jmix version: 2.8.5, 3.0.2
Description
SystemAuthenticator.begin()/end()(and thereforewithSystem,runWithSystem,withUser,runWithUser) are documented to change the authentication of the current thread only. In fact they affect all threads of the same HTTP session: UI request threads, background tasks started from a view, and push requests.As a result, while a
withSystemblock runs in a background task, UI requests of the same session run assystem: full permissions, row-level policies bypassed, audit attributes recorded assystem. If twobegin()/end()pairs overlap on two threads of the same session, the session stays authenticated assystemuntil logout.Other sessions are not affected.
Reported at https://forum.jmix.io/t/systemauthenticator-begin-end-can-result-in-priviledge-escalation/7651
Steps to reproduce
In a view of a project with a logged-in
admin:adminand click whoAmI.adminis not allowed to open.Actual behavior
UsernamePasswordAuthenticationToken / admin.SystemAuthenticationToken / system— the UI request runs assystem.adminagain.SystemAuthenticationToken / systemafter both tasks have finished, and the restricted view opens. The session stayssystemuntil logout.Expected behavior
begin()/end()affect only the current thread. Other threads of the same session always see the user's own authentication, and overlappingbegin()/end()pairs never leave the session with another authentication.