本サイトは、快適にご利用いただくためにクッキー(Cookie)を使用しております。
Cookieの使用に同意いただける場合は「同意する」ボタンを押してください。
なお本サイトのCookie使用については、「個人情報保護方針」をご覧ください。

最新情報

2017.08.30

Burp拡張を作ろう - PassiveScan編

みなさん、Burp suiteライフをいかがお過ごしでしょうか? 以前にBurp拡張を作ろうという記事を書かせていただきました。


今回は、Burp suite の Extensionをつかって脆弱性スキャナの実装を作成してみます。Extensionを作成するための基礎的な知識がない場合は、前回の記事を適宜参照していただければと思います。


脆弱性スキャン方式にはPassiveScanとActiveScanの2種類がありますが、まずはこのスキャン方法の違いを説明します。


PassiveScan
受動的なスキャン方式で、送信リクエストに手を加えることをせずに、送信リクエストやレスポンスの内容をもとに判断を行うスキャンになります。 Webサーバに影響を与えることのない安全なスキャンが可能です。
ActiveScan
能動的なスキャン方式で、送信リクエストを変更することによるレスポンスの挙動差やレスポンスの応答時間等を基に判断を行う動的なスキャンになります。 Webサーバに影響を与える可能性がありますが、PassiveScanよりも多くの情報を得られるスキャンです。

Burp suite にもScanner機能はついており、PassiveScan、ActiveScanとも利用可能です。また Burp Extension を利用することでシグネチャを追加することが可能となっています。 ところがこの Scanner機能はプロフェッショナル版(有償版)のみで利用可能な機能となっています。プロフェッショナル版はユーザライセンスで年間 $349.00 という結構いい値段です。 このためプロフェッショナル版をお持ちでない方も多いと思いますので、フリー版でも利用可能なスキャナを作成したいと思います。


PassiveScanとActiveScanはそれぞれの利点があり、どちらが優れているというのはないため状況に応じて使い分けるのがよいかと思います。 今回は、PassiveScanの実装を行います。これは、ActiveScanはプログラムが複雑になりがちでフリー版での実装も考慮するとかなりのコード量になってしまい、説明を十分にしきれなくなってしまいます。このためまずは、手ごろなPassiveScanの実装を行いたいと思います。



Extenderの設計

どのようなPassiveScanを実装するかについてですが、サーバのバナー情報を取得するスキャナを実装することにします。


サーバのバナー情報は、サーバで稼働しているサービスの種類やバージョンなどを含む情報となっており、取得できた情報が攻撃のヒントとなる場合があります。 ただし、取得できたサービスのバージョンが脆弱性を含むバージョンのものであったとしても、実際にはセキュリティパッチが当たっている場合も多々あるため、必ずしもしも問題があるわけではない点にご注意ください


バナーの例
Server: Apache/2.2.8 (Ubuntu) DAV/2
X-Powered-By: PHP/5.2.4-2ubuntu5.10

バナーの形式はほぼきまっており検出が容易なのと、またプロフェッショナル版のPassiveScanでも検出はされない事項であることから、PassiveScanの実装の例としては手ごろな事例といえるかと思います。



BurpExtenderクラスの作成

今回紹介するコードは、プロフェッショナル版の場合とフリー版の場合で処理を切り分けており、プロフェッショナル版を利用の場合は、プロフェッショナル版特有の機能を利用したスキャナとなっています。 まず、プロフェッショナル版とフリー版の判定方法ですが、これには、IBurpExtenderCallbacks インタフェースの getBurpVersion メソッドを利用します。 getBurpVersionメソッドはバージョンを含んだ配列を返します。取得した配列の0番目には、Burpのプロダクト名が含まれており、'Professional'という文字を含んでいた場合にプロフェッショナル版であると判断しています


その後の処理で、 bannerMap ハッシュ変数に、マッチさせたいバナーを正規表現形式でハッシュのキーとして格納しています。 ハッシュの値には、HighlightColor.GREEN を格納していますがこれについては後ほど説明します。


マッチさせるバナーとして以下の2パターンを指定しました。
(Apache/[0-9.]+)
(PHP/[0-9.]+)

バナーはサービスの種類のみの場合もありますが、ここではバージョン情報まで含む場合にマッチするようにしています。 ここは他にもバリエーションが考えられるかと思いますので、必要に応じて追加してもらえればと思います。


また、プロフェッショナル版の場合は、IBurpExtenderCallbacks インタフェースの registerScannerCheck メソッドにてPassiveスキャン処理を実装し、 フリー版の場合は、IBurpExtenderCallbacks インタフェースの registerHttpListener メソッドを利用することでPassiveスキャン処理を実装しました。


