目次
■信頼性と品質 / システム運用と管理
2022.11.09 2023.08.16 約3分
── ソフトウェアセキュリティの「つぎの一歩が見つかる、気づきと学びの場」 Security Studyシリーズ。 年々増加する不正アクセスや情報漏洩等のセキュリティインシデント。セキュリティ意識はありとあらゆるサービスで求められています。今回は、セキュリティに関する思想や向き合い方、具体的な技術論まで幅広く学びます。
本日のテーマは「フレームワークでは脆弱性を担保する素晴らしい機能があるのにどうして脆弱性が残ってしまうのか?」です。先に答えを言ってしまうと、勝手に安全になるものと、フレームワークでは担保されておらず、開発者自身が意識しないといけないものがあるからです。開発者が意識すべき脆弱性を見ていきましょう。
『よくわかるPHPの教科書』で紹介されているソースコードの一部を実際にデモで動かしてみます。
// ここまでで、認証済みであるこの検査が済んでいる
$id = $_REQUEST['id'];
// 投稿を検査する
$sql = sprintf('SELECT * FROM posts WHERE id=%d',
mysql_real_escape_string($id));
$record = mysql_query($sql) or die(mysql_error());
$table = mysql_fetch_assoc($record);
if ($table['member_id'] == $_SESSION['id']) { // 投稿者の確認
// 投稿した本人であれば、削除
mysql_query('DELETE FROM posts WHERE id=' .
mysql_real_escape_string($id)) or
die(mysql_error()); }
はい、ご覧ください。開発者ツールに先ほど設定したパスワードが平文でCookieに保存されています。
これは 徳丸本 に書いてあるダメな例です。
ひとこと掲示板に削除ボタンがありますので、これを今から悪用してみます。「佐藤」でログインしている私が、すぐ下に投稿している徳丸さんの投稿を削除してみます。
リンクをコピーし末尾を「id=91」と指定してみると 投稿は消えません。今度は末尾の引数を「id=92-1」と指定してみます。すると id91徳丸さんの投稿が消えました。
なぜでしょうか?
下記はsprintf書式を%dで打っているので92-1が変換されて92だけが残ります。
$sql = sprintf('SELECT * FROM posts WHERE id=%d', mysql_real_escape_string($id));
こちらはsprintfでも%dでもないので92-1がそのまま入ります。
mysql_query('DELETE FROM posts WHERE id=' .
mysql_real_escape_string($id)) or die(mysql_error());
下記のスライドで詳細を解説しています。
次は『いきなりはじめるPHP』ですが、こちらも大変よく売れているPHP入門書です。下記のコードはアンケート投稿の部分です。ポストパラメータを取り出し、それをHTMLエスケープしてインサートしています。prepareをexecuteしてるんですがプレースホルダを使ってないのでSQLインジェクションとなります。
SQLインジェクションがあるのは重大な点ですが、ここではひとまず置いておきます。何が問題かというと、インサートする前にHTMLエスケープしてるんですね。これはおかしいです。表示の際にやるべきです。しかし類似例はPHP入門書には極めて多いです。
フレームワークといえば Ruby on Rails を思い浮かべる方が多いと思いますが、 Ruby on Rails を正しく使っていればSQLインジェクションは防げます。
Ruby on Rails を使っているのにSQLインジェクションがある場合は、間違った使い方をしています。
NG例1:whereメソッドの引数に値を埋め込んでいる
@books = Book.where("price >= #{params[:price]}")
▲whereメソッド(where句を書けるメソッド)がありますが、ここに外部由来の値をそのまま埋め込んでしまってるという素朴な例です。意外なことに脆弱性診断などで結構この例を見かけます。
NG例2:演算子として外部パラメータを埋め込み
@books = Book.where("price #{params[:op]} ?", params[:price])
NG例3:orderメソッドに外部パラメータを指定(最新のRailsでは対策されている模様)
@books = order ? Book.order(params[:order])
Laravelにもwhereメソッドがあります。呼び出し方は Ruby on Rails と違い、列名、比較演算子、値の形になっており、これは非常に堅牢です。
$tasks = Task::where('kind', '=', $kind)->get();
▲プレースホルダでSQLインジェクション対策されている
$tasks = Task::where('kind = 1 or 1=1#', '=', $kind)->get();
▲列名を攻撃しようとしても、Column not found というエラーになる
$tasks = Task::where('kind', '=1 OR true# ', $kind)->get();
▲比較演算子を攻撃しようとしても、kind = ‘=1 OR true#’と解釈される
Laravelは非常に堅いのですが(列名、比較演算子、値)で記述できないパターンもあります。その場合は、whereRawメソッドを使用しましょう。
whereRaw等を用いる場合は、 Ruby on Rails と同様にプレイスホルダを用いましょう。
NG
$tasks = Task::whereRaw("kind = BINARY '$kind'")->get();
OK
$tasks = Task::whereRaw("kind = BINARY ?", [$kind])->get();
ここからは、やや上級者向けです。ぜひ動画をご覧ください。
セキュリティ機能が組み込まれている
単純なケースでは「なにもしなくても」対応される物が多い。一方、複雑なクエリについてはSQLインジェクションが入る余地があり、実際混入するケースが見られる
最近のフレームワークは自動エスケープが多いが手動(CakePHP等)もある
フレームワークの機能を正しく設定するだけ
フレームワークが全てのケースで対応してくれるわけではない
開発者が自覚的に対応し、テストで確認することが重要
ゆりかごから運ぶ墓場まで、Webセキュリティ全般に対応しています。
最重要の企画段階のコンサルティングや開発の段階のレビュー、開発ガイドライン作成、トレーニング、開発終了のテスト試験など幅広くサービスを提供しています。最近は脆弱性診断を自分で実施したいという要望にもお答えしております。こちらのWebサイトで導入事例を公開しているので、ぜひご覧ください。
本日はフレームワークを使ったアプリのセキュリティがテーマでしたが、やはり開発者が自覚的にセキュリティを考えないといけません。EGセキュアソリューションズ株式会社では、詳細なヒアリングを基に、受講者の理解度に合わせた講習を実施可能です。そのため、講習の成果をよりその後の実務に活かしていただけます。