Skip to content

Commit 1bb317c

Browse files
committed
[SECURITY] Enforce HTTP method assertions for backend modules
Resolves: #104456 Releases: main, 13.4, 12.4 Change-Id: Ic679584a343b6d35e81325a03148b0cff81f1d27 Security-Bulletin: TYPO3-CORE-SA-2025-003 Security-Bulletin: TYPO3-CORE-SA-2025-004 Security-Bulletin: TYPO3-CORE-SA-2025-005 Security-Bulletin: TYPO3-CORE-SA-2025-006 Security-Bulletin: TYPO3-CORE-SA-2025-007 Security-Bulletin: TYPO3-CORE-SA-2025-008 Security-References: CVE-2024-55893 Security-References: CVE-2024-55894 Security-References: CVE-2024-55920 Security-References: CVE-2024-55921 Security-References: CVE-2024-55922 Security-References: CVE-2024-55923 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/87744 Tested-by: Oliver Hader <oliver.hader@typo3.org> Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
1 parent 531b418 commit 1bb317c

File tree

8 files changed

+137
-60
lines changed

8 files changed

+137
-60
lines changed

Classes/Controller/BackendUserController.php

+46-3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
use TYPO3\CMS\Beuser\Service\UserInformationService;
3535
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
3636
use TYPO3\CMS\Core\Context\Context;
37+
use TYPO3\CMS\Core\Http\AllowedMethodsTrait;
3738
use TYPO3\CMS\Core\Imaging\IconFactory;
3839
use TYPO3\CMS\Core\Imaging\IconSize;
3940
use TYPO3\CMS\Core\Page\PageRenderer;
@@ -55,6 +56,8 @@
5556
*/
5657
class BackendUserController extends ActionController
5758
{
59+
use AllowedMethodsTrait;
60+
5861
protected ?ModuleData $moduleData = null;
5962
protected ModuleTemplate $moduleTemplate;
6063

@@ -309,6 +312,11 @@ public function compareAction(): ResponseInterface
309312
return $this->moduleTemplate->renderResponse('BackendUser/Compare');
310313
}
311314

315+
protected function initializeInitiatePasswordResetAction(): void
316+
{
317+
$this->assertAllowedHttpMethod($this->request, 'POST');
318+
}
319+
312320
/**
313321
* Starts the password reset process for a selected user.
314322
*/
@@ -335,7 +343,12 @@ public function initiatePasswordResetAction(int $user): ResponseInterface
335343
LocalizationUtility::translate('LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:flashMessage.resetPassword.success.title', 'beuser') ?? ''
336344
);
337345
}
338-
return new ForwardResponse('list');
346+
return $this->redirect('list');
347+
}
348+
349+
protected function initializeAddToCompareListAction(): void
350+
{
351+
$this->assertAllowedHttpMethod($this->request, 'POST');
339352
}
340353

341354
/**
@@ -344,7 +357,12 @@ public function initiatePasswordResetAction(int $user): ResponseInterface
344357
public function addToCompareListAction(int $uid): ResponseInterface
345358
{
346359
$this->addToCompareList('compareUserList', $uid);
347-
return new ForwardResponse('list');
360+
return $this->redirect('list');
361+
}
362+
363+
protected function initializeRemoveFromCompareListAction(): void
364+
{
365+
$this->assertAllowedHttpMethod($this->request, 'POST');
348366
}
349367

350368
/**
@@ -359,6 +377,11 @@ public function removeFromCompareListAction(int $uid, int $redirectToCompare = 0
359377
return $this->redirect('list');
360378
}
361379

380+
protected function initializeRemoveAllFromCompareListAction(): void
381+
{
382+
$this->assertAllowedHttpMethod($this->request, 'POST');
383+
}
384+
362385
/**
363386
* Removes all backend users from the compare list
364387
*/
@@ -368,6 +391,11 @@ public function removeAllFromCompareListAction(): ResponseInterface
368391
return $this->redirect('list');
369392
}
370393