BurpExtender.java
  1. package burp;
  2. import ...;
  3. public class BurpExtender implements IBurpExtender, IHttpListener {
  4. public enum HighlightColor {
  5. WHITE, RED, ORANGE, YELLOW, GREEN, CYAN, BLUE, PINK, MAGENTA, GRAY;
  6. @Override
  7. public String toString() {
  8. return name().toLowerCase();
  9. }
  10. };
  11. private IBurpExtenderCallbacks callbacks = null;
  12. private final Map<Pattern, HighlightColor> bannerMap = new HashMap();
  13. private boolean burpProfessional = false;
  14. @Override
  15. /* IBurpExtender interface implements method */
  16. public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
  17. this.callbacks = callbacks;
  18. // burp Professional 判定
  19. String[] version = callbacks.getBurpVersion();
  20. this.burpProfessional = (0 <= version[0].indexOf("Professional"));
  21. // banner登録
  22. this.bannerMap.put(Pattern.compile("(Apache/[0-9.]+)"), HighlightColor.GREEN);
  23. this.bannerMap.put(Pattern.compile("(PHP/[0-9.]+)"), HighlightColor.GREEN);
  24. // 必要に応じて追加
  25. // プロフェッショナル版の場合
  26. if (this.burpProfessional) {
  27. callbacks.registerScannerCheck(professionalPassiveScanCheck());
  28. }
  29. // フリー版の場合
  30. else {
  31. callbacks.registerHttpListener(this);
  32. }
  33. }
  34. // コード後半省略
  35. }

プロフェッショナル版の実装

次に professionalPassiveScanCheck メソッドを見てみましょう。 プロフェッショナル版においては、IScannerCheck インタフェースを実装することで独自のスキャンを実装可能となっています。 IScannerCheck インタフェースには doPassiveScan、doActiveScan、consolidateDuplicateIssues メソッドを持っていますが、 今回は PassiveScan の実装を行いたいため、doActiveScanメソッドでは常に null を返しています。 consolidateDuplicateIssues メソッドは同一のURLが複数回出現した場合などに、同じ問題のIssueが大量に報告されるのを防ぐためのメソッドです。 このメソッドでは報告済みのものと同一とみなせる場合は報告しないようにすることが可能です。


BurpExtender.java
  1. // プロフェッショナル版の実装
  2. public IScannerCheck professionalPassiveScanCheck() {
  3. return new IScannerCheck() {
  4. @Override
  5. public List<IScanIssue> doPassiveScan(IHttpRequestResponse baseRequestResponse) {
  6. String message = getByteToStr(baseRequestResponse.getResponse());
  7. List<int[]> responseMarkers = new ArrayList<>();
  8. for (Pattern ptn : bannerMap.keySet()) {
  9. Matcher m = ptn.matcher(message);
  10. while (m.find()) {
  11. // 検出範囲の追加
  12. responseMarkers.add(new int[]{m.start(), m.end()});
  13. }
  14. }
  15. if (responseMarkers.size() > 0) {
  16. List<IScanIssue> issues = new ArrayList<>();
  17. IHttpRequestResponseWithMarkers messageInfoMark = callbacks.applyMarkers(baseRequestResponse, null, responseMarkers);
  18. issues.add(makeBannerIssue(messageInfoMark));
  19. return issues;
  20. }
  21. return null;
  22. }
  23. @Override
  24. public List<IScanIssue> doActiveScan(IHttpRequestResponse baseRequestResponse, IScannerInsertionPoint insertionPoint) {
  25. return null;
  26. }
  27. @Override
  28. public int consolidateDuplicateIssues(IScanIssue existingIssue, IScanIssue newIssue) {
  29. if (existingIssue.getIssueName().equals(newIssue.getIssueName())) {
  30. // 同一とみなせる場合は報告をスキップ
  31. return -1;
  32. }
  33. return 0;
  34. }
  35. };
  36. }
  37. public IScanIssue makeBannerIssue(final IHttpRequestResponse messageInfo) {
  38. return new IScanIssue() {
  39. @Override
  40. public URL getUrl() {
  41. IRequestInfo reqInfo = callbacks.getHelpers().analyzeRequest(messageInfo.getHttpService(), messageInfo.getRequest());
  42. return reqInfo.getUrl();
  43. }
  44. @Override
  45. public String getIssueName() {
  46. return "Server Banner Disclosure";
  47. }
  48. // コード中略
  49. };
  50. }

実際のスキャン処理を実装している doPassiveScan メソッドを見てみましょう。 doPassiveScanメソッドの引数の baseRequestResponse 変数には、スキャン対象となるリクエストおよびレスポンスの内容を取得するための、 getRquest 及びgetResponse メソッドがありますが、これらのメソッドの戻り値はバイト配列になっています。 バイト配列のままでは、文字列をマッチさせる処理が不便なため、一旦バイト列をgetByteToStrメソッドで文字列に変換しています。


ここで重要なのは、文字コードとして ISO_8859_1(Latin-1) を指定しているということです。 リクエストやレスポンスの文字コードは、Webアプリケーションによってさまざまで、文字コードを決め打ちにすることはできません、 文字コードを指定できれば越したことはないのですが、適切な文字コードの判別自体が困難な場合も存在します。 このような場合に、誤った文字コードにマッピングすると、マッピングできない文字列が ? に変換されてしまうなどの現象が起きてしまいます。


