From a5602bca2566f1be370631c3ab2d40feedd4b3ad Mon Sep 17 00:00:00 2001 From: Johan Cwiklinski Date: Mon, 8 Nov 2021 23:36:58 +0100 Subject: [PATCH] Add CSRF Middleware Add csrf inputs on all forms Use an exception rather than default blank page Add CSRF check on ajax post requests Make CSRF token persistent to ease with ajax calls --- galette/composer.json | 3 +- galette/composer.lock | 106 +++++++++++++++++- galette/includes/dependencies.php | 25 +++++ galette/includes/main.inc.php | 2 + galette/lib/Galette/Middleware/SmartyCsrf.php | 96 ++++++++++++++++ galette/templates/default/admintools.tpl | 3 +- galette/templates/default/advanced_search.tpl | 1 + .../default/ajouter_contribution.tpl | 1 + .../templates/default/ajouter_transaction.tpl | 1 + .../default/attendance_sheet_details.tpl | 1 + galette/templates/default/change_passwd.tpl | 1 + galette/templates/default/common_scripts.tpl | 18 +++ galette/templates/default/config_fields.tpl | 1 + galette/templates/default/config_lists.tpl | 1 + galette/templates/default/confirm_removal.tpl | 1 + galette/templates/default/directlink.tpl | 1 + .../templates/default/edit_paymenttype.tpl | 1 + galette/templates/default/edit_title.tpl | 1 + galette/templates/default/editer_champ.tpl | 1 + galette/templates/default/editer_intitule.tpl | 1 + galette/templates/default/export.tpl | 1 + .../templates/default/forms_types/csrf.tpl | 2 + .../templates/default/gestion_adherents.tpl | 4 +- .../default/gestion_contributions.tpl | 2 + .../templates/default/gestion_intitules.tpl | 1 + .../templates/default/gestion_mailings.tpl | 1 + .../default/gestion_paymentstypes.tpl | 1 + .../templates/default/gestion_pdf_content.tpl | 1 + galette/templates/default/gestion_textes.tpl | 2 + galette/templates/default/gestion_titres.tpl | 1 + .../default/gestion_transactions.tpl | 1 + galette/templates/default/group.tpl | 1 + galette/templates/default/history.tpl | 1 + galette/templates/default/import.tpl | 2 + galette/templates/default/import_model.tpl | 1 + galette/templates/default/index.tpl | 1 + galette/templates/default/liste_membres.tpl | 1 + galette/templates/default/lostpasswd.tpl | 1 + .../templates/default/mailing_adherents.tpl | 2 +- .../default/mass_add_contribution.tpl | 1 + .../templates/default/mass_change_members.tpl | 1 + .../templates/default/mass_choose_type.tpl | 1 + galette/templates/default/member.tpl | 2 +- galette/templates/default/plugin_initdb.tpl | 1 + galette/templates/default/preferences.tpl | 1 + galette/templates/default/reminder.tpl | 1 + galette/templates/default/saved_searches.tpl | 1 + .../templates/default/traduire_libelles.tpl | 2 + galette/templates/default/trombinoscope.tpl | 1 + 49 files changed, 299 insertions(+), 6 deletions(-) create mode 100644 galette/lib/Galette/Middleware/SmartyCsrf.php create mode 100644 galette/templates/default/forms_types/csrf.tpl diff --git a/galette/composer.json b/galette/composer.json index f33d8d0058..b9d7244760 100644 --- a/galette/composer.json +++ b/galette/composer.json @@ -50,7 +50,8 @@ "doctrine/annotations": "^1.8", "laminas/laminas-servicemanager": "3.7", "symfony/polyfill-php80": "^1.23", - "ezyang/htmlpurifier": "^4.13" + "ezyang/htmlpurifier": "^4.13", + "slim/csrf": "0.8.3" }, "require-dev": { "atoum/atoum": "dev-master", diff --git a/galette/composer.lock b/galette/composer.lock index 79e9819a4e..511eb07ece 100644 --- a/galette/composer.lock +++ b/galette/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "47d56d47701d9038105bd93cf3b44a02", + "content-hash": "be12f467246cd29921b64f8f84dc17a8", "packages": [ { "name": "akrabat/rka-slim-session-middleware", @@ -2213,6 +2213,56 @@ }, "time": "2021-04-09T13:42:10+00:00" }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, { "name": "php-di/invoker", "version": "2.3.2", @@ -2808,6 +2858,60 @@ }, "time": "2017-10-23T01:57:42+00:00" }, + { + "name": "slim/csrf", + "version": "0.8.3", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim-Csrf.git", + "reference": "5f2bcf5d89adf86dc0455a32bea84d912ab466a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim-Csrf/zipball/5f2bcf5d89adf86dc0455a32bea84d912ab466a7", + "reference": "5f2bcf5d89adf86dc0455a32bea84d912ab466a7", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "^1.1|^2.0|^9.99", + "php": ">=5.5.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0", + "slim/slim": "~3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\Csrf\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + } + ], + "description": "Slim Framework 3 CSRF protection middleware", + "homepage": "http://slimframework.com", + "keywords": [ + "csrf", + "framework", + "middleware", + "slim" + ], + "support": { + "issues": "https://github.com/slimphp/Slim-Csrf/issues", + "source": "https://github.com/slimphp/Slim-Csrf/tree/master" + }, + "time": "2018-08-22T16:12:18+00:00" + }, { "name": "slim/flash", "version": "0.4.0", diff --git a/galette/includes/dependencies.php b/galette/includes/dependencies.php index 8cf6b47d92..c1e86bebb0 100644 --- a/galette/includes/dependencies.php +++ b/galette/includes/dependencies.php @@ -432,6 +432,31 @@ ) ); +$container->set( + 'csrf', + function (ContainerInterface $c) { + $storage = null; + $guard = new \Slim\Csrf\Guard( + 'csrf', + $storage, + null, + 200, + 16, + true + ); + + $guard->setFailureCallable(function ($request, $response, $next) { + Analog::log( + 'CSRF check has failed', + Analog::CRITICAL + ); + throw new \RuntimeException(_T('Failed CSRF check!')); + }); + + return $guard; + } +); + //For bad existing globals can be used... global $translator, $i18n; if ( diff --git a/galette/includes/main.inc.php b/galette/includes/main.inc.php index 7c1f190fcb..013bbbc5c9 100644 --- a/galette/includes/main.inc.php +++ b/galette/includes/main.inc.php @@ -92,6 +92,8 @@ // Set up dependencies require GALETTE_ROOT . '/includes/dependencies.php'; +$app->add(new \Galette\Middleware\SmartyCsrf($app->getContainer())); +$app->add($app->getContainer()->get('csrf')); if ($needs_update) { $app->add( diff --git a/galette/lib/Galette/Middleware/SmartyCsrf.php b/galette/lib/Galette/Middleware/SmartyCsrf.php new file mode 100644 index 0000000000..2305f5028b --- /dev/null +++ b/galette/lib/Galette/Middleware/SmartyCsrf.php @@ -0,0 +1,96 @@ +. + * + * @category Core + * @package Galette + * + * @author Johan Cwiklinski + * @copyright 2021 The Galette Team + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version + * @link http://galette.tuxfamily.org + * @since Available since 0.9.6dev - 2021-11-08 + */ + +namespace Galette\Middleware; + +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use Analog\Analog; +use DI\Container; + +/** + * Galette CSRF middleware + * + * @category Middleware + * @name SmartyCsrf + * @package Galette + * @author Johan Cwiklinski + * @copyright 2020 The Galette Team + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version + * @link http://galette.tuxfamily.org + * @since Available since 0.9.4dev - 2020-05-06 + */ +class SmartyCsrf +{ + private $smarty; + private $csrf; + + /** + * Constructor + * + * @param Container $container Container instance + */ + public function __construct(Container $container) + { + $view = $container->get('Slim\Views\Smarty'); + $this->smarty = $view->getSmarty(); + $this->csrf = $container->get('csrf'); + } + + /** + * Middleware invokable class + * + * @param \Psr\Http\Message\ServerRequestInterface $request PSR7 request + * @param \Psr\Http\Message\ResponseInterface $response PSR7 response + * @param callable $next Next middleware + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function __invoke(Request $request, Response $response, $next): Response + { + $nameKey = $this->csrf->getTokenNameKey(); + $valueKey = $this->csrf->getTokenValueKey(); + $name = $request->getAttribute($nameKey); + $value = $request->getAttribute($valueKey); + + $this->smarty->assign('csrf_name_key', $nameKey); + $this->smarty->assign('csrf_value_key', $valueKey); + $this->smarty->assign('csrf_name', $name); + $this->smarty->assign('csrf_value', $value); + + return $next($request, $response); + } +} diff --git a/galette/templates/default/admintools.tpl b/galette/templates/default/admintools.tpl index b1a9028d60..e72ac324bd 100644 --- a/galette/templates/default/admintools.tpl +++ b/galette/templates/default/admintools.tpl @@ -31,10 +31,11 @@

