contao / core

Contao 3 → see contao/contao for Contao 4
GNU Lesser General Public License v3.0
490 stars 214 forks source link

Whitelist für Referer bzw. Token-Prüfung #3164

Closed jantheofel closed 12 years ago

jantheofel commented 12 years ago

Wenn man generell die Referer-Prüfung (was ja in 2.10 eigentlich zur Token-Prüfung wird) generell nutzen möchte, wäre es sehr hilfreich einzelne Domains von der Prüfung ausnehmen zu können. Zwei Anwendungsbeispiele:

  1. Facebook Fanseiten können Contao-Inhalte als iframe einbinden (seit etwa April möglich) und senden dabei immer per POST Informationen mit (z.B. ob es sich um einen normalen Besucher oder einen Fan der eigenen Fanseite handelt).
  2. Fremde Webseiten sollen gezielt für das POST an bestimmte Formulare freigeschaltet werden.

Dazu schlage ich eine kommasperatierte Liste in den Einstellungen und dazu folgende Implementierung vor:

system/config/config.php respektive system/config/localconfig.php ergänzen:

$GLOBALS['TL_CONFIG']['refererWhiteList']    = '';

system/modules/backend/dca/tl_settings.php Palatte ergänzen:

[...]displayErrors,debugMode,disableRefererCheck,disableIpCheck,refererWhiteList;{files_legend:hide}[...]

und ergänzen:

                'refererWhiteList' => array
                (
                        'label'                   => &$GLOBALS['TL_LANG']['tl_settings']['refererWhiteList'],
                        'inputType'               => 'text',
                        'eval'                    => array('tl_class'=>'long')
                ),

Die Anfrage erfolgt in system/initialize.php:

if ($_POST && 

![]($GLOBALS['TL_CONFIG']['disableRefererCheck'])
{
        // Check for whitelisting
        $whitelisted = 0;
        foreach(trimsplit(',', $GLOBALS['TL_CONFIG']['refererWhiteList']) as $white)
        {
                if(preg_match("/".$white."$/", $referer['host']))
                {
                        $whitelisted++;
                }
        }

        // Exit if the token cannot be validated
        if ()$whitelisted && (

![]($objInput->post('REQUEST_TOKEN') || )is_array($_SESSION['REQUEST_TOKEN'][TL_MODE]) || !in_array($objInput->post('REQUEST_TOKEN'), $_SESSION['REQUEST_TOKEN'][TL_MODE])))
        {

--- Originally created on June 14th, 2011, at 02:19pm (ID 3164)

jantheofel commented 12 years ago

Natürlich braucht es auch noch zwei Einträge in den Languagefiles, die ich oben vergessen habe:

system/modules/backend/languages/en/tl_settings.php:

$GLOBALS['TL_LANG']['tl_settings']['refererWhiteList']    = array('Disable referer check for these domains', 'Do not check the referer host address for these domains when a form is submitted. Enter as comma seperated list. Choosing this option is a potential security risk

![](');

system/modules/backend/languages/de/tl_settings.php:

$GLOBALS['TL_LANG']['tl_settings']['refererWhiteList']    = array('Referer-Prüfung für diese Domains deaktivieren', 'Die Referer-Adresse wird für diese Domains beim Absenden eines Formulars nicht geprüft. Eingabe als kommaseparierte Liste. Warnung: potentielles Sicherheitsrisiko)');

--- Originally created on June 14th, 2011, at 02:23pm

leofeyer commented 12 years ago

Das würde den Sicherheitsmechanismus aushebeln, denn den Referer kann man beliebig manipulieren. Will heißen, ich sende einfach den Referer "facebook.com" mit und schon habe ich uneingeschränkt Zugriff! Genau deswegen wurde die Referer-Prüfung ja gegen ein Request-Token-System ausgetauscht. Insofern ist das keine Option.

Trotzdem sollten wir überlegen, wie wir das Problem "Kommunikation mit PayPal" oder "Kommunikation mit Facebook" lösen.

--- Originally created on June 14th, 2011, at 05:45pm

jantheofel commented 12 years ago

Zu Paypal kann ich nichts sagen. Für Facebook habe ich den Code gerade fertig, der in einem Modul prüft ob der Request dort her kommt. (In dem Fall werden einige Post-Daten mit Signatur übergeben.) Allerdings ist die Frage ob der Core das abfangen soll oder die Module die eh dahinter kommen müssen und mit Facebook, Paypal, etc. interagieren an diesen Stellen die Prüfung übernehmen.

--- Originally created on June 14th, 2011, at 06:34pm

ghost commented 12 years ago

IMO beides, der Core sollte die Tokens selbst checken, wenn dies nicht erfolgreich war, dann kommt ein HOOK ins Spiel, welcher weitere POST-Tests machen kann (wie die von Jan angesprochene Signatur checken - OAuth etc.).

Wenn also eines der registrierten Verfahren den POST als valide anerkennt, ist alles in Ordnung.

--- Originally created by xtra on July 7th, 2011, at 05:18pm

leofeyer commented 12 years ago

--- Originally closed on August 23rd, 2011, at 06:09pm

tristanlins commented 12 years ago

@leofeyer was ist besser: Eine "optionale Lücke", die man möglicherweise ausnutzen kann, wenn diese 1. offen ist und 2. man genau herausgefunden hat wie man diese Lücke ausnutzt (so einfach ist das in diesem Fall nämlich nicht). Oder aber, das vollständige Abschalten der Tokens, das muss man nämlich aktuell machen und wird man auch in 2.11 machen müssen.

leofeyer commented 12 years ago

Was bezweckst Du mit der Frage? Wieso muss man die Tokens in der 2.11 deaktivieren?

jantheofel commented 12 years ago

Wenn ich Tristan richtig verstehe will er damit sagen, dass es besser einen Hook einzubauen, der dann zum Beispiel für Facebook eine alternative Prüfung vorsieht als die Referrer-Prüfung komplett abzuschalten. Was sonst die einzige Alternative ist wenn man z.B. eine Contao-Seite in eine Facebook-Fanseite integrieren will.

leofeyer commented 12 years ago

Was ist denn mit der Konstante BYPASS_TOKEN_CHECK? Diese kann man in einem eigenen Entry-Skript (z.B. facebook.php) setzen und dann seine eigene Prüfung einbauen.

tristanlins commented 12 years ago

@leofeyer ich wollte genau das sagen, was @jantheofel interpretiert hat. Ich werde bei einem zukünftigen Projekt vor dem gleichen Problem stehen. Und da muss ich dann die Tokens für das ganze System deaktivieren. D.h. auch für alle nicht-FB Seiten innerhalb der Contao Installation.

Mh, weil man dann die Seitenstruktur nicht mehr dafür nutzen kann oder nur noch, in dem man das noch virtueller macht? Es geht ja nicht darum, irgendwelche Daten aus einer Extension an Facebook zu übergeben, sondern man will eine Seite aus Contao in Facebook anzeigen. Eventuell sogar mehrere. Man könnte das natürlich auch über ein Entry Script machen, aber wo ist der Sicherheitsgewinn dann? Dann kannst du auch /facebook_entry.php/acme.html anstelle von /acme.html aufrufen und immer noch alles machen. Im schlimmsten Fall könntest du dann sogar noch mehr "schindluder" damit treiben, wenn die facebook_entry.php nicht ordentlich gebaut ist.

Was wäre denn, wenn man die von @jantheofel vorgeschlagene Whitelist nicht nur auf Referer, sondern auch für lokale Pages beschränkt?

Alternativ, wir wollten schon immer einen systemInitialisationHook haben - der Hook sollte nach der Initialisierung des Systems (laden der Config) und der Initialisierungslogik (setzen von PHP Settings, prüfen von Referer etc.) aufgerufen werden -, ich glaube Andreas Schempp wollte/hat dazu auch ein Ticket gemacht. Wenn wir solch einen Hook hätten, könnten wir das eventuell auch als Extension machen.

leofeyer commented 12 years ago

Ich verstehe das Problem immer noch nicht. Wenn eine Contao-Seite in Facebook eingebunden wird, läuft Contao doch in einem iFrame. Und wo ist der Unterschied zwischen einer Whitelist und einem Entry-Skript? In beiden Fällen kann der Zugriff auf eine bestimmte Domain limitiert werden und beide Varianten haben dann dieselben Schwachstellen.

tristanlins commented 12 years ago

Wo ist dann der Praktische Unterschied, außer einer anderen URL? Ich finde persönlich das das Entry Script leichter ausnutzbar ist, als die Whitelist.

Eigentlich besteht nur die Problematik dass Contao afaik die Apache MultiView doch gar nicht mehr unterstützt? Also würde /facebook_entry.php/acme.html sowieso nicht funktionieren. (zumindest funktioniert /index.php/acme.html nicht mehr, sobald das URL Rewrite aktiviert ist).

jantheofel commented 12 years ago

Und ich habe von Leuten gehört, die nur Unterverzeichnisse oder Domains aber keine PHP-Dateien verlinken konnten. Also auf Facebook.php/xyz.html ging bei denen nicht...

jantheofel commented 12 years ago

Wie gerade auf der #ck2012 besprochen hier noch mal ein Ping, damit du da einen Hook einbaust, mit dem Extensions externe Post-Daten verifizieren und annehmen können, beispielsweise die von Facebook. Das umgeht die Whitelist und erfordert keine komplexeren URLs oder andere Lösungen. Danke!

SGehle commented 12 years ago

Ne Idee wann das ganze umgesetzt ist? Weil aktuell muss der Token-Prüfung ja noch komplett abgeschaltet werden...

leofeyer commented 12 years ago

Ich habe die Funktion in c967dd5fc94f0c716c7ddbc55ff50471dc32fa18 (Branch "token-whitelist") hinzugefügt. Allerdings habe ich noch immer Sicherheitsbedenken, denn wenn Du z.B. http://www.facebook.com auf die Whitelist setzt, muss ich einfach nur jedem Request den Referer-Header http://www.facebook.com mitsenden und umgehe so die Token-Prüfung – auch wenn der Request gar nicht von Facebook kommt und etwas ganz anderes macht.

Im Prinzip müsste jeder, der die Whitelist nutzt, gleichzeitig eine Prüfungsroutine mitliefern, die Anfragen mit dem Request-Header http://www.facebook.com anhand weiterer Kriterien prüft und jede ungültige Anfrage ablehnt. Andernfalls kann man die Token-Prüfung auch gleich ausschalten.

Problem hier: Die Whitelist ist im Backend konfigurierbar, dort kann man aber keine Prüfungsroutine hinzufügen. Ich wäre daher dafür, die Whitelist nicht im Backend zu befüllen, sondern in der config.php der Erweiterungen, die das Feature nutzen. Auf diese Weise verschiebt sich die Verantwortung vom Anwender zum Entwickler, der mit etwas Glück eine entsprechende Prüfungsroutine mitliefert.

leofeyer commented 12 years ago

@jantheofel und @contao/workgroup-core: Wie seht ihr das?

psi-4ward commented 12 years ago

Gefällt mir gar nicht! HTTP_REFERER spoofing ist zu trivial und facebook.com zu offensichtlich.

Sind denn die HTTP_POST Daten von Facebook interessant? Gibt es noch andere Anwendungsfälle außer Facebook?

jantheofel commented 12 years ago

Ich würde das nicht als Whitelist machen, schon gar nicht für den User konfigurierbar. Lieber einen Hook, in den sich Module einklinken können und dort z.B. die POST-Daten, di von Facebook kommen, prüfen und das dann freigeben.

psi-4ward commented 12 years ago

@jantheofel aber möchtest du nicht eine beliebige Seite auf Facebook darstellen? Irgendwie kann ich den Anwendungsfall gar nicht korrekt erfassen.

Für ein Modul haben wir die BYPASS_TOKE_CHECK Konstante!

jantheofel commented 12 years ago

Ich möchte beliebig viele Seiten dort darstellen können. Und dafür nicht manuell eine index-fb.php bauen, die Bypass macht und irgendwelche verrückten URLs und/oder extra .htaccess-Regeln braucht. Und es soll ja auch nicht einfach ein simpler Bypass sein sondern es soll ja auf jeden Fall die Sicherheitsprüfung kommen - nur eben eine andere!

leofeyer commented 12 years ago

Ich würde das nicht als Whitelist machen, schon gar nicht für den User konfigurierbar

Hm, genau so hast Du es aber oben vorgeschlagen und deswegen habe ich es so programmiert :)

Für ein Modul haben wir die BYPASS_TOKE_CHECK Konstante

Die fiele im Zuge der Whitelist bzw. des Hooks weg.

jantheofel commented 12 years ago

Oh, stimmt, das war meine ursprüngliche Anfrage. Ich hätte dazu schreiben sollen, dass es da wohl noch einen besseren Weg gibt nachdem der ja berechtigter Weise kritisiert worden ist. :-(

leofeyer commented 12 years ago

Halb so wild, Hauptsache wir finden jetzt noch gemeinsam eine gute Lösung.

leofeyer commented 12 years ago

Nächster Versuch: 8675d07f91e4ac3324c49c011f8f107b3378aed5

jantheofel commented 12 years ago

Sprich dann soll ich jetzt den Hook testen? ;-)

psi-4ward commented 12 years ago

Klaro mach mal ;) Denk dran den richtigen Branch auszu-checkouten

tristanlins commented 12 years ago

Wie wäre es mit einer Reverse-IP Prüfung? Die Request IP zu spoofen ist schon deutlich schwerer als den Referer.

Valid Domain: facebook.com (per config.php)

Request IP: 69.171.224.37 Reverse IP: 37.224.171.69.in-addr.arpa -> www-slb-10-01-prn1.facebook.com.

www-slb-10-01-prn1.facebook.com ist subdomain von facebook.com und damit zulässig.

Wäre das ein Lösungsansatz?

tristanlins commented 12 years ago

Nur ein kleines Experiment das euch vielleicht weiter hilft. :-)

<?php
// whitelist
$GLOBALS['TL_CONFIG']['whitelistDomains'] = array(
    // 'd1-online.com'
);
// blacklist
$GLOBALS['TL_CONFIG']['blacklistDomains'] = array(
    // 'customers.d1-online.com'
);

class AccessCheck // extends System
{
    function checkHost()
    {
        // get current hostname by remote address
        $hostname = gethostbyaddr($_SERVER['REMOTE_ADDR']);

        // check blacklist
        foreach ($GLOBALS['TL_CONFIG']['blacklistDomains'] as $blacklistDomain) {
            if (preg_match('#\.' . preg_quote($blacklistDomain) . '$#', $hostname)) {
                // $this->log('The request hostname ' . $hostname . ' is blacklisted.', 'AccessCheck', TL_ERROR);
                return false;
            }
        }

        // check whitelist
        foreach ($GLOBALS['TL_CONFIG']['whitelistDomains'] as $whitelistDomain) {
            if (preg_match('#\.' . preg_quote($whitelistDomain) . '$#', $hostname)) {
                // $this->log('The request hostname ' . $hostname . ' is whitelisted.', 'AccessCheck', TL_ERROR);
                return true;
            }
        }

        return false;
    }
}

$access = new AccessCheck();
var_dump($access->checkHost());

?>
leofeyer commented 12 years ago

Finde ich super. @jantheofel kommst Du damit auch ans Ziel?

psi-4ward commented 12 years ago

Diese Funktion könnte man dann über eine Erweiterung und dem Hook bereitstellen. Die Idee an sich gut!

leofeyer commented 12 years ago

Ich fände es auch gut, wenn die Prüfung direkt im Core wäre. Dann könnte man nämlich wirklich in den BE-Einstellungen die Whitelist komfortabel pflegen.

jantheofel commented 12 years ago

Ja, wenn wir die Prüfung wie von Tristan vorgeschlagen einbauen sollte das auch für Facebook funktionieren und wäre im Core denke ich besser aufgehoben. Allerdings würde ich dennoch keine manuelle Whitelist anlegen, sondern diese nur von Erweiterungen befüllen lassen.

So in der Art: $GLOBALS['TL_CONFIG']['whitelistDomains'][] = 'facebook.com';

Denn wenn eine Domain POST-Daten sendet, dann wird es wohl auch immer eine Erweiterung geben, die diese ausliest und auswertet. Den User mit einer weiteren Einstellung, die doch eher erklärungsbedürftig ist, zu verwirren, halte ich nicht für sinnvoll. Im Zweifelsfall sollte jeder, der so was wirklich braucht, in der Lage sein, ein paar Zeilen PHP dafür zu bemühen...

tristanlins commented 12 years ago

Im Zweifelsfall sollte jeder, der so was wirklich braucht, in der Lage sein, ein paar Zeilen PHP dafür zu bemühen...

Dem stimme ich zu, und wenn es ein $GLOBALS['TL_CONFIG']['whitelistDomains'][] = 'facebook.com'; innerhalb der localconfig ist, das man von Hand rein schreibt.

tristanlins commented 12 years ago

PS: mir fällt gerade auf, dass die Prüfung eigentlich noch ergänzt werden müsste:

<?php
...
if ($blacklistDomain == $hostname ||
    preg_match('#\.' . preg_quote($blacklistDomain) . '$#', $hostname)) {
...
if ($whitelistDomain == $hostname ||
    preg_match('#\.' . preg_quote($whitelistDomain) . '$#', $hostname)) {
...

Das wird zwar Facebook nicht betreffen, da die mehrere Server haben, aber vielleicht benötigt man das auch mal in einer Umgebung, wo die Domain equivalent ist.

leofeyer commented 12 years ago

Ich habe die Änderungen in 537196f6fe99acf926df9a9e8297177ea0f2a809 eingebaut. Bitte noch mal prüfen und dann eine Rückmeldung geben, damit ich den Branch mergen kann.

tristanlins commented 12 years ago

Ich habe es gerade lokal getestet, funktioniert auch mit localhost :-)

leofeyer commented 12 years ago

Implementiert in ee2a3f3e5663b53dd2eff347852af9ec082526a2.

bruc13 commented 11 years ago

Hi, ich habe die Änderung in 2.11 übertragen. Leider funktioniert es nicht so wie gedacht. $_SERVER['REMOTE_ADDR'] - hat immer meine IP zurückgegeben, getestet gestern in einer Facebook App. Also musste ich noch einen Umweg gehen. ...

foreach ($GLOBALS['TL_CONFIG']['requestTokenWhitelist'] as $strDomain)
{
    $ip = gethostbyname($strDomain);
    $strHostname = gethostbyaddr($ip);
if ($strDomain == $strHostname || 
preg_match('/\.' . preg_quote($strDomain, '/') . '$/', $strHostname))
    {
        return true;
    }
}

...