Was ist der cHash?

Mit jedem Aufruf einer Seite im Frontend muss TYPO3 eine ganze Reihe von Informationen aufbereiten. Das Aufwändigste hier dürfte wohl das Abarbeiten von TypoScript sein. Allein die Extension css_styled_content kommt schon mit über 1000 Zeilen TypoScript daher. Auch das Templating kostet Zeit. Suchen und Ersetzen der Platzhalter in Fluid oder in früheren Installationen die Marker und Subparts. All das kostet Zeit und die Idee eines Seitencaches war geboren. Ein Cache, in dem das TypoScript schon fertig abgearbeitet wurde und die Seiteninhalte bereits in die Platzhalter der Templates eingebunden wurde. All diese Daten liegen nun in der Datenbanktabelle cache_pages.

Da jede Seite sein eigenes TypoScript haben könnte, wird der Cache pro Seite neu erstellt. Mit jedem Aufruf einer weiteren Seite füllt sich die Tabelle cache_hash um einen weiteren Eintrag. Unterschieden werden die Cache-Einträge anhand der Seiten-UID, die der Webseite mithilfe eines URL-Parameters wie ?id=12 übergeben wird.

Zusätzlich zum id Parameter gibt es in TYPO3 noch den type Parameter, mit dem ein und dieselbe Seite unterschiedlich dargestellt werden kann. Auf diese Weise könnte man z.B. eine Druckansicht ?id=12&type=89 oder eine PDF-Ausgabe ?id=12&type=123 oder auch eine Ausgabe für mobile Endgeräte ?id=12&type=2 erstellen.

Bevor ein solcher Cache-Eintrag erstellt wird, prüft TYPO3 zunächst, ob es eine Seite mit der entsprechenden Seiten-UID gibt. Mithilfe der typeNum Eigenschaft innerhalb von TypoScript weiß TYPO3, welche Seitentypen zur Verfügung stehen. Ist, wie in unserem Beispiel, keine PDF-Ansicht mit der typeNum=123 konfiguriert, dann wird auch kein Cache-Eintrag erstellt.

Sicherheit

Wir haben gerade gelernt, dass TYPO3 nur für Seiten und Seitentypen einen Cache-Eintrag erstellt, wenn diese auch vorhanden sind. Diese Überprüfung schützt vor einer überfluteten cache_pages Tabelle. Stellen wir uns mal vor, was passieren würde, wenn dieser Check nicht vorhanden wäre: Dann könnte ein Angreifer mithilfe eines Skriptes unsere Seite mit beliebigen Parametern aufrufen und obwohl es die Seiten und Seitentypen überhaupt nicht gibt, würde trotzdem ein neuer Eintrag in der Cache-Tabelle erzeugt werden. Einige Hoster begrenzen die Größe der Datenbank auf ein paar wenige Gigabyte. Wenn dieses Limit erreicht ist, dann läuft die Seite gar nicht mehr.

Alle URL-Parameter, die nicht id oder type lauten, werden von TYPO3 nicht berücksichtigt.

Das Problem

Ihr als Entwickler programmiert nun eine Extension und bindet ein Plugin auf einer Seite ein. Für TYPO3 ist euer Plugin ein ganz normales Inhaltselement. Der Inhalt wird extrahiert und in den Platzhalter eures Templates eingebaut und die generierten Daten in die Tabelle cache_pages geschrieben. Wenn euer Plugin nur statische Werte ausliefert, mag das noch OK sein, aber wenn wir uns eine Extension wie news oder einen Blog vorstellen, dann soll nicht nur der erste aufgerufene Newsartikel im Cache gespeichert werden, sondern auch alle anderen Newsartikel.

Wir benötigen demnach einen weiteren Parameter in der URL, um zwischen den Newsartikeln unterscheiden zu können. Wie aber sollen wir das anstellen, wenn TYPO3, wie oben bereits erklärt, weitere URL-Parameter nicht verarbeitet?

Laut dem englischen Beitrag von Kasper gibt es nun folgende 3 Möglichkeiten:

Neben dem id und type Parameter gibt es noch den no_cache Parameter. Durch Anfügen dieses Parameters an die URL, wird zwar ein Eintrag in der cache_pages Tabelle erzeugt, aber dieser beinhaltet nur noch ein paar statische Werte. Jegliches TypoScript und auch das Verarbeiten des Templates geschieht nun mit jedem Seitenaufruf erneut, was diese Lösung zu der Langsamsten macht. Dieselbe Vorgehensweise erhaltet ihr auch mit der Checkbox no Cache in den Seiteneigenschaften, auf der ihr euer Plugin platziert habt. Als Extension-Entwickler habt ihr die Möglichkeit euer Plugin als USER_INT einzubinden. Diese Vorgehensweise cacht das verarbeitete TypoScript und erstellt auch ein Template. In diesem Template wird jedoch nicht der Inhalt des Plugins eingefügt, sondern ein Platzhalter. Wenn die Seite geladen wird, wird das Template aus der Datenbank geholt und TYPO3 prüft, ob noch ein Platzhalter in diesem Template vorhanden ist. Wenn ja, dann weiß TYPO3 anhand der Platzhalter-ID, welcher Inhalt dort einzubinden ist. In diesem Fall der Inhalt eures Plugins. Auf diese Weise ist das ganze Außendrum gecacht und nur euer Plugin wird mit jedem Seitenaufruf erneut ausgeführt. Diese Lösung bietet eine mittlere Performance. Gerade für die Ansicht von Suchergebnissen sollte diese Umsetzung bevorzugt werden, da sonst für jedes Suchwort ein eigener Cache-Eintrag erzeugt wird.

Die performanceorientierteste Lösung ist die Verwendung von dem TYPO3 eigenen URL-Parameter cHash. Wenn ihr für eure Extension weitere URL-Parameter benötigt, dann funktioniert das nur unter Verwendung des cHash Parameters. Dieser bildet eine Art PHP:md5() Hash über alle zusätzlichen Parameter in der URL. Sollte nun ein Hacker einen weiteren Parameter hinzufügen, dann stimmt die MD5-Summe der URL-Parameter nicht mehr mit der MD5-Summe des cHashes überein und es erscheint je nach Konfiguration eine Fehlermeldung oder die Seite wird ohne Cache-Eintrag aufgerufen.

Arbeitsweise des cHash-Parameters

Alle Methoden zur Generierung von cHash befinden sich in der PHP Klasse TYPO3\CMS\Frontend\Page\CacheHashCalculator.

Einfacher geht es jedoch, wenn ihr in euren Extensions immer die typolink Methoden verwendet. Diese generieren den cHash automatisch für euch.

Ihr habt in eurer Extension eine Liste von Produkten. Jedes Produkt beinhaltet einen Link zu der Detailansicht. Für jeden Link in dieser Liste wird nun die typolink Methode aufgerufen. Schauen wir uns einen solchen Link genauer an:

?id=21&type=89&tx_mxext[product]=1204&tx_myext[action]=detail

Die Parameter id und type, sind wie bereits erwähnt, in TYPO3 integriert und somit bekannt. Diese beiden Werte werden somit nicht zur Generierung des cHash herangezogen. Bleiben nur noch die beiden anderen Werte:

&tx_mxext[product]=1204&tx_myext[action]=detail

Die Parameter werden alphabetisch sortiert und der generierte cHash hinten angehängt:

&tx_myext[action]=detail&tx_mxext[product]=1204&cHash=cn4r3728cng483n2cfg7248nx782

Aufruf einer Seite mit cHash

Im Folgenden ein Beispiel mit dieser URL:

?id=21&type=89&tx_myext[action]=detail&tx_mxext[product]=1204&cHash=cn4r3728cng483n2cfg7248nx782

Die Parameter id und type sind TYPO3 bereits bekannt und werden herausgefiltert. Übrig bleibt:

tx_myext[action]=detail&tx_mxext[product]=1204&cHash=cn4r3728cng483n2cfg7248nx782

TYPO3 prüft nun auf den Parameter cHash und weiß, dass sich in der URL noch weitere Parameter befinden:

tx_myext[action]=detail&tx_mxext[product]=1204

