Skip to main content

5 Best Practices für die Sicherheit von React mit TypeScript

Artikel von

Marcelo Oliveira

feature typescript react

8. Dezember 2022

0 Min. Lesezeit

Als Bibliothek für die Entwicklung von Benutzeroberflächen und nicht als vollwertiges Framework ermöglicht React Entwicklerinnen und Entwicklern, für verschiedene Aspekte einer Anwendung bevorzugte Bibliotheken auszuwählen, etwa für Routing, Verlauf und Authentifizierung. Microsoft entwickelte TypeScript als Erweiterung von JavaScript, um einer ansonsten schwach typisierten Sprache optional statische Typisierung hinzuzufügen.

TypeScript bietet in Verbindung mit React zahlreiche Vorteile bei der Anwendungsentwicklung, darunter die Möglichkeit, einfachere React-Komponenten zu erstellen, und eine bessere Unterstützung für JavaScript XML (JSX) bei der statischen Typprüfung. Da sich JavaScript-Komponenten in einem TypeScript-Projekt verwenden lassen, können Entwicklungsteams mit JavaScript-Erfahrung ihr Wissen nutzen und von der strengen Typisierung profitieren.

Für React-Starterprojekte stehen viele Boilerplates zur Verfügung, darunter Create React App, Create Next App, Vite, React Boilerplate und React Starter Kit.

Create React App ist ein eigenständiges Tool, das sowohl mit npm als auch mit Yarn ausgeführt werden kann. Nach der Installation können wir mit nur wenigen Befehlen ein neues Projekt erstellen und ausführen.

Öffnen Sie zunächst Ihr Terminal und führen Sie den folgenden Befehl aus, um das Tool Create React App zu installieren:

npm install -g create-react-app

Erstellen Sie dann mithilfe der TypeScript-Vorlage und des Node-Paketbefehls (npx) ein Projekt:

npx create-react-app [webapp-name] --template typescript

Alternativ können Sie das Projekt mit dem Paketmanager Yarn erstellen:

yarn create react-app [webapp-name] --template typescript

potenzielle Sicherheitsrisiken und wie Sie diese minimieren können.

Den Strict-Modus aktivieren

Der Strict-Modus aktiviert automatisch die TypeScript-Compilerparameter für Regeln zu Datentypen. Ist der TypeScript-Strict-Modus aktiviert, prüft er den Code anhand strenger Typregeln. Dadurch müssen Entwicklerinnen und Entwickler Code schreiben, der die Einschränkungen der Datentypen für Variablen, Konstanten, Parameter und Funktionsrückgabewerte einhält. Der Strict-Modus ist wichtig, weil Entwicklerinnen und Entwickler damit Fehler durch Typkonflikte bereits früh in der Entwicklungsphase erkennen und beheben können.

Angenommen, eine Anwendung enthält die folgende Funktion:

export async function deleteComment(slug, commentId): Promise<void> {
  await axios.delete(`articles/${slug}/comments/${commentId}`);
}

Wir rufen die Funktion deleteComment auf und übergeben das Argument slug als String und commentId als Zahl:

deleteComment('whats-new-in-react', 1257);

Das hindert uns jedoch nicht daran, die Funktion mit zwei Strings aufzurufen:

deleteComment('foo', 'bar');

Der obige Code ist möglicherweise mit den Geschäftsregeln nicht vereinbar. Da wir jedoch keine Typen angegeben haben, wird für beide Parameter standardmäßig der Typ any angenommen. Daher kann TypeScript uns nicht dabei helfen, das Problem zu erkennen, solange wir den Strict-Modus nicht aktivieren.

Bei neu erstellten React-Anwendungen ist der Wert strict auf true gesetzt. Bei bestehenden Projekten kann dieser Wert jedoch abweichen.

Um sicherzustellen, dass unser TypeScript-Projekt im Strict-Modus läuft, öffnen Sie die Datei tsconfig.json und prüfen Sie, ob der Wert der Strict-Konfiguration auf true gesetzt ist:

{
  ...
  "strict": true,
  ...
}

Kehren wir zur Funktion deleteComment zurück. Eine kleine Änderung führt sofort zu TypeScript-Kompilierungsfehlern:

TypeScript-Editor mit Fehlern für implizite „any“-Typen bei den Parametern slug und commentId in conduit.ts

