脆弱性診断技術やサイバーセキュリティに関する情報を発信するブログメディア

gRPCベースのシステムの脆弱性診断とツール(前編)

gRPC

札幌オフィス在籍の岸谷です。近年gRPCの利用がだいぶ普及してきましたね。今回はgRPCを利用するシステムの脆弱性診断と、そのためのツールについて書きたいと思います。

gRPCって何?

本稿はgRPC自体の解説を目的としたものではありませんが、本題に進む前に簡単に概要に触れます。gRPCはGoogleで開発され、その後オープンソース化されたフレームワークです。grpc.ioには下記のようにあります。

gRPC is a modern open source high performance RPC framework that can run in any environment.

「環境を問わずハイパフォーマンスなRPC開発フレームワーク」ということで、XML-RPCやJSON-RPC、REST API開発用のフレームワークなどのように、他コンピュータ上のコードの呼び出しなどシステム間通信の開発に利用します。以下のような特徴があります。

gRPCのよいところ

gRPCのよいところは開発効率と通信効率でしょう。それぞれ見て行きましょう。

開発効率

特徴として挙げた通り、gRPCは標準のメッセージフォーマットとしてProtocol Buffersを利用します。
RPCクライアントとサーバで共有するスキーマ定義 = インターフェース仕様を.protoファイルに記述しておき、それをもとに自動生成したクライアントとサーバのコードを実装して開発を進めることができます。OpenAPI Generatorなども同様の流れですね。

小規模なプロジェクトなら、サーバとクライアントのインターフェース仕様を一個の.protoファイルに記述し、それにコメントで詳しいAPI仕様等も記載しプロジェクトのリポジトリで共有すれば、このファイルが実装の一部でありつつ複数のドキュメントの役割も兼ねられます。いくつもの形式のドキュメントを作る手間が減らせ、開発チーム間の認識相違による手戻りなどを防ぎつつ効率よく開発できそうです。

また例えばいわゆるmicroservices的な、多数の相互関連するサービスがそれぞれ別々のチームや言語で開発されていたり、多対多の場合を含めて相互にインターフェース仕様が整合した状態を維持して行くケースではgRPCを採用するメリットが大きいのではないでしょうか。

通信効率

ネットワーク上で同じ意味のメッセージを送る場合、データ量が小さければ小さいほど通信が早く完了し、よりよい利用者体験に繋がります。特にモバイルインターネット回線などでは回線帯域に優しいことは重要でしょう。Protocol Buffersを用いると、例えばHTTPクエリ文字列等で一般的な「key=value」形式やJSONのようなフォーマットに比べ、開発者が特に意識せずとも多くの場合にデータサイズが小さくなります。以下に、各フォーマットで同じ意味のデータを表現したメッセージ例を示します。

name=Naoki+Hanzawa&target=Akira+Owada&num_revenge=100
{"name":"Naoki Hanzawa","target":"Akira Owada","num_revenge":100}
\x00\x00\x00\x00\x1E\x0A\x0DNaoki Hanzawa\x12\x0BAkira Owada\x18\x64

※"\xXX" はASCII印字可能文字以外のバイト列

印字可能文字以外のバイト列のせいで一見gRPCが一番大きいような見え方になってしまいましたが、データ長は35バイトのgRPCが最も小さいことが分かります。数十バイトの通信の差は体感不能だと思いますが、これがある程度大きいメッセージの場合、さらには例えば秒間数十万~数百万のメッセージを処理するような大規模システムの場合、データ量の差は大きいものになるでしょう。
※現実の通信ではデータ圧縮などの要素が影響しますが、ここでは主旨から外れるため考慮しません

さて、どこでデータ長の差が生まれたのでしょうか?

大きな差異として、前2者にある「name」「target」といったパラメータ名がgRPC(上記では標準のProtocol Buffersエンコーディング)には含まれていません。その代わりの情報を「フィールド番号」として保持しており、クライアントとサーバはこの番号でパラメータを識別することができます。先に触れた.protoファイルに下記のようなメッセージ定義があれば、受信したメッセージをパースして「フィールド1番はname」「2番はtarget」と理解できるのです。

message Baigaeshi {
  string name = 1;
  string target = 2;
  int32 num_revenge = 3;
}

※「string」「int32」などが型、「name」などがフィールド名(パラメータ名)、1, 2, 3 がフィールド番号

