脆弱性診断サービスを支える技術 その一

こんにちは。技術部エンジニアの石垣です。

はじめに

診断サービスでは直接見える形で品質や体制等を判断することが難しい側面があると考えます。

そこでお客様から信頼いただくための取り組みとしてして安定的な脆弱性診断サービス提供するためにどのような基盤が必要となるか品質の確保にかかる取り組みの考察とシステム開発の企画、設計、実装、運用等の経緯およびについてご紹介したいと思います。

今回ご紹介するシステムは7-8年前にウェブアプリケーション診断向けに開発したシステムとなります。

背景とシステム概要

まず背景をご説明する前に弊社が行う脆弱性診断の一つであるWebアプリケーション脆弱性診断サービスの業務の流れをざっくりと説明させて頂きその後背景を説明したいと思います。

まずお客様から打診が届き、NDAを締結後、弊社営業担当、見積担当者(診断員がアサインされる)が診断対象のウェブアプリケーションに関してヒアリング(システム概要、用途、ユースケース、想定する脅威/リスクなど)を行います。その後診断対象の範囲および、診断で必要となる情報を受領し、ウェブアプリケーションの利用技術、特性等を確認の上見積を行います。そして診断対象、診断方針や内容、および実施日程の調整や見積提示を行い診断にかかる諸処の合意形成が行われ契約となります。

診断では範囲対象、システム特性、技術要件、業務要件等を鑑み診断員のチーム編成(診断員は2名以上がアサインされ診断結果の相互検証を行い品質を担保する方式をとっています。また弊社正社員が診断を行います。なおアルバイトや第三者への委託等による診断は行っておらず自社で完結しています。)をおこないます。その上で疑似攻撃を行い脆弱性の検証を進め、診断進捗状況を確認しつつ、必要に応じて速報をお客様に提供し、その後報告書を取りまとめ、必要に応じて報告会を行い、報告書を納品し、検収を受けて診断案件は終了となります。

弊社ではウェブアプリケーション診断を行う際には基本 Burp Suite Professional(必要に応じて他のツールを利用したり、ツールを作成することもあります。)を使用し診断を行っております。

2012年頃から社内では複数人のログを一括で管理を行う仕組としてBurp extensionを利用したシステムを既に運用していましたが、若干の取りこぼしのがあったり(再送機能も実装してあったけど……)、ログ収集サーバの負荷が他アプリケーションに影響を与えたり、統計分析/検索や履歴の確認等で必要なデータ項目が足りなかったりといくつかの課題を抱えていました。

また見積担当者によっては見積精度にばらつきがあったため、当初の見積を行った診断日数と実際に診断で必要となる診断日数に乖離が発生する場合がありました。

契約時に診断日程が確定していますので期日までに診断を終わらせる必要があります、従って診断期間中の進捗状況を把握したり、品質保持の観点から診断対象に対して、誰がいつ、どのURLのどのパラメータに対して何を行ったのかほぼリアルタイムに状況を把握したいという背景がありました。併せて収集した診断履歴を分析し診断支援を行う機械学習等の検討も同時に行っていました。

ということで設計を見直し再実装を行うことで

  • 見積作業の低減及び標準化
  • 診断作業の効率化、および品質向上支援(網羅性、緻密性の向上)

を実現できるBurp extensionと、ログを集約し解析するサーバプログラムを再実装しました。

弊社はセキュリティを生業とする会社ですので機能要件と非機能要件(可用性、機密性、完全性はもちろんのこと性能・拡張性、運用・保守性など)も考慮して設計から実装、テスト、リリース、運用、保守、管理までの全てのライフサイクルにおいて自社内で対応しています。

設計のポイントとしては診断サービスではログは無くなると監査的に非常に困るので、可用性を高めシステム耐障害性と保守運用の柔軟性を持たせるためにHA構成や処理能力も容易に拡張ができる構成にすべきと考えてスケールアウト可能な構成として Message Bus アーキテクチャをベースに作り込みました。

システム構成

MessageQueue(MQ)ではTIBCO® Rendezvous ™, ZeroMQ, OpenMAMA, NanoMQ などが有名ですが、障害性の高いミドルウェアである RabbitMQ( AMQP)を選定しています。

クライアントサイド実装

