Inhaltsverzeichnis

Systemsicherheit

(ein ganzes Paket an Einzelelementen. Von Passwortverschlüsselung bis zum Schutz vor CrossSiteScripting.)

CsfrTokens

Ein wesentlicher Bestandteil zum Schutz gegen CrossSiteScripting ist der Einsatz von Tokens bei der Übermittlung von Formularen und sonstigen Änderungsanforderungen. WebsiteBaker stellt dafür die Klasse bin\Security\CsfrTokens zur Verfügung. Diese Klasse wird grundsätzlich immer automatisch vom Kern geladen und stellt ihre Methoden zur Verfügung. Die Konfiguration der Klasse und auch der Einsatz der Methoden wurde so einfach wie möglich gehalten.

Begriffserklärung

TOKEN ⇒ Ehemals FTAN. Dieser Begriff wurde in Anlehnung an die TransAktionsNummern beim Onlinebanking gewählt, da auch sie nur für eine einmalige Transaktion innerhalb eines festgelegten Zeitraums gültig sind.
Im Unterschied zu der einfachen, 4-stelligen TAN jedoch besteht ein vollständiger Token aus einem 6-stelligen, alphanumerischen Bezeichner sowie dem zugehörigen, ebenfalls 6-stelligen, alphanumerischen Wert. Sowohl Bezeichner als auch der Wert ändern sich bei jedem normalen Request auf Zufallsbasis. Bei Ajaxaufrufen ändert sich nur der Value-Anteil des Tokens.

IDKEY ⇒ Ist das zweite Standbein der Sicherung. Er wird hauptsächlich eingesetzt, um die Datensatz-IDs in Formularen und anderen Requests zu verschleiern. Die ID wird dabei durch einen einmaligen, 6-stelligen, alphanumerischen Wert ersetzt. Wird ein gleicher Wert mehrmals verschlüsselt, so bekommt er immer einen anderen Ersatzwert. Für jeden Request, für jedes Formular können beliebig viele IDKEYs erzeugt werden. Der IDKEY verhindert z.B. zuverlässig, dass auf Clientseite in einem Änderungsformular die reale DatensatzId vor dem Absenden manipuliert werden kann und dadurch unzulässigerweise ein falscher Datensatz geändert oder gar gelöscht wird.

Feste Einstellungen

Konstante Wert Beschreibung
LIFETIME_MIN 900 Minimale Lebensdauer eines Tokens in Sekunden
LIFETIME_MAX 5400 Maximale Lebensdauer eines Tokens in Sekunden
LIFETIME_STEP 900 Einstellbare Schrittweite zwischen MIN und MAX in Sekunden
DEBUG_LIFETIME 180 Tokenlebensdauer in Sekunden im DEBUG-Modus

Die maximale Lebenszeit von Tokens ist auf eine vernünftige, sinnvolle Bearbeitungsdauer eingestellt und sollte auf keinen Fall verlängert werden. Grundsätzlich verfallen auch alle Tokens, wenn die aktuelle Session ihren Timeout erreicht.

Je länger die mögliche Bearbeitungszeit wird,
desto grösser wird das Risiko einer erfolgreichen Attacke.


Registry-Settings

(Variable Einstellmöglichkeiten)
Diese vier Werte können im ACP unter Settings-Sicherheit eingestellt werden.

SecTokenLifeTime
Die 'Lebensdauer' von Tokens kann in Schritten von 15 Minuten zwischen 15 Minuten und 1,5 Stunden eingestellt werden.
Wird ein negativer Wert (<0) eingegeben, so wird der Debug-Modus mit einer Lebensdauer von 3 Minuten aktiviert.

SecTokenFingerprint
Hiermit kann das Fingerprinting des Client eingeschaltet(true) oder ausgeschaltet(false) werden.
Die Abschaltung wird aus Sicherheitsgründen nicht empfohlen!

SecTokenIpv4Netmask
Hiermit wird der zu überprüfende Netzanteil einer IPv4 Adresse festgelegt. Möglich sind Netzmaskenlängen von 1-32 Bit. Eine Länge von 0 Bit setzt die IPv4-Überprüfung außer Funktion. Je höher der angegebene Wert ist, desto restriktiver ist die Einstellung.

