From 495d8e3adfb17edb95c4b208ebc4236b429a801b Mon Sep 17 00:00:00 2001 From: habibie11 Date: Mon, 16 Mar 2026 17:57:27 +0700 Subject: [PATCH 1/2] feat: Implement XSS prevention by integrating HTML Purifier, sanitizing content outputs, adding a custom CSP policy, and introducing XSS feature tests. --- .../Controllers/CMS/ArticleController.php | 6 ++ app/Http/Controllers/CMS/PageController.php | 6 ++ app/Policies/CustomCSPPolicy.php | 11 +- composer.json | 1 + composer.lock | 80 +++++++++++++- .../common/errors.blade.php | 2 +- resources/views/web/article.blade.php | 2 +- resources/views/web/articles.blade.php | 2 +- resources/views/web/artikel/index.blade.php | 2 +- resources/views/web/artikel/show.blade.php | 2 +- resources/views/web/page.blade.php | 2 +- tests/Feature/XssPreventionTest.php | 101 ++++++++++++++++++ 12 files changed, 205 insertions(+), 12 deletions(-) create mode 100644 tests/Feature/XssPreventionTest.php 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/composer.json b/composer.json index de80d462a..9148924ee 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,7 @@ "laravel/tinker": "^2.8", "laravel/ui": "^4.2", "league/flysystem-ftp": "^3.10", + "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 d488948f9..ab07877f4 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": "2ffcf31285ea53025ab70e536dafe411", + "content-hash": "e6c80fb59e61ffc48245d30a50a22485", "packages": [ { "name": "akaunting/laravel-apexcharts", @@ -3750,6 +3750,84 @@ }, "time": "2024-11-14T23:14:52+00:00" }, + { + "name": "mews/purifier", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/mewebstudio/Purifier.git", + "reference": "acc71bc512dcf9b87144546d0e3055fc76d244ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mewebstudio/Purifier/zipball/acc71bc512dcf9b87144546d0e3055fc76d244ff", + "reference": "acc71bc512dcf9b87144546d0e3055fc76d244ff", + "shasum": "" + }, + "require": { + "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": { + "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": { + "Purifier": "Mews\\Purifier\\Facades\\Purifier" + }, + "providers": [ + "Mews\\Purifier\\PurifierServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Mews\\Purifier\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Muharrem ERİN", + "email": "me@mewebstudio.com", + "homepage": "https://github.com/mewebstudio", + "role": "Developer" + } + ], + "description": "Laravel 5/6/7/8/9/10 HtmlPurifier Package", + "homepage": "https://github.com/mewebstudio/purifier", + "keywords": [ + "Laravel Purifier", + "Laravel Security", + "Purifier", + "htmlpurifier", + "laravel HtmlPurifier", + "security", + "xss" + ], + "support": { + "issues": "https://github.com/mewebstudio/Purifier/issues", + "source": "https://github.com/mewebstudio/Purifier/tree/3.4.3" + }, + "time": "2025-02-24T16:00:29+00:00" + }, { "name": "mobiledetect/mobiledetectlib", "version": "4.8.09", 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); + } +} From f4bdde9b8afe06df299301d73418bec34dc10d0e Mon Sep 17 00:00:00 2001 From: Abah Roland Date: Sun, 29 Mar 2026 13:39:24 +0700 Subject: [PATCH 2/2] [ci skip] memutahirkan catatan rilis --- catatan_rilis.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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