Skip to content

Commit 2beab7c

Browse files
authored
Merge pull request #54029 from nextcloud/backport/54019/stable30
[stable30] fix(settings): verify source of app-discover media
2 parents 7765711 + a34dc95 commit 2beab7c

File tree

1 file changed

+48
-3
lines changed

1 file changed

+48
-3
lines changed

apps/settings/lib/Controller/AppSettingsController.php

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
2424
use OCP\AppFramework\Http\Attribute\OpenAPI;
2525
use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
26-
use OCP\AppFramework\Http\Attribute\PublicPage;
2726
use OCP\AppFramework\Http\ContentSecurityPolicy;
2827
use OCP\AppFramework\Http\FileDisplayResponse;
2928
use OCP\AppFramework\Http\JSONResponse;
@@ -43,7 +42,9 @@
4342
use OCP\INavigationManager;
4443
use OCP\IRequest;
4544
use OCP\IURLGenerator;
45+
use OCP\IUserSession;
4646
use OCP\L10N\IFactory;
47+
use OCP\Security\RateLimiting\ILimiter;
4748
use OCP\Server;
4849
use Psr\Log\LoggerInterface;
4950

@@ -126,9 +127,8 @@ public function getAppDiscoverJSON(): JSONResponse {
126127
* @param string $image
127128
* @throws \Exception
128129
*/
129-
#[PublicPage]
130130
#[NoCSRFRequired]
131-
public function getAppDiscoverMedia(string $fileName): Response {
131+
public function getAppDiscoverMedia(string $fileName, ILimiter $limiter, IUserSession $session): Response {
132132
$getEtag = $this->discoverFetcher->getETag() ?? date('Y-m');
133133
$etag = trim($getEtag, '"');
134134

@@ -158,6 +158,26 @@ public function getAppDiscoverMedia(string $fileName): Response {
158158
$file = reset($file);
159159
// If not found request from Web
160160
if ($file === false) {
161+
$user = $session->getUser();
162+
// this route is not public thus we can assume a user is logged-in
163+
assert($user !== null);
164+
// Register a user request to throttle fetching external data
165+
// this will prevent using the server for DoS of other systems.
166+
$limiter->registerUserRequest(
167+
'settings-discover-media',
168+
// allow up to 24 media requests per hour
169+
// this should be a sane default when a completely new section is loaded
170+
// keep in mind browsers request all files from a source-set
171+
24,
172+
60 * 60,
173+
$user,
174+
);
175+
176+
if (!$this->checkCanDownloadMedia($fileName)) {
177+
$this->logger->warning('Tried to load media files for app discover section from untrusted source');
178+
return new NotFoundResponse(Http::STATUS_BAD_REQUEST);
179+
}
180+
161181
try {
162182
$client = $this->clientService->newClient();
163183
$fileResponse = $client->get($fileName);
@@ -179,6 +199,31 @@ public function getAppDiscoverMedia(string $fileName): Response {
179199
return $response;
180200
}
181201

202+
private function checkCanDownloadMedia(string $filename): bool {
203+
$urlInfo = parse_url($filename);
204+
if (!isset($urlInfo['host']) || !isset($urlInfo['path'])) {
205+
return false;
206+
}
207+
208+
// Always allowed hosts
209+
if ($urlInfo['host'] === 'nextcloud.com') {
210+
return true;
211+
}
212+
213+
// Hosts that need further verification
214+
// Github is only allowed if from our organization
215+
$ALLOWED_HOSTS = ['github.com', 'raw.githubusercontent.com'];
216+
if (!in_array($urlInfo['host'], $ALLOWED_HOSTS)) {
217+
return false;
218+
}
219+
220+
if (str_starts_with($urlInfo['path'], '/nextcloud/') || str_starts_with($urlInfo['path'], '/nextcloud-gmbh/')) {
221+
return true;
222+
}
223+
224+
return false;
225+
}
226+
182227
/**
183228
* Remove orphaned folders from the image cache that do not match the current etag
184229
* @param ISimpleFolder $folder The folder to clear

0 commit comments

Comments
 (0)