はじめまして。食べログでwebサービス開発のエンジニアをしている@ham0215です。
web開発をしているエンジニアなら一度は聞いたことがある「SQLインジェクション」や「クロスサイトスクリプティング」など有名な脆弱性があります。
ただ知識として知っていてもrailsのように普通に使っていたら勝手に対策されているものを使っていると、どんどん意識から離れていき無意識のうちに埋め込んでしまうことがあります。
そこでrailsでの具体例を交えて、いくつかの脆弱性についておさらいしようと思います。
クロスサイトスクリプティング
悪意のあるスクリプトがサイト内に埋め込まれてしまう脆弱性。
脆弱性が発生する例
ユーザーが入力したテキストを画面表示するシステムで下記を実施
railsでの対策
erbで<%= text %>
と書いたり、slimで= text
と書くだけでtextにhtmlタグが入っていたとしても自動的にエスケープされる。
- text = "<script>alert('クロスサイトスクリプティング!!');</script>"
/ ↓ <script>alert('クロスサイトスクリプティング!!');</script> とエスケープされてHTML出力される
= text
NGパターン
本文の特定キーワードを強調したり、httpから始まる箇所を<a>
タグに変換したいなどの要件があった場合に文字列全体をraw
やhtml_safe
でエスケープされない状態にしてしまいそれを画面出力してしまう。
slimの場合は== text
と=を2つ重ねるとエスケープされない。
# やりたいこと
# ->ユーザーがURLを本文に入れると<a>タグを付加してリンクに変換して表示
<% user_input = "ここがオススメのお店です。http://hogehoge" %>
# httpの部分を<a>タグに変換(ロジックは割愛)
<% convert_a = "ここがオススメのお店です。<a href='http://hogehoge'>http://hogehoge</a>" %>
# <a>タグを有効にした状態でHTML出力したいのでhtml_safeをつける
# ->目的達成
<%= convert_a.html_safe %>
# NG
# ->ユーザーがフリーテキスト部分にHTMLタグを埋め込んだ場合
<% user_input = "<script>alert('クロスサイトスクリプティング!!');</script>。http://hogehoge" %>
# httpの部分を<a>タグに変換(ロジックは割愛)
<% convert_a2 = "<script>alert('クロスサイトスクリプティング!!');</script>。<a href='http://hogehoge'>http://hogehoge</a>" %>
# <a>タグを有効にした状態でHTML出力したいのでhtml_safeをつける
# ->アラートが表示される・・・
<%= convert_a2.html_safe %>
チェック観点
- 入力項目には有効なHTMLタグを入れるテストを実施して、エスケープされることを必ず確認する。
- コードチェックでhtml_safeやslimの==を見かけたら本当に必要なのか疑う。必要な場合、システムで制御できない動的要素がないことをチェックする。
SQLインジェクション
実行されるSQLをユーザーが任意に操作して、データを改ざんしたり取得したりする脆弱性。
脆弱性が発生する例
ユーザーが入力したニックネームをデータベースに登録するシステムで下記を実施
-- 正常パターン
1. ユーザーがニックネーム:"hogehoge"を入力してリクエスト
2. 下記SQLが発行
insert into users (nickname) values ('hogehoge');
-- 異常パターン
1. ユーザーがニックネーム:"a');delete from users;--"を入力してリクエスト
2. 下記SQLが発行 -> usersテーブルに対してdelete文が発行される!!
insert into users (nickname) values ('a');delete from users;--');
railsでの対策
プレースホルダーを使うなど、SQLを直接文字列で生成しない書き方をすれば勝手にエスケープされます。
# params[:nickname] = "' OR '1"
User.where(nickname: params[:nickname])
User.where("nickname = ?", params[:nickname])
# どちらの書き方でもシングルクォーテーションが\'にエスケープされて実行される
# select * from users where nickname = '\' OR \'1';
NGパターン
SQL文を文字列で生成した場合はエスケープされません。
# params[:nickname] = "' OR '1"
User.where("nickname = #{params[:nickname]}")
# シングルクォーテーションがエスケープされず、全件検索される
# select * from users where nickname = '' OR '1';
チェック観点
- 入力項目にはシングルクォーテーションなどSQLの特殊文字を入れるテストを実施して、エスケープされることを必ず確認する。
- コードチェックでクエリーを文字列生成している箇所を見つけたら撲滅する。どうしても必要な場合は確実にエスケープされることをチェックする。
最後に
今回は個人的に埋め込みやすいと思っている2つの脆弱性をピックアップしました。
冒頭に記載した通り最近では自動的に対策されていることが多く、また脆弱性診断など別途脆弱性の確認をする工程をふむことで脆弱性を埋め込んだままリリースしてしまうリスクはかなり減っていると思います。
ただ、web開発をする上で重要な知識であることに変わりはないので、開発者自身もきちんと理解して回避することが重要ですね。
明日は@e-ronnyさんの「Fluentd + MySQL でログ保存 & 検索システムを考えてみた話」です。よろしくおねがいします!