Fügen Sie den Parametern von deleteComment die Typen String und Zahl hinzu:

export async function deleteComment(slug: string, commentId: number): Promise<void> {
  await axios.delete(`articles/${slug}/comments/${commentId}`);
}

Dadurch entstehen Kompilierungsfehler, die Entwicklerinnen und Entwickler daran hindern, Client-Code zu erstellen, der die Funktion deleteComment versehentlich mit ungültigen Typen aufruft, wie im folgenden Beispiel:

deleteComment(null, true);
TypeScript-Editor mit Fehlern in conduit.ts: null wird an einen String-Parameter und ein Boolean an einen Number-Parameter übergeben.

Die Strict-Option aktiviert automatisch weitere empfohlene Compileroptionen für eine strengere Typprüfung.

Verwenden Sie den Rückgabetyp any nicht für Callbacks, deren Rückgabewert ignoriert wird

Wenn Sie den Rückgabetyp eines Callbacks als any deklarieren und dann versehentlich dessen Rückgabewert verwenden, obwohl die Funktion keinen Wert zurückgibt, kann ein unentdeckter Fehler entstehen.

Betrachten Sie eine Funktion namens onListFieldKeyUp, die einen Callback namens onEnter als Parameter übernimmt:

export function onListFieldKeyUp(onEnter: () => any): (ev: React.KeyboardEvent) => void {
  return (ev) => {
    if (ev.key === 'Enter') {
      ev.preventDefault();  
      var enterResult = onEnter();
      //do something with enterResult
      }
    }
  };
}

Beachten Sie, dass die Variable enterResult oben das Ergebnis der Callback-Funktion onEnter für eine spätere Verwendung speichert. Obwohl der Callback keinen Wert zurückgibt, haben wir die Variable enterResult mit dem Typ any deklariert. Daher kann TypeScript Sie nicht auf ein Problem hinweisen.

Wie lässt sich das lösen?

Wenn Sie wissen, dass die Funktion onEnter keinen Wert zurückgibt, ersetzen Sie zunächst den Typ any im Callback-Parameter durch void. Nun zeigt TypeScript den Fehler „Ein Ausdruck vom Typ 'void' kann nicht auf seinen Wahrheitswert geprüft werden.“ an.

Code-Editor mit FormGroup.tsx und einem TypeScript-Fehler: Ein Ausdruck vom Typ „void“ kann nicht auf Wahrheitswert geprüft werden.

Entfernen Sie schließlich den Code, der den Rückgabewert von onEnter in der Variable enterResult speichert:

export function onListFieldKeyUp(onEnter: () => void): (ev: React.KeyboardEvent) => void {
  return (ev) => {
    if (ev.key === 'Enter') {
      ev.preventDefault();
      onEnter();
    }
  };
}

Server-Side-Rendering-Angriffe in React

Eine Webanwendung kann HTML entweder auf dem Client oder auf dem Server rendern. Moderne JavaScript-Frameworks und -Bibliotheken wie React nutzen Server-Side Rendering. Dieser Ansatz bietet Leistungsvorteile, darunter kürzere Ladezeiten: Das Backend kann die gesamte Seite schnell vorab rendern und die statischen HTML-, CSS- und JavaScript-Inhalte an das Frontend übergeben. So können Nutzerinnen und Nutzer die Webseite sofort aufrufen und sehen. Dieser Ansatz unterstützt auch die Suchmaschinenoptimierung (SEO), da schnell ladende Seiten in Suchalgorithmen höhere Bewertungen erzielen.

Cross-Site-Scripting (XSS) ist eine Angriffsmethode, bei der Angreiferinnen und Angreifer schädliche Client-Side-Skripte in eine Webseite einschleusen. React wurde so entwickelt, dass es vor XSS geschützt ist. Unsachgemäße Programmierung und Server-Side Rendering in React können jedoch zu einer XSS-Schwachstelle führen, die böswillige Nutzerinnen und Nutzer ausnutzen.

Verknüpfen Sie beispielsweise niemals ungeprüfte Daten mit der Ausgabe der Funktion renderToStaticMarkup, bevor Sie den String an den Client senden:

app.get("/", function (req, res) {
  return res.send(
    ReactDOMServer.renderToStaticMarkup(
      React.createElement("h1", null, "Hello World!")
    ) + someUnsanitizedData
  );
});