SecTokenIpv6Netmask
Hiermit wird der zu überprüfende Präfix einer IPv6 Adresse festgelegt. Möglich sind Längen von 1-128 Bit. Eine Länge von 0 Bit setzt die IPv6-Überprüfung außer Funktion. Je höher der angegebene Wert ist, desto restriktiver ist die Einstellung.


Verfügbare Methoden

::getToken

Prototype: <php>getToken(void):array</php>
Gibt ein Array zurück, das die FTAN des aktuellen Requests sowie zusätzliche Daten zurückgibt. Während des Programmstarts wird automatisch ein neuer Token erzeugt, der dann während des laufenden Requests beliebig oft abgerufen werden kann.

Gibt ein Array mit folgenden Schlüsseln zurück:

Schlüssel Value-Type Beschreibung
name string Bezeichner des Tokens
value string Wert des Tokens
remain int Sekunden bis zum Timeout
previous string vorheriger Wert des Tokens

Der Wert 'remain' kann zur Anzeige eines Timeout-Countdowns mittels Javascript genutzt werden.

::checkToken

Prototype: <php>checkTokenn(void):bool</php>
Es wird überprüft, ob im aktuellen Request ein gültiger Token übergeben wurde. Die Methode sucht die nötigen Angaben automatisch in den Argumenten des aktuellen Requests. Diese Methode merkt sich das Ergebnis der Prüfung, welches während eines Requests beliebig oft abgefragt werden kann. Bei gültigem Token ist der Rückgabewert TRUE andernfalls FALSE.

::createIdKey

Prototype: <php>createIdKey(scalar $mValue):string</php>
Der an die Methode übergebene Wert wird gesichert und dafür ein einmaliger, 6-stelliger, alphanumerischer Schlüsselwert zurückgegeben. Es können alle skalaren Datentypen übergeben werden: String, Integer, Float. Der zurückgegebene Schlüsselwert wird einfach anstatt des Originalwertes an den Client gesendet.

::isValidIdKey

Prototype: <php>isValidIdKey(string $sToken[, bool $bPreserve = false]):bool</php>

::decodeIdKey

Prototype: <php>decodeIdKey(string $sToken[, bool $bPreserve = false]):scalar</php>

::sanitizeTokenLifeTime

Prototype: <php>integer sanitizeTokenLifeTime(integer $iLifeTime)</php>
Ein übergebener Integer-Wert wird auf eine verfügbare Intervallstufe zwischen LIFETIME_MIN und LIFETIME_MAX korrigiert. Ein negativer Wert bei aktiviertem DEBUG-Modus auf DEBUG_LIFETIME, ansonsten auf LIFETIME_MIN. Ein ungültiger Wert grundsätzlich auf LIFETIME_MIN. Der berechnete Wert wird zurückgegeben.

::getTokenLifeTime

Prototype: <php>array getTokenLifeTime(void)</php>
Gibt ein Array mit folgenden Schlüsseln zurück:

Schlüssel Beschreibung
min minimal einstellbare Lebensdauer in Sekunden
max maximal einstellbare Lebensdauer in Sekunden
step Schrittweite in Sekunden
value Eingestellte Laufzeit in Sekunden

Wozu werden diese Werte benötigt? Das ACP um Beispiel holt sich diese Angaben, um die Einstellung komfortabel verändern zu können.


Anwendungsbeispiele

:!: Achtung: Requests müssen eindeutig sein! Der 'action' Parameter eines form Tags mit Method=„POST“ darf keine zusätzlichen URL-Argumente ( *.php?x=1 ) enthalten. Eventuell notwendige Zusatzargumente müssen mit <input type=„hidden“ …> übergeben werden.

Formular

<PHP> use bin\Security\CsfrTokens; $aToken = CsfrTokens::getToken(); $sOutput = '<form method=„post“ action=„index.php“>'

       . '<input type="hidden" name="'.$aToken['name'].'" value="'.$aToken['value'].'">'
       . '<input type="hidden" name="record_id" value="'.CsfrTokens::createIdKey($iRecordId).'">';

echo $sOutput;

