Bug#1093755: bookworm-pu: package lemonldap-ng/2.16.1+ds-deb12u5

Yadd yadd at debian.org
Wed Jan 22 08:34:05 GMT 2025


Package: release.debian.org
Severity: normal
Tags: bookworm
X-Debbugs-Cc: lemonldap-ng at packages.debian.org, security at debian.org
Control: affects -1 + src:lemonldap-ng
User: release.debian.org at packages.debian.org
Usertags: pu

[ Reason ]
Prior to 2.20.2, lemonldap-ng is vulnerable to a CSRTF issue into its
Second-Factors registration interface (CVE-2024-52948).

[ Impact ]
Medium security issue

[ Tests ]
Test updated by upstream, included in this patch

[ Risks ]
Low risk, test coverage is good

[ Checklist ]
  [X] *all* changes are documented in the d/changelog
  [X] I reviewed all changes and I approve them
  [X] attach debdiff against the package in (old)stable
  [X] the issue is verified as fixed in unstable

[ Changes ]
Add an header check to avoid common CSRF attacks, following
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#disallowing-simple-requests

Best regards,
Xavier
-------------- next part --------------
diff --git a/debian/changelog b/debian/changelog
index 5842e9160..567c04800 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+lemonldap-ng (2.16.1+ds-deb12u5) bookworm; urgency=medium
+
+  * Fiw CSRF on 2FA registration interface (Closes: CVE-2024-52948)
+
+ -- Yadd <yadd at debian.org>  Wed, 22 Jan 2025 09:27:53 +0100
+
 lemonldap-ng (2.16.1+ds-deb12u4) bookworm; urgency=medium
 
   * Fix authentication privilege (Closes: CVE-2024-52946)