Der obige Code ist unsicher, weil ein Hacker die Variable someUnsanitizedData kompromittiert und darin schädlichen JavaScript-Code untergebracht haben könnte, wie im folgenden Beispiel:

someUnsanitizedData = "</scrïpt><scrïpt>alert('You are compromised!')</scrïpt>

Verwenden Sie zum Schutz vor XSS-Angriffen einen HTML-Sanitizer wie DomPurify.

Opake Typen verwenden

Ein opaker Datentyp erzwingt das Verbergen von Informationen. Seine Datenstruktur ist in der Schnittstelle nicht definiert. So werden die Implementierung eines konkreten Datentyps verborgen und gekapselt. Externe Module können den opaken Typ verwenden, ohne auf seine internen Details zuzugreifen. Interne Funktionen, denen die fehlenden Informationen zur Verfügung stehen, können den Typ hingegen bearbeiten. Mit opaken Typen können Sie interne Details ändern und weiterentwickeln, ohne den Code anzupassen, der sie verwendet. Ihre Verwendung gilt daher als Best Practice in der Softwareentwicklung.

TypeScript bietet zwar keine opaken Typen von Haus aus, doch lassen sie sich leicht implementieren. Sehen wir uns einen Anwendungsfall aus der Praxis an.

Stellen Sie sich eine E-Commerce-Anwendung vor, die eine TypeScript-Funktion verwendet, um dem Warenkorb einer Kundin oder eines Kunden ein Produkt hinzuzufügen:

function addToCart(customerCode: string, productCode: string) {
  console.log(`Product ${productCode} has been added to the cart of the customer ${customerCode}`);
}

Dank dieser typisierten Parameter kann die Entwicklerin oder der Entwickler für die Parameter customerCode und productCode keine Zahlen oder andere Typen anstelle von Strings angeben:

addToCart('ABC-001984', 'SPC-004487');

Am Code ist zwar nichts auszusetzen, doch der Typ bildet die Werte nicht präzise genug ab. Anhand der Codezeile allein lässt sich nicht erkennen, welcher Parameter der Kunden- und welcher der Produktcode ist. TypeScript gibt außerdem keine Warnung aus, wenn die beiden Werte vertauscht werden.

Sehen Sie sich nun an, wie addToCart im folgenden Beispiel überarbeitet wurde, um opake Typen zu verwenden:

export type CustomerCode = string & { _: 'CustomerCode' };
export type ProductCode = string & { _: 'ProductCode' };

const makeCustomerCode =
  (customerCode: string): CustomerCode => {
    if (/^\w{3}-\d{6}$/.test(customerCode)) { //regex validation
      return customerCode as CustomerCode;
    } else {
      throw new Error('Not a customer code!');
    }
  };

const makeProductCode =
  (productCode: string): ProductCode => {
    if (/^\w{3}-\d{6}$/.test(productCode)) { //regex validation
      return productCode as ProductCode;
    } else {
      throw new Error('Not a product code!');
    }
  };

function addToCart(customerCode: CustomerCode, productCode: ProductCode) {
  console.log(`Product ${productCode} has been added to the cart of the customer ${customerCode}`);
}

let customerCode: CustomerCode = makeCustomerCode('ABC-001984');
let productCode: ProductCode = makeProductCode('SPC-004487');

addToCart(customerCode, productCode);

Dank unserer neuen opaken Typen und ihrer Regex-Validierung erzeugt der Code einen Compilerfehler, wenn Sie ungültige Codes angeben:

let customerCode: CustomerCode = makeCustomerCode('000-001984'); //Error: Not a customer code!
let productCode: ProductCode = makeProductCode('9871'); //Error: Not a product code!

Wann Sie dangerouslySetInnerHTML verwenden und wie Sie Inhalte korrekt bereinigen

React verwendet ein Virtual DOM als leichtgewichtige Methode, um den HTML-Code einer Seite effizient zu aktualisieren. So müssen Nutzerinnen und Nutzer HTML-Elemente nicht direkt mithilfe nativer Browser-APIs bearbeiten.

Manchmal müssen Sie diesen Mechanismus jedoch überschreiben und rohen HTML-Code in Ihren React-Anwendungen setzen. Um diesen HTML-Code direkt zu bearbeiten, verwendet React eine spezielle Komponenteneigenschaft namens dangerouslySetInnerHTML:

return (
<p dangerouslySetInnerHTML={{__html: data}}></p>);

