On Sun, Mar 12, 2006 at 01:07:11PM -0500, Joey Hess wrote: Hello, A patch is attached which should fix several holes. This patch was created against 4.07. Please read the following comments.
> Package: libcgi-session-perl
> Version: 4.03-1
> Severity: grave
> Tags: security
>
> Hole 1:
>
> CGI::Session with its default Driver::File contains this insecure code:
>
> $self->{Directory} ||= File::Spec->tmpdir();
>
> my $directory = $self->{Directory};
> my $path = File::Spec->catfile($directory, $file);
>
> unless ( sysopen(FH, $path, O_RDONLY) ) {
>
> File::Spec->tmpdir default to /tmp, and this is where CGI::Session
> defaults to storing its temporary files. Therefore, it writes files to
> /tmp without the O_EXCL flag set when opening them.
>
> This makes it vulnerable to symlink attacks when used with these default
> settings, provided that you can guess what session id will be generated
> before time. Of course session ids are supposed to be hard to guess,
> that's the point of them, but a failure mode that includes overwriting
> arbitrary files is a bit worse than would be expected. I suggest adding
> | O_EXCL to close this hole.
Should be fixed.
> Hole 2:
>
> Notice that the sysopen above does not include permissions. So with a
> standard umask, the session file defaults to world readable, and anyone
> on the machine can get access to all the session keys and whatever is
> secured via them, and whatever else is stored in the session files. I
> can see no reason not to pass a mode of 0400 to the sysopen above.
>
> Setting umask(077) before creating the session object will work around
> this problem. I know that perl's philisophy is to respect the umask by
> default, but I think that in this case a secure default is more
> important.
I agree. Should be fixed.
> Hole 3:
>
> Driver::db_file has documentation that falsely claims to write to
> /tmp/cgisessions.db by default, when in fact it seems to default to
> writing to ./cgisess.db. If your cgi script is ~/public_html/my.cgi,
> then it will unexpectedly write to ~/public_html/cgisess.db, which will
> be publically accessible, since it writes the file mode 644. Remote
> users can then download the file and take over other's sessions.
I changed the default behaviour to respect the documentation, and fixed
the umask.
> Hole 4:
>
> If you write the file somewhere else, then it's still mode
> 644, so local users can read all the session data from it.
>
> Setting umask(077) before creating the session object is a way to
> work around the permissions issue.
Fixed, too.
> Hole 5:
>
> If you use Driver::db_file and specify a FileName for the file (like the
> example does in the man page), you can get it to write to /tmp, which is
> again subject to symlink attacks.
>
> There's an attack possible the first time the file is created, but a
> better attack is against the .lck file used for locking, which is
> written to the same directory, and which is opened without O_EXCL.
>
> Just start creating a symlink /tmp/cgisessions.db.lck -> /some/file in a
> loop, hit the CGI script that uses CGI::Sessions a few times until you win
> the race, and voila, /some/file has been zeroed.
> /var/log/apache2/access.log might be a good candidate for /some/file..
Should be fixed also.
> Hole 6:
>
> Driver::sqlite writes to /tmp/sessions.sqlt by default. I have not
> checked to see if DBI->connect opens the file with O_EXCL, but I doubt
> it, so again we have symlink attacks.
>
> Hole 7:
>
> Also we again have the situation where an attacker can create the file
> in /tmp full of malicious data that exploits any holes in squlite,
> and wait for someone to use Driver::db_file for the first time. I have
> not checked to see if it creates world readable files too, but I'm
> guessing it does.
Don't know how to handle this right now, and not sure this is
CGI::Session bug, maybe a DBI one.
Cheers,
--
Julien Danjou
.''`. Debian Developer
: :' : http://julien.danjou.info
`. `' http://people.debian.org/~acid
`- 9A0D 5FD9 EB42 22F6 8974 C95C A462 B51E C2FE E5CD
diff -ur CGI-Session-4.07.orig/lib/CGI/Session/Driver/db_file.pm
CGI-Session-4.07/lib/CGI/Session/Driver/db_file.pm
--- CGI-Session-4.07.orig/lib/CGI/Session/Driver/db_file.pm 2006-03-09
13:02:19.000000000 +0100
+++ CGI-Session-4.07/lib/CGI/Session/Driver/db_file.pm 2006-03-15
12:06:15.000000000 +0100
@@ -21,7 +21,7 @@
$self->{FileName} ||= $CGI::Session::Driver::db_file::FILE_NAME;
unless ( $self->{Directory} ) {
- $self->{Directory} = dirname( $self->{FileName} );
+ $self->{Directory} = File::Spec->tmpdir(); );
$self->{FileName} = basename( $self->{FileName} );
}
unless ( -d $self->{Directory} ) {
@@ -37,7 +37,7 @@
my ($sid) = @_;
croak "retrieve(): usage error" unless $sid;
- my ($dbhash, $unlock) = $self->_tie_db_file(O_RDONLY) or return;
+ my ($dbhash, $unlock) = $self->_tie_db_file(O_RDONLY|O_EXCL) or return;
my $datastr = $dbhash->{$sid};
untie(%$dbhash);
$unlock->();
@@ -50,7 +50,7 @@
my ($sid, $datastr) = @_;
croak "store(): usage error" unless $sid && $datastr;
- my ($dbhash, $unlock) = $self->_tie_db_file(O_RDWR|O_CREAT, LOCK_EX) or
return;
+ my ($dbhash, $unlock) = $self->_tie_db_file(O_RDWR|O_CREAT|O_EXCL,
LOCK_EX) or return;
$dbhash->{$sid} = $datastr;
untie(%$dbhash);
$unlock->();
@@ -64,7 +64,7 @@
my ($sid) = @_;
croak "remove(): usage error" unless $sid;
- my ($dbhash, $unlock) = $self->_tie_db_file(O_RDWR, LOCK_EX) or return;
+ my ($dbhash, $unlock) = $self->_tie_db_file(O_RDWR|O_EXCL, LOCK_EX) or
return;
delete $dbhash->{$sid};
untie(%$dbhash);
$unlock->();
@@ -83,7 +83,7 @@
$lock_type ||= LOCK_SH;
my $lock_file = $db_file . '.lck';
- sysopen(LOCKFH, $lock_file, O_RDWR|O_CREAT) or die "couldn't create lock
file '$lock_file': $!";
+ sysopen(LOCKFH, $lock_file, O_RDWR|O_CREAT|O_EXCL) or die "couldn't create
lock file '$lock_file': $!";
flock(LOCKFH, $lock_type) or die "couldn't lock
'$lock_file': $!";
return sub {
close(LOCKFH) && unlink($lock_file);
@@ -101,7 +101,7 @@
my $db_file = File::Spec->catfile( $self->{Directory},
$self->{FileName} );
my $unlock = $self->_lock($db_file, $lock_type);
my %db;
- unless( tie %db, "DB_File", $db_file, $o_mode, 0666 ){
+ unless( tie %db, "DB_File", $db_file, $o_mode, 0600 ){
$unlock->();
return $self->set_error("_tie_db_file(): couldn't tie '$db_file': $!");
}
@@ -118,7 +118,7 @@
croak "traverse(): usage error";
}
- my ($dbhash, $unlock) = $self->_tie_db_file(O_RDWR, LOCK_SH);
+ my ($dbhash, $unlock) = $self->_tie_db_file(O_RDWR|O_EXCL, LOCK_SH);
unless ( $dbhash ) {
return $self->set_error( "traverse(): couldn't get db handle, " .
$self->errstr );
}
diff -ur CGI-Session-4.07.orig/lib/CGI/Session/Driver/file.pm
CGI-Session-4.07/lib/CGI/Session/Driver/file.pm
--- CGI-Session-4.07.orig/lib/CGI/Session/Driver/file.pm 2006-03-09
13:02:19.000000000 +0100
+++ CGI-Session-4.07/lib/CGI/Session/Driver/file.pm 2006-03-15
11:54:22.000000000 +0100
@@ -52,7 +52,8 @@
# make certain our filehandle goes away when we fall out of scope
local *FH;
- sysopen(FH, $path, O_RDONLY) || return $self->set_error( "retrieve():
couldn't open '$path': $!" );
+ umask(077);
+ sysopen(FH, $path, O_RDONLY | O_EXCL) || return $self->set_error(
"retrieve(): couldn't open '$path': $!" );
$self->{NoFlock} || flock(FH, LOCK_SH) or return $self->set_error(
"retrieve(): couldn't lock '$path': $!" );
my $rv = "";
signature.asc
Description: Digital signature

