CrackedRuby logo

CrackedRuby

CGI Sessions

Overview

CGI Sessions in Ruby provide server-side session management for web applications through the cgi/session library. Ruby implements sessions as persistent data stores that maintain user state across HTTP requests using session identifiers transmitted via cookies or URL parameters.

The core session functionality centers around the CGI::Session class, which handles session creation, data persistence, and cleanup. Sessions store data in key-value pairs and support multiple storage backends including files, memory, and custom stores.

require 'cgi'
require 'cgi/session'

cgi = CGI.new
session = CGI::Session.new(cgi,
  'database_manager' => CGI::Session::FileStore,
  'session_key' => '_session_id',
  'session_expires' => Time.now + 3600
)

session['user_id'] = 12345
session['username'] = 'alice'
session.close

Ruby's session implementation automatically generates unique session identifiers and manages the association between client requests and server-side data. The session ID gets transmitted to the client via cookies or URL parameters, depending on configuration.

# Reading session data
user_id = session['user_id']
if user_id
  puts "Welcome back, user #{user_id}"
else
  puts "Please log in"
end

Sessions integrate with CGI request processing by examining incoming cookies and URL parameters to locate existing sessions or create new ones. The session manager handles expiration, storage backend selection, and automatic cleanup of expired session data.

Basic Usage

Creating a session requires a CGI object and configuration options specifying the storage backend and session parameters. The most common pattern involves creating the session at the beginning of request processing and closing it when response generation completes.

require 'cgi'
require 'cgi/session'
require 'cgi/session/pstore'

cgi = CGI.new("html4")
session = CGI::Session.new(cgi,
  'database_manager' => CGI::Session::PStore,
  'session_key' => 'ruby_session_id',
  'session_expires' => Time.now + (30 * 60), # 30 minutes
  'prefix' => 'rubysess.'
)

The database_manager parameter determines where session data gets stored. Ruby provides several built-in options: FileStore for file-based storage, PStore for marshal-based persistence, and MemoryStore for in-memory sessions. Each storage backend offers different performance and persistence characteristics.

Sessions behave like hash objects for data access. Store any serializable Ruby object as session values, including strings, numbers, arrays, and custom objects that respond to marshaling.

# Storing various data types
session['user_preferences'] = {
  'theme' => 'dark',
  'language' => 'en',
  'notifications' => true
}

session['shopping_cart'] = ['item1', 'item2', 'item3']
session['last_login'] = Time.now
session['login_attempts'] = 0

Reading session data follows standard hash access patterns. Check for key existence before accessing values to handle cases where session data might not exist or sessions have expired.

# Safe session data access
if session.has_key?('user_id')
  user_id = session['user_id']
  # Load user data and personalize response
else
  # Redirect to login or show anonymous content
end

# Default values for missing session data
theme = session['theme'] || 'light'
cart_items = session['shopping_cart'] || []

Session lifecycle management requires explicit session closure to ensure data persistence. Ruby does not automatically save session changes until session.close gets called, which writes pending changes to the storage backend.

# Complete request processing cycle
begin
  cgi = CGI.new
  session = CGI::Session.new(cgi, session_options)
  
  # Process request and modify session data
  session['page_views'] = (session['page_views'] || 0) + 1
  
  # Generate response content
  puts cgi.header
  puts "<html><body>Page views: #{session['page_views']}</body></html>"
  
ensure
  session.close if session
end

Error Handling & Debugging

Session operations can fail due to storage backend issues, permission problems, or corruption in session data. Ruby raises specific exceptions for different session error conditions, requiring proper exception handling around session operations.

Storage backend failures represent the most common source of session errors. File permission issues, disk space problems, or corrupted session files can prevent session creation or data retrieval.

require 'cgi'
require 'cgi/session'

begin
  cgi = CGI.new
  session = CGI::Session.new(cgi,
    'database_manager' => CGI::Session::FileStore,
    'session_dir' => '/var/sessions'
  )
rescue ArgumentError => e
  # Invalid session configuration
  puts cgi.header('status' => 'SERVER_ERROR')
  puts "Session configuration error: #{e.message}"
rescue Errno::EACCES => e
  # Permission denied accessing session directory
  puts cgi.header('status' => 'SERVER_ERROR')
  puts "Session storage access denied"
rescue StandardError => e
  # Other session creation failures
  puts cgi.header('status' => 'SERVER_ERROR')
  puts "Session initialization failed"
end

Session data corruption can occur when stored objects cannot be unmarshaled or when session files become corrupted. Ruby raises marshal-related exceptions when attempting to load corrupted session data.

# Handling corrupted session data
begin
  user_data = session['complex_object']
