2022.04.08

GraphQL API を無料で効率よく脆弱性診断する【OWASP ZAP】

はじめに

次世代システム研究室のY.Cです。セキュリティを高めましょうという気運は年々強くなっていますが、アプリケーション開発において時間的・金銭的な制約がある中で専門機関に毎回脆弱性診断を依頼するのも難しいです。プロダクトを知り尽くした開発者自身が診断ができれば、認可制御を確実にクリアしたきめ細かい検査と的確でスピード感ある修正対応ができるというメリットがあります。今回はあまり情報がなかったGraphQL APIを対象に、無料脆弱性診断ツールのデファクトスタンダードであるOWASP ZAPを使った診断方法について、備忘録も兼ねて丁寧に解説したいと思います。GraphQL固有の部分を除けば、普通にRESTishなAPIにも適用できます。

TL;DR

  • クエリの引数はvariablesとして本文から分離する
  • リクエストの収集時、対象がhttpsでも内蔵firefoxやcurlの-kオプションを使えばZAPの証明書を追加しなくて良い
  • 動的スキャン実行時、入力ベクトルで攻撃しないキーを指定することでスキーマを壊さず効率よく試行できる

大まかな流れ

診断全体の流れは以下です。
  1. OWASP ZAPの起動・設定
    ZAPを立ち上げるとローカルプロキシとして稼働します。ポート番号を設定します。
  2. リクエストの収集
    ZAPプロキシ経由で診断対象にリクエストを投げることにより、ZAPにリクエストを記憶させます。
  3. 動的スキャン
    収集したリクエストのパラメータを変えながら脆弱性がないか検査します。
  4. 精査
    人の目で検出された脆弱性をチェックします。
  5. 修正
    必要に応じて修正をし、再検査します。
  6. レポートの生成
    検出した脆弱性の内容と証跡が一目でわかるレポートを自動生成し、精査・修正結果と合わせて診断の成果物とします。
 

OWASP ZAPの起動・設定

  1. セッション(試したリクエストや検出した脆弱性等もろもろの記録)をどう保存するか聞かれるので適当に選びます。ファイルメニューであとから名前をつけて保存もできます。
  2. 左上のセレクトボックスがプロテクトモードになっていることを必ず確認します。これにより誤って外部へ攻撃することを防ぎます。
  3. プロキシの設定を確認します。デフォルトではポート8080でlistenしています。他と干渉する場合、ツール -> オプション -> ローカル・プロキシ でポート番号を変更し、OKを押します。
  4. ウィンドウ左下に表示される稼働中のプロキシのポート番号が問題ないことを確認します。
 

リクエストの収集

ZAPプロキシを介して診断対象となるリクエストを送信することで、ZAPに記憶させます。

収集するリクエストの形式

収集するリクエストはクエリの中にパラメータを直接記述するのではなく、プレースホルダを使ってvariablesとして分離する形式にします。これによってvariables内のパラメータに対してのみ攻撃を試行できるようにします。GraphQL用クライアントを使うとこの形式でリクエストが発行されると思いますが、手動で生成する場合は注意です。

収集方法あれこれ

収集方法を紹介します。たまにZAPを使うと忘れがちですが、1番目と3番目はRESTishなAPIでも使えす。

【GraphQL・RESTish】リクエストがブラウザから送信される場合:内蔵firefoxでリクエストを発生させる

この場合は一番簡単です。ZAPはfirefoxを内蔵しているのですが、ここで発生した通信は全てZAPプロキシを経由するので、このブラウザ上で診断したいAPIへのリクエストを発生させる動作をガシガシ行えばよいです。
  1. firefoxの立ち上げ
    1. 緑のレーダーみたいなアイコンを押されていない状態にします。ZAP版firefox内に表示されるHUDを有効化するものなのですが、情報量が増えて取っつきづらくなるためひとまず不要です。
    2. firefoxのアイコンを押すと、ZAP版firefoxが立ち上がります。
  2. URL欄の雰囲気が通常版と違います。URLを入れてみます。
  3. ZAPに記録されました。ページ内で発生したPOSTも記録されます。このようにしてブラウザ内で実際に診断対象を動かすことでリクエストを収集していきます。
さらに、収集したページからAPIリクエストが発生するページまでの導線がある場合は、残りの収集を自動化できます。
  1. 診断対象を右クリック-> コンテキストへ含める -> 規定コンテキスト -> OK で診断対象をコンテキストに追加します。これにより対象への診断が可能になります。
  2. 再び診断対象を右クリック-> 攻撃 -> スパイダー -> スキャンを開始 とすることで、ページ内に存在するリンクを辿りながら発生したリクエストを収集していきます。