394+
protected function initializeTerminateBackendUserSessionAction(): void
395+
{
396+
$this->assertAllowedHttpMethod($this->request, 'POST');
397+
}
398+
371399
/**
372400
* Terminate BackendUser session and logout corresponding client
373401
* Redirects to onlineAction with message
@@ -379,7 +407,7 @@ protected function terminateBackendUserSessionAction(string $sessionId): Respons
379407
if ($success) {
380408
$this->addFlashMessage(LocalizationUtility::translate('LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:backendUser.online.flashMessage.terminateSessionSuccess', 'beuser') ?? '');
381409
}
382-
return new ForwardResponse('online');
410+
return $this->redirect('online');
383411
}
384412

385413
/**
@@ -475,6 +503,11 @@ public function compareGroupsAction(): ResponseInterface
475503
return $this->moduleTemplate->renderResponse('BackendUserGroup/Compare');
476504
}
477505

506+
protected function initializeAddGroupToCompareListAction(): void
507+
{
508+
$this->assertAllowedHttpMethod($this->request, 'POST');
509+
}
510+
478511
/**
479512
* Attaches one backend user group to the compare list
480513
*/
@@ -484,6 +517,11 @@ public function addGroupToCompareListAction(int $uid): ResponseInterface
484517
return $this->redirect('groups');
485518
}
486519

520+
protected function initializeRemoveGroupFromCompareListAction(): void
521+
{
522+
$this->assertAllowedHttpMethod($this->request, 'POST');
523+
}
524+
487525
/**
488526
* Removes given backend user group to the compare list
489527
*/
@@ -496,6 +534,11 @@ public function removeGroupFromCompareListAction(int $uid, int $redirectToCompar
496534
return $this->redirect('groups');
497535
}
498536

537+
protected function initializeRemoveAllGroupsFromCompareListAction(): void
538+
{
539+
$this->assertAllowedHttpMethod($this->request, 'POST');
540+
}
541+
499542
/**
500543
* Removes all backend user groups from the compare list
501544
*/

Resources/Private/Partials/BackendUser/PaginatedList.html

+24-19
Original file line numberDiff line numberDiff line change
@@ -135,16 +135,19 @@
135135
<div class="btn-group" role="group">
136136
<f:if condition="{backendUser.passwordResetEnabled}">
137137
<f:then>
138-
<f:link.action
139-
class="btn btn-default t3js-modal-trigger"
140-
action="initiatePasswordReset"
141-
arguments="{user: backendUser.uid}"
142-
title="{f:translate(key: 'resetPassword.label')}"
143-
role="button"
144-
data="{severity: 'warning', title: '{f:translate(key: \'resetPassword.confirmation.header\')}', bs-content: '{f:translate(key: \'resetPassword.confirmation.text\', arguments: {0: \'{backendUser.email}\'})}', button-close-text: '{f:translate(key: \'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:cancel\')}'}"
145-
>
138+
<f:form.button
139+
name="user"
140+
value="{backendUser.uid}"
141+
form="form-initiate-password-reset"
142+
class="btn btn-default t3js-modal-trigger"
143+
title="{f:translate(key: 'resetPassword.label')}"
144+
type="submit"
145+
data-severity="warning"
146+
data-title="{f:translate(key: 'resetPassword.confirmation.header')}"
147+
data-bs-content="{f:translate(key: 'resetPassword.confirmation.text', arguments: {0: '{backendUser.email}'})}"
148+
data-button-close-text="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:cancel')}">
146149
<core:icon identifier="actions-key" />
147-
</f:link.action>
150+
</f:form.button>
148151
</f:then>
149152
<f:else>
150153
<span class="btn btn-default disabled"><core:icon identifier="empty-empty" /></span>
@@ -173,28 +176,30 @@
173176
<div class="btn-group" role="group">
174177
<f:if condition="{compareUserUidList.{backendUser.uid}}">
175178
<f:then>
176-
<f:link.action
177-
action="removeFromCompareList"
178-
arguments="{uid: backendUser.uid}"
179+
<f:form.button
180+
form="form-remove-from-compare-list"
181+
name="uid"
182+
value="{backendUser.uid}"
183+
type="submit"
179184
class="btn btn-default"
180185
title="{f:translate(key: 'btn.removeFromCompareList')}"
181-
role="button"
182186
>
183187
<core:icon identifier="actions-minus" size="small"/>
184188
<f:translate key="LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:btn.compare" />
185-
</f:link.action>
189+
</f:form.button>
186190
</f:then>
187191
<f:else>
188-
<f:link.action
189-
action="addToCompareList"
190-
arguments="{uid: backendUser.uid}"
192+
<f:form.button
193+
form="form-add-to-compare-list"
194+
name="uid"
195+
value="{backendUser.uid}"
196+
type="submit"
191197
class="btn btn-default"
192198
title="{f:translate(key: 'btn.addToCompareList')}"
193-
role="button"
194199
>
195200
<core:icon identifier="actions-plus" size="small"/>
196201
<f:translate key="LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:btn.compare" />
197-
</f:link.action>
202+
</f:form.button>
198203
</f:else>
199204
</f:if>
200205
<beuser:SwitchUser class="btn btn-default" backendUser="{backendUser}" />