rescue ArgumentError, TypeError => e
  # Marshal errors indicate corrupted session data
  Rails.logger.error "Corrupted session data: #{e.message}"
  
  # Clear corrupted session and create fresh one
  session.delete
  session = CGI::Session.new(cgi, session_options)
  
  # Set flag to prompt user for re-authentication
  session['force_login'] = true
end

Session expiration handling requires checking session validity before accessing stored data. Ruby does not automatically clean up expired sessions, so applications must implement expiration logic or use storage backends that support automatic cleanup.

# Session expiration checking
if session['created_at'] && 
   Time.now - session['created_at'] > SESSION_TIMEOUT
  
  # Session has expired, clean up and redirect
  session.delete
  puts cgi.header('status' => 'FOUND', 'location' => '/login')
  exit
end

# Update session timestamp
session['last_accessed'] = Time.now

Debugging session issues often requires examining session storage directly and logging session operations. Enable session debugging by inspecting session IDs, storage locations, and data contents.

# Session debugging utilities
def debug_session(session, cgi)
  puts "Session ID: #{session.session_id}"
  puts "Session new?: #{session.new_session}"
  
  if defined?(session.path) # FileStore backend
    puts "Session file: #{session.path}"
    puts "File exists: #{File.exist?(session.path)}"
    puts "File size: #{File.size(session.path)} bytes" if File.exist?(session.path)
  end
  
  puts "Session data keys: #{session.keys.inspect}"
  puts "Cookie header: #{cgi.cookies['_session_id']}"
end

Performance & Memory

Session storage backend selection significantly impacts application performance and memory usage. Different backends offer trade-offs between speed, persistence, and scalability that affect overall application performance.

FileStore provides persistent sessions with moderate performance characteristics. File I/O operations occur on every session read and write, creating disk access bottlenecks under high traffic loads.

# FileStore performance characteristics
require 'benchmark'

Benchmark.bm do |x|
  x.report("FileStore write:") do
    1000.times do |i|
      session = CGI::Session.new(cgi,
        'database_manager' => CGI::Session::FileStore,
        'session_dir' => '/tmp/sessions'
      )
      session['data'] = "test_data_#{i}"
      session.close
    end
  end
end

# Typical results: 0.8-1.2 seconds for 1000 operations

MemoryStore offers the fastest session operations but lacks persistence across application restarts. Memory usage grows linearly with active session count and stored data size.

# MemoryStore performance comparison
Benchmark.bm do |x|
  x.report("MemoryStore write:") do
    1000.times do |i|
      session = CGI::Session.new(cgi,
        'database_manager' => CGI::Session::MemoryStore
      )
      session['data'] = "test_data_#{i}"
      session.close
    end
  end
end

# Typical results: 0.1-0.3 seconds for 1000 operations

PStore combines file persistence with better performance than FileStore by batching write operations and maintaining in-memory caches. However, PStore files can grow large with frequent updates.

Session data size directly impacts memory usage and serialization performance. Large objects stored in sessions consume memory proportional to the number of active sessions multiplied by average session size.

# Measuring session memory impact
require 'objspace'

# Create session with large data
session['large_array'] = Array.new(10000) { |i| "item_#{i}" }

# Measure object memory usage
session_size = ObjectSpace.memsize_of(session.instance_variable_get(:@data))
puts "Session data memory: #{session_size} bytes"

# Monitor total session memory across requests
total_memory = ObjectSpace.count_objects[:T_OBJECT] * 
               ObjectSpace.memsize_of({})
puts "Estimated total session memory: #{total_memory} bytes"

Session cleanup impacts both performance and storage usage. Implement regular cleanup processes to remove expired sessions and prevent storage backends from accumulating stale data.

# Automated session cleanup for FileStore
class SessionCleaner
  def self.cleanup_expired_sessions(session_dir, max_age = 3600)
    expired_count = 0
    
    Dir.glob(File.join(session_dir, 'ruby_sess.*')).each do |session_file|
      if File.mtime(session_file) < (Time.now - max_age)
        File.unlink(session_file)
        expired_count += 1
      end
    rescue Errno::ENOENT
      # File already deleted by another process
    end
    
    puts "Cleaned up #{expired_count} expired sessions"
  end
end

# Run cleanup periodically
Thread.new do
  loop do
    SessionCleaner.cleanup_expired_sessions('/var/sessions', 1800)
    sleep(300) # Clean up every 5 minutes
  end
end

Production Patterns

Production session management requires configuration for security, scalability, and reliability. Sessions in production environments must handle concurrent access, provide adequate security controls, and support horizontal scaling across multiple application servers.

Secure session configuration prevents common session-based attacks through proper settings for session ID generation, cookie attributes, and storage permissions.