Wie der Name schon andeutet, kann die Eigenschaft dangerouslySetInnerHTML eine Anwendung anfällig machen, wenn sie nicht korrekt verwendet wird. Hacker können Anwendungen, die auf dangerouslySetInnerHTML setzen, für Cross-Site-Scripting-(XSS-)Angriffe ausnutzen und schädliche Skripte in Ihre Website einschleusen, die als vertrauenswürdige Nutzereingaben getarnt sind.

Um dieses Risiko zu vermeiden, bereinigen Sie HTML-Inhalte immer vor dem Einfügen, um „Verunreinigungen“ und schädlichen Code zu entfernen. Dazu können Sie eine Bibliothek wie DOMPurify und die Funktion sanitize verwenden:

import DOMPurify from 'dompurify';

return (
<p dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(data)}}></p>);

Wichtig ist, dass DOMPurify wie jede andere Bibliothek gelegentlich Sicherheitslücken aufweisen kann. Die Snyk Vulnerability Database hat diese Schwachstellendaten schnell aufgenommen und eine Lösung bereitgestellt. Unabhängig von der bisherigen Stabilität einer Bibliothek sollten Sie dennoch sorgfältig vorgehen und sie regelmäßig mit einem Tool wie Snyk auf bekannte Schwachstellen prüfen.

Und das ist nur eines der Sicherheitsthemen, die Entwicklerinnen und Entwickler berücksichtigen sollten. In diesem Video erläutert Liran Tal, welche Fehler React-Entwicklerinnen und -Entwicklern unterlaufen können und zu weiteren Schwachstellen führen, die Hacker ausnutzen können.

Liran Tal - You thought your React application is secure? Think again | ReactNext 2021

Fazit

In diesem Artikel haben wir einige Best Practices für die Entwicklung von React-Anwendungen mit TypeScript besprochen, potenzielle Sicherheitsrisiken aufgezeigt und Lösungen vorgestellt.

  • Im Strict-Modus können Sie Typbeschränkungen durchsetzen und Fehler durch Typkonflikte früh in der Entwicklung erkennen. Mit void in Callbacks verhindern Sie, dass Entwicklerinnen und Entwickler einen Rückgabewert von Callbacks verwenden, die keinen Wert zurückgeben.

  • Command-Injection-Angriffe sind eine ernsthafte Sicherheitsbedrohung. Vermeiden Sie es, dynamischen Code mit verdächtigen Nutzereingaben zu erstellen, und verwenden Sie statt exec die Funktion execFile.

  • Auch HTML-Injections stellen eine ernsthafte Sicherheitsbedrohung dar. Wenn Sie dangerouslySetInnerHTML verwenden, bereinigen Sie das eingefügte Markup immer, damit Angreiferinnen und Angreifer Nutzereingaben nicht manipulieren und schädlichen Code einschleusen können.

  • Schließlich bieten opake Typen aussagekräftige Wertebereiche und helfen Ihnen, Typen leichter zu validieren und Duplikate zu vermeiden.

Mit TypeScript und React lassen sich ganz einfach schnelle und sichere Anwendungen entwickeln – insbesondere mit Frameworks wie Create React App und vorhandenem JavaScript-Wissen.

Weiterlesen

Blog

Frontier-Modelle fanden die Schwachstellen. Nur der Angreifer fand die Angriffsketten.

Die statische Analyse fand die Schwachstellen, doch erst Live-Angriffstests bewiesen, wie sie sich zu Angriffen verketten lassen. Ein Vergleich von Evo COS, Claude Security und Claude Code Security.

feature insights context
Blog

Autonome Angriffe sind bereits Realität. Die Verteidigung muss Schritt halten.

Autonome Angreifer verkürzen das Zeitfenster für die Verteidigung. Erfahren Sie, wie kontinuierliches Erkennen, Beheben, Validieren und Verhindern Sicherheitsteams helfen kann, Schritt zu halten.

Blog

Warum KI-Coding-Agenten immer wieder fehlerhafte Zugriffskontrollen schreiben

KI-Coding-Agenten können Autorisierungslogik erzeugen, die kompiliert und die Prüfung besteht, dabei aber die Daten eines Mandanten für einen anderen offenlegt. Erfahren Sie, warum fehlerhafte Zugriffskontrollen schwer zu erkennen sind und wie Sie sie verhindern.