"PERINGATAN: membersihkan HTML menghapus beberapa konten" dan cara menanganinya dengan benar

Setiap orang yang harus menyematkan konten HTML di DOM di Angular telah melihat pesan ini. Tentu saja, kita semua mendapatkan konten yang diperiksa dari server kita sendiri dan hanya ingin menutup pesan kesalahan. Atau kita menanamkan HTML dari konstanta kita, sebaris ikon SVG kita, karena kita hanya perlu mewarnai mereka dalam warna teks. Bagaimanapun, tidak ada hal buruk yang akan terjadi jika kita hanya mengatakan kepada Angular - bukan penyimpangan, semuanya bersih di sana.


Paling sering ini mungkin begitu, tetapi dalam proyek-proyek besar dengan banyak pengembang yang menulis komponen independen, Anda tidak pernah tahu pasti di mana kode Anda berada. Dan jika Anda, seperti saya, sedang mengembangkan perpustakaan komponen yang dapat digunakan kembali, maka situasi ini harus diselesaikan sejak awal.



Sanitasi dan DomSanitizer


Angular memiliki kelas abstrak yang implementasinya dirancang untuk menghapus konten sampah berbahaya:


abstract class Sanitizer { abstract sanitize(context: SecurityContext, value: string | {}): string | null } 

 abstract class DomSanitizer implements Sanitizer { abstract sanitize(context: SecurityContext, value: string | SafeValue): string | null abstract bypassSecurityTrustHtml(value: string): SafeHtml abstract bypassSecurityTrustStyle(value: string): SafeStyle abstract bypassSecurityTrustScript(value: string): SafeScript abstract bypassSecurityTrustUrl(value: string): SafeUrl abstract bypassSecurityTrustResourceUrl(value: string): SafeResourceUrl } 

Kelas dalam tertentu yang digunakan DomSanitizerImpl Angular dalam membangun DOM. Ini adalah orang-orangnya yang menambah arahan, komponen dan pipa untuk memberi tahu kerangka kerja bahwa konten ini dapat dipercaya.


Kode sumber untuk kelas ini cukup sederhana:
https://github.com/angular/angular/blob/8.1.0/packages/platform-browser/src/security/dom_sanitization_service.ts#L148


Layanan ini memperkenalkan konsep SafeValue . Intinya, ini hanya kelas pembungkus untuk sebuah string. Ketika Renderer menyisipkan nilai melalui pengikatan, baik itu innerHTML , @HostBinding , style atau src , nilai dijalankan melalui metode sanitize . Jika SafeValue sudah ada di sana, maka itu hanya mengembalikan string yang dibungkus, jika tidak, membersihkan konten sendiri.


Angular bukan perpustakaan pembersih kode malware khusus. Oleh karena itu, kerangka kerja dengan benar mengikuti jalan dengan risiko terendah dan memotong segala sesuatu yang menyebabkan kekhawatiran. Kode SVG berubah menjadi baris kosong, gaya inline dihapus, dll. Namun, ada perpustakaan yang dirancang hanya untuk mengamankan DOM, salah satunya adalah DOMPurify:
https://github.com/cure53/DOMPurify



Pipa SafeHtml yang benar


Setelah menghubungkan DOMPurify, kita dapat membuat pipa yang tidak hanya menandai konten sebagai aman, tetapi juga membersihkannya. Untuk melakukan ini, DOMPurify.sanitize input melalui metode DOMPurify.sanitize , dan kemudian tandai sebagai aman dengan konteks yang sesuai:


 @Pipe({name: 'dompurify'}) export class NgDompurifyPipe implements PipeTransform { constructor(private readonly domSanitizer: DomSanitizer) {} transform( value: {} | string | null, context: SecurityContext = SecurityContext.HTML, ): SafeValue | null { return this.bypassSecurityTrust(context, DOMPurify.sanitize(value)); } private bypassSecurityTrust( context: SecurityContext, purifiedValue: string, ): SafeValue | null { switch (context) { case SecurityContext.HTML: return this.domSanitizer.bypassSecurityTrustHtml(purifiedValue); case SecurityContext.STYLE: return this.domSanitizer.bypassSecurityTrustStyle(purifiedValue); case SecurityContext.SCRIPT: return this.domSanitizer.bypassSecurityTrustScript(purifiedValue); case SecurityContext.URL: return this.domSanitizer.bypassSecurityTrustUrl(purifiedValue); case SecurityContext.RESOURCE_URL: return this.domSanitizer.bypassSecurityTrustResourceUrl(purifiedValue); default: return null; } } } 

