Skip to content

Commit fe71d58

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 a4abf48 commit fe71d58

File tree

54 files changed

+661
-185
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+661
-185
lines changed

Build/Sources/Sass/dashboard/_widget.scss

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@
9191
opacity: .5;
9292
border-radius: 4px;
9393
transition: opacity .2s ease-in-out;
94+
background: none;
95+
border: none;
96+
appearance: none;
9497

9598
&:hover,
9699
&:focus {

Build/Sources/TypeScript/dashboard/dashboard-delete.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ class DashboardDelete {
2424
}
2525

2626
public initialize(): void {
27-
new RegularEvent('click', function (this: HTMLElement, e: Event): void {
27+
new RegularEvent('click', function (this: HTMLButtonElement, e: Event): void {
2828
e.preventDefault();
29+
const form = this.form;
2930
const modal = Modal.confirm(
3031
this.dataset.modalTitle,
3132
this.dataset.modalQuestion,
@@ -47,7 +48,7 @@ class DashboardDelete {
4748
modal.addEventListener('button.clicked', (e: Event): void => {
4849
const target = e.target as HTMLButtonElement;
4950
if (target.getAttribute('name') === 'delete') {
50-
window.location.href = this.getAttribute('href');
51+
form.requestSubmit();
5152
}
5253
Modal.dismiss();
5354
});

Build/Sources/TypeScript/dashboard/widget-remover.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ class WidgetRemover {
2424
}
2525

2626
public initialize(): void {
27-
new RegularEvent('click', function (this: HTMLElement, e: Event): void {
27+
new RegularEvent('click', function (this: HTMLButtonElement, e: Event): void {
2828
e.preventDefault();
29+
const form = this.form;
2930
const modal = Modal.confirm(
3031
this.dataset.modalTitle,
3132
this.dataset.modalQuestion,
@@ -47,7 +48,7 @@ class WidgetRemover {
4748
modal.addEventListener('button.clicked', (e: Event): void => {
4849
const target = e.target as HTMLButtonElement;
4950
if (target.getAttribute('name') === 'delete') {
50-
window.location.href = this.getAttribute('href');
51+
form.requestSubmit();
5152
}
5253
modal.hideModal();
5354
});

Build/Sources/TypeScript/dashboard/widget-selector.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,22 @@ class WidgetSelector {
1919

2020
private readonly selector: string = '.js-dashboard-addWidget';
2121

22+
private modal: ModalElement|null = null;
23+
2224
constructor() {
2325
this.initialize();
2426
}
2527

2628
public initialize(): void {
27-
new RegularEvent('click', function (this: HTMLElement, e: Event): void {
29+
new RegularEvent('click', (e: Event, trigger: HTMLElement): void => {
2830
e.preventDefault();
2931

3032
const modalContent = new DocumentFragment();
3133
modalContent.append((document.getElementById('widgetSelector') as HTMLTemplateElement).content.cloneNode(true));
3234

3335
const configuration = {
3436
type: Modal.types.default,
35-
title: this.dataset.modalTitle,
37+
title: trigger.dataset.modalTitle,
3638
size: Modal.sizes.medium,
3739
severity: SeverityEnum.notice,
3840
content: modalContent,
@@ -55,8 +57,15 @@ class WidgetSelector {
5557
modal.hideModal();
5658
}
5759
});
60+
this.modal = modal;
5861
}).delegateTo(document, this.selector);
5962

63+
new RegularEvent('typo3.dashboard.addWidgetDone', (): void => {
64+
this.modal?.hideModal();
65+
this.modal = null;
66+
location.reload();
67+
}).bindTo(top.document);
68+
6069
// Display button only if all initialized
6170
document.querySelectorAll(this.selector).forEach((item) => {
6271
item.classList.remove('hide');

Build/Sources/TypeScript/extensionmanager/main.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,13 @@ class ExtensionManager {
112112
text: TYPO3.lang['button.reimport'],
113113
btnClass: 'btn-warning',
114114
trigger: (): void => {
115-
window.location.href = target.href;
116-
Modal.dismiss();
115+
NProgress.start();
116+
new AjaxRequest(target.href).post({}).then((): void => {
117+
location.reload();
118+
}).finally((): void => {
119+
NProgress.done();
120+
Modal.dismiss();
121+
});
117122
},
118123
},
119124
],
@@ -198,7 +203,7 @@ class ExtensionManager {
198203

199204
private removeExtensionFromDisk(trigger: HTMLAnchorElement): void {
200205
NProgress.start();
201-
new AjaxRequest(trigger.href).get().then((): void => {
206+
new AjaxRequest(trigger.href).post({}).then((): void => {
202207
location.reload();
203208
}).finally((): void => {
204209
NProgress.done();
@@ -264,9 +269,9 @@ class ExtensionManager {
264269
btnClass: 'btn-warning',
265270
trigger: (e: Event, modal: ModalElement): void => {
266271
NProgress.start();
267-
new AjaxRequest(data.url).withQueryArguments({
272+
new AjaxRequest(data.url).post({
268273
version: (modal.querySelector('input[name="version"]:checked') as HTMLInputElement)?.value,
269-
}).get().finally((): void => {
274+
}).finally((): void => {
270275
location.reload();
271276
});
272277
modal.hideModal();

Build/Sources/TypeScript/extensionmanager/repository.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ class Repository {
9999

100100
private getResolveDependenciesAndInstallResult(url: string): void {
101101
NProgress.start();
102-
new AjaxRequest(url).get().then(async (response: AjaxResponse): Promise<void> => {
102+
new AjaxRequest(url).post({}).then(async (response: AjaxResponse): Promise<void> => {
103103
try {
104104
// FIXME: As of now, the endpoint doesn't set proper headers, thus we have to parse the response text
105105
// https://review.typo3.org/c/Packages/TYPO3.CMS/+/63438
@@ -165,6 +165,11 @@ class Repository {
165165
TYPO3.lang['extensionList.dependenciesResolveInstallError.message'] || 'Your installation failed while resolving dependencies.'
166166
);
167167
}
168+
}, (): void => {
169+
Notification.error(
170+
TYPO3.lang['extensionList.dependenciesResolveInstallError.title'] || 'Install error',
171+
TYPO3.lang['extensionList.dependenciesResolveInstallError.message'] || 'Your installation failed while resolving dependencies.'
172+
);
168173
}).finally((): void => {
169174
NProgress.done();
170175
});

Build/Sources/TypeScript/extensionmanager/update.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class ExtensionManagerUpdate {
6464
let reload = false;
6565

6666
NProgress.start();
67-
new AjaxRequest(url).get().then(async (response: AjaxResponse): Promise<void> => {
67+
new AjaxRequest(url).post({}).then(async (response: AjaxResponse): Promise<void> => {
6868
const data = await response.resolve();
6969
// Something went wrong, show message
7070
if (data.errorMessage.length) {

Build/Sources/TypeScript/form/backend/form-manager/view-model.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -473,8 +473,16 @@ function removeFormSetup(formManagerApp: FormManager): void {
473473
btnClass: 'btn-danger',
474474
name: 'createform',
475475
trigger: function(e: Event, modal: ModalElement) {
476-
document.location = formManagerApp.getAjaxEndpoint('delete') + '&formPersistenceIdentifier=' + that.data('formPersistenceIdentifier');
477-
modal.hideModal();
476+
$.post(formManagerApp.getAjaxEndpoint('delete'), {
477+
formPersistenceIdentifier: that.data('formPersistenceIdentifier'),
478+
}, function(data) {
479+
if (data.status === 'success') {
480+
document.location = data.url;
481+
} else {
482+
Notification.error(data.title, data.message);
483+
}
484+
modal.hideModal();
485+
});
478486
}
479487
});
480488

typo3/sysext/backend/Classes/Http/RouteDispatcher.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use TYPO3\CMS\Core\Core\Environment;
3232
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
3333
use TYPO3\CMS\Core\Http\Dispatcher;
34+
use TYPO3\CMS\Core\Http\Error\MethodNotAllowedException;
3435
use TYPO3\CMS\Core\Http\Security\ReferrerEnforcer;
3536
use TYPO3\CMS\Core\Utility\GeneralUtility;
3637

@@ -74,7 +75,11 @@ public function dispatch(ServerRequestInterface $request): ResponseInterface
7475
$targetIdentifier = $route->getOption('target');
7576
$target = $this->getCallableFromTarget($targetIdentifier);
7677
$arguments = [$request];
77-
return $target(...$arguments);
78+
try {
79+
return $target(...$arguments);
80+
} catch (MethodNotAllowedException $exception) {
81+
return $exception->createResponse();
82+
}
7883
}
7984

8085
/**

typo3/sysext/belog/Classes/Controller/BackendLogController.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use TYPO3\CMS\Belog\Domain\Repository\LogEntryRepository;
2626
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
2727
use TYPO3\CMS\Core\Database\ConnectionPool;
28+
use TYPO3\CMS\Core\Http\AllowedMethodsTrait;
2829
use TYPO3\CMS\Core\Type\Bitmask\Permission;
2930
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
3031
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
@@ -39,6 +40,8 @@
3940
*/
4041
class BackendLogController extends ActionController
4142
{
43+
use AllowedMethodsTrait;
44+
4245
public function __construct(
4346
protected readonly ModuleTemplateFactory $moduleTemplateFactory,
4447
protected readonly LogEntryRepository $logEntryRepository,
@@ -124,6 +127,11 @@ public function listAction(?Constraint $constraint = null, string $operation = '
124127
->renderResponse('BackendLog/List');
125128
}
126129

130+
public function initializeDeleteMessageAction(): void
131+
{
132+
$this->assertAllowedHttpMethod($this->request, 'POST');
133+
}
134+
127135
/**
128136
* Delete all log entries that share the same message with the log entry given
129137
* in $errorUid

0 commit comments

Comments
 (0)