- + {include file="forms_types/csrf.tpl"}
{/block} diff --git a/galette/templates/default/advanced_search.tpl b/galette/templates/default/advanced_search.tpl index 4c8767959b..1aa267893b 100644 --- a/galette/templates/default/advanced_search.tpl +++ b/galette/templates/default/advanced_search.tpl @@ -312,6 +312,7 @@ + {include file="forms_types/csrf.tpl"} {/block} diff --git a/galette/templates/default/ajouter_contribution.tpl b/galette/templates/default/ajouter_contribution.tpl index 69cb889be1..42ee6c91fd 100644 --- a/galette/templates/default/ajouter_contribution.tpl +++ b/galette/templates/default/ajouter_contribution.tpl @@ -184,6 +184,7 @@ {/if} + {include file="forms_types/csrf.tpl"} {else} {* No members *}
diff --git a/galette/templates/default/ajouter_transaction.tpl b/galette/templates/default/ajouter_transaction.tpl index 18a3ddde14..2f34546e46 100644 --- a/galette/templates/default/ajouter_transaction.tpl +++ b/galette/templates/default/ajouter_transaction.tpl @@ -46,6 +46,7 @@ + {include file="forms_types/csrf.tpl"}

{_T string="NB : The mandatory fields are in"} {_T string="red"}

diff --git a/galette/templates/default/attendance_sheet_details.tpl b/galette/templates/default/attendance_sheet_details.tpl index a52d37ffb0..bd9c2254e3 100644 --- a/galette/templates/default/attendance_sheet_details.tpl +++ b/galette/templates/default/attendance_sheet_details.tpl @@ -31,6 +31,7 @@ {foreach $selection as $member} {/foreach} + {include file="forms_types/csrf.tpl"}

{if not $ajax} diff --git a/galette/templates/default/change_passwd.tpl b/galette/templates/default/change_passwd.tpl index c793ad2cb5..187200b217 100644 --- a/galette/templates/default/change_passwd.tpl +++ b/galette/templates/default/change_passwd.tpl @@ -16,5 +16,6 @@ + {include file="forms_types/csrf.tpl"} {/block} diff --git a/galette/templates/default/common_scripts.tpl b/galette/templates/default/common_scripts.tpl index f4e1dc719d..499a7e8099 100644 --- a/galette/templates/default/common_scripts.tpl +++ b/galette/templates/default/common_scripts.tpl @@ -1,5 +1,23 @@