の間のAMQPメッセージ(以下ログメッセージ)には request + responseが含まれたログをProtocol Buffers形式で送信するBurp Suite extensionを作成しています。

動作としては

  1. ログメッセージの送信の前にクライアント(Burp Suite)はクライアント証明書認証方式でRabbitMQへ接続。
  2. 診断を行った際に発生する通信内容をProtocol Buffers形式に変換。
  3. 変換データはクライアント証明書を利用して改ざん防止のために HMAC を付与。
  4. 変換データは snappy で圧縮
  5. 圧縮データをRabbitMQのQueueにログメッセージとして送信。

の流れです。

当時の記録では開発環境のMacBookで約1000 messages (100kb) / sec 処理出来ていたので想定の性能要件を満たしていました。

サーバーサイド実装

MQ(Cluster RabbitMQ) ----> MQ Consumers(HTTP Message Parser Consumer,DB Logger Consumer,File Archiver Consumer )の動作は複数ありまして全体共通は

  • RabbitMQのQueueに保存された各ログメッセージを受信する。

そして各Consumerは以下の動作となります。

HTTP Message Parser Consumer

  • snappy形式で圧縮されたログメッセージを解凍しProtocol Buffers形式のデータにする。
  • Protocol Buffers形式のデータからHTTPのrequest/responseデータを取り出す
  • HTTPのrequest/responseデータを RFC2616 に従ってパースし構造化データにする
  • 構造化データをRabbitMQのQueue(DB Logger Consumerが受信する)へ送信(MQ Producerでもある)する。

DB Logger Consumer

  • 構造化済みHTTPメッセージ(及びメタ情報)をデータベースに保存する

File Archiver Consumer

  • ログメッセージをアーカイブ向けとして保存する。

こんな感じでメッセージは処理されていました。

非同期で処理されることと、同じメッセージを他のConsumersに振り分けることができるのでテストの際、プロダクションのデータを利用しながら動作検証できて非常に楽です。また開発者のスキルセットに併せてConsumer/Producer毎に開発言語をPythonやJava...etc選べるので実装しやすいです。

運用の観点ではConsumerが一つぐらい落ちても、Cluster RabbitMQでキューイングされるのでそんな困らないって感じです。

File Archiver ConsumerはJavaで実装しました。

またHTTP Message Parser ConsumerとDB Logger Consumerはhaskellで実装しました。

当時はstackが出てきたり hackageにライブラリがいろいろそろっていたので基本実装は非常に楽でしたが、httpのparserでrequest, response両方に対応したライブラリがどうも無かったので自分で実装してました。

作成したパーサはcriterionを利用しbenchmarkを計測した記録がありましたので貼り付けます。

計測データ一覧

  • post1.txt : 609 byte
  • post2.txt : 610 byte
  • multipart1.txt :5181 byte
  • multipart2.txt :18062 byte
  • response1.txt : 29939 byte

リクエストはPOSTとMultipartレスポンスデータ

※レスポンスデータは検索の為にcontent-typeのcharset から utf-8に変換し正規化

結果:

リクエスト+レスポンスデータのパースで平均85.15 μs、11743.98/sec のパフォーマンス。

benchmarking post request url-encoded 1
time                 33.21 μs   (32.82 μs .. 33.51 μs)
                     0.999 R²   (0.998 R² .. 0.999 R²)
mean                 33.27 μs   (32.85 μs .. 34.11 μs)
std dev              1.813 μs   (1.153 μs .. 3.039 μs)
variance introduced by outliers: 60% (severely inflated)

benchmarking post request url-encoded 2
time                 45.47 μs   (45.00 μs .. 45.88 μs)
                     1.000 R²   (0.999 R² .. 1.000 R²)
mean                 45.39 μs   (45.19 μs .. 45.69 μs)
std dev              800.5 ns   (596.6 ns .. 1.149 μs)
variance introduced by outliers: 14% (moderately inflated)

benchmarking post request multipart 1
time                 50.48 μs   (49.96 μs .. 51.07 μs)
                     0.999 R²   (0.999 R² .. 1.000 R²)
mean                 51.10 μs   (50.60 μs .. 51.81 μs)
std dev              1.903 μs   (1.403 μs .. 2.663 μs)
variance introduced by outliers: 40% (moderately inflated)

