A couple of questions about CGI::Session :
-
why is only the first part of the MD5 sum used in
the session_id ? -
why does CGI::Session::FileStore write the session
variables as text, instead of using Marshal ?
Tom
A couple of questions about CGI::Session :
why is only the first part of the MD5 sum used in
the session_id ?
why does CGI::Session::FileStore write the session
variables as text, instead of using Marshal ?
Tom
I add my own question:
il Thu, 29 May 2003 08:14:35 +0900, Tom Danielsen tom@mnemonic.no ha scritto::
A couple of questions about CGI::Session :
why is only the first part of the MD5 sum used in
the session_id ?why does CGI::Session::FileStore write the session
variables as text, instead of using Marshal ?Tom
Hi,
- why is only the first part of the MD5 sum used in
the session_id ?
Not to make file names too long (Perl’s CGI session does this too). I
believe it is secure enough.
- why does CGI::Session::FileStore write the session
variables as text, instead of using Marshal ?
Allow users to choose their own stringify method.
matz.
In message “[Q]: CGI::Session” on 03/05/29, Tom Danielsen tom@mnemonic.no writes:
gabriele renzi wrote:
il Thu, 29 May 2003 08:14:35 +0900, Tom Danielsen tom@mnemonic.no ha
scritto::A couple of questions about CGI::Session :
- why is only the first part of the MD5 sum used in
the session_id ?
someone (a russian security expert) told me that you can safely use a
substring of an MD5 sum in stead of the entire MD5 sum since every bit
in the MD5 sum will change value if the source data changes.
- why does CGI::Session::FileStore write the session
variables as text, instead of using Marshal ?
good question.
Tom
I add my own question:
- when the $TMP/file gets deleted?
AFAIK it doesn’t on my server I have this crontab:
10 * * * * find /tmp -amin +30 -user apache -type f -exec rm -f {} ;
/Anders
matz-
if it were cleaned up a bit, what would you think about including my (or a
better impl) of CGI::Session::Pstore in the standard lib?
i use it all the time for cgi scripts and have found it to be really useful.
-a
On Sat, 31 May 2003, Yukihiro Matsumoto wrote:
Hi,
In message “[Q]: CGI::Session” > on 03/05/29, Tom Danielsen tom@mnemonic.no writes:
- why is only the first part of the MD5 sum used in
the session_id ?Not to make file names too long (Perl’s CGI session does this too). I
believe it is secure enough.
- why does CGI::Session::FileStore write the session
variables as text, instead of using Marshal ?Allow users to choose their own stringify method.
matz.
Ara Howard
NOAA Forecast Systems Laboratory
Information and Technology Services
Data Systems Group
R/FST 325 Broadway
Boulder, CO 80305-3328
Email: ara.t.howard@fsl.noaa.gov
Phone: 303-497-7238
Fax: 303-497-7259
~ > ruby -e ‘p % ^) .intern’
====================================
gabriele renzi wrote:
il Thu, 29 May 2003 08:14:35 +0900, Tom Danielsen tom@mnemonic.no ha
scritto::A couple of questions about CGI::Session :
- why is only the first part of the MD5 sum used in
the session_id ?someone (a russian security expert) told me that you can safely use a
substring of an MD5 sum in stead of the entire MD5 sum since every bit
in the MD5 sum will change value if the source data changes.
As far as I know any 64-bit part of the hash is as good as any
other. But why not use the full 128-bit hash? Performance?
I modified session.rb to use the full hash, and it seems to
work ok. (Better collision resistance).
- why does CGI::Session::FileStore write the session
variables as text, instead of using Marshal ?good question.
Well, I had to try… but I ran into something that I don’t
understand. Perhaps 0330 is not the time my brain is at it’s
best?
I created a MarshaledFileStore class (slightly modified FileStore),
but it won’t work for some mysterious reason :
[Sat May 31 03:26:37 2003] [error] mod_ruby: error in ruby
/usr/local/lib/ruby/1.8/cgi/session.rb:155:in dump': singleton can't be dumped (TypeError) from /usr/local/lib/ruby/1.8/cgi/session.rb:155:in
update’
from /usr/local/lib/ruby/1.8/cgi/session.rb:161:in close' from /usr/local/lib/ruby/1.8/cgi/session.rb:96:in
close’
from /www/htdocs/app1.rbx:45
from /usr/local/lib/ruby/1.8/apache/ruby-run.rb:70:in load' from /usr/local/lib/ruby/1.8/apache/ruby-run.rb:70:in
handler’
This is what the hash looks like (output from my modified session.rb) :
/www% more /tmp/h.h
class of @hash is ‘Hash’
{“name”=>“test”, “visited”=>8}
‘name’ (String) => ‘test’ (String)
‘visited’ (String) => ‘8’ (Fixnum)
/www%
So…singleton ??
And here is the modified session.rb :
regards,
Tom
------------- cut -------------
require ‘cgi’
class CGI
class Session
attr_reader :session_id
def Session::callback(dbman)
lambda{
dbman[0].close unless dbman.empty?
}
end
def Session::create_new_id
require 'digest/md5'
md5 = Digest::MD5::new
md5.update(String(Time::now))
md5.update(String(rand(0)))
md5.update(String($$))
md5.update('foobar')
### TOM md5.hexdigest[0,16]
md5.hexdigest
end
def initialize(request, option={})
session_key = option['session_key'] || '_session_id'
id, = option['session_id']
unless id
if option['new_session']
id = Session::create_new_id
end
end
unless id
id, = request[session_key]
id = id.read if id.respond_to?(:read)
unless id
id, = request.cookies[session_key]
end
unless id
if option.key?('new_session') and not option['new_session']
raise ArgumentError, "session_key `%s' should be supplied"%session_key
end
id = Session::create_new_id
end
end
@session_id = id
dbman = option['database_manager'] || FileStore
@dbman = dbman::new(self, option)
request.instance_eval do
@output_hidden = {session_key => id}
@output_cookies = [
Cookie::new("name" => session_key,
"value" => id,
"expires" => option['session_expires'],
"domain" => option['session_domain'],
"secure" => option['session_secure'],
"path" => if option['session_path'] then
option['session_path']
elsif ENV["SCRIPT_NAME"] then
File::dirname(ENV["SCRIPT_NAME"])
else
""
end)
]
end
@dbprot = [@dbman]
ObjectSpace::define_finalizer(self, Session::callback(@dbprot))
end
def [](key)
unless @data
@data = @dbman.restore
end
@data[key]
end
def []=(key, val)
unless @write_lock
@write_lock = true
end
unless @data
@data = @dbman.restore
end
@data[key] = val
end
def update
@dbman.update
end
def close
@dbman.close
@dbprot.clear
end
def delete
@dbman.delete
@dbprot.clear
end
class MarshaledFileStore
def check_id(id)
/[^0-9a-zA-Z]/ =~ id.to_s ? false : true
end
def initialize(session, option={})
dir = option['tmpdir'] || ENV['TMP'] || '/tmp'
prefix = option['prefix'] || ''
id = session.session_id
unless check_id(id)
raise ArgumentError, "session_id `%s' is invalid" % id
end
path = dir+"/"+prefix+id
path.untaint
unless File::exist? path
@hash = {}
end
begin
@f = open(path, "r+")
rescue Errno::ENOENT
@f = open(path, "w+")
end
end
def restore
unless @hash
@hash = {}
@f.flock File::LOCK_EX
@f.rewind
@hash = Marshal.load(@f)
end
@hash
end
def update
return unless @hash
@f.rewind
File.open("/tmp/h.h","w+") { |f|
f.print "class of @hash is '#{@hash.class.to_s}'\n"
f.print @hash.inspect + "\n"
@hash.each { |k,v|
f.print "'#{k.to_s}' (#{k.class.to_s}) => '#{v.to_s}' (#{v.class.to_s})\n"
}
}
Marshal.dump(@hash, @f)
@f.truncate @f.tell
end
def close
return if @f.closed?
update
@f.close
end
def delete
path = @f.path
@f.close
File::unlink path
end
end
class FileStore
def check_id(id)
/[^0-9a-zA-Z]/ =~ id.to_s ? false : true
end
def initialize(session, option={})
dir = option['tmpdir'] || ENV['TMP'] || '/tmp'
prefix = option['prefix'] || ''
id = session.session_id
unless check_id(id)
raise ArgumentError, "session_id `%s' is invalid" % id
end
path = dir+"/"+prefix+id
path.untaint
unless File::exist? path
@hash = {}
end
begin
@f = open(path, "r+")
rescue Errno::ENOENT
@f = open(path, "w+")
end
end
def restore
unless @hash
@hash = {}
@f.flock File::LOCK_EX
@f.rewind
for line in @f
line.chomp!
k, v = line.split('=',2)
@hash[CGI::unescape(k)] = CGI::unescape(v)
end
end
@hash
end
def update
return unless @hash
@f.rewind
for k,v in @hash
@f.printf "%s=%s\n", CGI::escape(k), CGI::escape(String(v))
end
@f.truncate @f.tell
end
def close
return if @f.closed?
update
@f.close
end
def delete
path = @f.path
@f.close
File::unlink path
end
end
class MemoryStore
GLOBAL_HASH_TABLE = {}
def initialize(session, option=nil)
@session_id = session.session_id
GLOBAL_HASH_TABLE[@session_id] ||= {}
end
def restore
GLOBAL_HASH_TABLE[@session_id]
end
def update
# don't need to update; hash is shared
end
def close
# don't need to close
end
def delete
GLOBAL_HASH_TABLE.delete(@session_id)
end
end
end
end
On 30.05 04:02, Anders Borch wrote:
Hi,
- why is only the first part of the MD5 sum used in
the session_id ?Not to make file names too long (Perl’s CGI session does this too). I
believe it is secure enough.
Ok, I see this point. However - for me performance is not an issue,
it would be nice if this behaviour was configurable. For my use this
is just an unneccesary reduction of security (even if security still
is good).
- why does CGI::Session::FileStore write the session
variables as text, instead of using Marshal ?Allow users to choose their own stringify method.
matz.
matz-
if it were cleaned up a bit, what would you think about including my (or a
better impl) of CGI::Session::Pstore in the standard lib?i use it all the time for cgi scripts and have found it to be really useful.
I do agree, this would be nice to have in the standard distribution.
CGI::Session::Pstore works great! Now if i could just figure out what
went wrong with my code…
Tom
On 31.05 00:52, ahoward wrote:
On Sat, 31 May 2003, Yukihiro Matsumoto wrote:
In message “[Q]: CGI::Session” > > on 03/05/29, Tom Danielsen tom@mnemonic.no writes:
Hi,
In message “Re: [Q]: CGI::Session” on 03/05/31, ahoward ahoward@fsl.noaa.gov writes:
if it were cleaned up a bit, what would you think about including my (or a
better impl) of CGI::Session::Pstore in the standard lib?i use it all the time for cgi scripts and have found it to be really useful.
Sounds nice. Let me see.
matz.
I created a MarshaledFileStore class (slightly modified FileStore),
but it won’t work for some mysterious reason :
i did something similar a while back - i’ve used in many cgi programs and it
works great
http://raa.ruby-lang.org/list.rhtml?name=cgi-sess-pstore
-a
[Sat May 31 03:26:37 2003] [error] mod_ruby: error in ruby
/usr/local/lib/ruby/1.8/cgi/session.rb:155:indump': singleton can't be dumped (TypeError) from /usr/local/lib/ruby/1.8/cgi/session.rb:155:in
update’
from /usr/local/lib/ruby/1.8/cgi/session.rb:161:inclose' from /usr/local/lib/ruby/1.8/cgi/session.rb:96:in
close’
from /www/htdocs/app1.rbx:45
from /usr/local/lib/ruby/1.8/apache/ruby-run.rb:70:inload' from /usr/local/lib/ruby/1.8/apache/ruby-run.rb:70:in
handler’This is what the hash looks like (output from my modified session.rb) :
/www% more /tmp/h.h
class of @hash is ‘Hash’
{“name”=>“test”, “visited”=>8}
‘name’ (String) => ‘test’ (String)
‘visited’ (String) => ‘8’ (Fixnum)
/www%So…singleton ??
And here is the modified session.rb :
regards,
Tom------------- cut -------------
Copyright (C) 2001 Yukihiro “Matz” Matsumoto
Copyright (C) 2000 Network Applied Communication Laboratory, Inc.
Copyright (C) 2000 Information-technology Promotion Agency, Japan
require ‘cgi’
class CGI
class Sessionattr_reader :session_id def Session::callback(dbman) lambda{
dbman[0].close unless dbman.empty?
}
enddef Session::create_new_id require 'digest/md5' md5 = Digest::MD5::new md5.update(String(Time::now)) md5.update(String(rand(0))) md5.update(String($$)) md5.update('foobar') ### TOM md5.hexdigest[0,16] md5.hexdigest end def initialize(request, option={}) session_key = option['session_key'] || '_session_id' id, = option['session_id'] unless id
if option[‘new_session’]
id = Session::create_new_id
end
end
unless id
id, = request[session_key]
id = id.read if id.respond_to?(:read)
unless id
id, = request.cookies[session_key]
end
unless id
if option.key?(‘new_session’) and not option[‘new_session’]
raise ArgumentError, “session_key `%s’ should be supplied”%session_key
end
id = Session::create_new_id
end
end
@session_id = id
dbman = option[‘database_manager’] || FileStore
@dbman = dbman::new(self, option)
request.instance_eval do
@output_hidden = {session_key => id}
@output_cookies = [
Cookie::new(“name” => session_key,
“value” => id,
“expires” => option[‘session_expires’],
“domain” => option[‘session_domain’],
“secure” => option[‘session_secure’],
“path” => if option[‘session_path’] then
option[‘session_path’]
elsif ENV[“SCRIPT_NAME”] then
File::dirname(ENV[“SCRIPT_NAME”])
else
“”
end)
]
end
@dbprot = [@dbman]
ObjectSpace::define_finalizer(self, Session::callback(@dbprot))
enddef [](key) unless @data
@data = @dbman.restore
end
@data[key]
enddef []=(key, val) unless @write_lock
@write_lock = true
end
unless @data
@data = @dbman.restore
end
@data[key] = val
enddef update @dbman.update end def close @dbman.close @dbprot.clear end def delete @dbman.delete @dbprot.clear end class MarshaledFileStore def check_id(id)
/[^0-9a-zA-Z]/ =~ id.to_s ? false : true
enddef initialize(session, option={})
dir = option[‘tmpdir’] || ENV[‘TMP’] || ‘/tmp’
prefix = option[‘prefix’] || ‘’
id = session.session_id
unless check_id(id)
raise ArgumentError, “session_id `%s’ is invalid” % id
end
path = dir+“/”+prefix+id
path.untaint
unless File::exist? path
@hash = {}
end
begin
@f = open(path, “r+”)
rescue Errno::ENOENT
@f = open(path, “w+”)
end
enddef restore
unless @hash
@hash = {}
@f.flock File::LOCK_EX
@f.rewind
@hash = Marshal.load(@f)
end
@hash
enddef update
return unless @hash
@f.rewind
File.open(“/tmp/h.h”,“w+”) { |f|
f.print “class of @hash is ‘#{@hash.class.to_s}’\n”
f.print @hash.inspect + “\n”
@hash.each { |k,v|
f.print “‘#{k.to_s}’ (#{k.class.to_s}) => ‘#{v.to_s}’ (#{v.class.to_s})\n”
}
}
Marshal.dump(@hash, @f)
@f.truncate @f.tell
enddef close
return if @f.closed?
update
@f.close
enddef delete
path = @f.path
@f.close
File::unlink path
end
endclass FileStore def check_id(id)
/[^0-9a-zA-Z]/ =~ id.to_s ? false : true
enddef initialize(session, option={})
dir = option[‘tmpdir’] || ENV[‘TMP’] || ‘/tmp’
prefix = option[‘prefix’] || ‘’
id = session.session_id
unless check_id(id)
raise ArgumentError, “session_id `%s’ is invalid” % id
end
path = dir+“/”+prefix+id
path.untaint
unless File::exist? path
@hash = {}
end
begin
@f = open(path, “r+”)
rescue Errno::ENOENT
@f = open(path, “w+”)
end
enddef restore
unless @hash
@hash = {}
@f.flock File::LOCK_EX
@f.rewind
for line in @f
line.chomp!
k, v = line.split(‘=’,2)
@hash[CGI::unescape(k)] = CGI::unescape(v)
end
end
@hash
enddef update
return unless @hash
@f.rewind
for k,v in @hash
@f.printf “%s=%s\n”, CGI::escape(k), CGI::escape(String(v))
end
@f.truncate @f.tell
enddef close
return if @f.closed?
update
@f.close
enddef delete
path = @f.path
@f.close
File::unlink path
end
endclass MemoryStore GLOBAL_HASH_TABLE = {} def initialize(session, option=nil)
@session_id = session.session_id
GLOBAL_HASH_TABLE[@session_id] ||= {}
enddef restore
GLOBAL_HASH_TABLE[@session_id]
enddef update
don’t need to update; hash is shared
end def close
don’t need to close
end def delete
GLOBAL_HASH_TABLE.delete(@session_id)
end
end
end
end
-a
====================================
Ara Howard
NOAA Forecast Systems Laboratory
Information and Technology Services
Data Systems Group
R/FST 325 Broadway
Boulder, CO 80305-3328
Email: ara.t.howard@fsl.noaa.gov
Phone: 303-497-7238
Fax: 303-497-7259
~ > ruby -e ‘p % ^) .intern’
====================================
If you are using 64 bit hashes, then you need to have about 2^32 concurrent
sessions to get a reasonably high chance of a collision. That’s quite a lot
of sessions
On Fri, May 30, 2003 at 10:38:41AM +0900, Tom Danielsen wrote:
As far as I know any 64-bit part of the hash is as good as any
other. But why not use the full 128-bit hash? Performance?
I modified session.rb to use the full hash, and it seems to
work ok. (Better collision resistance).