概要
CSP ヘッダを明示しなくても大きな問題にはなりませんが基本的に何でも受け入れる設定なので可能な限り設定したほうがいいかなと思います
そもそも外部にアクセスせずに self のみで完結するのが理想ですがそういうわけにもいかないのでアクセスするべき外部サイトを明示化しておくことでセキュリティ対策になる感じです
今回は Sinatra アプリで CSP (Content Security Policy) レスポンスヘッダーを設定する方法を紹介します
環境
- Ruby 3.4.4
- Sinatra 4.1.1
サンプルコード
まずは CSP を管理するクラスを定義します
- vim libs/csp.rb
# frozen_string_literal: true
# Content-Security-Policy (CSP) の設定を行うクラス
class CSP
def header
"#{script_src} #{img_src} #{style_src} #{frame_src} #{connect_src} #{media_src} #{other_src}"
end
def script_src # rubocop:disable Metrics/MethodLength
script_src_url_list = [
'https://cdn.jsdelivr.net',
'https://code.jquery.com',
'https://www.googletagmanager.com',
'https://cdnjs.cloudflare.com',
'https://d3js.org',
'https://unpkg.com',
'https://www.youtube.com'
]
gtag_hash = "'sha256-3j3z4K5sw7JEbB9oTbDCJixRv+lWuUYVNr8dOyKK7C0='"
"script-src 'self' #{gtag_hash} #{script_src_url_list.join(' ')};"
end
def img_src # rubocop:disable Metrics/MethodLength
img_src_url_list = [
'https://lh3.googleusercontent.com',
'https://blogger.googleusercontent.com',
'https://addons.mozilla.org',
'https://pbs.twimg.com',
'http://pbs.twimg.com',
'https://avatars.githubusercontent.com',
'https://c10.patreonusercontent.com'
]
"img-src 'self' data: #{img_src_url_list.join(' ')};"
end
def style_src
style_src_url_list = [
'https://cdn.jsdelivr.net',
'https://unpkg.com',
'https://cdnjs.cloudflare.com',
'https://cdn.plyr.io/'
]
# public/js/fontawesome-all.min.js が書き換わったら以下のハッシュが変わる可能性もある
font_awesome_hash = "'sha256-bviLPwiqrYk7TOtr5i2eb7I5exfGcGEvVuxmITyg//c='"
# views/alchol.erb で使用しているd3.jsのハッシュ
d3_hash = "'sha256-WrkFMt0yMbnytekpJNs62cGCUpYDzgmKFnWzVnKQ6YY='"
"style-src 'self' #{font_awesome_hash} #{d3_hash} #{style_src_url_list.join(' ')};"
end
def frame_src
frame_src_url_list = [
'https://www.youtube.com'
]
"frame-src 'self' #{frame_src_url_list.join(' ')};"
end
def connect_src
connect_src_url_list = [
'https://cdn.plyr.io'
]
"connect-src 'self' #{connect_src_url_list.join(' ')};"
end
def media_src
media_src_url_list = [
'https://storage.googleapis.com'
]
"media-src 'self' #{media_src_url_list.join(' ')};"
end
def other_src
[
"form-action 'self';",
"frame-ancestors 'self';",
"font-src 'self';",
"object-src 'self';",
"manifest-src 'self';"
].join(' ')
end
end
基本的には各 CSP ヘッダごとに「許可するサイト」「許可するハッシュ」を定義します
許可するサイトは見慣れた CDN サイトが並ぶ感じになります
許可するハッシュはどうしても style タグを使って直接 CSS を指定している場合にだけ使うので CSS ファイルを外部ファイルで管理している場合には不要です
使う側
CSP レスポンスヘッダーを指定するのは before が一番簡単です
特定のパスだけ適用したくない場合などは unless いましょう
before do
# Content Security Policy (CSP) の設定
unless request.path.start_with?('/podcast/feed')
csp = CSP.new.header
headers 'Content-Security-Policy' => csp
end
end
動作確認
アプリを起動して問題なくすべてのコンテンツ (js or css or img etc) が取得できることを確認しましょう
Chrome などの開発者ツールでコンソールにエラーがないことを書くにしましょう
CSP は基本的にホワイトリスト形式なので設定が足りていないとコンテンツが取得できずエラーになりうまくサイトが表示できなくなります
基本的には CSP を設定した場合には style タグや onclick タグは使えないと思ったほうがいいです
css ファイルや js ファイルにすべて処理を移す感じになります
最後に
CSP レスポンスヘッダーを正しく設定することで XSS 対策しセキュアコードしましょう
0 件のコメント:
コメントを投稿