Jetzt wird erneut die Methode zum Generieren von cHashes aufgerufen und der generierte Wert mit dem Wert aus cHash verglichen. Wenn diese Werte übereinstimmen, dann darf ein Cache-Eintrag erstellt werden bzw. die Seite aus dem Cache geladen werden.

Stellen wir uns vor, dass ein Hacker die Produkt-UID in unserer Beispiel-URL ändert:

?id=21&type=89&tx_myext[action]=detail&tx_mxext[product]=2048&cHash=cn4r3728cng483n2cfg7248nx782

Nach der Generierung des cHash-Wertes wird dieser nicht dem Wert aus dem cHash Parameter in der URL übereinstimmen und die Seite wird nicht gecacht bzw. es erscheint eine Fehlermeldung.

Nochmal zurück zum Thema Sicherheit

Es bleibt die Frage, ob ein Hacker den cHash selbst generieren kann. Ich habe bereits oben schon geschrieben, dass der cHash nur so eine Art MD5-Hash ist. Laut den cHash Methoden wird den URI-Parametern noch ein weiterer Parameter hinzugefügt, bevor der cHash generiert wird:

$relevantParameters['encryptionKey'] = $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'];

Dieser Wert ist nur über das Installtool erreichbar. Das Installtool ist 2-fach abgesichert durch Passwort und dem Vorhandensein der Datei ENABLE_INSTALL_TOOL. Somit geht die eigentliche Gefahr wohl eher von unsicheren FTP-Accounts aus. Immer wieder weisen wir Kunden darauf hin, entweder sftp oder noch besser per SSH auf unsere Server zuzugreifen.

Nur wenn ein Hacker an diesen encryptionKey gelangt, hat er die Möglichkeit eigene cHashes generieren zu lassen.

Ein leerer cHash sorg für Verwirrung

Es passiert schon mal, dass Programmierer die URLs selber zusammenstellen und dabei oftmals den cHash vergessen. Wie bereits erwähnt interpretiert TYPO3 nur dann die zusätzlichen Parameter, wenn ein cHash Parameter vorhanden ist. Wenn dieser leer oder nicht vorhanden ist, dann ruft TYPO3 die Webseite so auf, als wären die zusätzlichen Parameter überhaupt nicht da. Es wird also nur der id und der type Parameter verwendet. So könnte z.B. laut URL das Produkt mit der ID 1204 angefragt werden, aber durch den fehlenden cHash Parameter wird das Produkt oder die Seite aufgerufen, die die Extension zuallererst angezeigt hat.

Erst durch ein Clear Cache im Backend wird die Tabelle cache_pages geleert und ein erneuter Aufruf zeigt dann das gewünschte Produkt an (ID 1204). Wird nun aber das Produkt 2048 ohne cHash Parameter aufgerufen wird weiterhin das Produkt 1204 angezeigt, weil eben nur der id und type Parameter verarbeitet werden.

Die Generierung von Links mithilfe von typolink ist von der Performance her für vereinzelte Links OK. Wenn ihr aber eine Listenansicht mit mehreren 1000 Datensätzen habt, oder Links für Veranstaltungen des kompletten Jahres erstellen wollt, dann kann euch die Generierung von Links mithilfe von typolink() einiges an Sekunden kosten. Deshalb hier ein Beispiel, wie ihr in einem Extbase Controller solche Links selbst zusammen bauen könnt:

$site = $this->request->getAttribute('site');
$uri = (string)$site->getRouter()->generateUri(
    $this->settings['pidOfDetailPage'],
    [
        'tx_events2_events' => [
            'controller' => 'Day',
            'action' => 'show',
            'day' => $dayUid,
        ]
    ],
));

Einstellungen aus dem Installtool

Im Installtool befinden sich ein paar Einstellungen, mit denen man aktiv auf die Generierung des cHash zugreifen kann:

EigenschaftErklärung
pageNotFoundOnCHashErrorWenn aktiviert, dann wird eine Fehlerseite im Frontend ausgegeben, wenn die Summe der Parameter mit dem angegebenen Wert aus dem cHash Parameter nicht übereinstimmt. Wenn deaktiviert, dann wird das Caching abgeschaltet und die Seite mit jedem Aufruf von neuem generiert (langsam).
cHashExcludedParametersAlle hier angegebenen Parameter werden von der cHash Generierung ausgeschlossen. Gerade bei Suchen ist das interessant, wenn sich das Suchwort auch in der URL befindet. Es handelt sich hierbei um eine kommaseparierte Liste von Parametern. Ein Beispiel könnte so aussehen: tx_myext[search],tx_myext_mypi[searchword]
cHashOnlyForParametersDie Angaben sind wie oben kommasepariert anzugeben. Normalerweise ist dieser Wert leer und in meinen Augen sollte dieser Wert auch leer bleiben, denn stellen wir uns mal vor, wir tragen hier etwas ein, dann wird innerhalb der cHash Generierung automatisch das Whitelisting aktiviert. Somit müssen alle benötigten Parameter hier in dieser Auflistung vorhanden sein. Bei größeren Projekten mit 50 Extensions, die je 5 Parameter haben, müssten demnach 250 Parameter hier eingetragen werden, damit die Webseite weiterhin einwandfrei läuft bzw. gecacht wird.
cHashRequiredParametersWir haben gelernt, dass bei fehlendem cHash Parameter alle zusätzlichen Parameter nicht mehr berücksichtigt werden. Im oberen Beispiel hatten wir das Produkt 1204 das gecacht wurde, aber auch nach Angabe der Produkt UID 2048 in der URL immer noch das Produkt 1204 angezeigt wurde. Um diese Verwirrung zu lösen, kann man hier Parameter angeben, die zwingend einen cHash erfordern. Somit würde statt dem Laden des Produktes 1204 nun eine Fehlermeldung erscheinen oder je nach Konfiguration die Seite ungecacht geladen.
cHashExcludedParametersIfEmptyNormalerweise werden URL-Parameter auch dann mit in die cHash Generierung reingerechnet, wenn diese leer sind. Mit der Angabe von * kann dem Generator mitgeteilt werden, dass alle Parameter, die leer sind nicht mit in die Berechnung einfließen sollen. Unter Angabe der Parameter als Komma separierte Liste können auch nur ganz bestimmte Parameter von der Berechnung ausgeschlossen werden.

Überschreiben der Parameter aus dem Installtool pro Extension

Es sollte nicht die Aufgabe eines TYPO3 Administrator sein, all diese Werte (oben) im Installtool zu pflegen. Viel besser wäre es, wenn die Extension-Entwickler diese Werte selbst setzen. Alle Werte aus dem Installtool, die für die cHash Generierung erforderlich sind, werden in der Bootstrap-Klasse in Arrays konvertiert: TYPO3\CMS\Core\Core\Bootstrap->setCacheHashOptions().

Ihr könnt euch dieses generierte Array im Backend unter Konfiguration » SelectBox: $GLOBALS['TYPO3_CONF_VARS'] » Aufklappen von FE » Aufklappen von cacheHash anschauen.

Die Methode setCacheHashOptions() wird nach dem Laden der LocalConfiguration.php geladen. Somit können diese Werte nicht mithilfe einer AdditionalConfiguration.php überschrieben werden. Erst innerhalb der ext_tables.php oder auch ext_localconf.php ist es euch möglich diese Werte zu überschreiben. Ich empfehle jedoch den Einsatz der ext_localconf.php.

Nach Eingabe von

$GLOBALS['TYPO3_CONF_VARS']['FE']['cHashExcludedParameters'] = 'L,tx_myext[search]';

erscheint automatisch dieses Array hier:

$GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters']['0'] = 'L';
$GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters']['1'] = 'tx_myext[search]';

Mit dieser Zeile in der ext_localconf.php

$GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters'][] = 'tx_myotherext[search]';

könnt ihr weitere Parameter diesem Array hinzufügen.

Der CacheHashCalculator besitzt einen Konstruktor, der dieses Array automatisch als interne Konfiguration wieder einließt:

public function __construct() {
    $this->setConfiguration($GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']);
}