Resources/Private/Partials/BackendUserGroup/PaginatedList.html

+12-10
Original file line numberDiff line numberDiff line change
@@ -105,27 +105,29 @@
105105
<div class="btn-group" role="group">
106106
<f:if condition="{compareGroupUidList.{backendUserGroup.uid}}">
107107
<f:then>
108-
<f:link.action
109-
action="removeGroupFromCompareList"
110-
arguments="{uid: backendUserGroup.uid}"
108+
<f:form.button
109+
form="form-remove-group-from-compare-list"
110+
name="uid"
111+
value="{backendUserGroup.uid}"
112+
type="submit"
111113
class="btn btn-default"
112114
title="{f:translate(key: 'btn.removeFromCompareList')}"
113-
role="button"
114115
>
115116
<core:icon identifier="actions-minus" size="small"/>
116117
<f:translate key="LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:btn.compare" />
117-
</f:link.action>
118+
</f:form.button>
118119
</f:then>
119120
<f:else>
120-
<f:link.action
121-
action="addGroupToCompareList"
122-
arguments="{uid: backendUserGroup.uid}"
121+
<f:form.button
122+
form="form-add-group-to-compare-list"
123+
name="uid"
124+
value="{backendUserGroup.uid}"
125+
type="submit"
123126
class="btn btn-default"
124127
title="{f:translate(key: 'btn.addToCompareList')}"
125-
role="button"
126128
>
127129
<core:icon identifier="actions-plus" size="small"/> <f:translate key="LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:btn.compare" />
128-
</f:link.action>
130+
</f:form.button>
129131
</f:else>
130132
</f:if>
131133
</div>

Resources/Private/Templates/BackendUser/Compare.html

+10-4
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,17 @@ <h1><f:translate key="LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:ba
5555
>
5656
<core:icon identifier="actions-open" />
5757
</backend:link.editRecord>
58-
<f:link.action
59-
action="removeFromCompareList"
60-
arguments="{uid: compareData.user.uid, redirectToCompare: 1}"
58+
59+
<f:form.button
60+
form="form-remove-from-compare-list"
61+
name="uid"
62+
value="{compareData.user.uid}"
63+
type="submit"
6164
class="btn btn-default btn-sm"
6265
title="{f:translate(key:'LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:btn.removeFromCompareList')}"
6366
>
6467
<core:icon identifier="actions-minus" size="small" />
65-
</f:link.action>
68+
</f:form.button>
6669
</div>
6770
</div>
6871
</th>
@@ -287,6 +290,9 @@ <h1><f:translate key="LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:ba
287290
</table>
288291
</div>
289292

293+
<f:form action="removeFromCompareList" method="post" id="form-remove-from-compare-list" class="hidden">
294+
<f:form.hidden name="redirectToCompare" value="1"/>
295+
</f:form>
290296
</f:section>
291297

292298
</html>

Resources/Private/Templates/BackendUser/List.html

