"WARNUNG: Durch das Bereinigen von HTML wurden einige Inhalte entfernt" und wie man richtig damit umgeht

Jeder, der HTML-Inhalte in das DOM in Angular einbetten musste, hat diese Nachricht gesehen. Natürlich erhalten wir alle den überprüften Inhalt von unserem eigenen Server und möchten nur die Fehlermeldung abdecken. Oder wir binden HTML aus unseren Konstanten ein, inline unsere SVG-Symbole, weil wir sie nur in der Farbe des Textes einfärben müssen. Schließlich wird nichts Schlimmes passieren, wenn wir nur zu Angular sagen - kein Drift, dort ist alles sauber.


Meistens mag dies so sein, aber in großen Projekten mit einer Masse von Entwicklern, die unabhängige Komponenten schreiben, wissen Sie nie genau, wo sich Ihr Code befindet. Und wenn Sie wie ich eine Bibliothek wiederverwendbarer Komponenten entwickeln, muss diese Situation im Keim gelöst werden.



Desinfizieren und DomSanitizer


Angular verfügt über abstrakte Klassen, deren Implementierungen den Inhalt von böswilligem Müll löschen sollen:


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 } 

Die spezifische innere Klasse DomSanitizerImpl Angular verwendet beim DomSanitizerImpl des DOM. Es sind seine Mitarbeiter, die Richtlinien, Komponenten und Pipes ergänzen, um dem Framework mitzuteilen, dass diesem Inhalt vertraut werden kann.


Der Quellcode für diese Klasse ist ziemlich einfach:
https://github.com/angular/angular/blob/8.1.0/packages/platform-browser/src/security/dom_sanitization_service.ts#L148


Dieser Service führt das Konzept von SafeValue . Im Wesentlichen ist dies nur eine Wrapper-Klasse für eine Zeichenfolge. Wenn der Renderer einen Wert durch Bindung einfügt, sei es innerHTML , @HostBinding , style oder src , wird der Wert über die sanitize Methode ausgeführt. Wenn SafeValue bereits dort SafeValue ist, gibt es einfach die SafeValue Zeichenfolge zurück, andernfalls wird der Inhalt selbst bereinigt.


Angular ist keine spezialisierte Bibliothek zur Bereinigung von Malware-Code. Daher folgt der Rahmen zu Recht dem Weg des geringsten Risikos und schneidet alles ab, was Anlass zur Sorge gibt. SVG-Code wird in leere Zeilen umgewandelt, Inline-Stile werden gelöscht usw. Es gibt jedoch Bibliotheken, die nur zum Sichern des DOM entwickelt wurden. Eine davon ist DOMPurify:
https://github.com/cure53/DOMPurify



Die richtige SafeHtml-Pipe


Nach dem Anschließen von DOMPurify können wir eine Pipe erstellen, die den Inhalt nicht nur als sicher markiert, sondern auch bereinigt. DOMPurify.sanitize Eingabewert über die Methode DOMPurify.sanitize und markieren Sie ihn mit dem entsprechenden Kontext als sicher:


 @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; } } } 

Das ist alles, was Sie brauchen, um die Anwendung beim Einfügen von HTML in die Seite zu sichern. + 7KB gzip von DOMPurify und Micropipe. Da wir jedoch hier geklettert sind, werden wir versuchen, weiterzumachen. Die Abstraktheit der Klassen bedeutet, dass Angular die Möglichkeit vorschlägt, eigene Implementierungen zu erstellen. Warum eine Pipe erstellen und den Inhalt als sicher markieren, wenn Sie DOMPurify sofort als DomSanitizer ?



DomPurifyDomSanitizer


Erstellen Sie eine Klasse, die von DomSanitizer erbt und das Löschen von Werten an DOMPurify delegiert. Für die Zukunft werden wir den Sanitizer Service sofort implementieren und sowohl in der Pipe als auch in DomSanitizer . Dies wird uns in Zukunft helfen, da ein einzelner Einstiegspunkt für DOMPurify angezeigt wird. Die Implementierung von SafeValue und alles, was mit diesem Konzept zu SafeValue privater Code, daher müssen wir ihn selbst schreiben. Wie wir im Quellcode gesehen haben, ist dies jedoch nicht schwierig.