# Production-ready session configuration
PRODUCTION_SESSION_CONFIG = {
  'database_manager' => CGI::Session::FileStore,
  'session_dir' => '/var/app/sessions',
  'session_key' => '_secure_session_id',
  'session_expires' => Time.now + (2 * 3600), # 2 hours
  'new_session' => false,
  'prefix' => 'prod_sess_',
  'suffix' => '.session'
}.freeze

# Secure cookie configuration
cgi = CGI.new
session = CGI::Session.new(cgi, PRODUCTION_SESSION_CONFIG)

# Set secure cookie attributes
cookie = CGI::Cookie.new(
  'name' => '_secure_session_id',
  'value' => session.session_id,
  'expires' => Time.now + (2 * 3600),
  'secure' => true,      # HTTPS only
  'httponly' => true,    # Not accessible via JavaScript
  'samesite' => 'Strict' # CSRF protection
)

Load balancing across multiple application servers requires session storage that all servers can access. File-based sessions must use shared storage, or applications should implement database-backed session storage.

# Shared session storage for load-balanced applications
require 'cgi/session/pstore'

class DatabaseSessionStore < CGI::Session::PStore
  def initialize(session, option = {})
    @database_config = option['database_config'] || {}
    super(session, option)
  end
  
  private
  
  def path
    # Store sessions in database instead of files
    session_id = @session.session_id
    database_path = "/shared/sessions/#{session_id}.pstore"
    
    # Ensure shared directory exists and has proper permissions
    FileUtils.mkdir_p(File.dirname(database_path))
    File.chmod(0700, File.dirname(database_path))
    
    database_path
  end
end

# Use shared storage in production
session = CGI::Session.new(cgi,
  'database_manager' => DatabaseSessionStore,
  'database_config' => production_db_config
)

Session monitoring and logging provide visibility into session usage patterns, performance bottlenecks, and security events. Implement comprehensive session logging for production troubleshooting.

# Production session monitoring
class SessionMonitor
  def self.log_session_event(event_type, session_id, details = {})
    log_entry = {
      timestamp: Time.now.iso8601,
      event: event_type,
      session_id: session_id,
      details: details
    }
    
    Rails.logger.info("SESSION: #{log_entry.to_json}")
  end
  
  def self.track_session_metrics(session)
    # Track session usage metrics
    session_age = Time.now - (session['created_at'] || Time.now)
    data_size = Marshal.dump(session.instance_variable_get(:@data)).size
    
    log_session_event('metrics', session.session_id, {
      age_seconds: session_age.to_i,
      data_size_bytes: data_size,
      keys_count: session.keys.size
    })
  end
end

# Integrate monitoring into session lifecycle
begin
  session = CGI::Session.new(cgi, production_config)
  
  if session.new_session
    SessionMonitor.log_session_event('created', session.session_id)
    session['created_at'] = Time.now
  end
  
  # Track metrics periodically
  SessionMonitor.track_session_metrics(session)
  
ensure
  SessionMonitor.log_session_event('closed', session.session_id) if session
  session&.close
end

Production deployment requires proper session directory permissions, backup procedures, and recovery plans for session data loss scenarios.

# Production session directory setup
def setup_production_session_storage
  session_dir = '/var/app/sessions'
  backup_dir = '/var/backups/sessions'
  
  # Create directories with secure permissions
  [session_dir, backup_dir].each do |dir|
    FileUtils.mkdir_p(dir)
    FileUtils.chmod(0700, dir)
    FileUtils.chown('app', 'app', dir)
  end
  
  # Set up log rotation for session logs
  logrotate_config = <<~CONFIG
    /var/log/app/sessions.log {
      daily
      missingok
      rotate 30
      compress
      delaycompress
      copytruncate
    }
  CONFIG
  
  File.write('/etc/logrotate.d/app-sessions', logrotate_config)
end

Common Pitfalls

Session ID prediction represents a critical security vulnerability when session IDs use predictable patterns or insufficient entropy. Ruby's default session ID generation provides adequate randomness, but custom implementations may introduce weaknesses.

# Vulnerable session ID generation (DO NOT USE)
class WeakSessionStore < CGI::Session::FileStore
  def create_new_id
    # Predictable IDs based on timestamp
    "session_#{Time.now.to_i}_#{rand(1000)}"
  end
end

# Secure session ID generation
class SecureSessionStore < CGI::Session::FileStore
  def create_new_id
    # Use cryptographically secure random data
    require 'securerandom'
    SecureRandom.hex(32) # 256 bits of entropy
  end
end

Storing sensitive data directly in sessions creates security risks if session storage becomes compromised or if sessions get transmitted insecurely. Store session references to sensitive data rather than the data itself.

# Insecure: storing sensitive data in session
session['credit_card_number'] = '4111-1111-1111-1111'
session['ssn'] = '123-45-6789'
session['password'] = user_password

