From 62ffce88d51a9922ac2d93c7514cb732a3894c7d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?C=C3=B4me=20Chilliet?= <come@opensides.be>
Date: Mon, 4 Jun 2018 17:10:29 +0200
Subject: [PATCH] Merge branch
 '5840-security-vulnerability-cross-site-request-forgery' into '1.3-dev'

Resolve "Security Vulnerability: Cross Site Request Forgery"

See merge request fusiondirectory/fd!280

(cherry picked from commit 5edd2e32473bec27575719bff751308e57167731)

d8dbd130 :sparkels: feat(core) Add CSRF protection token
6e31f486 :sparkles: feat(core) Reject CSRF based on HEADER values
d9089973 :ambulance: fix(core) Remove useless line in CSRFProtection
3b51b48c :ambulance: fix(core) Use FusionDirectoryException instead of Exception
1ac4c8b5 :ambulance: fix(core) sonar fix
---
 html/main.php                     |  8 ++--
 html/setup.php                    |  3 ++
 ihtml/themes/breezy/framework.tpl |  1 +
 include/class_CSRFProtection.inc  | 69 +++++++++++++++++++++++++++++++
 setup/setup_frame.tpl             |  1 +
 5 files changed, 79 insertions(+), 3 deletions(-)
 create mode 100644 include/class_CSRFProtection.inc

diff --git a/html/main.php b/html/main.php
index 571ad1826..77b69f4cd 100644
--- a/html/main.php
+++ b/html/main.php
@@ -1,9 +1,8 @@
 <?php
-
 /*
   This code is part of FusionDirectory (http://www.fusiondirectory.org/)
   Copyright (C) 2003-2010  Cajus Pollmeier
-  Copyright (C) 2011-2016  FusionDirectory
+  Copyright (C) 2011-2018  FusionDirectory
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
@@ -54,6 +53,8 @@ if (!session::global_is_set('connected')) {
   exit;
 }
 
+CSRFProtection::check();
+
 $ui     = session::global_get('ui');
 $config = session::global_get('config');
 
@@ -318,7 +319,8 @@ if (session::is_set('errors') && session::get('errors') != "") {
 $focus = '<script type="text/javascript">';
 $focus .= 'next_msg_dialog();';
 $focus .= '</script>';
-$smarty->assign("focus", $focus);
+$smarty->assign('focus',      $focus);
+$smarty->assign('CSRFtoken',  CSRFProtection::getToken());
 
 /* Set channel if needed */
 //TODO: * move all global session calls to global_
diff --git a/html/setup.php b/html/setup.php
index 183840d0c..5a0e39c1f 100644
--- a/html/setup.php
+++ b/html/setup.php
@@ -49,6 +49,8 @@ session::start();
 session::global_set('DEBUGLEVEL', 0);
 session::set('errorsAlreadyPosted', array());
 
+CSRFProtection::check();
+
 /* Attribute initialization, reset errors */
 reset_errors();
 
@@ -123,6 +125,7 @@ $smarty->assign("navigation",     $setup->get_navigation_html());
 $smarty->assign("headline_image", $setup->get_header_image());
 $smarty->assign("headline",       $setup->get_header_text());
 $smarty->assign("focus",          $focus);
+$smarty->assign('CSRFtoken',      CSRFProtection::getToken());
 $smarty->assign("msg_dialogs",    msg_dialog::get_dialogs());
 
 if ($error_collector != "") {
diff --git a/ihtml/themes/breezy/framework.tpl b/ihtml/themes/breezy/framework.tpl
index d1061fe28..ba6b98d15 100644
--- a/ihtml/themes/breezy/framework.tpl
+++ b/ihtml/themes/breezy/framework.tpl
@@ -55,6 +55,7 @@
     {$errors}
     {$focus}
     <input type="hidden" name="php_c_check" value="1"/>
+    <input type="hidden" name="CSRFtoken" value="{$CSRFtoken}"/>
   </form>
 
 
diff --git a/include/class_CSRFProtection.inc b/include/class_CSRFProtection.inc
new file mode 100644
index 000000000..a2d814888
--- /dev/null
+++ b/include/class_CSRFProtection.inc
@@ -0,0 +1,69 @@
+<?php
+/*
+  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
+  Copyright (C) 2017-2018 FusionDirectory
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+class CSRFProtection
+{
+  public static function check()
+  {
+    if (empty($_POST)) {
+      return;
+    }
+    if (empty($_POST['CSRFtoken'])) {
+      throw new FusionDirectoryException('CSRF protection token missing');
+    }
+
+    static::checkHeaders();
+
+    if ($_POST['CSRFtoken'] !== static::getToken()) {
+      throw new FusionDirectoryException('CSRF protection token invalid');
+    }
+  }
+
+  public static function getToken()
+  {
+    if (!session::is_set('CSRFtoken')) {
+      session::set('CSRFtoken', standAlonePage::generateRandomHash());
+    }
+    return session::get('CSRFtoken');
+  }
+
+  public static function checkHeaders()
+  {
+    $origin = FALSE;
+    if (!empty($_SERVER['HTTP_ORIGIN'])) {
+      $origin = $_SERVER['HTTP_ORIGIN'];
+    } elseif (!empty($_SERVER['HTTP_REFERER'])) {
+      $origin = $_SERVER['HTTP_REFERER'];
+    }
+    if ($origin) {
+      $origin = preg_replace('|^[^/]+://([^/]+)(/.*)?$|', '\1', $origin);
+      $target = FALSE;
+      if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
+        $target = $_SERVER['HTTP_X_FORWARDED_HOST'];
+      } else
+      if (!empty($_SERVER['HTTP_HOST'])) {
+        $target = $_SERVER['HTTP_HOST'];
+      }
+      if ($target && !hash_equals($origin, $target)) {
+        throw new FusionDirectoryException('CSRF detected: origin and target are not matching ('.$origin.' != '.$target.')');
+      }
+    }
+  }
+}
diff --git a/setup/setup_frame.tpl b/setup/setup_frame.tpl
index 071f5365c..03dd16c50 100644
--- a/setup/setup_frame.tpl
+++ b/setup/setup_frame.tpl
@@ -39,6 +39,7 @@
     {$errors}
     {$focus}
     <input type="hidden" name="setup_goto_step" value=""/>
+    <input type="hidden" name="CSRFtoken" value="{$CSRFtoken}"/>
   </form>
 
 </body>
-- 
GitLab