Benutzer-Werkzeuge

Webseiten-Werkzeuge


dev:284:security

Systemsicherheit

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

SecureTokens

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\SecureTokens 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

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 eine vollständige FTAN 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 der FTAN.

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 ausser Funktion. Je höher der angegebene Wert ist, desto restriktiver ist die Einstellung.

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


Verfügbare Methoden

::getFtan

Prototype: getFtan(void):array
Gibt ein Array zurück, das die FTAN des aktuellen Requests sowie zusätzliche Daten zurückgibt. Beim ersten Aufruf von getFTAN() innerhalb eines Requests wird eine neue FTAN erzeugt, die dann während des laufenden Requests beliebig oft abgerufen werden kann.

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

Schlüssel 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.

::checkFtan

Prototype: checkFtan(void):bool
Es wird überprüft, ob im aktuellen Request eine gültige FTAN ü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ültiger FTAN ist der Rückgabewert TRUE andernfalls FALSE.

::getIdKey

Prototype: getIdKey(scalar $mValue):string
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.

::decodeIdKey

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

  • $sToken Ein Token, bzw. IdKey, dessen realer Wert ausgelesen werden soll. Dieser Wert kann nur ein einziges MAl gelesen werden.

::sanitizeLifeTime

Prototype: integer sanitizeLifeTime(integer $iLifeTime)
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: array getTokenLifeTime(void)
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

use bin\security\SecureTokens;
$aFtan = SecureTokens::getFtan();
$sOutput = '<form method="post" action="index.php">'
         . '<input type="hidden" name="'.$aFtan['name'].'" value="'.$aFtan['value'].'">'
         . '<input type="hidden" name="record_id" value="'.SecureTokens::getIdKey($iRecordId).'">';
echo $sOutput;    
 
// Auswertung
 
if (SecureTokens::checkFtan()) {
    /* alles OK */ 
    $iRecordId = SecureTokens::checkIdKey('record_id');
    []
}
use bin\security\SecureTokens;
$aFtan = SecureTokens::getFtan();
$sOutput = '<a href="index.php?'.$aFtan['name'].'='.$aFtan['value'].'&record_id='
         . SecureTokens::getIdKy($iRecordId),'" title="xx">Tu was</a>';
echo $sOutput;    
 
// Auswertung
 
if (SecureTokens::checkFtan()) {
    /* alles OK */
    $iRecordId = SecureTokens::checkIdKey('record_id');
    []
}

Twig-Template

use bin\security\SecureTokens;
/* view-script */
 
$aTwigdata['TargetUrl'] = 'index.php';
$aTwigdata['FTAN']      = SecureTokens::getFtan();
$aTwigdata['RecordId']  = SecureTokens::getIdkey($record_id);
twig-template
 
// Beispiel 1
 <form method="post" action="{{ TargetUrl }}">
   <input type="hidden" name="{{ FTAN.name }}" value="{{ FTAN.value }}">
   <input type="hidden" name="record_id" value="{{ RecordId }}">
   []
 </form>
 
// Beispiel 2
 <a href="{{ TargetUrl }}?{{ FTAN.name }}={{ FTAN.value }}&record_id={{ RecordId }}" title="xx">Tu was</a>
/* save-script */
 
// Beispiel 1 + 2
use bin\security\SecureTokens;
if (SecureTokens::checkFtan()) {
    $record_id = SecureTokens::checkIdKey('record_id');
    []
}

Einsatz mit AJAX

SecureTokens 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 eine gültige FTAN 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 FTANs 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 FTANs eingetragen. Jeder weitere Aufruf würde also deshalb zwingend eine Fehlermeldung zur Folge haben.
Im Rückschluss bedeutet das, dass in der Ajax-Antwort eine neue FTAN mit zurückgeliefert werden muss, die die bisherige ersetzt.

Ab der Version 1.0.1 stellt SecureTokens in Verbindung mit WebsiteBaker ab 2.11.0 die FTAN-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 der FTAN (name, value) senden.
  2. Ein AJAX-Request muss den HTTP-Header KIPA-XAJAX: 1 senden.
  3. Ein AJAX-Response muss vom Server ein Ftan Objekt zurück bringen.
  4. Browserseitig muss ein Script dafür sorgen, dass das zurückgelieferte Ftan Objekt verarbeitet wird (RenewFtan(oFtan)).

zu Punkt (1):
Bei GET Requests genügt ein einfacher URL-Argumentestring mit den FTAN-Werten url?foo=xx&abcdef=123456 angehängt.
Für POST wird einfach ein zusätzliches Inputfeld <input type="hidden" name="abcdef" value="123456"> verwendet.

zu Punkt (2):
Vor dem Senden der Daten den KIPA_XAJAX HTTP-Header senden. XHR.setRequestHeader('KIPA-XAJAX', '1');
WebsiteBaker benötigt diesen Header, um einen AJAX-Request sicher zu detektieren.

zu Punkt (3):
die Rückgabe des Ftan - Objektes. Das ist durch einen Einzeiler sehr einfach zu erledigen.
echo json_encode(\bin\security\SecureTokens::getFtan()); 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 FTAN Objekt muss an z.B. die Funktion RenewFtan() übergeben werden. Diese Funktion tauscht sämtliche alten FTANs im gesamten Dokument gegen die neu erhaltenen aus. Zusätzlich kann noch eine Funktion übergeben werden, die eine SessionProgressBar neu startet: RenewFtan(oFtan, callBackFunction); IDKEYs werden durch Ajax-Requests nicht verändert oder ungültig.

FtanResponse.js
<script>
function RenewFtan(oFtan, refreshProgress) {
    var i, ax, regex = new RegExp(oFtan.name + "=" + oFtan.previous, "g");
    ax = document.links;
    for (i = 0; i < ax.length; i++) {
        ax[i].href = ax[i].href.replace(regex, oFtan.name + "=" + oFtan.value);
    }
    ax = document.getElementsByName(oFtan.name);
    for (i = 0; i < ax.length; i++) {
        ax[i].value = oFtan.value;
    }
    if (refreshProgress) {
        refreshProgress(oFtan.remain);
    }
}
</script>

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!

KiSessionProgress.css
/* css */
<style>
#KiSessionProgress {
    position: relative;
    width: 100%;
    height:1.6em;
    margin: 2px 0 3px 0;
    padding: 0;
    font-size: 0.85em;
    background-color: transparent; /*#eeeeee;*/
    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>
KiSessionProgress.html
<!-- html -->
<div id="KiSessionProgress">
    <div>&nbsp;</div><div>{{ Lang.SESSION_TIMEOUT }}!!|{{ Lang.REMAINING_TIME }}&nbsp;&nbsp;</div>
</div>
KiSessionProgress.js
/* javascript */
<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 = ((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;
/*
 * put the following line somwhere after the script above!
 */
updateKiSessionProgressBar({{ Ftan.remain }});
</script>
dev/284/security.txt · Zuletzt geändert: 18.07.2018 17:20 von Manuela v.d.Decken