Hanya itu yang diperlukan untuk mengamankan aplikasi saat memasukkan HTML ke halaman. + 7KB gzip dari DOMPurify dan micropipe. Namun, karena kita naik ke sini, kita akan mencoba untuk melanjutkan. Abstraksi kelas berarti bahwa Angular menyarankan kemungkinan untuk membuat implementasi Anda sendiri. Mengapa membuat pipa dan menandai konten sebagai aman jika Anda dapat menggunakan DOMPurify segera sebagai DomSanitizer ?



DomPurifyDomSanitizer


Buat kelas yang mewarisi dari DomSanitizer dan mendelegasikan kliring nilai ke DOMPurify. Untuk masa depan, kami akan segera mengimplementasikan layanan Sanitizer dan kami akan menggunakannya baik di pipa maupun di DomSanitizer . Ini akan membantu kami di masa mendatang, karena satu titik masuk ke DOMPurify akan muncul. Implementasi SafeValue dan segala sesuatu yang berkaitan dengan konsep ini adalah kode pribadi Angular, jadi kita harus menulis sendiri. Namun, seperti yang kita lihat di kode sumber, ini tidak sulit.


Perlu dicatat bahwa selain HTML, ada SecurityContext lain dan, khususnya, untuk membersihkan gaya DOMPurify karena itu tidak cocok. Namun, fungsinya diperluas menggunakan kait, lebih lanjut tentang itu nanti. Selain itu, objek konfigurasi dapat diteruskan ke DOMPurify, dan injeksi dependensi sangat cocok untuk ini. Tambahkan token ke kode kami yang memungkinkan kami memberikan parameter untuk DOMPurify:


 @NgModule({ // ... providers: [ { provide: DOMPURIFY_CONFIG, useValue: {FORBID_ATTR: ['id']}, }, ], // ... }) export class AppModule {} 

 @Injectable({ providedIn: 'root', }) export class NgDompurifySanitizer extends Sanitizer { constructor( @Inject(DOMPURIFY_CONFIG) private readonly config: NgDompurifyConfig, ) { super(); } sanitize( _context: SecurityContext, value: {} | string | null, config: NgDompurifyConfig = this.config, ): string { return sanitize(String(value || ''), config); } } 

DomSanitizer dengan gaya diimplementasikan sedemikian rupa sehingga hanya menerima nilai aturan CSS pada input, tetapi bukan nama gaya itu sendiri. Jadi, untuk memungkinkan pembersih kami menghapus gaya, kita perlu memberinya metode yang akan menerima string nilai dan mengembalikan string yang dibersihkan. Untuk melakukan ini, tambahkan token lain dan tentukan secara default sebagai fungsi yang tidak mengosongkan string, karena kami tidak akan bertanggung jawab atas gaya yang berpotensi berbahaya.


 export const SANITIZE_STYLE = new InjectionToken<SanitizeStyle>( 'A function that sanitizes value for a CSS rule', { factory: () => () => '', providedIn: 'root', }, ); 

  sanitize( context: SecurityContext, value: {} | string | null, config: NgDompurifyConfig = this.config, ): string { return context === SecurityContext.STYLE ? this.sanitizeStyle(String(value)) : sanitize(String(value || ''), config); } 

Dengan cara ini kita akan menghapus gaya yang terlibat langsung dalam pengikatan melalui [style.*] Atau @HostBinding('style.*') . Seperti yang kita ingat, perilaku pembersih Angular bawaan adalah untuk menghapus semua gaya inline. Sejujurnya, saya tidak tahu mengapa mereka memutuskan untuk melakukan ini, karena mereka menulis metode untuk menghapus aturan CSS yang mencurigakan, tetapi jika Anda perlu menerapkan, katakanlah, editor WYSIWYG, Anda tidak dapat melakukannya tanpa gaya inline. Untungnya, DOMPurify memungkinkan Anda menambahkan kait untuk memperluas kemampuan perpustakaan. Bagian contoh bahkan memiliki kode yang menghapus gaya pada elemen DOM dan seluruh tag HTMLStyleElement .


Kait


Menggunakan DOMPurify.addHook() Anda dapat mendaftarkan metode yang menerima elemen saat ini pada tahap pembersihan yang berbeda. Untuk menghubungkannya, kami akan menambahkan token ketiga dan terakhir. Kami sudah memiliki metode untuk membersihkan gaya, Anda hanya perlu menyebutnya untuk semua aturan sebaris. Untuk melakukan ini, daftarkan pengait yang diperlukan di konstruktor layanan kami. Kami akan membahas semua kait yang datang kepada kami menggunakan DI:


  constructor( @Inject(DOMPURIFY_CONFIG) private readonly config: NgDompurifyConfig, @Inject(SANITIZE_STYLE) private readonly sanitizeStyle: SanitizeStyle, @Inject(DOMPURIFY_HOOKS) hooks: ReadonlyArray<NgDompurifyHook>, ) { super(); addHook('afterSanitizeAttributes', createAfterSanitizeAttributes(this.sanitizeStyle)); hooks.forEach(({name, hook}) => { addHook(name, hook); }); } 

Layanan kami siap dan tetap hanya untuk menulis DomSanitizer Anda yang hanya akan mentransfer konten untuk dibersihkan. Anda juga perlu menerapkan metode bypassSecurityTrust*** , tetapi kami sudah memata-matai mereka dalam kode sumber Angular. Sekarang DOMPurify tersedia baik secara pointwise, menggunakan pipa atau layanan, dan secara otomatis di seluruh aplikasi.


Kesimpulan


Kami menemukan DomSanitizer di Angular dan tidak lagi menutupi masalah memasukkan HTML sewenang-wenang. Sekarang Anda dapat dengan aman memasukkan pesan SVG dan HTML dari server. Agar pembersih berfungsi dengan baik, Anda masih perlu memberinya metode untuk membersihkan gaya. CSS berbahaya jauh lebih jarang daripada jenis serangan lainnya, waktu ekspresi CSS sudah lama berlalu dan apa yang Anda lakukan dengan CSS terserah Anda. Anda dapat menulis sendiri pawang, Anda dapat meludah dan melewati segala sesuatu yang tidak memiliki tanda kurung. Atau Anda bisa menipu sedikit dan melihat lebih dekat sumber-sumber Angular. Metode _sanitizeStyle ada dalam paket @angular/core , sementara DomSanitizerImpl ada dalam paket @angular/platform-browser , dan meskipun metode ini bersifat pribadi dalam paketnya, tim Angular tidak ragu untuk mengaksesnya dengan nama pribadi ɵ_sanitizeStyle . Kita dapat melakukan hal yang sama dan mentransfer metode ini ke pembersih kami. Dengan demikian, kita mendapatkan tingkat gaya pembersihan yang sama dengan default, tetapi dengan kemampuan untuk menggunakan gaya sebaris saat memasukkan kode HTML. Nama impor pribadi ini tidak berubah sejak muncul di versi ke-6, tetapi Anda harus berhati-hati dengan hal-hal seperti itu, tidak ada yang mencegah tim Angular mengubah nama, memindahkan, atau menghapusnya kapan saja.


Kode yang dijelaskan dalam artikel ini ada di Github
Juga tersedia sebagai paket tinkoff / ng-dompurify npm:
https://www.npmjs.com/package/@tinkoff/ng-dompurify
Anda dapat bermain sedikit dengan stackblitz dengan demo

Source: https://habr.com/ru/post/id459396/


All Articles