ISO_8859_1(Latin-1) の文字コードを利用することで元の文字列の情報を失うことなく利用することが可能です。 これは、Burp suite の Extensionを作成する上で必須のテクニックともいえます。


getStrToByte,getByteToStr メソッド
  1. // 文字列を生のバイト列に変換
  2. public static byte [] getStrToByte(String str) {
  3. return str.getBytes(StandardCharsets.ISO_8859_1);
  4. }
  5. // 生のバイト列を文字列に変換
  6. public static String getByteToStr(byte [] bytes) {
  7. return new String(bytes, 0, bytes.length, StandardCharsets.ISO_8859_1);
  8. }

ちなみに、これと同様な処理を行うメソッドとして IExtensionHelpers インタフェースの bytesToString 及び stringToBytes メソッドがありますが筆者はBurp suiteが用意したメソッドは利用せず、 自作のメソッドをつくって呼び出しています、これは、IExtensionHelpers インタフェースが存在する以前からExtensionをよく作成していたため、過去との一貫性を考慮した点と IExtensionHelpers インタフェースのメソッドは static メソッドではない為、インスタンスを取得する必要があり、共通ライブラリとして利用するにはやや不便なためです。


話が脱線してしまいましたが、その後の処理を見てみましょう、プロフェッショナル版 のScanner機能には検出した範囲をハイライトさせる機能がついており、IBurpExtenderCallbacks インタフェースの applyMarkers メソッドにて 正規表現でマッチハイライトしたい範囲を追加しています。


makeBannerIssue メソッドは、脆弱性のアドバイザリとして表示する情報を作成しています。 この内容は動的に変更することも可能ですが今回は、URL以外は固定の内容としています。


実際に検出された場合の画面キャプチャを以下に示します。



ApacheやPHPのバナー情報がハイライトされているのが確認できるかと思います。 このようにプロフェッショナル版では、問題となっている箇所のハイライトまで可能なためかなり使い勝手がよいのがわかるかと思います。


フリー版の実装

さてフリー版でもおなじようなことを行いたいわけですが、当然ながらScannerタブの利用はできません。 Scannerタブと同じような機能を作りこめば理論上は可能かとは思いますがそこまで作りこむのは面倒です。 ここでは、フリー版にもある、HTTP historyのハイライトカラーを変更する機能とコメントを利用して実装してみることにします。


ここでは、IHttpListener インタフェースのprocessHttpMessage メソッドをオーバライドしています。 processHttpMessage メソッドの中でフリー版の処理を呼び出しています。


BurpExtender.java
  1. // フリー版の実装
  2. @Override
  3. public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) {
  4. if (toolFlag == IBurpExtenderCallbacks.TOOL_PROXY && !messageIsRequest) {
  5. freePassiveScan(messageInfo);
  6. }
  7. }
  8. public void freePassiveScan(IHttpRequestResponse messageInfo) {
  9. String message = getByteToStr(messageInfo.getResponse());
  10. for (Pattern ptn : this.bannerMap.keySet()) {
  11. Matcher m = ptn.matcher(message);
  12. if (m.find()) {
  13. String bunner = m.group(1);
  14. HighlightColor color = this.bannerMap.get(ptn);
  15. // 既にコメントがある場合は追記
  16. String comment = (messageInfo.getComment() == null) ? "" : messageInfo.getComment() + " ";
  17. messageInfo.setComment(comment + bunner);
  18. messageInfo.setHighlight(color.toString());
  19. }
  20. }
  21. }

freePassiveScan メソッド見てみましょう。 正規表現でマッチする処理は プロフェッショナル版と同じです。 マッチ後は検出したバナー情報をHTTP historyのコメントに設定するのと、bannerMap ハッシュ変数に値として設定された色をハイライトカラーとして設定しています。 これによりHTTP historyを見たときに問題の箇所を探しやすくなります。


実際に検出された場合の画面キャプチャを以下に示します。



検出された箇所が見やすくなったかと思います。 ハイライトカラーとコメントを変更した場合にいいところは、もうひとつあって、絞込みが容易ということです。 HTTP historyのフィルターには Filter by annotation という項目があって、ここの Show only commented items、Show only highlighted items にチェックすることで 絞りこめる点です。



プロフェッショナル版のように検出したバナーをハイライトすることまではできませんが、かなり探しやすくなったかと思います。 ただし、今回の実装方法では、同一の問題とみなせる場合でも常に報告してしまい、プロフェッショナル版のように報告済みの問題の排除まで行えていません。 この辺はみなさまへの課題としたいと思います。このほかにも独自のスキャナを実装してみるのもよいかと思います。


今回作成したソースを以下においていますので必要に応じて、利用頂いて構いません。


ソースへのリンク



関連ブログ記事

プロフェッショナルサービス事業部
諌山貴由