benchmarking post request multipart 2
time                 66.47 μs   (65.70 μs .. 67.23 μs)
                     0.999 R²   (0.998 R² .. 0.999 R²)
mean                 66.80 μs   (65.99 μs .. 67.96 μs)
std dev              3.242 μs   (2.571 μs .. 4.119 μs)
variance introduced by outliers: 52% (severely inflated)

benchmarking response text/html
time                 42.50 μs   (42.02 μs .. 43.03 μs)
                     0.999 R²   (0.998 R² .. 0.999 R²)
mean                 42.83 μs   (42.37 μs .. 43.44 μs)
std dev              1.653 μs   (1.360 μs .. 2.100 μs)
variance introduced by outliers: 43% (moderately inflated)

benchmarking response + reqest
time                 83.97 μs   (82.99 μs .. 84.99 μs)
                     0.999 R²   (0.998 R² .. 0.999 R²)
mean                 85.15 μs   (84.04 μs .. 87.22 μs)
std dev              5.063 μs   (3.202 μs .. 8.153 μs)
variance introduced by outliers: 61% (severely inflated)

コア一つで上記結果なので、複数のConsumerを動かすと処理性能はほぼ線形になります。

上記コンシューマーは稼働させてから数年運用していましたが、致命的なバグも無くプログラムは静かに稼働していましたが…….。現在は別の実装に置き換わっています。

システム基盤構成について

複数のRabbitMQとProducert/Consumerを動かすためにDockerを利用し運用していました。数台の筐体で冗長構成にしつつ多数のプログラムを動かすため、リソースの分離を検討していました。当時は既にLXCがありましたがテストや環境構築、デプロイが行いやすかったため最終的にDockerを選定しています。

今となっては当たり前に利用されてる基盤ですがが当時はDockerがリリースされて間もない時期であったため色々バグを踏み抜いたり試行錯誤しましたが今となっては色々勉強できたいい思い出です。

当時も現在も同じですが弊社では診断の情報は全て自社内で完結する方針をとっておりハードウェアレイヤーからアプリケーションまで全て自社で管理を行いシステム環境を構築し運用を行っています。このようにする事でクラウドインサイダーやサードパーティ(最近は Bring Your Own Key サービスが利用出来たりするので緩和等が可能となりつつある)によるデータへのアクセスの脅威や保管するデータやサーバにまつわるデータローカライゼーション等の法規制の影響を避けるためでもあります。また診断情報などは機微な情報(例えばゼロデイエクスプロイト)となるため、法規制を知らずにデータを保管すると法令違反となる可能性があるため全て自社内で完結させています。

インサイダーやサードパーティの脅威、データローカライゼーションやゼロデイエクスプロイト扱い関する詳細を知りたい方は以下に色々記載されていますので参照下さい。

securityboulevard.com

github.com

www.soumu.go.jp

www.mofa.go.jp

他のシステムとの連動について

ウェブアプリケーション診断に限らずネットワーク、制御系、ハードウェア診断等でも速報や最終的に報告書を依頼元へ成果物として納品しますので、診断対象のシステム概要、環境、診断条件、脆弱性の取りまとめを行う別の内製システム(通称レポ)と連動し、ログ履歴を作業履歴を参照可能となる様に連動させていました。

レポシステムではセキュリティ管理のために、FIPS 201規格の物理トークンを利用しクライアント証明書認証を行い、更に診断に参加している診断員のみが参照可能であったりと、様々なセキュリティアクセス管理等を行っていますが本件とは別チームが開発を行っているので、詳細は別チームの誰かが記事を投稿するのを期待したいと思います。

おわりに

構築したシステムが稼働してからは細かな分析を行う環境ができましたのでR Studio等を利用して見積の補助的な分析や、様々な診断を行った結果を統計分析したり、診断の過程においてリアルタイムに進捗状況等把握したり等、概ね当初想定した通りの課題解決が実現出来ました。とは言え時間が経つともっとこうした方がいいと考えた社内のエンジニアがさらに改善を重ね現在は別のシステムが社内では稼働しています。

本稿では簡単ではありますが過去に開発し運用していた弊社内製システムの一部を紹介させていただきました。

© 2016 - 2024 DARK MATTER / Built with Hugo / Theme Stack designed by Jimmy