LoginSignup
0
0

More than 1 year has passed since last update.

Ruby CGI::Session(CGI::Cookie)をSameSite属性対応する

Last updated at Posted at 2023-03-07

Ruby CGI::Session(CGI::Cookie)をSameSite属性対応する

ruby標準ライブラリ CGI でセッション(やCookie)を利用していて、なおかつCookieにSameSite属性を付けたい人向けの記事です。

Railsなんかでは対応されているようですが、標準の CGI::Cookie や CGI::Session クラスではSameSite属性に対応していないんですね。自力でなんとかしてみます

過去の対応方法(ruby-2.7.7では無効)

今までは単純に path などの末尾に ; SameSite=xxx 付けてしまえば大丈夫でした。

sample.cgi
#!ruby
#-*- coding:utf-8 -*-
require 'cgi'
require 'cgi/session'
cgi = CGI.new
path = ENV['SCRIPT_NAME'] ? File.dirname(ENV['SCRIPT_NAME']) : '/'
path += '; SameSite=Strict'
session = CGI::Session.new(cgi, {"session_path" => path})
session['counter'] ||= 0
session['counter'] = session['counter'].to_i + 1
cgi.out("type" => "text/plain") do
  "counter: #{session['counter']}"
end

ただしこの方法は ruby 2.7.7 で CGI::Cookie に脆弱性の対応が入り使えなくなりました。
path は正規表現でチェックされるようになり、; などが含まれているとエラーです。

ruby-2.7.7以降の対応方法

CGI::Cookie, CGI::Sessionクラスを改造することにしました

cgisamesite.rb
#-*- coding:utf-8 -*-
require 'cgi'
require 'cgi/session'

class CGI::Cookie
  attr_accessor :samesite
  
  SAMESITE_VALUE_RE = %r"\A(Lax|Strict|None)\z"i
  
  alias :initialize_original :initialize
  alias :to_s_original :to_s
  
  def initialize(name = "", *value)
    @samesite = nil
    initialize_original(name, *value)
    if !name.kind_of?(String) && name.has_key?('samesite')
      self.samesite = name['samesite']
    end
  end
  
  def samesite=(str)
    if str and !SAMESITE_VALUE_RE.match?(str)
      raise ArgumentError, "invalid samesite: #{str.dump}"
    end
    @samesite = str
  end
  
  def to_s
    buf = to_s_original
    buf << "; SameSite=#{@samesite}" if @samesite
    buf
  end
end

class CGI::Session
  alias :initialize_original :initialize
  
  def initialize(request, option={})
    initialize_original(request, option)
    session_key = option['session_key'] || '_session_id'
    session_id = @session_id
    request.instance_eval do
      @output_cookies =  [
        ::CGI::Cookie::new(
          "name" => session_key,
          "value" => session_id,
          "expires" => option['session_expires'],
          "domain" => option['session_domain'],
          "secure" => option['session_secure'],
          "path" => option['session_path'] || (ENV["SCRIPT_NAME"] ? File::dirname(ENV["SCRIPT_NAME"]) : ""),
          "samesite" => option['session_samesite']
        )
      ] unless option['no_cookies']
    end
  end
end

これを require して以下のように使います

sample2.cgi
#!ruby
#-*- coding:utf-8 -*-
require 'cgi'
require 'cgi/session'
require './cgisamesite.rb'
cgi = CGI.new
session = CGI::Session.new(cgi, {"session_samesite" => "Strict"})
session['counter'] ||= 0
session['counter'] = session['counter'].to_i + 1
cgi.out("type" => "text/plain") do
  "counter: #{session['counter']}"
end

CGI::Session.new の第2引数に session_samesite オプションを渡すだけでよくなりました。

CGI::Cookie や CGI::Session の構造が変わらない限りは使えると思います

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0