+12-7
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,16 @@ <h2><f:translate key="LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:ba
6565
>
6666
<core:icon identifier="actions-open" />
6767
</backend:link.editRecord>
68-
<f:link.action
69-
action="removeFromCompareList"
70-
arguments="{uid: compareUser.uid}"
68+
<f:form.button
69+
form="form-remove-from-compare-list"
70+
name="uid"
71+
value="{compareUser.uid}"
72+
type="submit"
7173
class="btn btn-default"
7274
title="{f:translate(key: 'btn.removeFromCompareList')}"
73-
role="button"
7475
>
7576
<core:icon identifier="actions-minus" />
76-
</f:link.action>
77+
</f:form.button>
7778
</td>
7879
</tr>
7980
</f:for>
@@ -94,16 +95,20 @@ <h2><f:translate key="LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:ba
9495
<core:icon identifier="actions-code-compare" />
9596
<f:translate key="LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:backendUser.list.btn.compareList" />
9697
</f:link.action>
97-
<f:link.action action="removeAllFromCompareList" class="btn btn-default">
98+
<f:form.button type="submit" class="btn btn-default" form="form-remove-all-from-compare-list">
9899
<core:icon identifier="actions-selection-delete" />
99100
<f:translate key="LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:btn.clearCompareList" />
100-
</f:link.action>
101+
</f:form.button>
101102

102103
<h2><f:translate key="LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:backendUser.list.section.allUsers" /></h2>
103104
</f:if>
104105
<f:render partial="BackendUser/Filter" arguments="{demand: demand, backendUserGroups: backendUserGroups}" />
105106
<f:render partial="BackendUser/PaginatedList" arguments="{_all}" />
106107

108+
<f:form action="initiatePasswordReset" method="post" id="form-initiate-password-reset" class="hidden"/>
109+
<f:form action="removeFromCompareList" method="post" id="form-remove-from-compare-list" class="hidden"/>
110+
<f:form action="addToCompareList" method="post" id="form-add-to-compare-list" class="hidden"/>
111+
<f:form action="removeAllFromCompareList" method="post" id="form-remove-all-from-compare-list" class="hidden"/>
107112
</f:section>
108113

109114
</html>

Resources/Private/Templates/BackendUser/Online.html

+13-7
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,21 @@ <h1><f:translate key="LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:ba
6767
<td class="col-control">
6868
<f:if condition="{currentSessionId} == {session.id}">
6969
<f:else>
70-
<f:link.action
70+
<f:form.button
71+
name="sessionId"
72+
value="{session.id}"
73+
form="form-terminate-backend-user-session"
7174
class="btn btn-default t3js-modal-trigger"
72-
action="terminateBackendUserSession"
73-
controller="BackendUser"
74-
arguments="{sessionId: session.id}"
75-
data="{severity: 'warning', title: '{f:translate(key: \'backendUser.online.endSession\')}', bs-content: '{f:translate(key: \'backendUser.online.reallyLogout\')} {onlineUser.backendUser.userName}?', button-close-text: '{f:translate(key: \'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:cancel\')}'}"
76-
role="button"
75+
title="{f:translate(key: 'resetPassword.label')}"
76+
type="submit"
77+
data-severity="warning"
78+
data-title="{f:translate(key: 'backendUser.online.endSession')}"
79+
data-bs-content="{f:translate(key: 'backendUser.online.reallyLogout')} {onlineUser.backendUser.userName}?"
80+
data-button-close-text="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:cancel')}"
7781
>
7882
<core:icon identifier="actions-close" />
7983
<f:translate key="LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:backendUser.online.endSession" />
80-
</f:link.action>
84+
</f:form.button>
8185
</f:else>
8286
</f:if>
8387
<a
@@ -108,6 +112,8 @@ <h1><f:translate key="LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:ba
108112
</table>
109113
</div>
110114

115+
<f:form id="form-terminate-backend-user-session" action="terminateBackendUserSession" controller="BackendUser" method="post"class="hidden" />
116+
111117
</f:section>
112118

113119
</html>

0 commit comments

Comments
 (0)