Es ist anzumerken, dass es neben HTML noch andere SecurityContext und insbesondere für die Bereinigung von Stilen DOMPurify als solches nicht geeignet ist. Die Funktionen werden jedoch mithilfe von Hooks erweitert, dazu später mehr. Darüber hinaus kann ein Konfigurationsobjekt an DOMPurify übergeben werden, und die Abhängigkeitsinjektion ist hierfür perfekt. Fügen Sie unserem Code ein Token hinzu, mit dem wir Parameter für DOMPurify bereitstellen können:


 @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 Arbeit von DomSanitizer mit Stilen wird so implementiert, dass nur der Wert der CSS-Regel an der Eingabe empfangen wird, nicht jedoch der Name des Stils. Damit unser Desinfektionsprogramm die Stile löschen kann, müssen wir ihm eine Methode zur Verfügung stellen, die eine Wertzeichenfolge empfängt und eine gelöschte Zeichenfolge zurückgibt. Fügen Sie dazu ein weiteres Token hinzu und definieren Sie es standardmäßig als eine Funktion, die die Zeichenfolge auf Null löscht, da wir keine Verantwortung für potenziell schädliche Stile übernehmen.


 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); } 

Auf diese Weise löschen wir die Stile, die direkt an der Bindung beteiligt sind, über [style.*] Oder @HostBinding('style.*') . Wie wir uns erinnern, besteht das Verhalten des integrierten Angular-Desinfektionsmittels darin, alle Inline-Stile zu entfernen. Ehrlich gesagt, ich weiß nicht, warum sie sich dazu entschlossen haben, da sie Methoden zum Entfernen verdächtiger CSS-Regeln geschrieben haben. Wenn Sie jedoch beispielsweise einen WYSIWYG-Editor implementieren müssen, können Sie nicht auf Inline-Stile verzichten. Glücklicherweise können Sie mit DOMPurify Hooks hinzufügen, um die Funktionen der Bibliothek zu erweitern. Der Beispielabschnitt enthält sogar Code, der Stile sowohl für DOM-Elemente als auch für die gesamten HTMLStyleElement Tags HTMLStyleElement .


Haken


Mit DOMPurify.addHook() Sie Methoden registrieren, die das aktuelle Element in einer anderen Reinigungsphase empfangen. Um sie zu verbinden, fügen wir das dritte und letzte Token hinzu. Wir haben bereits eine Methode zum Bereinigen von Stilen. Sie müssen sie nur für alle Inline-Regeln aufrufen. Registrieren Sie dazu den erforderlichen Hook im Konstruktor unseres Dienstes. Wir werden alle Haken durchgehen, die mit DI zu uns gekommen sind:


  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); }); } 

Unser Service ist bereit und es bleibt nur das Schreiben Ihres DomSanitizer der nur Inhalte zur Reinigung darauf überträgt. Sie müssen auch die Methoden bypassSecurityTrust*** implementieren, die wir jedoch bereits im Angular-Quellcode ausspioniert haben. Jetzt ist DOMPurify sowohl punktweise über eine Pipe oder einen Service als auch automatisch in der gesamten Anwendung verfügbar.


Fazit


Wir haben herausgefunden, DomSanitizer in Angular DomSanitizer und das Problem des Einfügens von beliebigem HTML nicht länger maskiert. Jetzt können Sie SVG- und HTML-Nachrichten sicher vom Server einbinden. Damit das Desinfektionsmittel ordnungsgemäß funktioniert, müssen Sie ihm noch eine Methode zum Reinigen der Stile zur Verfügung stellen. Schädliches CSS ist viel seltener als andere Arten von Angriffen, die Zeiten der CSS-Ausdrücke sind lange vorbei und was Sie mit CSS tun, liegt bei Ihnen. Sie können selbst einen Handler schreiben, Sie können sicher alles spucken und überspringen, was keine Klammern hat. Oder Sie können ein wenig schummeln und sich die Quellen von Angular genauer ansehen. Die _sanitizeStyle Methode befindet sich im @angular/core _sanitizeStyle @angular/core Paket, während DomSanitizerImpl im @angular/platform-browser DomSanitizerImpl @angular/platform-browser Paket enthalten ist. Obwohl diese Methode in ihrem Paket privat ist, zögert das Angular-Team nicht, unter ihrem privaten Namen ɵ_sanitizeStyle . Wir können das Gleiche tun und diese Methode auf unser Desinfektionsmittel übertragen. Somit erhalten wir die gleiche Ebene an Bereinigungsstilen wie die Standardeinstellung, jedoch mit der Möglichkeit, beim Einfügen von HTML-Code Inline-Stile zu verwenden. Der Name dieses privaten Imports hat sich seit dem Erscheinen in der 6. Version nicht geändert, aber Sie müssen mit solchen Dingen vorsichtig sein. Nichts hindert das Angular-Team daran, ihn jederzeit umzubenennen, zu verschieben oder zu löschen.


Der im Artikel beschriebene Code ist auf Github
Auch als npm tinkoff / ng-dompurify-Paket erhältlich:
https://www.npmjs.com/package/@tinkoff/ng-dompurify
Mit einer Demo können Sie ein wenig mit Stackblitz spielen

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


All Articles