試しにHUDのチュートリアルページをスパイダーで収集してみた結果が以下です。ちゃんと増えてますね。
Before :

After:


 

この内蔵firefoxを使うことで、わざわざ普段使用しているブラウザの設定を変えてプロキシを通るようにしたり、https通信のためにZAPで発行したオレオレ証明書をブラウザに追加する必要もなくなります。ただ、ブラウザ上で目的のリクエストを発生させるのが面倒だったり、BFFアーキテクチャを採用しておりクライアントからは診断したいリクエストが発生しなかったり、この手法が適切でない場合も多いと思います。またスパイダーを使用した場合、動的に生成されるページに対応できなかったり、単純なGETリクエストで収集結果が膨大になってしまい確認が大変になったりと、うまくいかないことも多々あるかと思います。そのような場合のために、次に1件ずつ診断したいAPIを直接試していく手法を紹介します。

【GraphQL】GraphQL PlaygroundやGraphiQLが組み込まれている場合:内蔵firefoxで直接クエリを実行する

開発環境であればGraphQL PlaygroundのようなGUI上でクエリを試せるツールが組み込まれていることも多いと思います。その場合、内蔵firefox上でツールを開き、1点ずつクエリを実行することでZAPに記憶させることができます。
  1. variablesを使って引数を指定したクエリを実行してみます。
  2. ZAPに記録されました。きちんとvariablesがqueryと分離されていますね。

【GraphQL・RESTish】curlコマンドを利用する

Postmanやスタンドアロン版GraphQL Playgroundといったツールを使って、送信したリクエストのcurlコマンドを生成することができます。それにオプションをつけてZAPプロキシを経由するようにします。
  1. curlコマンドの生成
    • Postmanの場合
      RESTish APIではよくお世話になるPostmanですが、Bodyの形式としてGraphQLを選択できます。Sendボタンでリクエスト送信後、右側の</>アイコンを押すとCode snippetが開くので、セレクトボックスからcURLを選択するとcurl文が生成されます。
    • スタンドアロン版GraphQL Playgroundの場合
      GraphQL Playgroundはスタンドアロン版が存在しており、URL endpointを入力することでクエリを試すことができます。
      クエリの実行後、右上のCOPY CURLを押すとクリップボードにcurl文がコピーされます。
  2. curlでZAPプロキシを経由する
    下記のようにcurlコマンドにオプションをつけるだけでZAPにリクエストが記録されるようにります。
    {生成したcurl文}  -x http://localhost:{listenしているポート}
    元のリクエストがhttpsだと証明書エラーが発生しますが、-kオプションをつけることでエラーを無視できます。
    {生成したcurl文}  -x http://localhost:{listenしているポート} -k
    【余談】
    画面ポチポチするよりもcurlで済む方が都合がよい場面もあるのではないでしょうか。特に-kオプションが使えることに気づいた時は内心小躍りでした。もう証明書エラーとはおさらばじゃ!
    と、ここで疑問に思ってしまいました。-kオプションってセキュリティ的に大丈夫?脆弱性検査してセキュリティリスク増大したら元も子もありません。結論、-kオプションは安全性に影響なし(ただし安全とは言っていない)。順を追って説明します。
    1. まず、下記のようにcurlやブラウザからのhttpsリクエストがZAPプロキシを経由することで証明書エラーが発生しているとします。
    2. これに対処するアプローチとしては下記2つがあります。
      1. ブラウザであれば、ZAPで生成したオレオレ証明書を追加することで正常にhttps通信を行うことができます。
      2. curlの場合は-kオプションで証明書の検証をしないことで、エラーを抑制できます。ブラウザの場合でも設定次第でエラーを無視できると思います。
    3. 証明書を検証してもしなくても、ローカルでの通信なので基本どっちでもいいです。問題はZAPと診断先との通信です。実はそもそもZAP自体はhttps通信で証明書を検証しません。証明書を検証しないと中間者攻撃により通信が盗聴・改ざんされる恐れがあります。
      この件についてこちらでIssueが立っています。(記事執筆時点より2週間前にCVEに登録されて脆弱性扱いされています。ZAPってもう出来てから20年経つんですが笑)
      ZAPユーザーはテスト先のサイトについてよく知っていて、センシティブな情報は送らないことが期待されるとのこと。ZAPが使われるような環境では自己署名証明書が一般的とも述べられています。確かにそうですね。ということで、インターネット越しにZAPを使う際は漏れたらまずい情報は流さない、ということは頭に入れておきましょう。ちなみに証明書エラーが試せるbadssl.comというサイトに内蔵firefoxでアクセスすると、確かにエラーなしでアクセス出来てしまいました。キャーコワイ
 

