diff --git a/app/Http/Controllers/CMS/ArticleController.php b/app/Http/Controllers/CMS/ArticleController.php index 2c56b5e47..ef2f41dce 100644 --- a/app/Http/Controllers/CMS/ArticleController.php +++ b/app/Http/Controllers/CMS/ArticleController.php @@ -55,6 +55,9 @@ public function create() public function store(CreateArticleRequest $request) { $input = $request->all(); + if (isset($input['content'])) { + $input['content'] = \Mews\Purifier\Facades\Purifier::clean($input['content']); + } if ($request->file('foto')) { $input['thumbnail'] = $this->uploadFile($request, 'foto'); } @@ -109,6 +112,9 @@ public function update($id, UpdateArticleRequest $request) return redirect(route('articles.index')); } $input = $request->all(); + if (isset($input['content'])) { + $input['content'] = \Mews\Purifier\Facades\Purifier::clean($input['content']); + } $removeThumbnail = $request->get('remove_thumbnail'); if ($request->file('foto')) { $input['thumbnail'] = $this->uploadFile($request, 'foto'); diff --git a/app/Http/Controllers/CMS/PageController.php b/app/Http/Controllers/CMS/PageController.php index 1a5b42763..d257fd1c6 100644 --- a/app/Http/Controllers/CMS/PageController.php +++ b/app/Http/Controllers/CMS/PageController.php @@ -55,6 +55,9 @@ public function create() public function store(CreatePageRequest $request) { $input = $request->all(); + if (isset($input['content'])) { + $input['content'] = \Mews\Purifier\Facades\Purifier::clean($input['content']); + } if ($request->file('foto')) { $this->pathFolder .= '/profile'; $input['thumbnail'] = $this->uploadFile($request, 'foto'); @@ -111,6 +114,9 @@ public function update($id, UpdatePageRequest $request) return redirect(route('pages.index')); } $input = $request->all(); + if (isset($input['content'])) { + $input['content'] = \Mews\Purifier\Facades\Purifier::clean($input['content']); + } $removeThumbnail = $request->get('remove_thumbnail'); if ($request->file('foto')) { $input['thumbnail'] = $this->uploadFile($request, 'foto'); diff --git a/app/Policies/CustomCSPPolicy.php b/app/Policies/CustomCSPPolicy.php index f6750c128..294906c2f 100644 --- a/app/Policies/CustomCSPPolicy.php +++ b/app/Policies/CustomCSPPolicy.php @@ -54,6 +54,10 @@ public function configure() ])->addDirective(Directive::CONNECT, [ config('app.serverPantau'), config('app.databaseGabunganUrl'), + ])->addDirective(Directive::OBJECT, [ + Keyword::NONE, + ])->addDirective(Directive::BASE, [ + Keyword::SELF, ]); } @@ -65,11 +69,8 @@ public function shouldBeApplied(Request $request, Response $response): bool config(['csp.enabled' => false]); } - // jika mode debug aktif maka disable CSP - if (env('APP_DEBUG')) { - config(['csp.enabled' => false]); - } - + // CSP tetap aktif di semua environment termasuk debug untuk menjaga keamanan + return config('csp.enabled'); } } diff --git a/catatan_rilis.md b/catatan_rilis.md index db956ff1a..d1b616f02 100644 --- a/catatan_rilis.md +++ b/catatan_rilis.md @@ -14,4 +14,5 @@ Di rilis ini, versi 2603.0.0 berisi penambahan dan perbaikan yang diminta penggu #### Perubahan Teknis 1. [#943](https://github.com/OpenSID/OpenKab/issues/943) N+1 Query problem pada manajemen user. -2. [#969](https://github.com/OpenSID/OpenKab/issues/969) Terapkan CAPTCHA pada Login & Endpoint Auth untuk Batasi Bot/Bruteforce. \ No newline at end of file +2. [#969](https://github.com/OpenSID/OpenKab/issues/969) Terapkan CAPTCHA pada Login & Endpoint Auth untuk Batasi Bot/Bruteforce. +3. [#962](https://github.com/OpenSID/OpenKab/issues/962) Pencegahan Kerentanan XSS (Cross-Site Scripting). \ No newline at end of file diff --git a/composer.json b/composer.json index e27c2c349..ce932c50c 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "laravel/tinker": "^2.8", "laravel/ui": "^4.2", "league/flysystem-ftp": "^3.10", - "mews/captcha": "^3.3", + "mews/purifier": "^3.4", "openspout/openspout": "^4.24", "proengsoft/laravel-jsvalidation": "^4.8", "shetabit/visitor": "^4.1", diff --git a/composer.lock b/composer.lock index 20d42c624..57f53cc7c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6dcbd39df69d7b30dd08e12d487b0780", + "content-hash": "e6c80fb59e61ffc48245d30a50a22485", "packages": [ { "name": "akaunting/laravel-apexcharts", @@ -3817,41 +3817,43 @@ "time": "2024-11-14T23:14:52+00:00" }, { - "name": "mews/captcha", - "version": "3.3.3", + "name": "mews/purifier", + "version": "3.4.3", "source": { "type": "git", - "url": "https://github.com/mewebstudio/captcha.git", - "reference": "e996a9a5638296de3e9dac41782dbdcf3d14ce11" + "url": "https://github.com/mewebstudio/Purifier.git", + "reference": "acc71bc512dcf9b87144546d0e3055fc76d244ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mewebstudio/captcha/zipball/e996a9a5638296de3e9dac41782dbdcf3d14ce11", - "reference": "e996a9a5638296de3e9dac41782dbdcf3d14ce11", + "url": "https://api.github.com/repos/mewebstudio/Purifier/zipball/acc71bc512dcf9b87144546d0e3055fc76d244ff", + "reference": "acc71bc512dcf9b87144546d0e3055fc76d244ff", "shasum": "" }, "require": { - "ext-gd": "*", - "illuminate/config": "~5|^6|^7|^8|^9|^10|^11", - "illuminate/filesystem": "~5|^6|^7|^8|^9|^10|^11", - "illuminate/hashing": "~5|^6|^7|^8|^9|^10|^11", - "illuminate/session": "~5|^6|^7|^8|^9|^10|^11", - "illuminate/support": "~5|^6|^7|^8|^9|^10|^11", - "intervention/image": "~2.5", - "php": "^7.2|^8.1|^8.2|^8.3" + "ezyang/htmlpurifier": "^4.16.0", + "illuminate/config": "^5.8|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/filesystem": "^5.8|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/support": "^5.8|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "php": "^7.2|^8.0" }, "require-dev": { - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^8.5|^9.5.10|^10.5" + "graham-campbell/testbench": "^3.2|^5.5.1|^6.1", + "mockery/mockery": "^1.3.3", + "phpunit/phpunit": "^8.0|^9.0|^10.0" + }, + "suggest": { + "laravel/framework": "To test the Laravel bindings", + "laravel/lumen-framework": "To test the Lumen bindings" }, "type": "package", "extra": { "laravel": { "aliases": { - "Captcha": "Mews\\Captcha\\Facades\\Captcha" + "Purifier": "Mews\\Purifier\\Facades\\Purifier" }, "providers": [ - "Mews\\Captcha\\CaptchaServiceProvider" + "Mews\\Purifier\\PurifierServiceProvider" ] } }, @@ -3860,7 +3862,7 @@ "src/helpers.php" ], "psr-4": { - "Mews\\Captcha\\": "src/" + "Mews\\Purifier\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3875,19 +3877,22 @@ "role": "Developer" } ], - "description": "Laravel 5/6/7/8/9/10/11 Captcha Package", - "homepage": "https://github.com/mewebstudio/captcha", + "description": "Laravel 5/6/7/8/9/10 HtmlPurifier Package", + "homepage": "https://github.com/mewebstudio/purifier", "keywords": [ - "captcha", - "laravel5 Security", - "laravel6 Captcha", - "laravel6 Security" + "Laravel Purifier", + "Laravel Security", + "Purifier", + "htmlpurifier", + "laravel HtmlPurifier", + "security", + "xss" ], "support": { - "issues": "https://github.com/mewebstudio/captcha/issues", - "source": "https://github.com/mewebstudio/captcha/tree/3.3.3" + "issues": "https://github.com/mewebstudio/Purifier/issues", + "source": "https://github.com/mewebstudio/Purifier/tree/3.4.3" }, - "time": "2024-03-20T16:15:48+00:00" + "time": "2025-02-24T16:00:29+00:00" }, { "name": "mobiledetect/mobiledetectlib", diff --git a/resources/views/vendor/adminlte-templates/common/errors.blade.php b/resources/views/vendor/adminlte-templates/common/errors.blade.php index 559de7173..dc1aa9b76 100644 --- a/resources/views/vendor/adminlte-templates/common/errors.blade.php +++ b/resources/views/vendor/adminlte-templates/common/errors.blade.php @@ -2,7 +2,7 @@ @if($errors->any()) @endif diff --git a/resources/views/web/article.blade.php b/resources/views/web/article.blade.php index 357fae4c5..db46b0554 100644 --- a/resources/views/web/article.blade.php +++ b/resources/views/web/article.blade.php @@ -20,7 +20,7 @@
- {!! $object->content !!} + {!! clean($object->content) !!}
diff --git a/resources/views/web/articles.blade.php b/resources/views/web/articles.blade.php index c3eef1356..4afac9964 100644 --- a/resources/views/web/articles.blade.php +++ b/resources/views/web/articles.blade.php @@ -37,7 +37,7 @@ class="card-img-top object-fit-cover" alt="{{ $article->title }}"> @endif
-