Protocol BuffersメッセージにはASCII印字可能文字以外のバイト列も含まれますので、テキストデータを期待するソフトウェアで表示すると文字化けしたようになってしまいます。

エディタで表示した様子

どのようにデコードするか見てみましょう。メッセージを再掲します。

\x00\x00\x00\x00\x1E\x0A\x0DNaoki Hanzawa\x12\x0BAkira Owada\x18\x64

gRPCでは先頭1バイト(\x00)は圧縮フラグ、続く4バイト(\x00\x00\x00\x1E)が後続のメッセージ本文の長さです。これにProtocol Buffersなどのメッセージ本文が続きます。
フィールド番号1番を示す箇所は6バイト目 “\x0A” です。これを2進数表現して “00001010” の2~5ビット目(0001)でフィールド番号の1を表し、残り3ビット(010)で “wire type” (伝送上の型情報。“010” = 2ならstringなどを含むLength-delimited型)を表しています。長さ情報が必要な型の場合、続く7バイト目の “\x0D” で値の長さ(バイト)を表すといった形式です。フィールド番号とwire type、必要に応じ長さ、データ本文バイト列が必要数分連結されます。
※ここではvarint形式についてなど多くの説明を省略していますので、詳細に興味がある方は公式ドキュメントをご覧ください

key=value形式やJSONでもパラメータ名を番号にすることはできますが、それらをシステム上の変数名等とマッピングする工数をかけなくては開発しにくいですし、ドキュメントも複雑になりバグを量産してしまいそうですね。

他にはデリミタや数値の基数(何進数表現か)のような細かい差異もありますが、これらの影響はケースバイケースでしょう。

加えてgRPCはHTTP/2を利用しますので、ヘッダ圧縮(HPACK)やTCPハンドシェイク回数の減少などによる通信効率向上も期待できます。

こうしたことから、回線の速度や通信容量に制限があるモバイル端末向けアプリやIoT機器の通信や、速度が重視されるシステム間通信などで、gRPCは高パフォーマンスで安定した選択肢として今後も普及が進みそうです。
一方で、WebブラウザがクライアントとなるWebアプリケーション界隈では、Webブラウザ向け実装のgRPC-Webもあるものの、機能的な制限があったり構成に一手間要することなどから、まだまだこれからという状況かと思います。

脆弱性診断する上での課題

さて、ようやく本題です。gRPCを利用しているシステム、とりわけインターネット向けなどクライアントを信用できない環境下にあるサーバサイドアプリケーションのブラックボックス形式での脆弱性診断を考えます。

オーソドックスなWebアプリケーションやREST APIの脆弱性診断では主にHTTPメッセージの内容を読み、書き換えながら問題点がないか様々なことを試行します。加えて限られた時間の中で多くの作業を行う必要があるため効率化のためのツールを利用します。gRPCにはこれまでに挙げた特徴から、従来の脆弱性診断と比べ次のようなテスタビリティ面の課題があります。

  1. Protocol Buffersの利用に関する課題
  2. HTTP/2の利用に関する課題

Protocol Buffersの利用に関する課題

従来の一般的なWebアプリケーション等では、HTTPリクエスト / レスポンスはヘッダとデータともに大部分がテキストとして扱える形式でした。脆弱性診断でも多くの作業はkey=value形式やJSONなどをテキストとして書き換えて行うことができました。

しかし先に触れた通り、Protocol Buffersは印字可能文字以外のバイト列も含むバイナリデータです。また、一つひとつのパラメータがフィールド番号とデータ型、長さといったメタデータとセットになっています。何かをテストするために一つのパラメータを書き換えたい時、メッセージ全体の正常性、整合性を保ちつつ書き換えるには値そのものに加えそれらのメタデータも正しく更新する必要があります。また1バイトの中でビットごとに役割がある場合もあり、そうしたことも考慮してHexエディタで複数箇所を適切に更新しなくてはいけません。これを手作業で都度行うのは手間がかかります。

Hexエディタで表示した様子

とは言え、これについては特段難しい課題という訳ではありません。Protocol Buffers v3以降はJSONと相互変換可能な仕様で、必要に応じてJSONなどに変換した上で編集し、送信する時には再度Protocol Buffersにエンコードすれば解決できます。各種ツールやその拡張機能でそのようなことをするものもいくつかあります。
※ただしパラメータ名がフィールド番号だと役割を直感的に読み取れず手間がかかります。この解決方法は追って紹介します。