Auswertung if (CsfrTokens::checkToken()) { $iRecordId = CsfrTokens::decodeIdKey('record_id'); […] } </PHP> === Link === <PHP> use bin\Security\CsfrTokens; $aToken = CsfrTokens::getToken(); $sOutput = '<a href=„index.php?'.$aToken['name'].'='.$aToken['value'].'&record_id=' . CsfrTokens::createIdKy($iRecordId),'“ title=„xx“>Tu was</a>'; echo $sOutput; Auswertung

if (CsfrTokens::checkToken()) {

  /* alles OK */
  $iRecordId = CsfrTokens::decodeIdKey('record_id');
  […]

} </PHP>

Twig-Template

<PHP> use bin\Security\CsfrTokens;

$aTwigdata['TargetUrl'] = 'index.php'; $aTwigdata['TOKEN'] = CsfrTokens::getToken(); $aTwigdata['RecordId'] = CsfrTokens::createIdkey($record_id); </PHP> <PHP> twig-template

Beispiel 1 <form method=„post“ action=„targeturl“> <input type=„hidden“ name=„token.name“ value=„token.value“> <input type=„hidden“ name=„record_id“ value=„recordid“> […] </form> Beispiel 2 <a href=„targeturl?token.name=token.value&record_id=recordid“ title=„xx“>Tu was</a> </PHP> <PHP>

Beispiel 1 + 2 use bin\Security\CsfrTokens; if (CsfrTokens::checkToken()) { $record_id = CsfrTokens::decodeIdKey('record_id'); […] } </PHP> ==== Einsatz mit AJAX ==== CsfrTokens in Verbindung mit Ajax erfordert etwas mehr Aufmerksamkeit. Wobei es Unterschiede zwischen den Major-Versionen von WebsiteBaker gibt.

Die folgende Beschreibung gilt vorerst nur für Versionen der 2er Serie, deren Module noch mit der veralteten Technik arbeiten und PHP-Dateien im Modulverzeichnis direkt aufrufen.

Grundsätzlich gilt natürlich auch für Ajax-Requests die übliche Regel, dass Änderungsanfragen einen gültigen TOKEN mitliefern müssen. Prinzipiell ist das bei normalen Requests ja kein Problem, da als Antwort immer eine komplett neue Seite aufgebaut wird die automatisch auch neue, gültige TOKENSs etc. enthält.
Bei einem Ajax-Request hingegen wird die Seite ja nicht automatisch neu aufgebaut. Also werden auch für die verbrauchten keine neuen TOKENs eingetragen. Jeder weitere Aufruf würde also deshalb zwingend eine Fehlermeldung zur Folge haben.
Im Rückschluss bedeutet das, dass in der Ajax-Antwort ein neuer TOKEN mit zurückgeliefert werden muss, die die bisherige ersetzt.
Ab der Version 1.0.1 stellt CsfrTokens die TOKEN-Unterstützung von AJAX-Requests bereit.
Um diese neue Funktionalität nutzen zu können, müssen 4 Bedingungen erfüllt werden.

  1. Ein AJAX-Request muss selbstverständlich die beiden Teile des TOKEN (name, value) senden.
  2. Ein AJAX-Request muss den HTTP-Header KIPA-XAJAX: 1 senden.
  3. Ein AJAX-Response muss vom Server ein TOKEN Objekt zurück bringen.
  4. Browserseitig muss ein Script dafür sorgen, dass das zurückgelieferte Token Objekt verarbeitet wird (RenewToken(oToken)).

