はじめに
フレームワークを使っているとつい意識が薄くなりがちなセキュリティ関係の知識のメモ
本編
XSS
Cross Site Scriptingのこと。
Javascriptを埋め込まれ、対策をしていないとそれが実行され、さまざまな悪影響につながる。
被害例
Javascriptでできることはなんでもやられてしまうと思って良さそう。
- 悪意あるサイトに勝手に飛ばされる
<script>location.href="https://example.com/"</script>
- 別のユーザーの入力した内容(パスワードなど)が攻撃者のサーバーに送信され、情報漏洩につながる
- Cookieが読み取られてしまう
document.cookie
手口
- 入力フォーム
- 悪意あるスクリプトを入力して送信したり、DBに保存された値がスクリプトで、画面に表示した時に実行されてしまう、など
- HTMLアトリビュート
- aタグのhrefにユーザーの入力値を適用する場合、注意が必要
-
<a href="javascript:alert('XSS')"></a>- 事前に入力値のチェックをする処理をする必要がある
原因
ユーザーの入力した内容がHTMLのタグやJavascriptとして認識され、実行されてしまうことによるため。
Railsの対策
基本的には自動でやってくれているので、意識する必要はない。
Railsのviewでよくみる下記は、自動でエスケープ処理をしてくれる。
HTMLエンティティを自動的にエスケープしてから、ActiveSupport::SafeBufferを利用して文字列を結合し、レンダリングする。
<%= @comment %>
【エスケープ処理とは?】
特定の文字列や文字を、別の文字列に変換する処理のこと。
例えばこんな感じ
> → <
< → >
" → "
' → '
- セキュリティヘッダーを使う
- Content-Security-Policy
- Web ブラウザで使える機能を細かく設定ができる仕組み
- Railsガイドが参考になりそう。https://railsguides.jp/security.html#content-security-policy%EF%BC%88csp%EF%BC%89
- X-XSS-Protection(Railsでは自動でON)
- Content-Security-Policy
実装面での注意
入力値にはバリデーションチェックを設けること
rails-ujsを使用している場合
<%= link_to '削除する', user_path(@user), method: :delete, data: { confirm: "#{@user.name}を削除しますか" } %
data属性やmethod属性はエスケープされない。
上記の@user.name部分。
sanitizeやhメソッドを使って自前でエスケープする必要がある。
link_toを使用する場合
デフォルトでエスケープ対象とならないぽい?
下記の@user.linkのところ。
https://techracho.bpsinc.jp/hachi8833/2022_05_31/25326
<%= link_to “LINK”, @user.link %>
エスケープ処理をしたくない時(注意!!)
html_safe raw <%==
完全にエスケープを回避したい場合に使う。
<%= @comment.html_safe %>
<%= raw @comment %>
<%== @comment %>
使う時は、下記のようにあらかじめエスケープする処理と組み合わせることが多い。
h(@comment).gsub("\n", '<br>').html_safe
sanitize
ある程度タグをそのまま出したいものの危険なタグは出力しないようにしたい場合に使う。
例えば、改行、太字、斜線は有効にしたいときとか。
デフォルトで有効になるタグもあるが、オプションで、ホワイトリストにタグを追加することも可能。
<%= @comment.sanitize %>
DOM-based XSS
ブラウザ上で動くJavaScriptによって発生する種類のXSSのこと。
JavaScriptによってHTMLを操作・生成する実装をする時に発生する可能性がある。
div = document.getElementById("test");
div.innerHTML = location.hash.substring(1);
CSRF
クロスサイトリクエストフォージェリのこと。
悪意のあるユーザーがサーバーへのリクエストを捏造して正当なものに見せかけ、認証済みユーザーを装うという攻撃手法。
善意のユーザーがログインしている状態で、別のサイトに用意したコンテンツ上にある罠のリンクを踏ませること等をきっかけとして、本人が気づかないところで、不正なコード(予期しないリクエストを送る)を実行させ、何かしら被害を被らせること。
被害例
- ショッピングの決済やアカウント退会を強制的に引き起こさせる
- 自分のSNSの投稿が勝手に変わったり消される
- 誤認逮捕事件https://atmarkit.itmedia.co.jp/ait/articles/1211/14/news012.html
- ぼくはまちちゃん事件https://atmarkit.itmedia.co.jp/ait/articles/0504/27/news129.html
Railsの対策
サーバーがリクエストを受け取ったときに、そのリクエストが本当にユーザーの意図したものかを判別する必要があります。
form_withなどを使用するフォームでは自動でauthenticity_tokenが埋め込まれ、入力された値と合わせてサーバに送られる。
「発行したトークン」と「送られてきたトークン」が一致するか確認することで、正規の入力フォームからの入力かを判断する仕組み。
ちなみに、一致の確認のためには、セッションに含まれる_csrf_tokenが使われており、これと「送られてきたトークン」を比較している。
ApplicationControllerにprotect_from_forgeryを追加することで有効になる。(今はデフォルトでONなので書く必要はない)
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
end
HTMLヘッダーには以下が含まれ、Ajaxリクエストなどでもトークンを利用できるようにしています。
<%= csrf_meta_tags %>
ActionController::InvalidAuthenticityToken例外が発生した時
トークンが一致しないと上記エラーが出るので、下記のようにコントローラーに書くと無効化できる。
class ApplicationController < ActionController::Base
skip_before_action :verify_authenticity_token
end
外部からのAPIアクセスの時のみ解除するなどし、無闇に無効化しない。
(例えば、フロントを分離したSPAなどの場合、
authenticity_tokenの値をajaxリクエスト時に送るようにするなどするのがメジャーな方法らしい)また、解除するときはAPIトークンやJWTによる認証を大体手段として検討すること。
実装面での注意
GETとそれ以外を適切に使い分ける
攻撃に利用されるのは、POST PUT PATCH。
また、GET内で状態変更する機能を作ること自体を避けること。
Rails8.2からの変更点
トークンを使わない方法に変わるかも。
https://blog.willnet.in/entry/2025/12/15/192245
SQLインジェクション
攻撃者がSQL文に不正なクエリを注入することで、攻撃を行うこと。
ユーザーからのパラメータを検証せずにSQLに組み込んでしまうと、意図しないSQLが実行されてしまう。
例えば、' OR '1'='1を仕込む手法が有名。
被害例
- 任意のレコードを参照されてしまう(別ユーザーのパスワードとか)
- 勝手にデータを消されたり、更新される
Railsの対策
ActiveRecordは、自動的にパラメータのエスケープ処理を行い、安全なSQL文を生成する。
基本は下記のようにハッシュで渡す方法を用いること。
user = User.where(email: params[email]})
実装面での注意
複雑なクエリのためにハッシュを使えない場合、Placeholderを使う
user = User.where(["user_id = '?', current_user.id]})
SQLのLIKE演算子を使う場合はActiveRecord::Base.sanitize_sql_likeを使用する
おまけ find_by_*の脆弱性
昔のRailsではSQLインジェクション脆弱性があったらしい。
https://blog.tokumaru.org/2013/01/Ruby-on-Rails-find-by-method-SQL-Injection-CVE-2012-5664.html
HSTS
HTTP通信→通信内容(URLの一部、送受信データ、認証情報など)が暗号化されないため、途中で盗み見(盗聴)や改ざんされるリスクがある。
HTTPS通信→SSL/TLSで暗号化された安全な通信
最近ではHTTPS通信が一般的になっている。
HSTSとは、レスポンスヘッダにStrict-Transport-Securityを付与することで行われる。
ブラウザはStrict-Transport-Securityヘッダーを受け取るとそのサーバーのドメインを記憶し、以後そのドメインにアクセスするときは、たとえhttp://を指定した場合でもHTTPS通信にリダイレクトを試みるようになる。
Railsの対策
config/environments/production.rbでconfig.force_ssl = trueと設定すると有効になる。
現在はproduction環境において、デフォルトで有効。
マスアサインメント
攻撃者がフォームのパラメータに任意のキーと値を設定して送信すること。
フレームワークの自動マッピング機能を悪用するもの。
例えば、以下のような方法で任意のパラメータをサーバーに送信できる。
- 開発者ツールのElementsパネルで任意のinputタグを追加後、フォームを送信
- 開発者ツールのConsoleパネルで任意のフォームデータを生成し、fetch APIでサーバーにリクエストを送れてしまう
被害例
- ユーザーの新規作成時、任意のレコードを作成できてしまう
-
is_adminをtrueにしたりとか
-
Railsの対策
Strong Parametersを使い、あらかじめ決めた任意のパラメータしか受け付けないようにすることができる。
def user_params
# 許可されたパラメータのみ
params.expect(user: [:email, :password])
end
IDOR
例えば下記のような実装。
パラメータに任意の値を入れれば、他のユーザーのデータも無制限に閲覧できる。よって、URLを改竄するだけで、簡単に他人のデータにアクセス可能。
def show
@comment = Comment.find params[:id]
end
実装面での注意
そのため、他人のデータは閲覧できないという仕様が求められる場合は、下記のようにログインユーザーのデータに限定すべき。
def show
@comment = current_user.comments.find_by(id: params[:id])
end
参考
https://qiita.com/toduq/items/b88716bad35865ef2dc9
https://techracho.bpsinc.jp/hachi8833/2023_09_19/133406
https://woshidan.hatenadiary.jp/entry/2021/12/12/192958
https://jpcertcc.github.io/OWASPdocuments/CheatSheets/RubyOnRails.html
https://zenn.dev/hogeisfuga/articles/e6abb1723c8271
https://techracho.bpsinc.jp/hachi8833/2021_11_26/46891
https://techblog.glpgs.com/entry/2016/09/12/165939
https://zenn.dev/cobachie/articles/rails-security
https://qiita.com/minimabot/items/69f07810dfd4f5d83015
https://cysec148.hatenablog.com/entry/2025/03/21/165021