【GraphQL】スキーマファイルを利用する

診断対象が多い場合はスキーマファイルの利用が効果的です。
  1. ウィンドウ上部のツール -> オプション と進み、左側のメニューからGraphQLを選択します。
  2. クエリの深さが選べますが、リクエストが大量になりがちなので、全て1にします。深さが欲しい場合は様子見して増やしたり、スキーマファイルではなく個別に追加するなどするのをお勧めします。Specify Argumentsは、ここでは後述する診断設定に合わせてUsing Variablesにしておきます。
  3. ウィンドウ上部のインンポート -> Import a GraphQL Schema from a Fileを選択し、スキーマファイルのパスとGraphQLエンドポイントのURLを指定するとインポートが始まります。
    手元にあったスキーマファイルを編集して試してみましたが、記載した分は全て反映されていたので中々有効そうです。enumやtypeの数が多く階層構造になっているとコピペが大変なので、一度queryとmutation以外を全てコピペしてしまって、その後診断したいqueryとmutationを選んでコピペすると楽でした。
    ちなみに一番下にURLからインポートできるメニューがあります。規模の大きいサイトでまとめて検査したい場合これを使いたくなりそうですが、現時点ではインポートした時点でbad request = スキーマが壊れているリクエストが多く、そのまま診断してもやはりbad requestとなりました。各クエリが正しくインポートできたかチェックする労力が必要になるため、まだスキーマファイルを使う方が良さそうです。

動的スキャン

いよいよ収集したリクエストに対して動的スキャンをかけて診断していきます。
  1. プロテクトモードになっていることを確認の上、診断対象を右クリックしてコンテキストに追加します。
  2. 【GraphQL】診断対象を右クリック-> 攻撃 -> 動的スキャンと進み、入力ベクトルタブで以下のようにチェックボックスを設定します。
    Injectable Targets: POST Data のみチェック
    Built-in Input Vector Handlers: JSON のみチェック
    Enabled Script Input Vectors: オフさらに、下段の無視するパラメータに名前:query, Where: JSONを追加します。リクエストに含まれていれば、名前:operationName, Where: JSONも追加します。
    これにより、POSTデータをJSONとして解析し、queryやoperationNameという名前のパラメータは無視する、すなわちvariablesパラメータだけ攻撃を試行する、という挙動になります。またEnabled Script Input Vectorsをオフにすることで、query文のインライン引数を書き換えてしまうInput Vectorsスクリプトを無効にしています。つまり、query文自体はノータッチとなります。
  3. 先ほどのメニュー下のスキャンを開始で、診断を実施します。variablesパラメータに対し、様々な攻撃パターンで大量のリクエストを発生させます。
    ここで、前述した設定を行なっていなかった場合、以下のようにbad requestが大量に発生します。これはパラメータを変えた結果GraphQLのスキーマが壊れたためです。これではAPIのロジック自体に問題があるのかを検証できません。
    前述の設定を行なった結果が以下です。bad requestはほとんどなくなると思います。攻撃リクエストの中身を覗くと、確かにGraphQLのスキーマを守りつつvariablesの中身が書き換わっているのが分かります。

結果の精査

赤い旗のアラートタブを押すと検出した脆弱性が確認できます。誤検知も多いので、リクエストとレスポンスの中身を確認したり、再送したりするなどしてしっかりと人の目で精査し、修正対応が必要か、リスク受容するのか、といった判断をします。


 

修正

精査の結果修正が必要と判断した脆弱性については修正し、再検査をして直っていることを確認します。

レポートの生成

ウィンドウ上部のレポート -> Generate Report… からhtmlファイルでレポートを生成できます。検出した脆弱性の説明や証跡がわかりやすく記載されます。これを生成して見せるだけでも、すごく仕事した感が出ると思います。精査結果と修正結果をまとめて一緒につければ完璧です。


 

おわりに

今回はGraphQL APIを診断する上での勘所について、試行錯誤して分かったことをまとめました。OWASP ZAPはメニューが多すぎて、たまにしか触らないと毎回大変な思いなのですが、1年後の自分でもこれを見ればまた同じクオリティで診断ができる、、、と信じたいです。診断を生業にしている人は有料ツールを使うせいなのか、ZAPに関する情報ってイマイチ少ない気がします。でも無料でこのレベルの診断ができるということで、セキュリティが気になってきたら試してみてください。

 

次世代システム研究室では、 Web アプリケーション開発を行うアーキテクトを募集しています。募集職種一覧 からご応募をお待ちしています。

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ

関連記事