diff --git a/lib/Mojolicious/Controller.pm b/lib/Mojolicious/Controller.pm index 1b92f5ea69..e385a2477e 100644 --- a/lib/Mojolicious/Controller.pm +++ b/lib/Mojolicious/Controller.pm @@ -59,6 +59,10 @@ sub encrypted_cookie { my $app = $self->app; my $secret = $app->secrets->[0]; my $moniker = $app->moniker; + + Carp::croak 'Your secret passphrase must be changed to set encrypted cookies (see FAQ for more)' + if !$ENV{MOJO_ALLOW_INSECURE_SECRET} and (!length $secret or $secret eq $moniker); + return $self->cookie($name, Mojo::Util::encrypt_cookie($value, $secret, $moniker), $options); } @@ -242,12 +246,24 @@ sub session { my $stash = $self->stash; $self->app->sessions->load($self) unless exists $stash->{'mojo.active_session'}; - # Hash my $session = $stash->{'mojo.session'} //= {}; + + # Require changed secret if session data will be set + my $set_operation = !!(@_ > 1 || ref $_[0]); + if (!$ENV{MOJO_ALLOW_INSECURE_SECRET} and (keys %$session or $set_operation)) { + my $app = $self->app; + my $secret = $app->secrets->[0]; + my $moniker = $app->moniker; + + Carp::croak 'Your secret passphrase must be changed to set session data (see FAQ for more)' + if !length $secret or $secret eq $moniker; + } + + # Hash return $session unless @_; # Get - return $session->{$_[0]} unless @_ > 1 || ref $_[0]; + return $session->{$_[0]} unless $set_operation; # Set my $values = ref $_[0] ? $_[0] : {@_}; @@ -262,8 +278,15 @@ sub signed_cookie { # Request cookie return $self->every_signed_cookie($name)->[-1] unless defined $value; + my $app = $self->app; + my $secret = $app->secrets->[0]; + my $moniker = $app->moniker; + + Carp::croak 'Your secret passphrase must be changed to set signed cookies (see FAQ for more)' + if !$ENV{MOJO_ALLOW_INSECURE_SECRET} and (!length $secret or $secret eq $moniker); + # Response cookie - my $sum = Digest::SHA::hmac_sha256_hex("$name=$value", $self->app->secrets->[0]); + my $sum = Digest::SHA::hmac_sha256_hex("$name=$value", $secret); return $self->cookie($name, "$value--$sum", $options); } @@ -443,6 +466,9 @@ the same name, and you want to access more than just the last one, you can use L are encrypted with ChaCha20-Poly1305, to prevent tampering, and the ones failing decryption will be automatically discarded. Note that this method is B and might change without warning! +The L B be changed from the default to a secure value to set +encrypted cookies. This requirement can be bypassed by setting the C environment variable. + =head2 every_cookie my $values = $c->every_cookie('foo'); @@ -745,6 +771,10 @@ Persistent data storage for the next few requests, all session data gets seriali Base64 encoded in HMAC-SHA256 signed cookies, to prevent tampering. Note that cookies usually have a C<4096> byte (4KiB) limit, depending on browser. +The L B be changed from the default to a secure value to set +session data in a signed or encrypted cookie. This requirement can be bypassed by setting the +C environment variable. + # Manipulate session $c->session->{foo} = 'bar'; my $foo = $c->session->{foo}; @@ -770,6 +800,9 @@ same name, and you want to access more than just the last one, you can use L B be changed from the default to a secure value to set +signed cookies. This requirement can be bypassed by setting the C environment variable. + =head2 stash my $hash = $c->stash; diff --git a/t/mojolicious/app.t b/t/mojolicious/app.t index eeabc1f31c..a7ed8e4d19 100644 --- a/t/mojolicious/app.t +++ b/t/mojolicious/app.t @@ -17,6 +17,7 @@ use Mojo::Date; use Mojo::File qw(path); use Mojo::Home; use Mojo::IOLoop; +use Mojo::Util qw(generate_secret); use Mojolicious; use Mojolicious::Controller; @@ -658,6 +659,7 @@ subtest 'Override deployment plugins' => sub { }; $t = Test::Mojo->new('MojoliciousTest'); +$t->app->secrets([generate_secret]); # MojoliciousTestController::Foo::plugin_upper_case $t->get_ok('/plugin/upper_case') diff --git a/t/mojolicious/embedded_lite_app.t b/t/mojolicious/embedded_lite_app.t index 3451499313..ee5d7aac49 100644 --- a/t/mojolicious/embedded_lite_app.t +++ b/t/mojolicious/embedded_lite_app.t @@ -15,6 +15,9 @@ use Test::Mojo; package TestApp; use Mojolicious::Lite; +use Mojo::Util qw(generate_secret); + +app->secrets([generate_secret]); get '/hello' => sub { my $c = shift; diff --git a/t/mojolicious/longpolling_lite_app.t b/t/mojolicious/longpolling_lite_app.t index a05573b0d7..ade04b6a5f 100644 --- a/t/mojolicious/longpolling_lite_app.t +++ b/t/mojolicious/longpolling_lite_app.t @@ -5,6 +5,7 @@ BEGIN { $ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll' } use Test::Mojo; use Test::More; use Mojo::IOLoop; +use Mojo::Util qw(generate_secret); use Mojolicious::Lite; package MyTestApp::Controller; @@ -14,6 +15,8 @@ sub DESTROY { shift->stash->{destroyed} = 1 } package main; +app->secrets([generate_secret]); + app->controller_class('MyTestApp::Controller'); get '/write' => sub { diff --git a/t/mojolicious/rebased_lite_app.t b/t/mojolicious/rebased_lite_app.t index 008b7e3535..1bf1240c00 100644 --- a/t/mojolicious/rebased_lite_app.t +++ b/t/mojolicious/rebased_lite_app.t @@ -5,8 +5,11 @@ BEGIN { $ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll' } use Test::Mojo; use Test::More; use Mojo::URL; +use Mojo::Util qw(generate_secret); use Mojolicious::Lite; +app->secrets([generate_secret]); + # Rebase hook app->hook( before_dispatch => sub { diff --git a/t/mojolicious/session_lite_app.t b/t/mojolicious/session_lite_app.t index 5d329b07d2..d3a07cff08 100644 --- a/t/mojolicious/session_lite_app.t +++ b/t/mojolicious/session_lite_app.t @@ -91,4 +91,21 @@ subtest 'Rotating secrets' => sub { }; }; +subtest 'Insecure secret' => sub { + subtest 'Insecure secret (signed cookie)' => sub { + $t->reset_session; + $t->app->secrets([app->moniker]); + $t->app->sessions->encrypted(0); + $t->get_ok('/login')->status_is(500); + }; + + subtest 'Insecure secret (encrypted cookie)' => sub { + plan skip_all => 'CryptX required!' unless Mojo::Util->CRYPTX; + $t->reset_session; + $t->app->secrets([app->moniker]); + $t->app->sessions->encrypted(1); + $t->get_ok('/login')->status_is(500); + }; +}; + done_testing(); diff --git a/t/mojolicious/validation_lite_app.t b/t/mojolicious/validation_lite_app.t index fdf08e489b..27dd679b57 100644 --- a/t/mojolicious/validation_lite_app.t +++ b/t/mojolicious/validation_lite_app.t @@ -6,8 +6,11 @@ use Test::Mojo; use Test::More; use Mojo::Asset::Memory; use Mojo::Upload; +use Mojo::Util qw(generate_secret); use Mojolicious::Lite; +app->secrets([generate_secret]); + # Custom check app->validator->add_check(two => sub { length $_[2] == 2 ? undef : "e:$_[1]" });