食べログ Advent Calendar 2019 16日目の記事です。
先日、情報セキュリティ関連の研修を2つほど受けてきました。最近ではフレームワークが便利に担ってくれているおかげで、意識しなくても安全に作れる部分が増えてきていますし、脆弱性診断してくれる会社様もありますので、エンジニアの負担は大きくありません。
でも逆に、脆弱性が入り込んでしまった状態って見たことありますか?
XSSは時々見かけますが、SQLインジェクションなどはライブラリがしっかりブロックしてくれてるおかげで実際に見たことありません。実際に再現させて体感して、直してみる。これによって知識を深めたいなと思いました。
どんな脆弱性があるんだろうか
少し古いですが、情報処理推進機構(IPA)から出ている資料があります。
安全なウェブサイトの作り方
ここで定義されている代表的な脆弱性は以下の通りです。
- SQL インジェクション
- OS コマンド・インジェクション
- パス名パラメータの未チェック/ディレクトリ・トラバーサル
- セッション管理の不備
- クロスサイト・スクリプティング
- CSRF(クロスサイト・リクエスト・フォージェリ)
- HTTP ヘッダ・インジェクション
- メールヘッダ・インジェクション
- クリックジャッキング
- バッファオーバーフロー
- アクセス制御や認可制御の欠落
どう実装するとこのような脆弱性を作り込んでしまうのか、気になりますね。
EasyBuggyを利用してみる
脆弱性のサンプルとしてとても有名なものとして、EasyBuggyがあります。
EasyBuggyはバグだらけのWebアプリケーションとして作られており、様々な脆弱性だけでなく、バグも体感することができます。またOSSとして公開されていますので、具体的に問題となるコードまで読むことが出来る、大変素晴らしいものとなります。これを使って脆弱性を体感し、具体的に問題となるコードを確認してみます。
ダウンロードから起動まで
EasyBuggyは、SpringBootに対応版が提供されております。起動するにはそちらの方が手取り早いので、今回はSpringBoot版を使ってみます。
ちなみに私の環境です
- java 8系
- maven 3.6系
java8とmaven3.6がすでに入っていたので、それを使って動かしてみました。
やることは簡単で、git clone
してからmvn spring-boot:run
を叩くだけです。
EasyBuggy Bootの日本語Readmeにしっかり書いてあります。
起動したら、localhost:8080 にアクセスするだけです。超簡単。
色々なメニューがあり、どれを見てみるか迷うレベルです。
基本の基「SQLインジェクション」を見てみる
やはり基本から。脆弱性の中で最も有名と言っても過言ではないと思います。そして今は意識することなく対策出来てしまうため、少なくとも私は実際に見たことがありません。今回はこれを体感してみましょう。
SQLインジェクションを再現
下の方に再現方法が記載されています。とてもわかりやすいです。試しに Mark
/password
を入力して普通に使ってみると、無事Markの暗証番号をゲットすることが出来ました。
それではいよいよ、SQLインジェクションを試してみます。説明に書いてある通り、 Mark
/' OR '1'='1
を入力してみます。
adminとか出てる・・・
なんかあからさまにやばそうです。なぜこうなってしまったのでしょうか。
実装を確認する
入力されたパスワードがどう処理されているか追ってみます。
処理はここから始まります。
入り口
少し下で、前後の空白を取り除いてます。
空白を取り除く
その後は特に加工せず、SQL文を作って発行しています。
SQL文を作って発行
発行されているSQLはこんな感じ。
password
と入力した場合
SELECT name, secret FROM users WHERE ispublic = 'true' AND name='Mark' AND password='password'
' OR '1'='1
と入力した場合
SELECT name, secret FROM users WHERE ispublic = 'true' AND name='Mark' AND password='' OR '1'='1'
おっと、これはダメですね。。。 文字列を連結させてSQLを組むと、攻撃者がwhere句を操作出来てしまうことがわかります。
直してみる
こういう時はどうすべきでしょうか。SQLで利用する記号であるシングルクォートをエスケープすればうまく動きそうなので、とりあえずpasswordの入力値をエスケープしてみます。エスケープはシングルクォート2つのようですのでこんな感じ。
password = password.replace("'", "''");
おお! いい感じです。発行されたSQLはこちら。シングルクォートをエスケープして、構文として認識しなくなったことがわかります。
SELECT name, secret FROM users WHERE ispublic = 'true' AND name='Mark' AND password=''' OR ''1''=''1'
ちなみに、パスワードにシングルクォートを使っているユーザーを追加して、シングルクォートを使った検索がうまく動くことも確認してます。
今回は自分でエスケープしましたが、自分で1文字ずつ変換するのはとても面倒ですし、漏れがあるかもしれません。また、利用するDBによってスケープする方法が異なりますので、あまり良い方法とは言えません。よってJavaの標準機能であるPreparedStatementを使うのが良いです。今回はSpringBootなのでこんな感じで。(JdbcTemplateの話とか、このコードを書くために追加したエラー処理などは省略)。このコードであれば、先ほどのようにパスワードを自分でエスケープする必要はありません。
final String sql = "SELECT name, secret FROM users WHERE ispublic = 'true' AND name= ? AND password= ?";
return jdbcTemplate.query(sql, new String[]{name, password}, new RowMapper<User>() {
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setName(rs.getString("name"));
user.setSecret(rs.getString("secret"));
return user;
}
});
Railsではどうか
食べログはRailsで出来ていますので、RailsでSQLインジェクションを作り込まないようにするにはどうすれば良いのでしょうか。Railsの場合は公式ドキュメントに素晴らしい情報がまとまっており、その中にSQLインジェクションも記載されてます。
Rails セキュリティガイド #SQLインジェクション
Ruby on Railsには、特殊なSQL文字をフィルタが組み込まれており、「'」「"」「NULL」「改行」をエスケープします。Model.find(id)やModel.find_by_なんちゃら(かんちゃら)といったクエリでは自動的にこの対応策が適用されます
基本はRailsが勝手にやってくれるようですが
ただし、SQLフラグメント、特に条件フラグメント (where("..."))、connection.execute()またはModel.find_by_sql()メソッドについては手動でエスケープする必要があります。
使うAPIによっては考慮が必要ですね。
基本を知っていて損はない
SQLインジェクションって、誰もが知っているようなものだと思うのです。もちろん私も知ってますし説明も出来ます。でもやっぱり、一度再現して自分で直してみるって凄く大事だと思います。
例えば使っているミドルウェアに脆弱性があるとわかったとき、まずは再現させて、対策して、再現しないことを確認するというプロセスを踏むと思います。一度再現させた経験があれば、「この脆弱性知ってはいるけど、具体的にどうやって再現するんだっけ・・」と悩まずに済みますね。
EasyBuggyは教材として超優秀です。若手にも、もちろんベテランにもオススメです。
おまけ
IPAでは、前年に社会影響の大きかった情報セキュリティ事案をランキング形式でまとめてくれています。
情報セキュリティ10大脅威 2019
これを見ると、最近増えている攻撃方法がわかって良いです。そして正しいコードを書くだけでは防げないということもよくわかります。これを踏まえて、安全に利用者に使ってもらえる設計もやはり大事だなと痛感します。毎年目を通しておきたい資料ですね。
さて、明日は@zettaittenaniさんによる 「食べログの機械学習基盤について」 です。