HTTP/2 の利用に関する課題

HTTP/2 は2015年にRFC7540として標準化され、一般的なWebサーバやクライアントであればほとんどはサポート済の状態です。しかし、Webアプリケーション診断に関わる多くの方が愛用するBurp Suiteなど汎用的なツールの多くは長らくHTTP/2に未対応または部分的対応の状態でした。そもそもHTTP/2メッセージを扱えないという課題です。
※なお、Burp Suiteはセキュアスカイ・テクノロジーさんのエンジニアブログに記事がある通り、2020年6月にHTTP/2の試験的・部分的サポートを始めました。

HTTP/2はプロトコルとしてHTTP/1.1までとはだいぶ異なります。
HTTP/1.1はリクエストとレスポンスに共通して、基本的にはテキストベースで1メッセージ内にヘッダとボディを順に記述する形でシンプルです。
それに対してHTTP/2ではヘッダはHEADERSフレームで送り、従来のボディにあたる部分を別途DATAフレームで送るなどコネクションとストリーム、フレームの考え方や、バイナリ指向であったりなど多くの面で異なります。ヒューマンフレンドリーから、よりマシンフレンドリーで効率重視のプロトコルになりました。

ただし、一般的なWebブラウザなどは1.1と2の両対応が通常で、通信相手と双方が対応している方を選択してくれます。URLやCookieなどのヘッダ、データ本文といった従来と同じメッセージを伝送する方式が異なるだけで、そこはブラウザやサーバがうまくやってくれます。
そのため、Webサーバ層を含むアプリケーションの場合は除きますが、Webアプリケーションのコードを書く時にどちらの通信かを考えなくてもHTTP/2対応サイトを作ることはでき、1.1との互換性も保たれます。従って脆弱性診断ツールもHTTP/1.1だけで従来通り多くのWebアプリケーションを診断可能で、HTTP/2対応の優先順位が高くなかったのではないかと思います。

しかしgRPCの利用も含め、ブラウザ以外のソフトウェアでHTTP/2のみを使用するシステムも増えてきました。そのような中mitmproxyやDeNAさんが公開されているPacketProxy、直近でBurp SuiteなどのツールもHTTP/2を徐々にサポートしてきたものの、それぞれにまだ部分部分で未サポートの動作があります。例えば通信開始の仕方がいくつかある中で、Webブラウザが通常行う以下の1, 2番は概ね対応していますが、3番には未対応であるなどです。

  1. ALPNを介したHTTP/2接続
  2. 一度HTTP/1.1で接続した上でUpgradeヘッダによってHTTP/2へ切替
  3. 通信相手がHTTP/2対応である前提で、ネゴシエーションを省略してConnection Prefaceを送信する

