Skip to content

Commit 62069fd

Browse files
DeepDiver1975claudejvillafanez
authored
feat: release 10.16.3 (#41559)
* fix(security): OC10-75 - restrict AppConfigController read methods to full admins only (#41550) * fix(security): restrict AppConfigController read methods to full admins only OC10-75: Subadmins could read all oc_appconfig values including SMTP passwords and LDAP credentials via getApps/getKeys/getValue endpoints. Remove @NoAdminRequired so AdminMiddleware enforces full-admin-only access, consistent with the write methods. CVSS: 7.7 Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com> * fix: replace OC.AppConfig.getValue in users.js with server-rendered checkbox state The umgmt_set_password value is already rendered server-side into #CheckBoxPasswordOnUserCreate's checked attribute. Remove the redundant AJAX call which would now return 403 for Subadmins after the security fix. Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com> * chore: add changelog entry for OC10-75 (#41550) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com> --------- Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(comments): prevent IDOR in WebDAV comments API (#41558) * test(comments): stub objectType/objectId in EntityCollection happy-path tests Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com> * test(comments): add failing IDOR regression tests for EntityCollection Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com> * fix(comments): prevent IDOR in WebDAV comments API by checking comment ownership An authenticated user could PROPFIND/DELETE/PROPPATCH any comment by supplying an arbitrary comment_id paired with any file_id they own. EntityCollection::getChild() and childExists() now verify that the fetched comment's objectType and objectId match the collection's own entity type and file ID before returning or confirming the node. Fixes OC10-53 Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com> * docs: add changelog entry for OC10-53 IDOR fix in WebDAV comments API Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com> --------- Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(security): update phpseclib/phpseclib to 3.0.52 for CVE-2026-40194 CVE-2026-40194: timing attack in SSH binary packet processing fixed in 3.0.51. Also picks up 3.0.52 correctness fixes (ASN.1 hardening, OpenSSL 3.2+ RSA compat). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com> * fix(security): update symfony/routing to 5.4.52 for CVE-2026-45065 CVE-2026-45065: UrlGenerator regex alternation anchoring bypass allowing off-site URL injection via route requirement validation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com> * fix: check for the identifier alias for the storage backend (#41538) * fix: check for the identifier alias for the storage backend * test: add unit tests to local external storage * chore: add changelog entry * fix: move backend checks to a different place * fix: adjust unit tests * fix: visibility for local storage for the admin based on flag Without the visibility, the admin won't be able to create local storages, and the previously created local mounts will be hidden and inaccessible * fix: review comments * fix: remove user mounting check in controller and rely on validation * fix: adjust code based on reviews (cherry picked from commit 5c7dfc0) Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com> * fix: use the right user id when changing the email (#41539) * fix: use the right user id when changing the email * test: adjust unit test to ensure the mail is set for the user2 Previously, the test only used one user, so it was difficult to verify that the mail was changed for the user2 instead of user1 * docs: add changelog entry for PR #41539 * fix: include test to ensure the mail of the caller isn't changed --------- Co-authored-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com> (cherry picked from commit 3ff2884) Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com> * chore: add changelog for 10.16.3 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com> * ci: fix lint pipeline and sync with master * chore: set version properly and generate changelog Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com> --------- Signed-off-by: Thomas Müller <1005065+DeepDiver1975@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Juan Pablo Villafañez <jpvillafanez@izertis.com>
1 parent 5854c05 commit 62069fd

29 files changed

Lines changed: 787 additions & 211 deletions

.github/workflows/lint-and-codestyle.yml

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,3 @@ jobs:
5959
- name: Run PHP Phan
6060
run: |
6161
make test-php-phan
62-
63-
git-commit-messages:
64-
name: Git Commit Messages
65-
runs-on: ubuntu-latest
66-
steps:
67-
- name: Checkout code
68-
uses: actions/checkout@v4
69-
with:
70-
fetch-depth: 0
71-
72-
- name: Validate commit messages
73-
uses: docker://aevea/commitsar:latest
74-

CHANGELOG.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Table of Contents
22

3+
* [Changelog for 10.16.3](#changelog-for-owncloud-core-10163-2026-05-22)
34
* [Changelog for 10.16.2](#changelog-for-owncloud-core-10162-2026-04-02)
45
* [Changelog for 10.16.1](#changelog-for-owncloud-core-10161-2026-02-18)
56
* [Changelog for 10.16.0](#changelog-for-owncloud-core-10160-2025-10-23)
@@ -27,6 +28,77 @@
2728
* [Changelog for 10.4.1](#changelog-for-owncloud-core-1041-2020-03-30)
2829
* [Changelog for 10.4.0](#changelog-for-owncloud-core-1040-2020-02-10)
2930
* [Changelog for 10.3.2](#changelog-for-owncloud-core-1032-2019-12-04)
31+
# Changelog for ownCloud Core [10.16.3] (2026-05-22)
32+
33+
The following sections list the changes in ownCloud core 10.16.3 relevant to
34+
ownCloud admins and users.
35+
36+
[10.16.3]: https://github.com/owncloud/core/compare/v10.16.2...v10.16.3
37+
38+
## Summary
39+
40+
* Security - Update phpseclib to 3.0.52 for CVE-2026-40194: [#41529](https://github.com/owncloud/core/pull/41529)
41+
* Security - Restrict AppConfigController read methods to full admins only: [#41550](https://github.com/owncloud/core/pull/41550)
42+
* Security - Update symfony/routing to 5.4.52 for CVE-2026-45065: [#41559](https://github.com/owncloud/core/pull/41559)
43+
* Bugfix - Prevent mounting local storage if not allowed: [#41538](https://github.com/owncloud/core/pull/41538)
44+
* Bugfix - Use the correct user ID when changing email via admin API: [#41539](https://github.com/owncloud/core/pull/41539)
45+
* Bugfix - Prevent IDOR in WebDAV comments API: [#41558](https://github.com/owncloud/core/pull/41558)
46+
47+
## Details
48+
49+
* Security - Update phpseclib to 3.0.52 for CVE-2026-40194: [#41529](https://github.com/owncloud/core/pull/41529)
50+
51+
CVE-2026-40194: Timing attack vulnerability in SSH binary packet processing.
52+
Upgraded phpseclib/phpseclib from 3.0.50 to 3.0.52.
53+
54+
https://github.com/owncloud/core/pull/41529
55+
https://github.com/owncloud/core/pull/41541
56+
https://github.com/phpseclib/phpseclib/releases/tag/3.0.51
57+
58+
* Security - Restrict AppConfigController read methods to full admins only: [#41550](https://github.com/owncloud/core/pull/41550)
59+
60+
Subadmin users could read all oc_appconfig values including SMTP passwords, LDAP
61+
bind credentials, and encryption master keys via the Settings API. Removed
62+
@NoAdminRequired from getApps, getKeys, and getValue so that the AdminMiddleware
63+
enforces full-admin-only access, consistent with the write methods.
64+
65+
https://github.com/owncloud/core/pull/41550
66+
67+
* Security - Update symfony/routing to 5.4.52 for CVE-2026-45065: [#41559](https://github.com/owncloud/core/pull/41559)
68+
69+
CVE-2026-45065: UrlGenerator route-requirement bypass via unanchored regex
70+
alternation allowing off-site URL injection. Upgraded symfony/routing from
71+
5.4.48 to 5.4.52.
72+
73+
https://github.com/owncloud/core/pull/41559
74+
https://symfony.com/cve-2026-45065
75+
76+
* Bugfix - Prevent mounting local storage if not allowed: [#41538](https://github.com/owncloud/core/pull/41538)
77+
78+
Mounting a local storage was possible if the internal class name was used as
79+
backend, despite local storage not allowed to be mounted. This problem is fixed
80+
and the local storage can't be mounted if it was explicitly disallowed in the
81+
configuration.
82+
83+
https://github.com/owncloud/core/pull/41538
84+
85+
* Bugfix - Use the correct user ID when changing email via admin API: [#41539](https://github.com/owncloud/core/pull/41539)
86+
87+
The admin API endpoint for changing a user's email address was incorrectly using
88+
the requesting admin's user ID instead of the target user's ID, causing the
89+
admin's email to be updated rather than the intended user's.
90+
91+
https://github.com/owncloud/core/pull/41539
92+
93+
* Bugfix - Prevent IDOR in WebDAV comments API: [#41558](https://github.com/owncloud/core/pull/41558)
94+
95+
Authenticated users could read, edit, or delete comments on files they have no
96+
access to by supplying an arbitrary comment ID in the WebDAV comments endpoint.
97+
The fix verifies that a requested comment belongs to the file in the URL before
98+
returning it.
99+
100+
https://github.com/owncloud/core/pull/41558
101+
30102
# Changelog for ownCloud Core [10.16.2] (2026-04-02)
31103

32104
The following sections list the changes in ownCloud core 10.16.2 relevant to

apps/comments/lib/Dav/EntityCollection.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ public function getId() {
9898
public function getChild($name) {
9999
try {
100100
$comment = $this->commentsManager->get($name);
101+
// objectType/objectId identify the entity (e.g. file) the comment is attached to
102+
if ($comment->getObjectType() !== $this->name || $comment->getObjectId() !== $this->id) {
103+
throw new NotFound();
104+
}
101105
return new CommentNode(
102106
$this->commentsManager,
103107
$comment,
@@ -151,8 +155,10 @@ public function findChildren($limit = 0, $offset = 0, \DateTime $datetime = null
151155
*/
152156
public function childExists($name) {
153157
try {
154-
$this->commentsManager->get($name);
155-
return true;
158+
$comment = $this->commentsManager->get($name);
159+
// objectType/objectId identify the entity (e.g. file) the comment is attached to
160+
return $comment->getObjectType() === $this->name
161+
&& $comment->getObjectId() === $this->id;
156162
} catch (NotFoundException $e) {
157163
return false;
158164
}

apps/comments/tests/unit/Dav/EntityCollectionTest.php

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,14 @@ public function testGetId(): void {
7070
}
7171

7272
public function testGetChild(): void {
73+
$comment = $this->createMock(IComment::class);
74+
$comment->method('getObjectType')->willReturn('files');
75+
$comment->method('getObjectId')->willReturn('19');
76+
7377
$this->commentsManager->expects($this->once())
7478
->method('get')
7579
->with('55')
76-
->will($this->returnValue($this->createMock(IComment::class)));
80+
->willReturn($comment);
7781

7882
$node = $this->collection->getChild('55');
7983
$this->assertInstanceOf(\OCA\Comments\Dav\CommentNode::class, $node);
@@ -118,6 +122,15 @@ public function testFindChildren(): void {
118122
}
119123

120124
public function testChildExistsTrue(): void {
125+
$comment = $this->createMock(IComment::class);
126+
$comment->method('getObjectType')->willReturn('files');
127+
$comment->method('getObjectId')->willReturn('19');
128+
129+
$this->commentsManager->expects($this->once())
130+
->method('get')
131+
->with('44')
132+
->willReturn($comment);
133+
121134
$this->assertTrue($this->collection->childExists('44'));
122135
}
123136

@@ -129,4 +142,60 @@ public function testChildExistsFalse(): void {
129142

130143
$this->assertFalse($this->collection->childExists('44'));
131144
}
145+
146+
public function testGetChildWrongFile(): void {
147+
$this->expectException(\Sabre\DAV\Exception\NotFound::class);
148+
149+
$comment = $this->createMock(IComment::class);
150+
$comment->method('getObjectType')->willReturn('files');
151+
$comment->method('getObjectId')->willReturn('999'); // different file
152+
153+
$this->commentsManager->expects($this->once())
154+
->method('get')
155+
->with('55')
156+
->willReturn($comment);
157+
158+
$this->collection->getChild('55');
159+
}
160+
161+
public function testGetChildWrongObjectType(): void {
162+
$this->expectException(\Sabre\DAV\Exception\NotFound::class);
163+
164+
$comment = $this->createMock(IComment::class);
165+
$comment->method('getObjectType')->willReturn('albums'); // wrong type
166+
$comment->method('getObjectId')->willReturn('19');
167+
168+
$this->commentsManager->expects($this->once())
169+
->method('get')
170+
->with('55')
171+
->willReturn($comment);
172+
173+
$this->collection->getChild('55');
174+
}
175+
176+
public function testChildExistsWrongFile(): void {
177+
$comment = $this->createMock(IComment::class);
178+
$comment->method('getObjectType')->willReturn('files');
179+
$comment->method('getObjectId')->willReturn('999'); // different file
180+
181+
$this->commentsManager->expects($this->once())
182+
->method('get')
183+
->with('44')
184+
->willReturn($comment);
185+
186+
$this->assertFalse($this->collection->childExists('44'));
187+
}
188+
189+
public function testChildExistsWrongObjectType(): void {
190+
$comment = $this->createMock(IComment::class);
191+
$comment->method('getObjectType')->willReturn('albums'); // wrong type
192+
$comment->method('getObjectId')->willReturn('19');
193+
194+
$this->commentsManager->expects($this->once())
195+
->method('get')
196+
->with('44')
197+
->willReturn($comment);
198+
199+
$this->assertFalse($this->collection->childExists('44'));
200+
}
132201
}

apps/files_external/lib/Controller/GlobalStoragesController.php

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,10 @@ public function create(
8686
$applicableGroups,
8787
$priority
8888
) {
89-
$canCreateNewLocalStorage = \OC::$server->getConfig()->getSystemValue('files_external_allow_create_new_local', false);
90-
91-
if ($backend === 'local' && $canCreateNewLocalStorage === false) {
92-
return new DataResponse(
93-
null,
94-
Http::STATUS_FORBIDDEN
95-
);
96-
}
97-
89+
// NOTE: files_external_allow_create_new_local is checked during backend
90+
// registration, and it will remove the related storage visibility
91+
// if applicable. If the storage isn't visible, the `validate` method
92+
// will fail.
9893
$newStorage = $this->createStorage(
9994
$mountPoint,
10095
$backend,

apps/files_external/lib/Controller/StoragesController.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ protected function validate(IStorageConfig $storage) {
183183
$backend->getIdentifier()
184184
])
185185
],
186-
Http::STATUS_UNPROCESSABLE_ENTITY
186+
Http::STATUS_FORBIDDEN
187187
);
188188
}
189189
if (!$authMechanism->isVisibleFor($this->service->getVisibilityType())) {
@@ -194,7 +194,7 @@ protected function validate(IStorageConfig $storage) {
194194
$authMechanism->getIdentifier()
195195
])
196196
],
197-
Http::STATUS_UNPROCESSABLE_ENTITY
197+
Http::STATUS_FORBIDDEN
198198
);
199199
}
200200

@@ -308,7 +308,7 @@ public function show($id, $testOnly = true) {
308308
} catch (NotFoundException $e) {
309309
return new DataResponse(
310310
[
311-
'message' => (string)$this->l10n->t('Storage with id "%i" not found', [$id])
311+
'message' => (string)$this->l10n->t('Storage with id "%d" not found', [$id])
312312
],
313313
Http::STATUS_NOT_FOUND
314314
);
@@ -335,7 +335,7 @@ public function destroy($id) {
335335
} catch (NotFoundException $e) {
336336
return new DataResponse(
337337
[
338-
'message' => (string)$this->l10n->t('Storage with id "%i" not found', [$id])
338+
'message' => (string)$this->l10n->t('Storage with id "%d" not found', [$id])
339339
],
340340
Http::STATUS_NOT_FOUND
341341
);

apps/files_external/lib/Controller/UserStoragesController.php

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
use OCP\Files\External\IStorageConfig;
3232
use OCP\Files\External\NotFoundException;
3333
use OCP\Files\External\Service\IUserStoragesService;
34-
use OCP\IConfig;
3534
use OCP\IL10N;
3635
use OCP\ILogger;
3736
use OCP\IRequest;
@@ -45,15 +44,13 @@ class UserStoragesController extends StoragesController {
4544
* @var IUserSession
4645
*/
4746
private $userSession;
48-
private IConfig $config;
4947

5048
public function __construct(
5149
$AppName,
5250
IRequest $request,
5351
IL10N $l10n,
5452
IUserStoragesService $userStoragesService,
5553
IUserSession $userSession,
56-
IConfig $config,
5754
ILogger $logger
5855
) {
5956
parent::__construct(
@@ -64,7 +61,6 @@ public function __construct(
6461
$logger
6562
);
6663
$this->userSession = $userSession;
67-
$this->config = $config;
6864
}
6965

7066
protected function manipulateStorageConfig(IStorageConfig $storage) {
@@ -82,12 +78,6 @@ protected function manipulateStorageConfig(IStorageConfig $storage) {
8278
* @return DataResponse
8379
*/
8480
public function index() {
85-
if (!$this->isUserMountingAllowed()) {
86-
return new DataResponse(
87-
null,
88-
Http::STATUS_FORBIDDEN
89-
);
90-
}
9181
return parent::index();
9282
}
9383

@@ -122,20 +112,11 @@ public function create(
122112
$backendOptions,
123113
$mountOptions
124114
) {
125-
if (!$this->isUserMountingAllowed()) {
126-
return new DataResponse(
127-
null,
128-
Http::STATUS_FORBIDDEN
129-
);
130-
}
131-
$canCreateNewLocalStorage = \OC::$server->getConfig()->getSystemValue('files_external_allow_create_new_local', false);
132-
if ($backend === 'local' && $canCreateNewLocalStorage === false) {
133-
return new DataResponse(
134-
null,
135-
Http::STATUS_FORBIDDEN
136-
);
137-
}
138-
115+
// NOTE: local backend isn't allowed for regular users regardless
116+
// of the value of files_external_allow_create_new_local. The backend
117+
// won't be visible for regular users, so the `validate` method will fail.
118+
// Only admins can create local storages (if files_external_allow_create_new_local
119+
// is true)
139120
$newStorage = $this->createStorage(
140121
$mountPoint,
141122
$backend,
@@ -188,12 +169,6 @@ public function update(
188169
$mountOptions,
189170
$testOnly = true
190171
) {
191-
if (!$this->isUserMountingAllowed()) {
192-
return new DataResponse(
193-
null,
194-
Http::STATUS_FORBIDDEN
195-
);
196-
}
197172
$storage = $this->createStorage(
198173
$mountPoint,
199174
$backend,
@@ -241,17 +216,6 @@ public function update(
241216
* {@inheritdoc}
242217
*/
243218
public function destroy($id) {
244-
if (!$this->isUserMountingAllowed()) {
245-
return new DataResponse(
246-
null,
247-
Http::STATUS_FORBIDDEN
248-
);
249-
}
250-
251219
return parent::destroy($id);
252220
}
253-
254-
private function isUserMountingAllowed(): bool {
255-
return $this->config->getAppValue('files_external', 'allow_user_mounting', 'no') === 'yes';
256-
}
257221
}

0 commit comments

Comments
 (0)