CVE-2018-25100: Insufficient Information (NVD-CWE-noinfo)
value: The Mojolicious module before 7.66 for Perl may leak cookies in certain situations related to multiple similar cookies for the same domain. This affects Mojo::UserAgent::CookieJar.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-03-24T01:15Z
Repository: https://github.com/mojolicious/mojo
Language: Perl
Files changed: Changes, lib/Mojo/Cookie/Response.pm, lib/Mojo/UserAgent/CookieJar.pm
Commit date: 2018-02-13 17:53:02 +0100
Commit message: fix a security issue in Mojo::UserAgent::CookieJar

Diff: diff --git a/Changes b/Changes
index 44bf9b576..05aedb1ba 100644
--- a/Changes
+++ b/Changes
@@ -1,7 +1,10 @@
 
-7.66  2018-02-12
+7.66  2018-02-13
+  - This release contains fixes for security issues, everybody should upgrade!
+  - Removed origin attribute of Mojo::Cookie::Response.
+  - Added host_only attribute to Mojo::Cookie::Response.
 
 7.65  2018-02-11
   - Added EXPERIMENTAL timing->begin, timing->elapsed, timing->rps and
     timing->server_timing helpers to Mojolicious::Plugin::DefaultHelpers.
   - Added EXPERIMENTAL server_timing method to Mojo::Headers.
diff --git a/lib/Mojo/Cookie/Response.pm b/lib/Mojo/Cookie/Response.pm
index 657062424..aa3a900d1 100644
--- a/lib/Mojo/Cookie/Response.pm
+++ b/lib/Mojo/Cookie/Response.pm
@@ -2,11 +2,11 @@ package Mojo::Cookie::Response;
 use Mojo::Base 'Mojo::Cookie';
 
 use Mojo::Date;
 use Mojo::Util qw(quote split_cookie_header);
 