他に、近年の流れもあってHTTPSでHTTP/2を利用することが主だと思いますが、平文(http://)での通信も可能で区別のためh2cと呼びます。mitmproxyやBurp Suiteはこれを書いている時点ではh2cに対応していません。
Server push、PriorityといったHTTP/2の機能や、Trailer(データの後に続くヘッダ)も対応していないものが多いようです。他にもBurp Suiteで言うと透過Proxy(Invisible proxy)モードではHTTP/2が未サポートで、gRPC通信でもこれらが支障になりがちです。

例えば、利用する開発フレームワーク等にもよると思われますが、いくつかの言語でドキュメントに従ってHTTPS対応のgRPCクライアントを素直に書いてみると、信頼するCA証明書をクライアントに持たせることが多いようで、これまでに見たシステムはそのパターンがほとんどでした。つまりシステム(OS)が持つCA証明書を信頼するのではなく、クライアントソフトウェア自身が持つCA証明書により通信相手のサーバ証明書を検証します。これは広義でTLS証明書Pinningの一形態と言えます。
※言語やフレームワークによる例外もあるかもしれませんが、もちろんOSの持つCA証明書を使用させる実装も可能です

Pinningは適切に運用すれば中間者攻撃対策を強化でき、セキュリティの観点からすると推奨されます。しかし、ソフトウェア品質のために行う脆弱性診断もまた中間者として行う手法が主流で、脆弱性診断対象とする環境およびビルドでは一時解除頂くなど、事前に方針をご相談させて頂きます。

と言うのは、Burp SuiteなどのProxy型ツールでは、Proxyが発行したCA証明書を予めTLSクライアント側のOSに信頼させた上で、そのCAが署名した診断対象ドメインのサーバ証明書をTLS/Proxyクライアントであるブラウザ等に提示することで、クライアントに対して診断対象サーバのふりをしてTLS通信に介入します。Pinningされていると通信に介入できず、これはgRPCやHTTP/2以外でも回避しなければアプリケーション層の効率的な診断の支障になります。

クライアントの動作を外から改変してPinningを無効化する方法はありますが、必ずしも短時間でクライアントの正常動作に影響なく無効化できるとは限りません。また、特にスマホアプリは正規の署名が施されたアプリでないとOSやストア等のアクセス面で不都合が生じたり、root化して証明書検証を回避しようとしたらそれが検知されたりしがちです。

開発元で診断対象クライアントのPinningを無効化して頂くことが可能ならそうして頂けるよう事前相談させて頂きますが、その可否や必要工数は実装や状況によりけりで、場合によってはスケジュールに影響なく行うことは不可という結論の場合もあります。
また開発者の立場から言えば、忙しい中一部のテストだけのために本来想定していない動作変更を、ある程度以上の工数をかけて行うというのは本末転倒であるというのがごく自然な考えです。

Pinningを無効化できなかった場合の残りの選択肢として、開発元で比較的現実的な工数と変更範囲でPinningもろとも「HTTPS自体をOFFにして頂く」という手が考えられます。これはいくつかの言語で見る限りgRPCクライアントとサーバで接続と待受の最小1箇所ずつのメソッドの変更で実現できることがあります。
…しかしh2cだと今度はツール側で対応していないのでした。

また、前項に引っかからない場合も、OSのProxy設定を使用しないgRPCクライアントが多い印象です。通信がProxy型ツールを通ってくれないといけませんので、対処としてネットワーク上でクライアントからの通信をProxyにルーティングしてみても、それを受け付けるための透過ProxyモードだとHTTP/2に対応していないのでした。骨が折れます。

下図のようにBurp Suiteなどの前後にHTTP/2対応Proxyを置いてHTTP/2とHTTP/1.1を相互変換するというやり方もありますが、私の経験の限りgRPCでは、Trailerや大きいサイズのデータの扱いなどに問題が生じたりと、組み合わせ等にもよると思われますが有効な場面は限定的でした。HTTP/2をLISTENして内部的にはHTTP/1.1メッセージとして扱うツールもあるようで、それらも同様でした。

HTTP/2とHTTP/1.1の相互変換(Burp SuiteのHTTP/2対応前の社内勉強会スライドより抜粋)
HTTP/2とHTTP/1.1の相互変換(Burp SuiteのHTTP/2対応前の社内勉強会スライドより抜粋)

既存のツールやその拡張スクリプトを書くなどしたり色々試してはその度に多数ある落とし穴のどれかにはまることを繰り返し、前項も踏まえてたどり着いた結論としては、『少なくとも今のところはgRPCの対応にフォーカスした、既存ツールに近い使い勝手の専用ツールを用意するのが確実だろう』ということでした。

※なお補足として、各ツールとも日々開発が進んでおり、HTTP/2でもブラウザ向けの一般的なWebアプリケーションに限れば概ね普通に脆弱性診断できるようになってきました。PacketProxyは最近の数回のリリースでgRPCの対応が改善され、以前動作しなかったシステムでも今は動作するようになっていたりと、着実に良くなっています。個人的にはそちらやBurp Suiteの拡張などでgRPCなどが十分に扱えるようになると良いな…と思う一方、現段階ではまだ選択肢が複数あった方が実務者として問題解決の助けになる段階かと考えています。

開発したツールを紹介したいと思います…が、ここまでで長くなってしまいましたので、記事を分けて後編に続きます。

<後編へ続く>

セキュリティ診断ならお任せください

Webサービスやアプリにおけるセキュリティ上の問題点を解消し、収益の最大化を実現する相談役としてぜひお気軽にご連絡ください。
国内トップクラスのセキュリティエンジニアが診断を行います 。

ホワイトハッカー

セキュリティ診断
ご相談はこちら