Skip to content

Commit f4553f6

Browse files
authored
Please don't tell anyone about this commit. It's a secret. (#4465)
1 parent 51be8c6 commit f4553f6

5 files changed

Lines changed: 95 additions & 6 deletions

File tree

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ RUN apt-get update \
8989
ghostscript \
9090
# Update with respect to vulnerabilities detected with Trivy
9191
libgssapi-krb5-2 \
92+
libssh2-1t64 \
9293
&& sed -i '/<\/policymap>/i \ <policy domain="coder" rights="read|write" pattern="PDF" \/>' /etc/ImageMagick-7/policy.xml \
9394
&& install-php-extensions \
9495
pdo_mysql \

app/Http/Controllers/SecurePathController.php

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
use Illuminate\Support\Facades\Crypt;
2525
use Illuminate\Support\Facades\Log;
2626
use Illuminate\Support\Facades\Storage;
27+
use Safe\Exceptions\FilesystemException;
28+
use function Safe\realpath;
2729

2830
/**
2931
* Controller responsible for serving files securely.
@@ -48,10 +50,6 @@ public function __invoke(SecurePathRequest $request, ?string $path)
4850
throw new InvalidSignatureException();
4951
}
5052

51-
if (is_null($path)) {
52-
throw new WrongPathException();
53-
}
54-
5553
if ($request->configs()->getValueAsBool('secure_image_link_enabled')) {
5654
try {
5755
$path = Crypt::decryptString($path);
@@ -60,7 +58,14 @@ public function __invoke(SecurePathRequest $request, ?string $path)
6058
}
6159
}
6260

63-
$file = Storage::disk(StorageDiskType::LOCAL->value)->path($path);
61+
$this->prevalidation_path($path);
62+
63+
try {
64+
$file = realpath(Storage::disk(StorageDiskType::LOCAL->value)->path($path));
65+
} catch (FilesystemException) {
66+
throw new WrongPathException();
67+
}
68+
6469
$valid_path_start = Storage::disk(StorageDiskType::LOCAL->value)->path('');
6570
if (!str_starts_with($file, $valid_path_start)) {
6671
Log::error('Invalid path for secure path request.', [
@@ -104,4 +109,26 @@ private function signatureHasNotExpired(Request $request)
104109

105110
return !($expires !== null && $expires !== '' && Carbon::now()->getTimestamp() > $expires);
106111
}
112+
113+
/**
114+
* Validate the path.
115+
*
116+
* @param string|null $path
117+
*
118+
* @return void
119+
*
120+
* @throws WrongPathException
121+
*/
122+
private function prevalidation_path(?string $path): void
123+
{
124+
if (is_null($path)) {
125+
throw new WrongPathException();
126+
}
127+
128+
foreach (['..', '%2e', '%2f', '\\'] as $invalid_sequence) {
129+
if (str_contains($path, $invalid_sequence)) {
130+
throw new PathTraversalException('Invalid path for secure path request.');
131+
}
132+
}
133+
}
107134
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/**
4+
* SPDX-License-Identifier: MIT
5+
* Copyright (c) 2017-2018 Tobias Reich
6+
* Copyright (c) 2018-2026 LycheeOrg.
7+
*/
8+
9+
use Illuminate\Database\Migrations\Migration;
10+
use Illuminate\Support\Facades\Artisan;
11+
use Illuminate\Support\Facades\DB;
12+
use Symfony\Component\Console\Output\ConsoleOutput;
13+
use Symfony\Component\Console\Output\ConsoleSectionOutput;
14+
15+
return new class() extends Migration {
16+
private ConsoleOutput $output;
17+
private ConsoleSectionOutput $msg_section;
18+
19+
public function __construct()
20+
{
21+
$this->output = new ConsoleOutput();
22+
$this->msg_section = $this->output->section();
23+
}
24+
25+
/**
26+
* Run the migrations.
27+
*
28+
* @return void
29+
*/
30+
public function up(): void
31+
{
32+
DB::table('configs')->where('key', 'version')->update(['value' => '070604']);
33+
try {
34+
Artisan::call('cache:clear');
35+
} catch (\Throwable $e) {
36+
$this->msg_section->writeln('<error>Warning:</error> Failed to clear cache for version 7.6.4');
37+
38+
return;
39+
}
40+
$this->msg_section->writeln('<info>Info:</info> Cleared cache for version 7.6.4');
41+
}
42+
43+
/**
44+
* Reverse the migrations.
45+
*
46+
* @return void
47+
*/
48+
public function down(): void
49+
{
50+
DB::table('configs')->where('key', 'version')->update(['value' => '070603']);
51+
}
52+
};

tests/Feature_v2/SecureImageLinksTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,13 @@ public function testDisabledSecureImageLinkIsForbidden(): void
121121
$response = $this->get($broken_url);
122122
$this->assertUnauthorized($response);
123123
}
124+
125+
public function testPathTraversalIsForbidden(): void
126+
{
127+
$this->setTemporaryLink();
128+
$path = URL::route('image', ['path' => 'c3/3d/c661c594a5a781cd44db06828783.png']);
129+
$traversal_path = str_replace('c3/3d/c661c594a5a781cd44db06828783.png', '../.env', $path);
130+
$response = $this->actingAs($this->userMayUpload1)->get($traversal_path);
131+
$this->assertStatus($response, 418);
132+
}
124133
}

version.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
7.6.3
1+
7.6.4

0 commit comments

Comments
 (0)