Fix: validasi valid_file menolak PDF sah karena string generik "function" - #1765
Open
pandigresik wants to merge 1 commit into
Open
pandigresik wants to merge 1 commit into
pandigresik wants to merge 1 commit into
Conversation
Contributor
|
🔄 AI PR Review sedang antri di server...
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Rule custom
valid_filemembaca seluruh isi file denganFile::get($value)lalu menjalankan regex teks/<\?php|<script|function|__halt_compiler|<html/itanpa membedakan jenis format. Karena PDF/Office adalah file biner yang dapat memuat stringfunctionsecara normal pada struktur dan metadata-nya, dokumen yang sah ditolak dengan pesan "Format Jenis berkas yang anda unggah berbahaya."Perbaikan ini memindahkan logika ke
App\Rules\SafeFileContentdan membelah pemeriksaan menjadi dua lap: penanda skrip universal (<?php,<?=,<script,__halt_compiler) untuk semua format, dan pola XSS tambahan yang hanya dijalankan pada format berbasis markup (SVG/XML/HTML). Token generikfunctiondihapus, sementara proteksi terhadap PDF yang disisipi kode PHP justru dipertahankan dan diperkuat.Changes made:
valid_filedipindahkan dari closure diapp/Providers/AppServiceProvider.phpke class baruapp/Rules/SafeFileContent.phpagar dapat diuji terpisah (TDD) dan mengikuti polaApp\Rules\SecureFileUploadyang sudah ada.functiondan<htmldihapus dari pola global; digantikan dua konstanta —SCRIPT_SIGNATURES(semua format) danMARKUP_DANGERS(hanya SVG/XML/HTML, mencakupon*=event handler,javascript:,vbscript:,<foreignObject>,<iframe>,<embed>,<object>,data:text/html).File::get($value)digantifread()dengan batas 1 MB (SCAN_LIMIT) — pemindaian tidak lagi memuat seluruh file ke memori.! $value instanceof UploadedFile || ! $value->isValid()ditambahkan agar input non-file ditolak secara deterministik.X-Content-Type-Options: nosniffdipasang diapp/Http/Middleware/SecurityHeaders.phppada semua environment (sebelumnya middleware hanya memasang CSP di production). propertiunwantedHeadersyang dideklarasikan tetapi tidak pernah dipakai kini benar-benar diterapkan.locationblocker di.docker/nginx/conf.d/default.confuntuk menolak eksekusi skrip di bawah/storage/— path ini disajikan langsung oleh webserver melalui symlinkpublic/storagesehingga melewati seluruh middleware Laravel.tests/Unit/ValidFileContentTest.php(15 test),tests/Unit/SecurityHeadersTest.php(3 test),tests/Arch/NginxUploadGuardTest.php(3 test).Reason for change:
RENJA PERUBAHAN KECAMATAN PECANGAAN 2025_compressed.pdf(702.935 byte,application/pdf,isValid() = true, di bawahmax:2048) ditolak solely karena mengandung 14 kemunculan stringFunction. Regex lama tidak memberikan nilai keamanan apa pun untuk file biner — PHP di dalam PDF tidak pernah dieksekusi karena file disajikan sebagaiapplication/pdfdenganContent-Disposition: attachmentdan ekstensi dibuat server-side.%PDF-yang disisipi<?php, dan file.phpyang menyamar sebagai PDF, keduanya kini ditolak olehSCRIPT_SIGNATURES.public/storage→storage/app/publicdan dilayani nginx secara langsung, sehingga melewati middleware. SVG yang diizinkan padaDokumenRequestmenjadi same-origin URL yang dapat dieksekusi browser. Regex versi issue hanya menangkap<script; versi ini juga menangkaponload=,xlink:href="javascript:", dan<foreignObject>.location ~ \.php$padadefault.confmemilikitry_filesyang dikomentari sehingga tidak ada verifikasi keberadaan file; guard/storage/menutup celah ini dan ditulis sebelumlocation ~ \.php$agar tidak ditimpa oleh regex location yang di Evaluasi berurutan.Impact of change:
✅ Dokumen binary sah: PDF/DOC/DOCX/XLS/XLSX/PPT/PPTX/JPG/PNG/GIF tidak lagi ditolak akibat string yang kebetulan cocok.
✅ Deteksi penyisipan skrip:
<?php,<?=,<script,__halt_compilertetap terdeteksi pada semua format termasuk di dalam PDF.✅ Proteksi SVG/HTML: meningkat dari 3 penanda menjadi 9 penanda (event handler,
javascript:,foreignObject,iframe,embed,object,data:text/html).✅ Performa: Pemindaian dibatasi 1 MB dan memakai stream
fread, bukanFile::get()yang memuat seluruh file.✅ Keamanan header:
X-Content-Type-Options: nosniffmencegah content-type sniffing pada seluruh response web & API.✅ Keterbacaan: Logika validasi terisolasi di class khusus yang mengikuti
ValidationRuleLaravel, bukan closure inline.Related Issue
Closes #1761
Steps to Reproduce
Before fix (problem):
nama_dokumen,jenis_dokumen,retention_days,description,status.RENJA PERUBAHAN KECAMATAN PECANGAAN 2025_compressed.pdf(702.935 byte, MIMEapplication/pdf).isValid() = true, upload error0, ukuran 687 KB di bawahmax:2048.grep -aoiE '<\?php|<script|function|__halt_compiler|<html' *.pdf | sort | uniq -cmenghasilkan14 Function.valid_filemengembalikanfalse→ pesan "Format Jenis berkas yang anda unggah berbahaya." despite file valid.After fix (solution):
Testing on related features:
function✅ Lolos<script>✅ Ditolak.pdf✅ DitolakChecklist
Technical Details
Technical Explanation
Diagram alur validasi
valid_filesetelah perubahan:Kunci perbedaannya: pemeriksaan konten yang spesifik per-format hanya dijalankan bila formatnya memang bisa dieksekusi browser. PDF/Office tidak masuk jalur kedua, sehingga string biner yang wajar tidak lagi berpengaruh, sementara marker skrip universal tetap diperiksa untuk semua format.
Perubahan di
app/Providers/AppServiceProvider.php:Perubahan di
.docker/nginx/conf.d/default.conf:Perubahan di
app/Http/Middleware/SecurityHeaders.php:Configuration changes
Tidak ada perubahan pada file konfigurasi aplikasi (
.env,config/*.php). Perubahan pada.docker/nginx/conf.d/default.confmemerlukan restart/reload container nginx (docker compose restart nginx) agar guard/storage/aktif.Dependencies added
No new dependencies.
Testing
Manual Testing
functionmelalui Form Dokumen → berhasil<script>alert(1)</script>→ ditolak.pdfyang isinya pure PHP → ditolak.pdfber-header%PDF-tapi disisipi<?php→ ditolakAutomated Testing
tests/Unit/ValidFileContentTest.php(15 test, 18 assertion)pdf valid yang mengandung string Function tetap lolospdf yang menyamar dengan header pdf tetapi berisi kode php ditolakfile php yang diberi ekstensi pdf ditolaksvg dengan tag script / event handler onload / javascript pada xlink href / foreignObject ditolaksvg bersih lolosgambar png dan jpeg asli tetap lolosfile yang menyamar sebagai gambar tetapi berisi html ditolaktests/Unit/SecurityHeadersTest.php(3 test, 3 assertion)menambahkan X-Content-Type-Options nosniff di productionnosniff tetap dipasang di luar productiontidak membocorkan versi servertests/Arch/NginxUploadGuardTest.php(3 test, 11 assertion)memblokir eksekusi skrip di bawah /storage/guard /storage/ ditulis sebelum location .php agar tidak ditimpablokir mencakup daftar ekstensi eksekutable yang umumphp vendor/bin/pest tests/Unit tests/Arch→ 501 passed (1193 assertions)php vendor/bin/pest tests/Feature/Security tests/Feature/Upload tests/Feature/Surat tests/Feature/Validation tests/Feature/Content→ 117 passed, 4 failure pre-existing (terverifikasi identik padarilis-devviagit stash)vendor/bin/pint --testpada 6 file yang diubah →passednginx -tpada.docker/nginx/conf.d/default.conf→syntax is okBrowser Compatibility
Tidak ada perubahan UI/frontend. Tidak ada perubahan perilaku di sisi peramban selain penambahan header keamanan.
Screenshots / Video
Tidak ada perubahan antarmuka pengguna. Pesan error "Format Jenis berkas yang anda unggah berbahaya." tidak lagi muncul pada upload PDF yang valid.
Breaking Changes
None. Nama rule
valid_filedan pesan validasilang/id/validation.php(validation.valid_file) tidak berubah, sehingga seluruh 30+ pemanggil existing (DokumenRequest,ArtikelRequest,GaleriRequest,ProsedurRequest,RegulasiRequest,PotensiRequest,MediaSosialRequest,PengurusRequest,ProfilRequest,SistemKomplainRequest,StoreKomplainRequest,UserRequest,SlideRequest,SinergiProgramRequest,AlbumRequest) tidak perlu disesuaikan.Migration Guide
Not required.
References
valid_filemenolak PDF yang valid karena false positive pada isi file #1761: [BUG] Validasivalid_filemenolak PDF yang valid karena false positive pada isi file #176115ad14cd— "Buat validasi file valid (Buat validasi file valid #651)", 16 Maret 2023X-Content-Type-Options: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-OptionsAdditional notes:
Pola tidak sengaja dihapus:
functiondan<htmldihapus dari daftar global.<htmlmasih terdeteksi untuk MIME markup melaluiMARKUP_DANGERS, sedangkanfunctiondihapus permanen karena tidak ada nilai keamanan — string tersebut adalah token pemrograman umum yang wajar muncul di dokumen teknis.Batas 1 MB (
SCAN_LIMIT): pemindaian sengaja tidak membaca seluruh file. Penanda skrip pada payload yang disisipkan berada di bagian awal, danFile::get()sebelumnya berpotensi menjadi vektor kehabisan memori pada upload besar. Catatan: untuk PDF, payload PHP praktis tidak pernah dieksekusi, sehingga cakupan 1 MB cukup untuk deteksi dan bukan merupakan boundary keamanan.Perbedaan dari solusi sementara pada issue: usulan di issue hanya memindai
image/svg+xmldengan 3 penanda. Pendekatan ini mempertahankan pemindaian universal terhadap marker skrip (termasuk di dalam PDF) dan memperluas daftar penanda SVG dari 3 menjadi 9, karena regex<scriptsaja mudah dilewati lewatonload=atauxlink:href="javascript:".Catatan operasional untuk reviewer: 4 test failure di
tests/Feature/FilePreviewFeatureTest.php,tests/Feature/Upload/FileUploadTest.php, dantests/Feature/ArtikelControllerTest.phpbersifat pre-existing dan tidak terkait perubahan ini. Penyebabnya seedTipePotensikosong pada environment test dan perbedaan URL redirect. Sudah diverifikasi dengan menjalankan suite yang sama padarilis-dev(hasil identik: 4 failed).Catatan tooling:
composer pinttanpa argumen akan mereformat ratusan file di repository ini karena banyak file belum comply dengan konfigurasi Pint saat ini. Untuk PR ini, Pint hanya dijalankan secara scoped pada 6 file yang diubah. Sebaiknya issue terpisah untuk memformat seluruh codebase.