ブラウザを用いたリスク検出ツールchromepatrolの紹介とChrome DevTools Protocolの話
山田快
こんにちは。RECRUIT Job for Student 2020(通称、夏バイト)に参加していた、山田快(Twitter: @gpioblink)と申します!この記事では、期間中に作成したサイトの問題を検出するツールや、開発中に学んだChrome DevTools Protocol(以下、CDP)の話をしていきます。
TL;DR
- 近年、ブラウザの仕様変更など外部の要因による問題や障害が起きやすくなった
- CDPを使えばブラウザから詳細な情報が取れる
- CDPから取れる情報をもとにブラウザのリスク検証をするツール「chromepatrol」を作成した
自己紹介
じぶりん(@gpioblink)と申します。最近は低レイヤーに興味を持ち、低レイヤー寄りのリバースエンジニアリングを行なっています。
今回、RECRUIT Job for Student 2020に参加したのは、「セキュリティの仕事がどんなものか経験したい」理由からになります。
リクルートとの面接を通して、セキュリティの部署であるPSG(プラットフォームセキュリティグループ)に配属されました。PSGではプロダクトセキュリティ向上のためのツール開発や各サービスの開発チームに入り込んだ技術支援を行なっており、今回ツール開発に携わりました。
作ったもの
今回は、「ブラウザの仕様変更に起因するリスクの検出ツールの作成」というテーマで開発を行い、ブラウザのリスク検出ツール「chromepatrol」を作成しました。
サービスは外部のシステムと連携されて動いている
近年のサービスは、社内で構築したシステムだけで完結することはまずありません。広告ネットワークや決済APIなど、外部のシステムと連携することが当たり前になっています。そのため、自社開発の部分だけではなく、社外の接続先に対する問題を検出する必要があります。
そこで、今回は社外の接続先のリスク検知もできるツール、名付けて「chromepatrol」を作成しました。
「chromepatrol」は、その名の通りGoogle Chromeを起動し、対象のシステムのページをブラウザでレンダリングしながらリスク検出を行います。リスク検出には、CDPを経由してChromeのデベロッパーツールから取れる情報を使用しています。今回は、このCDPから取れる情報を掘り下げて説明していきたいと思います。
CDPから取れる情報
CDPは、Chromeのデベロッパーツールが内部で使用しているAPIです。CDPはWebSocketベースのプロトコルですが、それをプログラムから簡単に操作するためのnodeやgoのライブラリが公開されています。今回はnodeのライブラリであるchrome-remote-interfaceを用いて実装しました。
Chrome DevTools Protocol
https://chromedevtools.github.io/devtools-protocol/
CDPから取得できる情報の解析
CDPからはかなり多くの情報が取れるのですが、使う情報を決めやすくするためChromeのデベロッパーツールが行なっている通信を覗きながら作業を行いました。
この作業は、Macの場合次の手順で行えます。
1 2 3 |
$ alias chrome="/Applications/Google Chrome.app/Contents/MacOS/Google\ Chrome" $ chrome --headless --disable-gpu --remote-debugging-port=9222 https://example.com |
この後、通常の手段で立ち上げたChromeからDevTools Protocolが起動しているポート(http://localhost:9222)にアクセスし、開きたいタブを選択するとデバッグ画面が表示されます。さらにショートカットキーでデベロッパーツールツールを開き、WebSocketの通信を覗くとデベロッパーツールツールがCDPで操作している内容が分かります。
devtoolをdevtoolで開きCDPの処理を調べました
Chromeが行なっているCDPの操作
ここで、実際にデベロッパーツールツールを見ながら、処理を見ていると、CDPはネットワーク、セキュリティといった機能ごとに、enableというコマンドが用意されていることに気づきました。このコマンドでenableにするとその機能のイベントが取得できるようになります。
このツールではリスク検証をするために少しでも多くの情報を入手したいため、全ての機能を有効にしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
async function enableEventListeners(client) { const {Network, Page, Runtime, DOM, CSS, Debugger, Overlay, Profiler, Log, Audits, ServiceWorker, Inspector, HeapProfiler, Security} = client; //全ての機能を有効にする await Network.enable(); await Page.enable(); await Runtime.enable(); await DOM.enable(); await CSS.enable(); await Debugger.enable(); await Overlay.enable(); await Profiler.enable(); await Log.enable(); await Audits.enable(); await ServiceWorker.enable(); await Inspector.enable(); await HeapProfiler.enable(); await Security.enable(); } |
Chromeから通知されるリスクの取得
Chromeでは、今後廃止になるような機能を使用している場合、「deprecation warning」といったメッセージで、デベロッパーツールのissueやconsole欄に通知が表示されます。これらの情報は、CDPの場合、Audits.issueAdded
やNetwork.requestWillBeSentExtraInfo
、Network.responseReceivedExtraInfo
、Security.securityStateChanged
といったイベントに関連づいています。
- Audits.issueAdded: SameSiteCookieIssue, MixedContentIssue, BlockedByResponseIssue, HeavyAdIssue, ContentSecurityPolicyIssueを検出したらイベントを返す。
- Network.requestWillBeSentExtraInfo: 送信されなかったCookieがある場合、そのCookieの詳細と送信されない理由を返す
- Network.responseReceivedExtraInfo: 受信時にブロックされたCookieがある場合、そのCookieの詳細とブロックされた理由を返す
- Security.securityStateChanged: Chromeのセキュリティタブにある証明書の詳細情報や暗号化の状態について返す
また、特段のイベントとして用意されていないものもあったので、
- Runtime.consoleAPICalled: コンソールに表示するメッセージが投げられた時に、その文字列やlog,debugなどの種類を返す
を使用して、コンソール上にWarningやErrorとして表示されるメッセージも取り出せるようにしました。
カスタムルールの作成
リスクを十分に取得するためには、Chrome側から通知されるリスクだけでは不足します。
そのため、今回はいくつかのカスタムルールも追加しました。
例えば、以下のカスタムルールでは、30日以内に期限が切れるサーバ証明書を検出しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// 証明書失効 N日前検知ルール (N = 30) const EXPIRE_N = 30; Security.securityStateChanged((params) => { const report = {title: `Custom - CertificateAlmostExpired`, description: "", advise: ""}; let problemFlag = false; params.explanations.forEach(element => { element.certificate.forEach(cert => { const certDate = getCertificateExpirationDate(cert); if(certDate < getDateNDaysBeforeFromToday(EXPIRE_N)) { problemFlag = true; report.description += `A certificate for this site will be expired at ${certDate}\n`; } }); }); if(problemFlag) { result.push(report); console.log(report.title); } }); |
こちらでは、信頼取り消しが予定されているサーバ証明書を検出しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 任意の認証機関を検知するルール const BAD_SSL_AUTHORITIE = "Bad CA"; Security.securityStateChanged((params) => { const report = {title: `Custom - BadSSLAuthoritie`, description: "", advise: ""}; let problemFlag = false; params.explanations.forEach(element => { element.certificate.forEach(cert => { const certName = getCertificateCommonName(cert); if(certName.indexOf(BAD_SSL_AUTHORITIE) != -1) { problemFlag = true; report.description += `A certificate has bad authoritie. ${certName}\n`; } }); }); if(problemFlag) { report.advise = `${BAD_SSL_AUTHORITIE} causes many problem. Please consider to use other certificate.`; result.push(report); console.log(report.title); } }); |
自動でリスク検知する
ここまで、「chromepatrol」について説明してきましたが、今の状態では単一のサイトにアクセスしてリスク検知することしかできません。そのため、複数サイトから同時かつ定期的にリスク検知するため、NT-Dというツールに統合しました。
NT-Dのアーキテクチャ
NT-Dとは、PSGの西村(nishimunea)さんが開発しているオープンソースのリスク検知向けタスクマネージャーです。Kubernetes上でURLやドメインにwpscanやnmapといったOSSのリスク検出ツールを定期的に実行できる仕組みになっています。今回私は、このNT-D上でchromepatrolが動作するように調整しました。
まとめ
今回、作成したコードは、私のGitHub上にOSSとして公開したので、興味のある方はぜひ利用してみてください。
https://github.com/gpioblink/chromepatrol
また、RECRUIT Job for Student 2021(通称、冬バイト)の応募も始まっています。
私はセキュリティ部署に実際に入り実際に開発することで、セキュリティエンジニアの働き方が具体的に分かり、とても有意義な経験ができました。みなさんもぜひ参加してみてください。
最後までご覧いただきありがとうございました。