Überlegungen zur Sicherheit
Das JSON:API-Modul wurde entwickelt, um das in Drupal über die Entity API, Field API und Typed Data API definierte Datenmodell über eine API bereitzustellen, die der JSON:API-Spezifikation entspricht. Damit wird die Interaktion mit den von Drupal verwalteten Daten (Entities) erleichtert.
Dabei werden alle Drupal-Sicherheitsmechanismen für diese Daten beachtet:
- Der Zugriff auf Entitäten wird respektiert.
- Der Zugriff auf Felder wird respektiert.
- Beim Ändern von Daten werden Validierungsregeln eingehalten.
- Das
internal
-Flag wird beachtet (siehe Dokumentation, wie es auf eine Entity-, Feld- oder Property-Definition gesetzt werden kann).
Mit anderen Worten: JSON:API umgeht keine bestehenden Sicherheitsmechanismen und fügt auch keine eigene Sicherheitsschicht hinzu; es nutzt die Drupal-Basis vollständig aus.
Fehler in Entity-Typen, Feldtypen und Datentypen können zu Sicherheitslücken führen
Trotzdem gibt es Fehler in der Implementierung von Entity-Typen, Feldtypen und Datentypen sowie in deren Access-Control-Handlern und Validierungsregeln. Das liegt vor allem an Drupals Historie: Früher gab es keine Validierungsregeln, sondern nur Formular-Validierungen; der Wechsel zu einer API-First-Architektur ist im Drupal-Core vollzogen, aber bei Contrib- oder eigenen Modulen nicht garantiert.
Solche Fehler können zu Sicherheitslücken führen – und haben das in der Vergangenheit auch. Diese Lücken betreffen nicht nur das JSON:API-Modul, sondern z.B. auch das RESTful Web Services-Modul oder beliebigen PHP-Code, der die Entity API verwendet.
Da ein Angreifer aber viel leichter auf eine HTTP-API wie JSON:API oder RESTful Web Services zugreifen kann als auf eine PHP-API, ist hier besondere Vorsicht geboten. Im Gegensatz zu anderen HTTP-API-Modulen ist die API-Oberfläche von JSON:API größer: Alle nicht-internal
-Entity-Typen sind standardmäßig verfügbar (wobei Entity Access beachtet wird), um das Entwicklererlebnis zu verbessern.
Sechs Sicherheitshinweise
1. Die Bedeutung stabiler Contributed Modules
Sicherheitslücken in Entity-Typen, Feldtypen und Datentypen werden nur für stabile, auf Drupal.org veröffentlichte Module, die unter die Security Advisory Policy fallen, schnell behoben. Eigene Module und nicht-stabile Contributed Modules sind nicht abgedeckt. Bei deren Einsatz ist besondere Vorsicht geboten.
2. Überprüfe Entity- und Feldzugriffsrechte
Unabhängig davon, ob du JSON:API oder andere API-Module nutzt, solltest du den Entity Access & Field Access auf Drupal-Sites auditieren. Das ist besonders wichtig, wenn die Schreibfunktion von JSON:API aktiviert ist.
3. Exponiere nur, was du wirklich brauchst
Wenn bestimmte Ressourcentypen (Entity-Typ + Bundle) nicht zugänglich sein sollen, kann man – nachdem der Zugriff verweigert wurde – noch weitergehen und sie komplett deaktivieren. Dies geht per PHP-API im eigenen Modul oder über das JSON:API Extras Modul, das eine UI dafür bietet. Das ist nicht immer möglich, aber wenn der Site-Owner auch alle API-Clients kontrolliert, kann man so die API-Oberfläche so klein wie möglich halten.
4. Nur-Lese-Modus
Falls du nur Lesezugriff brauchst, kannst du im JSON:API-Modul unter /admin/config/services/jsonapi
den Nur-Lese-Modus aktivieren. Dadurch werden Risiken durch potentielle Bugs in Validierungsregeln und Write-Logik reduziert. Da viele moderne decoupled Drupal-Setups ohnehin nur Lesezugriff benötigen, ist der Nur-Lese-Modus standardmäßig aktiviert (in Drupal Core und seit Version 2.4 des Contributed-Moduls).
5. Sicherheit durch Verschleierung: Geheimer Basis-Pfad
Standardmäßig ist der Basis-Pfad für JSON:API /jsonapi
. Dies kann auf z.B. /hidden/b69dhj027ooae/jsonapi
geändert werden, um automatisierte Angriffe zu erschweren. Erstelle sites/example.com/services.yml
(sofern nicht vorhanden) und füge hinzu:
parameters:
jsonapi.base_path: /hidden/b69dhj027ooae/jsonapi
6. Begrenze, welche Entity-Bundles erstellt/editiert werden dürfen (durch Entfernen von Routen)
Wenn du nur bestimmte Entity-Bundles per JSON:API erstellen oder bearbeiten lassen willst, kannst du einen Event-Subscriber in einem eigenen Modul schreiben, der nur eine Whitelist von POST- und PATCH-Routen zulässt. Dies wirkt sich nach Deaktivierung des Nur-Lese-Modus aus und erfordert ggf. einen Router-Rebuild.
Füge in der services.yml deines Moduls Folgendes hinzu:
services:
mymodule.route_subscriber:
class: Drupal\mymodule\Routing\JsonapiLimitingRouteSubscriber
tags:
- { name: event_subscriber }
Beispiel für einen Event Subscriber (löscht auch alle DELETE-Routen):
<?php
namespace Drupal\mymodule\Routing;
use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;
/**
* Klasse JsonapiLimitingRouteSubscriber.
*
* Entfernt alle DELETE-Routen von JSON:API-Resourcen zum Schutz von Inhalten.
* Entfernt POST- und PATCH-Routen von JSON:API-Resourcen, außer für die,
* die von Endbenutzern via API erstellt/geändert werden dürfen.
*/
class JsonapiLimitingRouteSubscriber extends RouteSubscriberBase {
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
$mutable_types = $this->mutableResourceTypes();
foreach ($collection as $name => $route) {
$defaults = $route->getDefaults();
if (!empty($defaults['_is_jsonapi']) && !empty($defaults['resource_type'])) {
$methods = $route->getMethods();
if (in_array('DELETE', $methods)) {
// Löschen nie erlauben, nur deaktivieren.
$collection->remove($name);
}
else {
$resource_type = $defaults['resource_type'];
if (empty($mutable_types[$resource_type])) {
if (in_array('POST', $methods) || in_array('PATCH', $methods)) {
$collection->remove($name);
}
}
}
}
}
}
/**
* Gibt veränderbare Resourcentypen zurück, die via API bearbeitet werden dürfen.
*
* @return array
* Liste der JSON:API-Resourcentypen (als Schlüssel).
*/
public function mutableResourceTypes(): array {
return [
'node--article' => TRUE,
'node--document' => TRUE,
'custom_entity--custom_entity' => TRUE,
];
}
}
Zugriff auf alle JSON:API-Routen mit zusätzlicher Berechtigung einschränken
Wenn du JSON:API für Backend-Integrationen, eingeschränkte API-Clients oder andere nicht-öffentliche Einsätze nutzt, kann es sinnvoll sein, den Zugriff auf alle JSON:API-Endpunkte an eine spezielle Berechtigung zu knüpfen. Ergänze dazu den Route Subscriber wie folgt:
// Zugriff auf alle jsonapi-Routen über ein extra Recht begrenzen.
foreach ($collection as $route) {
$defaults = $route->getDefaults();
if (!empty($defaults['_is_jsonapi'])) {
$route->setRequirement('_permission', 'FOO custom access jsonapi');
}
}
Lege diese Berechtigung in FOO.permissions.yml an und weise sie den gewünschten Rollen zu.
Artikel aus der Drupal Dokumentation.