# Secure: store references and lookup data server-side
session['user_id'] = user.id
session['payment_token'] = SecureToken.create(user.id)

# Retrieve sensitive data from secure storage when needed
def get_user_payment_info(session)
  user_id = session['user_id']
  payment_token = session['payment_token']
  
  return nil unless user_id && payment_token
  
  SecureVault.get_payment_data(user_id, payment_token)
end

Race conditions occur when multiple concurrent requests modify the same session simultaneously. Ruby's session implementations are not thread-safe, leading to data corruption or lost updates.

# Race condition example (problematic code)
def increment_counter(session)
  current_value = session['counter'] || 0
  sleep(0.1) # Simulates processing delay
  session['counter'] = current_value + 1
end

# Thread-safe session updates using file locking
require 'fcntl'

def safe_increment_counter(session)
  session_file = session.instance_variable_get(:@path)
  
  File.open("#{session_file}.lock", 'w') do |lock_file|
    lock_file.flock(File::LOCK_EX)
    
    # Reload session data under lock
    current_value = session['counter'] || 0
    session['counter'] = current_value + 1
    session.close # Force write while locked
  end
end

Session fixation attacks occur when applications accept externally provided session IDs without regenerating them after authentication. Always regenerate session IDs during privilege escalation.

# Vulnerable to session fixation
def authenticate_user(cgi, session, username, password)
  if valid_credentials?(username, password)
    session['authenticated'] = true
    session['user_id'] = find_user_id(username)
    # Session ID remains the same - vulnerable!
  end
end

# Protected against session fixation
def secure_authenticate_user(cgi, session, username, password)
  if valid_credentials?(username, password)
    # Generate new session ID after authentication
    old_data = session.to_hash
    session.delete # Remove old session
    
    new_session = CGI::Session.new(cgi, session_options)
    new_session['authenticated'] = true
    new_session['user_id'] = find_user_id(username)
    
    # Copy over non-sensitive session data
    old_data.each do |key, value|
      next if ['session_id', 'created_at'].include?(key)
      new_session[key] = value
    end
    
    new_session
  end
end

Memory leaks develop when applications create sessions without proper cleanup or when session storage backends accumulate data without expiration. Monitor memory usage and implement cleanup procedures.

# Memory leak from unclosed sessions
def leaky_session_handling(cgi)
  1000.times do
    session = CGI::Session.new(cgi, session_options)
    session['data'] = Array.new(1000, 'large_string')
    # Missing session.close - sessions accumulate in memory
  end
end

# Proper session resource management
def proper_session_handling(cgi)
  1000.times do
    begin
      session = CGI::Session.new(cgi, session_options)
      session['data'] = process_request_data
      
      # Generate response using session data
      generate_response(session)
      
    ensure
      session&.close # Always close sessions
    end
  end
end

Reference

Core Classes and Methods

Class Purpose Storage Type
CGI::Session Main session interface Backend-dependent
CGI::Session::FileStore File-based session storage Persistent files
CGI::Session::PStore Marshal-based storage Persistent binary files
CGI::Session::MemoryStore In-memory session storage Memory only

Session Creation Parameters

Parameter Type Default Description
database_manager Class FileStore Storage backend class
session_key String "_session_id" Cookie/parameter name
session_expires Time nil Session expiration time
new_session Boolean true Allow new session creation
session_dir String "/tmp" Directory for file storage
prefix String "ruby_sess." Session file prefix
suffix String "" Session file suffix

Session Instance Methods

Method Parameters Returns Description
#[]=(key, value) key (String), value (Object) Object Store session data
#[](key) key (String) Object Retrieve session data
#delete(key=nil) key (String, optional) Object or nil Delete session data or entire session
#has_key?(key) key (String) Boolean Check if session contains key
#keys None Array<String> Get all session data keys
#close None nil Save and close session
#session_id None String Get unique session identifier
#new_session None Boolean Check if session was newly created

Storage Backend Options

Backend Persistence Performance Scalability Use Case
FileStore Yes Medium Limited Single server
PStore Yes High Limited Single server, complex data
MemoryStore No Highest Memory-bound Development, caching

Exception Hierarchy

Exception Cause Handling Strategy
ArgumentError Invalid configuration Validate session parameters
Errno::EACCES Permission denied Check directory permissions
Errno::ENOENT Missing session file Create new session
TypeError Marshal errors Clear corrupted session data
StandardError General session errors Implement fallback behavior

Security Configuration Checklist

Setting Secure Value Purpose
Cookie secure true HTTPS-only transmission
Cookie httponly true Prevent XSS access
Cookie samesite "Strict" CSRF protection
Session timeout <= 2 hours Limit exposure window
Storage permissions 0700 Restrict file access
Session ID entropy >= 128 bits Prevent prediction attacks