-has [qw(domain expires httponly max_age origin path secure)];
+has [qw(domain expires host_only httponly max_age path secure)];
 
 my %ATTRS = map { $_ => 1 } qw(domain expires httponly max-age path secure);
 
 sub parse {
   my ($self, $str) = @_;
@@ -98,10 +98,18 @@ Cookie domain.
   my $expires = $cookie->expires;
   $cookie     = $cookie->expires(time + 60);
 
 Expiration for cookie.
 
+=head2 host_only
+
+  my $bool = $cookie->host_only;
+  $cookie  = $cookie->host_only($bool);
+
+Host-only flag, indicating that the canonicalized request-host is identical to
+the cookie's L</"domain">.
+
 =head2 httponly
 
   my $bool = $cookie->httponly;
   $cookie  = $cookie->httponly($bool);
 
@@ -113,17 +121,10 @@ cookie.
   my $max_age = $cookie->max_age;
   $cookie     = $cookie->max_age(60);
 
 Max age for cookie.
 
-=head2 origin
-
-  my $origin = $cookie->origin;
-  $cookie    = $cookie->origin('mojolicious.org');
-
-Origin of the cookie.
-
 =head2 path
 
   my $path = $cookie->path;
   $cookie  = $cookie->path('/test');
 
diff --git a/lib/Mojo/UserAgent/CookieJar.pm b/lib/Mojo/UserAgent/CookieJar.pm
index 1ac47c5e8..fed261830 100644
--- a/lib/Mojo/UserAgent/CookieJar.pm
+++ b/lib/Mojo/UserAgent/CookieJar.pm
@@ -20,16 +20,15 @@ sub add {
 
     # Check cookie size
     next if length($cookie->value // '') > $size;
 
     # Replace cookie
-    my $origin = $cookie->origin // '';
-    next unless my $domain = lc($cookie->domain // $origin);
+    next unless my $domain = lc($cookie->domain // '');
     next unless my $path   = $cookie->path;
     next unless length(my $name = $cookie->name // '');
     my $jar = $self->{jar}{$domain} ||= [];
-    @$jar = (grep({ _compare($_, $path, $name, $origin) } @$jar), $cookie);
+    @$jar = (grep({ _compare($_, $path, $name, $domain) } @$jar), $cookie);
   }
 
   return $self;
 }
 
@@ -43,12 +42,13 @@ sub collect {
 
   my $url = $tx->req->url;
   for my $cookie (@{$tx->res->cookies}) {
 
     # Validate domain
-    my $host   = lc $url->ihost;
-    my $domain = lc($cookie->domain // $cookie->origin($host)->origin);
+    my $host = lc $url->ihost;
+    $cookie->domain($host)->host_only(1) unless $cookie->domain;
+    my $domain = lc $cookie->domain;
     if (my $cb = $self->ignore) { next if $cb->($cookie) }
     next if $host ne $domain && ($host !~ /\Q.$domain\E$/ || $host =~ /\.\d+$/);
 
     # Validate path
     my $path = $cookie->path // $url->path->to_dir->to_abs_string;
@@ -70,11 +70,11 @@ sub find {
     next unless my $old = $self->{jar}{$domain};
 
     # Grab cookies
     my $new = $self->{jar}{$domain} = [];
     for my $cookie (@$old) {
-      next unless $cookie->domain || $host eq $cookie->origin;
+      next if $cookie->host_only && $host ne $cookie->domain;
 
       # Check if cookie has expired
       if (defined(my $expires = $cookie->expires)) { next if time > $expires }
       push @$new, $cookie;
 
@@ -99,13 +99,15 @@ sub prepare {
   my $req = $tx->req;
   $req->cookies(@{$self->find($req->url)});
 }
 
 sub _compare {
-  my ($cookie, $path, $name, $origin) = @_;
-  return 1 if $cookie->path ne $path || $cookie->name ne $name;
-  return ($cookie->origin // '') ne $origin;
+  my ($cookie, $path, $name, $domain) = @_;
+  return
+       $cookie->path ne $path
+    || $cookie->name ne $name
+    || $cookie->domain ne $domain;
 }
 
 sub _path { $_[0] eq '/' || $_[0] eq $_[1] || index($_[1], "$_[0]/") == 0 }
 
 1;

Vulnerable code: 7.66  2018-02-12
has [qw(domain expires httponly max_age origin path secure)];
=head2 origin

  my $origin = $cookie->origin;
  $cookie    = $cookie->origin('mojolicious.org');

Origin of the cookie.

    my $origin = $cookie->origin // '';
    next unless my $domain = lc($cookie->domain // $origin);
    @$jar = (grep({ _compare($_, $path, $name, $origin) } @$jar), $cookie);
    my $host   = lc $url->ihost;
    my $domain = lc($cookie->domain // $cookie->origin($host)->origin);
      next unless $cookie->domain || $host eq $cookie->origin;
  my ($cookie, $path, $name, $origin) = @_;
  return 1 if $cookie->path ne $path || $cookie->name ne $name;
  return ($cookie->origin // '') ne $origin;
Fixed code: 7.66  2018-02-13
  - This release contains fixes for security issues, everybody should upgrade!
  - Removed origin attribute of Mojo::Cookie::Response.
  - Added host_only attribute to Mojo::Cookie::Response.
has [qw(domain expires host_only httponly max_age path secure)];
=head2 host_only

  my $bool = $cookie->host_only;
  $cookie  = $cookie->host_only($bool);

Host-only flag, indicating that the canonicalized request-host is identical to
the cookie's L</"domain">.

    next unless my $domain = lc($cookie->domain // '');
    @$jar = (grep({ _compare($_, $path, $name, $domain) } @$jar), $cookie);
    my $host = lc $url->ihost;
    $cookie->domain($host)->host_only(1) unless $cookie->domain;
    my $domain = lc $cookie->domain;
      next if $cookie->host_only && $host ne $cookie->domain;
  my ($cookie, $path, $name, $domain) = @_;
  return
       $cookie->path ne $path
    || $cookie->name ne $name
    || $cookie->domain ne $domain;