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