diff --git a/debian/patches/CVE-2024-52948.patch b/debian/patches/CVE-2024-52948.patch
new file mode 100644
index 000000000..e46d93b72
--- /dev/null
+++ b/debian/patches/CVE-2024-52948.patch
@@ -0,0 +1,1945 @@
+Description: fix CSRF on 2FA registration
+Author: Maxime Besson <maxime.besson at worteks.com>
+Origin: upstream, https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/-/merge_requests/644
+Bug: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/-/issues/3258
+Forwarded: not-needed
+Applied-Upstream: 2.20.2, https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/-/merge_requests/638/diffs
+Reviewed-By: Yadd <yadd at debian.org>
+Last-Update: 2025-01-22
+
+--- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Base.pm
++++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Base.pm
+@@ -72,4 +72,10 @@
+     return $name;
+ }
+ 
++sub checkCsrf {
++    my ( $self, $req ) = @_;
++
++    return $req->headers->header('X-CSRF-Check');
++}
++
+ 1;
+--- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Generic.pm
++++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Generic.pm
+@@ -49,6 +49,10 @@
+ 
+     # Send a code to generic
+     if ( $action eq 'sendcode' ) {
++
++        $self->checkCsrf($req)
++            or return $self->p->sendError( $req, 'csrfError', 400 );
++
+         my $generic = $req->param('generic');
+ 
+         unless ($generic) {
+@@ -85,6 +89,10 @@
+ 
+     # Verification that user has a valid generic
+     elsif ( $action eq 'verify' ) {
++
++        $self->checkCsrf($req)
++            or return $self->p->sendError( $req, 'csrfError', 400 );
++
+         my $generic     = $req->param('generic');
+         my $tokenid     = $req->param("token");
+         my $genericcode = $req->param('genericcode');
+@@ -150,6 +158,9 @@
+ 
+     elsif ( $action eq 'delete' ) {
+ 
++        $self->checkCsrf($req)
++            or return $self->p->sendError( $req, 'csrfError', 400 );
++
+         # Check if unregistration is allowed
+         return $self->p->sendError( $req, 'notAuthorized', 400 )
+           unless $self->userCanRemove;
+--- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Password.pm
++++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/Password.pm
+@@ -56,6 +56,9 @@
+     # Verification that user has a valid password
+     if ( $action eq 'verify' ) {
+ 
++        $self->checkCsrf($req)
++            or return $self->p->sendError( $req, 'csrfError', 400 );
++
+         # Check Password
+         my $password       = $req->param('password');
+         my $passwordverify = $req->param('passwordverify');
+@@ -129,6 +132,9 @@
+     }
+     elsif ( $action eq 'delete' ) {
+ 
++        $self->checkCsrf($req)
++            or return $self->p->sendError( $req, 'csrfError', 400 );
++
+         # Check if unregistration is allowed
+         return $self->p->sendError( $req, 'notAuthorized', 400 )
+           unless $self->conf->{password2fUserCanRemoveKey};
+--- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/TOTP.pm
++++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/TOTP.pm
+@@ -41,6 +41,9 @@
+     # Verification that user has a valid TOTP app
+     if ( $action eq 'verify' ) {
+ 
++        $self->checkCsrf($req)
++            or return $self->p->sendError( $req, 'csrfError', 400 );
++
+         # Get form token
+         my $token = $req->param('token');
+         unless ($token) {
+@@ -140,6 +143,9 @@
+     elsif ( $action eq 'getkey' ) {
+         my ( $nk, $secret, $issuer ) = ( 0, '' );
+ 
++        $self->checkCsrf($req)
++            or return $self->p->sendError( $req, 'csrfError', 400 );
++
+         # Read existing TOTP 2F
+         my @totp2f =
+           $self->find2fDevicesByType( $req, $req->userData, $self->type );
+@@ -184,6 +190,9 @@
+     # Delete TOTP
+     elsif ( $action eq 'delete' ) {
+ 
++        $self->checkCsrf($req)
++            or return $self->p->sendError( $req, 'csrfError', 400 );
++
+         # Check if unregistration is allowed
+         return $self->p->sendError( $req, 'notAuthorized', 400 )
+           unless $self->userCanRemove;
+--- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/U2F.pm
++++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/U2F.pm
+@@ -202,6 +202,9 @@
+ 
+     elsif ( $action eq 'delete' ) {
+ 
++        $self->checkCsrf($req)
++            or return $self->p->sendError( $req, 'csrfError', 400 );
++
+         # Check if unregistration is allowed
+         return $self->p->sendError( $req, 'notAuthorized', 400 )
+           unless $self->userCanRemove;
+--- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/WebAuthn.pm
++++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Register/WebAuthn.pm
+@@ -79,6 +79,10 @@
+ 
+ sub _registrationchallenge {
+     my ( $self, $req, $user ) = @_;
++
++    $self->checkCsrf($req)
++        or return $self->p->sendError( $req, 'csrfError', 400 );
++
+     my @alldevices       = $self->find2fDevicesByType( $req, $req->userData );
+     my $challenge_base64 = encode_base64url( Crypt::URandom::urandom(32) );
+ 
+@@ -119,6 +123,9 @@
+ sub _registration {
+     my ( $self, $req, $user ) = @_;
+ 
++    $self->checkCsrf($req)
++        or return $self->p->sendError( $req, 'csrfError', 400 );
++
+     # Recover creation parameters, including challenge
+     my $state_id = $req->param('state_id');
+     unless ($state_id) {
+@@ -205,6 +212,9 @@
+ sub _verificationchallenge {
+     my ( $self, $req, $user ) = @_;
+ 
++    $self->checkCsrf($req)
++        or return $self->p->sendError( $req, 'csrfError', 400 );
++
+     $self->logger->debug( $self->prefix . '2f: verification challenge req' );
+ 
+     my $request = $self->generateChallenge( $req, $req->userData );
+@@ -229,6 +239,9 @@
+ sub _verification {
+     my ( $self, $req, $user ) = @_;
+ 
++    $self->checkCsrf($req)
++        or return $self->p->sendError( $req, 'csrfError', 400 );
++
+     my $credential_json = $req->param('credential');
+ 
+     unless ($credential_json) {
+@@ -274,6 +287,9 @@
+ sub _delete {
+     my ( $self, $req, $user ) = @_;
+ 
++    $self->checkCsrf($req)
++        or return $self->p->sendError( $req, 'csrfError', 400 );
++
+     # Check if unregistration is allowed
+     return $self->p->sendError( $req, 'notAuthorized', 400 )
+       unless $self->userCanRemove;
+--- a/lemonldap-ng-portal/site/coffee/2fregistration.coffee
++++ b/lemonldap-ng-portal/site/coffee/2fregistration.coffee
+@@ -41,6 +41,8 @@
+ 			url: "#{portal}2fregisters/#{prefix}/delete"
+ 			data:
+ 				epoch: epoch
++			headers:
++				"X-CSRF-Check": 1
+ 			dataType: 'json'
+ 			error: displayError
+ 			success: (resp) ->
+--- a/lemonldap-ng-portal/site/coffee/generic2fregistration.coffee
++++ b/lemonldap-ng-portal/site/coffee/generic2fregistration.coffee
+@@ -32,6 +32,8 @@
+ 			dataType: 'json'
+ 			data:
+ 				generic: generic
++			headers:
++				"X-CSRF-Check": 1
+ 			error: displayError
+ 			success: (data) ->
+ 				if data.error
+@@ -62,6 +64,8 @@
+ 				genericname: genericname
+ 				genericcode: genericcode
+ 				token: token
++			headers:
++				"X-CSRF-Check": 1
+ 			error: displayError
+ 			success: (data) ->
+ 				if data.error
+--- a/lemonldap-ng-portal/site/coffee/password2fregistration.coffee
++++ b/lemonldap-ng-portal/site/coffee/password2fregistration.coffee
+@@ -33,6 +33,8 @@
+ 			data:
+ 				password: password
+ 				passwordverify: passwordverify
++			headers:
++				"X-CSRF-Check": 1
+ 			error: displayError
+ 			success: (data) ->
+ 				if data.error
+--- a/lemonldap-ng-portal/site/coffee/totpregistration.coffee
++++ b/lemonldap-ng-portal/site/coffee/totpregistration.coffee
+@@ -27,6 +27,8 @@
+ 		type: "POST",
+ 		url: "#{portal}/2fregisters/totp/getkey"
+ 		dataType: 'json'
++		headers:
++			"X-CSRF-Check": 1
+ 		error: displayError
+ 		# Display key and QR code
+ 		success: (data) ->
+@@ -73,6 +75,8 @@
+ 				token: token
+ 				code: val
+ 				TOTPName: $('#TOTPName').val()
++			headers:
++				"X-CSRF-Check": 1
+ 			error: displayError
+ 			success: (data) ->
+ 				if data.error
+--- a/lemonldap-ng-portal/site/coffee/webauthnregistration.coffee
++++ b/lemonldap-ng-portal/site/coffee/webauthnregistration.coffee
+@@ -31,6 +31,8 @@
+ 		url: "#{portal}2fregisters/webauthn/registrationchallenge"
+ 		data: {}
+ 		dataType: 'json'
++		headers:
++			"X-CSRF-Check": 1
+ 		error: displayError
+ 		success: (ch) ->
+ 			# 2 build response
+@@ -47,6 +49,8 @@
+ 						credential: JSON.stringify response
+ 						keyName: $('#keyName').val()
+ 					dataType: 'json'
++					headers:
++						"X-CSRF-Check": 1
+ 					success: (resp) ->
+ 						if resp.error
+ 							if resp.error.match /badName/
+@@ -67,6 +71,8 @@
+ 		url: "#{portal}2fregisters/webauthn/verificationchallenge"
+ 		data: {}
+ 		dataType: 'json'
++		headers:
++			"X-CSRF-Check": 1
+ 		error: displayError
+ 		success: (ch) ->
+ 			# 2 build response
+@@ -81,6 +87,8 @@
+ 						state_id: ch.state_id
+ 						credential: JSON.stringify response
+ 					dataType: 'json'
++					headers:
++						"X-CSRF-Check": 1
+ 					success: (resp) ->
+ 						if resp.error
+ 							setMsg 'webAuthnFailed', 'danger'
+--- a/lemonldap-ng-portal/site/htdocs/static/common/js/2fregistration.js
++++ b/lemonldap-ng-portal/site/htdocs/static/common/js/2fregistration.js
+@@ -54,6 +54,9 @@
+       data: {
+         epoch: epoch
+       },
++      headers: {
++        "X-CSRF-Check": 1
++      },
+       dataType: 'json',
+       error: displayError,
+       success: function(resp) {
+--- a/lemonldap-ng-portal/site/htdocs/static/common/js/generic2fregistration.js
++++ b/lemonldap-ng-portal/site/htdocs/static/common/js/generic2fregistration.js
+@@ -45,6 +45,9 @@
+         data: {
+           generic: generic
+         },
++        headers: {
++          "X-CSRF-Check": 1
++        },
+         error: displayError,
+         success: function(data) {
+           if (data.error) {
+@@ -83,6 +86,9 @@
+           genericcode: genericcode,
+           token: token
+         },
++        headers: {
++          "X-CSRF-Check": 1
++        },
+         error: displayError,
+         success: function(data) {
+           if (data.error) {
+--- a/lemonldap-ng-portal/site/htdocs/static/common/js/password2fregistration.js
++++ b/lemonldap-ng-portal/site/htdocs/static/common/js/password2fregistration.js
+@@ -46,6 +46,9 @@
+           password: password,
+           passwordverify: passwordverify
+         },
++        headers: {
++          "X-CSRF-Check": 1
++        },
+         error: displayError,
+         success: function(data) {
+           if (data.error) {
+--- a/lemonldap-ng-portal/site/htdocs/static/common/js/totpregistration.js
++++ b/lemonldap-ng-portal/site/htdocs/static/common/js/totpregistration.js
+@@ -38,6 +38,9 @@
+       type: "POST",
+       url: portal + "/2fregisters/totp/getkey",
+       dataType: 'json',
++      headers: {
++        "X-CSRF-Check": 1
++      },
+       error: displayError,
+       success: function(data) {
+         var qr, s, secret;
+@@ -91,6 +94,9 @@
+           code: val,
+           TOTPName: $('#TOTPName').val()
+         },
++        headers: {
++          "X-CSRF-Check": 1
++        },
+         error: displayError,
+         success: function(data) {
+           if (data.error) {
+--- a/lemonldap-ng-portal/site/htdocs/static/common/js/webauthnregistration.js
++++ b/lemonldap-ng-portal/site/htdocs/static/common/js/webauthnregistration.js
+@@ -44,6 +44,9 @@
+       url: portal + "2fregisters/webauthn/registrationchallenge",
+       data: {},
+       dataType: 'json',
++      headers: {
++        "X-CSRF-Check": 1
++      },
+       error: displayError,
+       success: function(ch) {
+         var request;
+@@ -60,6 +63,9 @@
+               keyName: $('#keyName').val()
+             },
+             dataType: 'json',
++            headers: {
++              "X-CSRF-Check": 1
++            },
+             success: function(resp) {
+               if (resp.error) {
+                 if (resp.error.match(/badName/)) {
+@@ -91,6 +97,9 @@
+       url: portal + "2fregisters/webauthn/verificationchallenge",
+       data: {},
+       dataType: 'json',
++      headers: {
++        "X-CSRF-Check": 1
++      },
+       error: displayError,
+       success: function(ch) {
+         var request;
+@@ -105,6 +114,9 @@
+               credential: JSON.stringify(response)
+             },
+             dataType: 'json',
++            headers: {
++              "X-CSRF-Check": 1
++            },
+             success: function(resp) {
+               if (resp.error) {
+                 return setMsg('webAuthnFailed', 'danger');
+--- a/lemonldap-ng-portal/site/htdocs/static/languages/ar.json
++++ b/lemonldap-ng-portal/site/htdocs/static/languages/ar.json
+@@ -155,6 +155,7 @@
+ "contextSwitching_ON":"Impersonate another user",
+ "continue":"?????",
+ "createAccount":"???? ????",
++"csrfError":"CSRF check failed",
+ "current":"Current",
+ "currentPwd":"???? ?????? ???????",
+ "date":"?????",
+--- a/lemonldap-ng-portal/site/htdocs/static/languages/de.json
++++ b/lemonldap-ng-portal/site/htdocs/static/languages/de.json
+@@ -155,6 +155,7 @@
+ "contextSwitching_ON":"Impersonate another user",
+ "continue":"Weiter",
+ "createAccount":"Konto erstellen",
++"csrfError":"CSRF check failed",
+ "current":"Current",
+ "currentPwd":"Aktuelles Passwort",
+ "date":"Datum",
+--- a/lemonldap-ng-portal/site/htdocs/static/languages/en.json
++++ b/lemonldap-ng-portal/site/htdocs/static/languages/en.json
+@@ -155,6 +155,7 @@
+ "contextSwitching_ON":"Impersonate another user",
+ "continue":"Continue",
+ "createAccount":"Create an account",
++"csrfError":"CSRF check failed",
+ "current":"Current",
+ "currentPwd":"Current password",
+ "date":"Date",
+--- a/lemonldap-ng-portal/site/htdocs/static/languages/es.json
++++ b/lemonldap-ng-portal/site/htdocs/static/languages/es.json
+@@ -155,6 +155,7 @@
+ "contextSwitching_ON":"Suplantar otro usuario",
+ "continue":"Continuar",
+ "createAccount":"Crear una cuenta",
++"csrfError":"CSRF check failed",
+ "current":"Actual",
+ "currentPwd":"Contrase?a actual",
+ "date":"Fecha",
+--- a/lemonldap-ng-portal/site/htdocs/static/languages/fi.json
++++ b/lemonldap-ng-portal/site/htdocs/static/languages/fi.json
+@@ -155,6 +155,7 @@
+ "contextSwitching_ON":"Esiinny toisena k?ytt?j?n?",
+ "continue":"Jatka",
+ "createAccount":"Luo k?ytt?j?tili",
++"csrfError":"CSRF check failed",
+ "current":"Nykyinen",
+ "currentPwd":"Nykyinen salasana",
+ "date":"P?iv?m??r?",
+--- a/lemonldap-ng-portal/site/htdocs/static/languages/fr.json
++++ b/lemonldap-ng-portal/site/htdocs/static/languages/fr.json
+@@ -155,6 +155,7 @@
+ "contextSwitching_ON":"Endosser l'identit? d'un autre utilisateur",
+ "continue":"Continuer",
+ "createAccount":"Cr?er un compte",
++"csrfError":"CSRF check failed",
+ "current":"Courante",
+ "currentPwd":"Mot de passe actuel",
+ "date":"Date",
+--- a/lemonldap-ng-portal/site/htdocs/static/languages/he.json
++++ b/lemonldap-ng-portal/site/htdocs/static/languages/he.json
+@@ -155,6 +155,7 @@
+ "contextSwitching_ON":"?????? ?????? ???",
+ "continue":"??????",
+ "createAccount":"????? ?????",
++"csrfError":"CSRF check failed",
+ "current":"??????",
+ "currentPwd":"????? ??????",
+ "date":"?????",
+--- a/lemonldap-ng-portal/site/htdocs/static/languages/it.json
++++ b/lemonldap-ng-portal/site/htdocs/static/languages/it.json
+@@ -155,6 +155,7 @@
+ "contextSwitching_ON":"Impersonate another user",
+ "continue":"Procedi",
+ "createAccount":"Crea un account",
++"csrfError":"CSRF check failed",
+ "current":"Current",
+ "currentPwd":"Password attuale",
+ "date":"Data",
+--- a/lemonldap-ng-portal/site/htdocs/static/languages/pl.json
++++ b/lemonldap-ng-portal/site/htdocs/static/languages/pl.json
+@@ -155,6 +155,7 @@
+ "contextSwitching_ON":"Podszyj si? pod innego u?ytkownika",
+ "continue":"Kontynuuj",
+ "createAccount":"Utw?rz konto",
++"csrfError":"CSRF check failed",
+ "current":"Obecny",
+ "currentPwd":"Aktualne has?o",
+ "date":"Data",
+--- a/lemonldap-ng-portal/site/htdocs/static/languages/pt.json
++++ b/lemonldap-ng-portal/site/htdocs/static/languages/pt.json
+@@ -155,6 +155,7 @@
+ "contextSwitching_ON":"Personifique outro usu?rio",
+ "continue":"Continue",
+ "createAccount":"Criar uma conta",
++"csrfError":"CSRF check failed",
+ "current":"Atual",
+ "currentPwd":"Senha atual",
+ "date":"Data",
+--- a/lemonldap-ng-portal/site/htdocs/static/languages/pt_BR.json
++++ b/lemonldap-ng-portal/site/htdocs/static/languages/pt_BR.json
+@@ -155,6 +155,7 @@
+ "contextSwitching_ON":"Personifique outro usu?rio",
+ "continue":"Continue",
+ "createAccount":"Criar uma conta",
++"csrfError":"CSRF check failed",
+ "current":"Atual",
+ "currentPwd":"Senha atual",
+ "date":"Data",
+--- a/lemonldap-ng-portal/site/htdocs/static/languages/tr.json
++++ b/lemonldap-ng-portal/site/htdocs/static/languages/tr.json
+@@ -155,6 +155,7 @@
+ "contextSwitching_ON":"Ba?ka bir kullan?c? gibi g?r?n",
+ "continue":"Devam Et",
+ "createAccount":"Hesap olu?tur",
++"csrfError":"CSRF check failed",
+ "current":"Ge?erli",
+ "currentPwd":"Mevcut parola",
+ "date":"Tarih",
+--- a/lemonldap-ng-portal/site/htdocs/static/languages/vi.json
++++ b/lemonldap-ng-portal/site/htdocs/static/languages/vi.json
+@@ -155,6 +155,7 @@
+ "contextSwitching_ON":"M?o danh ng??i d?ng kh?c",
+ "continue":"Ti?p t?c",
+ "createAccount":"T?o m?t t?i kho?n",
++"csrfError":"CSRF check failed",
+ "current":"Hi?n t?i",
+ "currentPwd":"M?t kh?u hi?n t?i",
+ "date":"Ng?y",
+--- a/lemonldap-ng-portal/site/htdocs/static/languages/zh.json
++++ b/lemonldap-ng-portal/site/htdocs/static/languages/zh.json
+@@ -155,6 +155,7 @@
+ "contextSwitching_ON":"???????",
+ "continue":"??",
+ "createAccount":"????",
++"csrfError":"CSRF check failed",
+ "current":"??",
+ "currentPwd":"????",
+ "date":"??",
+--- a/lemonldap-ng-portal/site/htdocs/static/languages/zh_TW.json
++++ b/lemonldap-ng-portal/site/htdocs/static/languages/zh_TW.json
+@@ -155,6 +155,7 @@
+ "contextSwitching_ON":"???????",
+ "continue":"??",
+ "createAccount":"????",
++"csrfError":"CSRF check failed",
+ "current":"??",
+ "currentPwd":"?????",
+ "date":"??",
+--- a/lemonldap-ng-portal/t/01-WebAuthn-Registration.t
++++ b/lemonldap-ng-portal/t/01-WebAuthn-Registration.t
+@@ -129,6 +129,9 @@
+                 IO::String->new('{}'),
+                 cookie => "lemonldap=$id",
+                 length => 2,
++                custom => {
++                    HTTP_X_CSRF_CHECK => 1,
++                },
+             ),
+             'Registration challenge'
+         );
+@@ -168,6 +171,9 @@
+                 IO::String->new($registration_response),
+                 cookie => "lemonldap=$id",
+                 length => length($registration_response),
++                custom => {
++                    HTTP_X_CSRF_CHECK => 1,
++                },
+             ),
+             'Registration challenge'
+         );
+@@ -198,6 +204,9 @@
+                 IO::String->new('{}'),
+                 cookie => "lemonldap=$id",
+                 length => 2,
++                custom => {
++                    HTTP_X_CSRF_CHECK => 1,
++                },
+             ),
+             'Registration challenge'
+         );
+@@ -232,6 +241,9 @@
+                 IO::String->new($verification_response),
+                 cookie => "lemonldap=$id",
+                 length => length($verification_response),
++                custom => {
++                    HTTP_X_CSRF_CHECK => 1,
++                },
+             ),
+             'Registration challenge'
+         );
+@@ -334,12 +346,29 @@
+         ok( $epoch, "Found epoch for $name" );
+ 
+         my $delete_query = buildForm( { epoch => $epoch } );
++        {
++            my $delete_query = buildForm( { epoch => $epoch } );
++            $res = $client->_post(
++                '/2fregisters/webauthn/delete',
++                $delete_query,
++                length => length($delete_query),
++                cookie => "lemonldap=$id",
++            );
++            my $json = expectBadRequest($res);
++            ok(
++                $res->[2]->[0] =~ 'csrfError',
++                "Deletion expects valid CSRF token"
++            );
++        }
+         ok(
+             $res = $client->_post(
+                 '/2fregisters/webauthn/delete',
+                 $delete_query,
+                 length => length($delete_query),
+                 cookie => "lemonldap=$id",
++                custom => {
++                    HTTP_X_CSRF_CHECK => 1,
++                },
+             ),
+             'Delete WebAuthn query'
+         );
+--- a/lemonldap-ng-portal/t/35-REST-sessions-with-AuthBasic-handler-with-2FA.t
++++ b/lemonldap-ng-portal/t/35-REST-sessions-with-AuthBasic-handler-with-2FA.t
+@@ -84,9 +84,13 @@
+         # JS query
+         ok(
+             $res = $p->_post(
+-                '/2fregisters/totp/getkey', IO::String->new(''),
++                '/2fregisters/totp/getkey',
++                IO::String->new(''),
+                 cookie => "lemonldap=$id",
+                 length => 0,
++                custom => {
++                    HTTP_X_CSRF_CHECK => 1,
++                },
+             ),
+             'Get new key'
+         );
+@@ -111,6 +115,9 @@
+                 IO::String->new($s),
+                 length => length($s),
+                 cookie => "lemonldap=$id",
++                custom => {
++                    HTTP_X_CSRF_CHECK => 1,
++                },
+             ),
+             'Post code'
+         );
+@@ -118,22 +125,22 @@
+         ok( not($@), 'Content is JSON' )
+           or explain( $res->[2]->[0], 'JSON content' );
+         ok( $res->{result} == 1, 'Key is registered' );
+-    ok( $res = $p->_get( '/', accept => 'text/html' ), 'Get Menu', );
+-    ( $host, $url, $query ) =
+-      expectForm( $res, '#', undef, 'user', 'password' );
+-
+-    $query =~ s/user=/user=dwho/;
+-    $query =~ s/password=/password=dwho/;
+-    ok(
+-        $res = $p->_post(
+-            '/',
+-            IO::String->new($query),
+-            length => length($query),
+-            accept => 'text/html',
+-        ),
+-        'Auth query'
+-    );
+-    ( $host, $url, $query ) = expectForm( $res, undef, '/totp2fcheck' );
++        ok( $res = $p->_get( '/', accept => 'text/html' ), 'Get Menu', );
++        ( $host, $url, $query ) =
++          expectForm( $res, '#', undef, 'user', 'password' );
++
++        $query =~ s/user=/user=dwho/;
++        $query =~ s/password=/password=dwho/;
++        ok(
++            $res = $p->_post(
++                '/',
++                IO::String->new($query),
++                length => length($query),
++                accept => 'text/html',
++            ),
++            'Auth query'
++        );
++        ( $host, $url, $query ) = expectForm( $res, undef, '/totp2fcheck' );
+ 
+         ok(
+             $res = handler(
+@@ -166,12 +173,11 @@
+             ),
+             'AuthBasic request'
+         );
+-        ok( $res->[0] == 401, "Authentication rejected");
++        ok( $res->[0] == 401, "Authentication rejected" );
+     }
+     ok( $subtest == 1, 'REST requests were done by handler' );
+ 
+-
+-    $subtest=0;
++    $subtest = 0;
+     foreach my $user (qw(dwho)) {
+         ok(
+             $res = handler(
+@@ -204,8 +210,8 @@
+             ),
+             'New AuthBasic request'
+         );
+-        ok( $subtest == 1, 'Handler used its local cache' );
+-        ok( $res->[0] == 401, 'Authentication rejected a second time');
++        ok( $subtest == 1,    'Handler used its local cache' );
++        ok( $res->[0] == 401, 'Authentication rejected a second time' );
+     }
+ 
+     foreach my $user (qw(rtyler)) {
+--- a/lemonldap-ng-portal/t/36-Combination-with-TOTP.t
++++ b/lemonldap-ng-portal/t/36-Combination-with-TOTP.t
+@@ -13,8 +13,7 @@
+     }
+     require Lemonldap::NG::Common::TOTP;
+ 
+-    my $client = LLNG::Manager::Test->new(
+-        {
++    my $client = LLNG::Manager::Test->new( {
+             ini => {
+                 logLevel               => 'error',
+                 loginHistoryEnabled    => 0,
+@@ -80,9 +79,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -106,6 +109,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+--- a/lemonldap-ng-portal/t/38-No-persistent-session.t
++++ b/lemonldap-ng-portal/t/38-No-persistent-session.t
+@@ -17,8 +17,7 @@
+     }
+     require Lemonldap::NG::Common::TOTP;
+ 
+-    my $client = LLNG::Manager::Test->new(
+-        {
++    my $client = LLNG::Manager::Test->new( {
+             ini => {
+                 logLevel                 => 'error',
+                 totp2fSelfRegistration   => 1,
+@@ -99,9 +98,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -125,6 +128,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+--- a/lemonldap-ng-portal/t/61-BruteForceProtection-with-Incremental-lockTimes-and-TOTP.t
++++ b/lemonldap-ng-portal/t/61-BruteForceProtection-with-Incremental-lockTimes-and-TOTP.t
+@@ -14,8 +14,7 @@
+     }
+     my $res;
+ 
+-    my $client = LLNG::Manager::Test->new(
+-        {
++    my $client = LLNG::Manager::Test->new( {
+             ini => {
+                 logLevel                             => 'error',
+                 authentication                       => 'Demo',
+@@ -50,9 +49,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -77,6 +80,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+--- a/lemonldap-ng-portal/t/64-StayConnected-with-2F-and-History.t
++++ b/lemonldap-ng-portal/t/64-StayConnected-with-2F-and-History.t
+@@ -159,9 +159,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -187,6 +191,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+--- a/lemonldap-ng-portal/t/67-CheckUser.t
++++ b/lemonldap-ng-portal/t/67-CheckUser.t
+@@ -8,8 +8,7 @@
+ 
+ my $res;
+ 
+-my $client = LLNG::Manager::Test->new(
+-    {
++my $client = LLNG::Manager::Test->new( {
+         ini => {
+             logLevel                  => 'error',
+             authentication            => 'Demo',
+@@ -123,9 +122,13 @@
+ # JS query
+ ok(
+     $res = $client->_post(
+-        '/2fregisters/totp/getkey', IO::String->new(''),
++        '/2fregisters/totp/getkey',
++        IO::String->new(''),
+         cookie => "lemonldap=$id",
+         length => 0,
++        custom => {
++            HTTP_X_CSRF_CHECK => 1,
++        },
+     ),
+     'Get new key'
+ );
+@@ -149,6 +152,9 @@
+         IO::String->new($s),
+         length => length($s),
+         cookie => "lemonldap=$id",
++        custom => {
++            HTTP_X_CSRF_CHECK => 1,
++        },
+     ),
+     'Post code'
+ );
+--- a/lemonldap-ng-portal/t/68-ContextSwitching-with-2F-allowed.t
++++ b/lemonldap-ng-portal/t/68-ContextSwitching-with-2F-allowed.t
+@@ -100,9 +100,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -126,6 +130,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+@@ -369,9 +376,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id2",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -396,6 +407,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id2",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+@@ -431,6 +445,9 @@
+             IO::String->new("epoch=$epoch"),
+             length => 16,
+             cookie => "lemonldap=$id2",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Delete TOTP query'
+     );
+@@ -563,6 +580,9 @@
+             IO::String->new("epoch=$epoch"),
+             length => 16,
+             cookie => "lemonldap=$id2",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Delete TOTP query'
+     );
+--- a/lemonldap-ng-portal/t/68-ContextSwitching-with-2F.t
++++ b/lemonldap-ng-portal/t/68-ContextSwitching-with-2F.t
+@@ -99,9 +99,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -125,6 +129,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+@@ -361,9 +368,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id2",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -380,6 +391,9 @@
+             IO::String->new("epoch=1234567890"),
+             length => 16,
+             cookie => "lemonldap=$id2",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Delete TOTP query'
+     );
+@@ -399,6 +413,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id2",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+@@ -449,6 +466,9 @@
+             IO::String->new("epoch=1234567890"),
+             length => 16,
+             cookie => "lemonldap=$id2",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Delete U2F key query'
+     );
+--- a/lemonldap-ng-portal/t/68-ContextSwitching-with-TOTP-and-Notification.t
++++ b/lemonldap-ng-portal/t/68-ContextSwitching-with-TOTP-and-Notification.t
+@@ -22,8 +22,7 @@
+ ]';
+ close F;
+ 
+-my $client = LLNG::Manager::Test->new(
+-    {
++my $client = LLNG::Manager::Test->new( {
+         ini => {
+             logLevel                        => 'error',
+             authentication                  => 'Demo',
+@@ -63,9 +62,13 @@
+ # JS query
+ ok(
+     $res = $client->_post(
+-        '/2fregisters/totp/getkey', IO::String->new(''),
++        '/2fregisters/totp/getkey',
++        IO::String->new(''),
+         cookie => "lemonldap=$id",
+         length => 0,
++        custom => {
++            HTTP_X_CSRF_CHECK => 1,
++        },
+     ),
+     'Get new key'
+ );
+@@ -90,6 +93,9 @@
+         IO::String->new($s),
+         length => length($s),
+         cookie => "lemonldap=$id",
++        custom => {
++            HTTP_X_CSRF_CHECK => 1,
++        },
+     ),
+     'Post code'
+ );
+@@ -144,9 +150,13 @@
+ # JS query
+ ok(
+     $res = $client->_post(
+-        '/2fregisters/totp/getkey', IO::String->new(''),
++        '/2fregisters/totp/getkey',
++        IO::String->new(''),
+         cookie => "lemonldap=$id",
+         length => 0,
++        custom => {
++            HTTP_X_CSRF_CHECK => 1,
++        },
+     ),
+     'Get new key'
+ );
+@@ -170,6 +180,9 @@
+         IO::String->new($s),
+         length => length($s),
+         cookie => "lemonldap=$id",
++        custom => {
++            HTTP_X_CSRF_CHECK => 1,
++        },
+     ),
+     'Post code'
+ );
+--- a/lemonldap-ng-portal/t/68-Impersonation-with-2F.t
++++ b/lemonldap-ng-portal/t/68-Impersonation-with-2F.t
+@@ -80,9 +80,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -108,6 +112,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+@@ -303,9 +310,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -322,6 +333,9 @@
+             IO::String->new("epoch=1234567890"),
+             length => 16,
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Delete TOTP query'
+     );
+@@ -341,6 +355,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+@@ -391,6 +408,9 @@
+             IO::String->new("epoch=1234567890"),
+             length => 16,
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Delete U2F key query'
+     );
+--- a/lemonldap-ng-portal/t/68-Impersonation-with-TOTP.t
++++ b/lemonldap-ng-portal/t/68-Impersonation-with-TOTP.t
+@@ -106,9 +106,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -133,6 +137,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+--- a/lemonldap-ng-portal/t/70-2F-Password.t
++++ b/lemonldap-ng-portal/t/70-2F-Password.t
+@@ -5,8 +5,7 @@
+ 
+ require 't/test-lib.pm';
+ 
+-my $client = LLNG::Manager::Test->new(
+-    {
++my $client = LLNG::Manager::Test->new( {
+         ini => {
+             logLevel                   => 'error',
+             password2fSelfRegistration => 1,
+@@ -72,6 +71,9 @@
+                 IO::String->new($s),
+                 length => length($s),
+                 cookie => "lemonldap=$id",
++                custom => {
++                    HTTP_X_CSRF_CHECK => 1,
++                },
+             )
+         ),
+         'Post registration (mismatched)'
+@@ -86,6 +88,9 @@
+                 IO::String->new($s),
+                 length => length($s),
+                 cookie => "lemonldap=$id",
++                custom => {
++                    HTTP_X_CSRF_CHECK => 1,
++                },
+             )
+         ),
+         'Post registration (mismatched)'
+@@ -100,6 +105,9 @@
+                 IO::String->new($s),
+                 length => length($s),
+                 cookie => "lemonldap=$id",
++                custom => {
++                    HTTP_X_CSRF_CHECK => 1,
++                },
+             )
+         ),
+         'Post registration (mismatched)'
+--- a/lemonldap-ng-portal/t/70-2F-TOTP-8-with-global-storage.t
++++ b/lemonldap-ng-portal/t/70-2F-TOTP-8-with-global-storage.t
+@@ -17,8 +17,7 @@
+     }
+     require Lemonldap::NG::Common::TOTP;
+ 
+-    my $client = LLNG::Manager::Test->new(
+-        {
++    my $client = LLNG::Manager::Test->new( {
+             ini => {
+                 logLevel               => 'error',
+                 totp2fSelfRegistration => 1,
+@@ -76,9 +75,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -102,6 +105,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+--- a/lemonldap-ng-portal/t/70-2F-TOTP-and-U2F-with-TTL-and-JSON.t
++++ b/lemonldap-ng-portal/t/70-2F-TOTP-and-U2F-with-TTL-and-JSON.t
+@@ -69,9 +69,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -95,6 +99,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+@@ -119,10 +126,14 @@
+     # Ajax registration request
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/u/register', IO::String->new(''),
++            '/2fregisters/u/register',
++            IO::String->new(''),
+             accept => 'application/json',
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get registration challenge'
+     );
+--- a/lemonldap-ng-portal/t/70-2F-TOTP-and-U2F-with-authnLevels-and-UpgradeOnly.t
++++ b/lemonldap-ng-portal/t/70-2F-TOTP-and-U2F-with-authnLevels-and-UpgradeOnly.t
+@@ -62,9 +62,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -87,6 +91,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+--- a/lemonldap-ng-portal/t/70-2F-TOTP-encryption.t
++++ b/lemonldap-ng-portal/t/70-2F-TOTP-encryption.t
+@@ -18,8 +18,7 @@
+     }
+     require Lemonldap::NG::Common::TOTP;
+ 
+-    my $client = LLNG::Manager::Test->new(
+-        {
++    my $client = LLNG::Manager::Test->new( {
+             ini => {
+                 logLevel               => 'error',
+                 totp2fSelfRegistration => 1,
+@@ -77,9 +76,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -103,6 +106,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+--- a/lemonldap-ng-portal/t/70-2F-TOTP-with-History-and-Refresh.t
++++ b/lemonldap-ng-portal/t/70-2F-TOTP-with-History-and-Refresh.t
+@@ -14,8 +14,7 @@
+     }
+     require Lemonldap::NG::Common::TOTP;
+ 
+-    my $client = LLNG::Manager::Test->new(
+-        {
++    my $client = LLNG::Manager::Test->new( {
+             ini => {
+                 logLevel               => 'error',
+                 totp2fSelfRegistration => 1,
+@@ -65,9 +64,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -91,6 +94,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+--- a/lemonldap-ng-portal/t/70-2F-TOTP-with-Range.t
++++ b/lemonldap-ng-portal/t/70-2F-TOTP-with-Range.t
+@@ -19,8 +19,7 @@
+     }
+     require Lemonldap::NG::Common::TOTP;
+ 
+-    my $client = LLNG::Manager::Test->new(
+-        {
++    my $client = LLNG::Manager::Test->new( {
+             ini => {
+                 logLevel               => 'error',
+                 totp2fSelfRegistration => 1,
+@@ -74,9 +73,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -101,6 +104,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+--- a/lemonldap-ng-portal/t/70-2F-TOTP-with-TTL-and-JSON.t
++++ b/lemonldap-ng-portal/t/70-2F-TOTP-with-TTL-and-JSON.t
+@@ -15,8 +15,7 @@
+     }
+     require Lemonldap::NG::Common::TOTP;
+ 
+-    my $client = LLNG::Manager::Test->new(
+-        {
++    my $client = LLNG::Manager::Test->new( {
+             ini => {
+                 logLevel               => 'error',
+                 totp2fSelfRegistration => 1,
+@@ -75,9 +74,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -101,6 +104,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+--- a/lemonldap-ng-portal/t/70-2F-TOTP-with-TTL-and-XML.t
++++ b/lemonldap-ng-portal/t/70-2F-TOTP-with-TTL-and-XML.t
+@@ -19,8 +19,7 @@
+     }
+     require Lemonldap::NG::Common::TOTP;
+ 
+-    my $client = LLNG::Manager::Test->new(
+-        {
++    my $client = LLNG::Manager::Test->new( {
+             ini => {
+                 logLevel                   => 'error',
+                 totp2fSelfRegistration     => 1,
+@@ -78,9 +77,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -104,6 +107,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+--- a/lemonldap-ng-portal/t/70-2F-TOTP-with-TTL.t
++++ b/lemonldap-ng-portal/t/70-2F-TOTP-with-TTL.t
+@@ -13,8 +13,7 @@
+     }
+     require Lemonldap::NG::Common::TOTP;
+ 
+-    my $client = LLNG::Manager::Test->new(
+-        {
++    my $client = LLNG::Manager::Test->new( {
+             ini => {
+                 logLevel               => 'error',
+                 totp2fSelfRegistration => '$uid eq "dwho"',
+@@ -79,9 +78,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -109,6 +112,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+@@ -129,6 +135,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+--- a/lemonldap-ng-portal/t/73-2F-UTOTP-TOTP-and-U2F-with-History.t
++++ b/lemonldap-ng-portal/t/73-2F-UTOTP-TOTP-and-U2F-with-History.t
+@@ -69,9 +69,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -95,6 +99,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+--- a/lemonldap-ng-portal/t/73-2F-UTOTP-TOTP-and-U2F.t
++++ b/lemonldap-ng-portal/t/73-2F-UTOTP-TOTP-and-U2F.t
+@@ -72,9 +72,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -98,6 +102,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+@@ -137,6 +144,9 @@
+             '/2fregisters',
+             cookie => "lemonldap=$id",
+             accept => 'text/html',
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Form registration'
+     );
+--- a/lemonldap-ng-portal/t/73-2F-UTOTP-TOTP-only-with-History.t
++++ b/lemonldap-ng-portal/t/73-2F-UTOTP-TOTP-only-with-History.t
+@@ -62,9 +62,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -88,6 +92,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+--- a/lemonldap-ng-portal/t/73-2F-UTOTP-TOTP-only.t
++++ b/lemonldap-ng-portal/t/73-2F-UTOTP-TOTP-only.t
+@@ -61,9 +61,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -87,6 +91,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+--- a/lemonldap-ng-portal/t/74-2F-Required-Issuer-Timeouts.t
++++ b/lemonldap-ng-portal/t/74-2F-Required-Issuer-Timeouts.t
+@@ -13,8 +13,7 @@
+     }
+     require Lemonldap::NG::Common::TOTP;
+ 
+-    my $client = LLNG::Manager::Test->new(
+-        {
++    my $client = LLNG::Manager::Test->new( {
+             ini => {
+                 logLevel               => 'error',
+                 totp2fSelfRegistration => 1,
+@@ -35,8 +34,7 @@
+     ok(
+         $res = $client->_get(
+             '/cas/login',
+-            query => buildForm(
+-                {
++            query => buildForm( {
+                     service => "http://cas.example.com/",
+                 }
+             ),
+@@ -80,9 +78,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => $pdata,
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -109,6 +111,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => $pdata,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+--- a/lemonldap-ng-portal/t/74-2F-Required.t
++++ b/lemonldap-ng-portal/t/74-2F-Required.t
+@@ -56,9 +56,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => $pdata,
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -82,6 +86,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => $pdata,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+--- a/lemonldap-ng-portal/t/75-2F-Registers.t
++++ b/lemonldap-ng-portal/t/75-2F-Registers.t
+@@ -98,9 +98,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -124,6 +128,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
+@@ -316,6 +323,9 @@
+             IO::String->new("epoch=$1"),
+             length => 16,
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Delete TOTP query'
+     );
+@@ -361,6 +371,9 @@
+             IO::String->new("epoch=$1"),
+             length => 16,
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Delete U2F key query'
+     );
+@@ -477,6 +490,9 @@
+             IO::String->new("epoch=$1"),
+             length => 16,
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Delete U2F key query'
+     );
+--- a/lemonldap-ng-portal/t/77-2F-Extra-Register.t
++++ b/lemonldap-ng-portal/t/77-2F-Extra-Register.t
+@@ -132,6 +132,9 @@
+         length => length $query,
+         cookie => "lemonldap=$id",
+         accept => 'application/json',
++        custom => {
++            HTTP_X_CSRF_CHECK => 1,
++        },
+     );
+ 
+     $res = expectJSON($res);
+@@ -156,6 +159,9 @@
+         length => length $query,
+         cookie => "lemonldap=$id",
+         accept => 'application/json',
++        custom => {
++            HTTP_X_CSRF_CHECK => 1,
++        },
+     );
+ 
+     $res = expectJSON($res);
+@@ -234,6 +240,9 @@
+         length => length $query,
+         cookie => "lemonldap=$id",
+         accept => 'application/json',
++        custom => {
++            HTTP_X_CSRF_CHECK => 1,
++        },
+     );
+ 
+     $res = expectJSON($res);
+@@ -258,6 +267,9 @@
+         length => length $query,
+         cookie => "lemonldap=$id",
+         accept => 'application/json',
++        custom => {
++            HTTP_X_CSRF_CHECK => 1,
++        },
+     );
+     expectReject( $res, 400, 'PE96' );
+     ok( !getPSession('dwho')->data->{_2fDevices},
+@@ -294,6 +306,9 @@
+         length => length $query,
+         cookie => "lemonldap=$id",
+         accept => 'application/json',
++        custom => {
++            HTTP_X_CSRF_CHECK => 1,
++        },
+     );
+ 
+     $res = expectJSON($res);
+@@ -316,6 +331,9 @@
+         length => length $query,
+         cookie => "lemonldap=$id",
+         accept => 'application/json',
++        custom => {
++            HTTP_X_CSRF_CHECK => 1,
++        },
+     );
+ 
+     $res = expectJSON($res);
+@@ -374,13 +392,35 @@
+     ok( $epoch,  "Found epoch on delete button" );
+     ok( $prefix, "Found prefix on delete button" );
+ 
+-    $query = buildForm( { epoch => $epoch, } );
++    {
++        my $delete_query = buildForm( { epoch => $epoch } );
++        $res = $client->_post(
++            "/2fregisters/$prefix/delete",
++            $delete_query,
++            length => length($delete_query),
++            cookie => "lemonldap=$id",
++        );
++        my $json = expectBadRequest($res);
++        ok( $res->[2]->[0] =~ 'csrfError',
++            "Deletion expects valid CSRF token" );
++    }
++
++    $res = $client->_get(
++        '/2fregisters',
++        cookie => "lemonldap=$id",
++        accept => "test/html",
++    );
++
++    $query = buildForm( { epoch => $epoch } );
+     ok(
+         $res = $client->_post(
+             "/2fregisters/$prefix/delete",
+             IO::String->new($query),
+             cookie => "lemonldap=$id",
+             length => length($query),
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post deletion'
+     );
+--- a/lemonldap-ng-portal/t/78-2F-UpgradeOnly-with-forceFlag.t
++++ b/lemonldap-ng-portal/t/78-2F-UpgradeOnly-with-forceFlag.t
+@@ -14,8 +14,7 @@
+     }
+     require Lemonldap::NG::Common::TOTP;
+ 
+-    my $client = LLNG::Manager::Test->new(
+-        {
++    my $client = LLNG::Manager::Test->new( {
+             ini => {
+                 logLevel               => 'error',
+                 checkUser              => 1,
+@@ -82,9 +81,13 @@
+     # JS query
+     ok(
+         $res = $client->_post(
+-            '/2fregisters/totp/getkey', IO::String->new(''),
++            '/2fregisters/totp/getkey',
++            IO::String->new(''),
+             cookie => "lemonldap=$id",
+             length => 0,
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Get new key'
+     );
+@@ -112,6 +115,9 @@
+             IO::String->new($s),
+             length => length($s),
+             cookie => "lemonldap=$id",
++            custom => {
++                HTTP_X_CSRF_CHECK => 1,
++            },
+         ),
+         'Post code'
+     );
diff --git a/debian/patches/series b/debian/patches/series
index 3f59f7886..7d48760f3 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -13,3 +13,4 @@ SSRF-issue.patch
 CVE-2024-48933.patch
 fix-auth-level-escalation.patch
 fix-xss-in-upgrade-plugin.patch
+CVE-2024-52948.patch


More information about the pkg-perl-maintainers mailing list