{!! Str::limit($article->content, 100) !!}

+

{{ Str::limit(strip_tags($article->content), 100) }}

{{ $article->kategori_nama ?? 'Kategori' }}
- {!! Str::words(strip_tags($article->isi ?? ''), 20, '...') !!} + {{ Str::words(strip_tags($article->isi ?? ''), 20, '...') }}
diff --git a/resources/views/web/artikel/show.blade.php b/resources/views/web/artikel/show.blade.php index cccd82a42..e0b6dee57 100644 --- a/resources/views/web/artikel/show.blade.php +++ b/resources/views/web/artikel/show.blade.php @@ -41,7 +41,7 @@ class="fa fa-calendar-alt text-primary me-2">{{ isset($object->tgl_upload) ?
- {!! $object->isi ?? '' !!} + {!! clean($object->isi ?? '') !!}
diff --git a/resources/views/web/page.blade.php b/resources/views/web/page.blade.php index 5077aec61..72120c770 100644 --- a/resources/views/web/page.blade.php +++ b/resources/views/web/page.blade.php @@ -28,7 +28,7 @@
- {!! $object->content !!} + {!! clean($object->content) !!}
diff --git a/tests/Feature/XssPreventionTest.php b/tests/Feature/XssPreventionTest.php new file mode 100644 index 000000000..c8661d72a --- /dev/null +++ b/tests/Feature/XssPreventionTest.php @@ -0,0 +1,101 @@ +create(); + + $xssPayload = '

Normal text

Click'; + + $response = $this->post(route('articles.store'), [ + 'title' => 'Judul Artikel XSS', + 'slug' => 'judul-artikel-xss', + 'content' => $xssPayload, + 'category_id' => $category->id, + 'published_at' => now()->format('d/m/Y'), + 'state' => 1, + ]); + + $response->assertRedirect(route('articles.index')); + + $article = Article::where('slug', 'judul-artikel-xss')->first(); + + $this->assertNotNull($article); + $this->assertStringNotContainsString('', + 'id_kategori' => 1, + 'kategori_nama' => 'Berita Desa', + 'tgl_upload' => '2023-10-01 10:00:00', + 'enabled' => 1, + ]); + + $this->app->instance(ArtikelService::class, $mockService); + + $response = $this->get(route('web.artikel.show', ['id' => 1])); + $response->assertStatus(200); + $response->assertViewIs('web.artikel.show'); + + // Assert view does not contain script tags in the output + $response->assertDontSee('', false); + $response->assertDontSee('javascript:alert(1)', false); + } +}