zu Punkt (1):
Bei GET Requests genügt ein einfacher URL-Argumentestring mit den TOKEN-Werten <html>url?foo=xx&abcdef=123456</html> angehängt.
Für POST wird einfach ein zusätzliches Inputfeld <html><input type=„hidden“ name=„abcdef“ value=„123456“></html> verwendet. zu Punkt (2):
Vor dem Senden der Daten den KIPA_XAJAX HTTP-Header senden. <php>XHR.setRequestHeader('KIPA-XAJAX', '1');</php>
WebsiteBaker benötigt diesen Header, um einen AJAX-Request sicher zu detektieren. zu Punkt (3):
die Rückgabe des Token-Objektes. Das ist durch einen Einzeiler sehr einfach zu erledigen.
<php>echo json_encode(\bin\Security\CsfrTokens::getToken());</php> Selbstverständlich kann dieser Einzeiler auch in jede komplexere Rückgaberoutine eingebaut werden.
zu Punkt (4):
Das ist der 'aufwändigste' Punkt. Das zurückgegebene TOKEN Objekt muss an z.B. die Funktion RenewToken() übergeben werden. Diese Funktion tauscht sämtliche alten TOKENs im gesamten Dokument gegen die neu erhaltenen aus. Zusätzlich kann noch eine Funktion übergeben werden, die eine SessionProgressBar neu startet: <html>RenewTokenFtan(oToken, callBackFunction);</html> IDKEYs werden durch Ajax-Requests nicht verändert oder ungültig. <code javascript TokenResponse.js> <script> function RenewToken(oToken, refreshProgress) { var i, ax, regex = new RegExp(oToken.name + „=“ + oToken.previous, „g“); ax = document.links; for (i = 0; i < ax.length; i++) { ax[i].href = ax[i].href.replace(regex, oToken.name + „=“ + oToken.value); } ax = document.getElementsByName(oToken.name); for (i = 0; i < ax.length; i++) { ax[i].value = oToken.value; } if (refreshProgress) { refreshProgress(oToken.remain); } } </script> </code> ==== Timeout Progressbar ==== Dies ist eine von sehr vielen Möglichkeiten, eine Progressbar einzubauen, die die Restzeit bis zum Ablauf der Session/des Tokens anzeigt. Es ist nur ein Beispiel, wie derartiges funktionieren könnte. Selbstverständlich kann jeder seine persönliche Lösung des Problems erstellen! <file css KiSessionProgress.css> <style> #KiSessionProgress { position: relative; width: 100%; height:1.6em; margin: 2px 0 3px 0; padding: 0; font-size: 0.85em; background-color: transparent; background-image: url(); background-repeat: repeat-x; border: solid 1px #a0a0a0; } #KiSessionProgress div:first-of-type { position: absolute; left: 0; top: 0; width: 100%; height: 100%; background-color: #00ff00; z-index: 10; } #KiSessionProgress div:last-of-type { position: absolute; left: 0; top: 0; width: 100%; height: 100%; text-align: center!important; background-color: transparent; color: #000; z-index: 20; } </style> </file> <file html5 KiSessionProgress.html> <!– html –>

  <div>&nbsp;</div><div>{{ Lang.SESSION_TIMEOUT }}!!|{{ Lang.REMAINING_TIME }}&nbsp;&nbsp;</div>

</file> <file javascript KiSessionProgress.js> <script> function updateKiSessionProgressBar(duration) { function addLeadZero(value) { while (value.length < 2) { value = '0'+value; } return value; } function formatSeconds(seconds) { var s, m, h; s = seconds % 60; m = 1)

1)
seconds - s) % 3600) / 60;
      h = (seconds - s -(m * 60)) / 3600
      return h.toString(10) + ':'
             + addLeadZero(m.toString(10)) + ':'
             + addLeadZero(s.toString(10));
  }
  function moveBar() {
      if (width <= 0) {
          ProgressBar.style.width = 100 + '%';
          ProgressCaption.style.color = '#ffffff';
          ProgressBar.style.backgroundColor = '#ff0000';
          Progress.style.backgroundImage = '';
          ProgressCaption.innerHTML = TextArray[0];
          clearInterval(KiSessionProgressBarId);
      } else {
          width = Math.round(((100 * remaining)/duration)*1000)/1000;
          ProgressBar.style.width = width + '%';
          ProgressCaption.innerHTML = TextArray[1] + formatSeconds(remaining);
          remaining --;
          if (remaining <= 0) { width = 0; }
          if (remaining < 120) {
              Progress.style.backgroundImage = 'url("../images/TimeoutWarning.gif")';
          }
      }
  }
  if (KiSessionProgressBarId != 0) { clearInterval(KiSessionProgressBarId); }
  var remaining = duration, width = 100,
      Progress        = document.getElementById("KiSessionProgress"),
      ProgressBar     = Progress.firstElementChild,
      ProgressCaption = Progress.lastElementChild,
      TextArray = ProgressCaption.innerHTML.split('|');
  KiSessionProgressBarId = setInterval(moveBar, 1000);
} var KiSessionProgressBarId = 0; updateKiSessionProgressBar(otoken.remain); </script> </file>