0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Metasploitで、davtestのようなモジュールを作成する

0
Last updated at Posted at 2026-04-11

はじめに

Metasploitで、davtestのようなモジュールを作成します。
davtestでurlだけ指定して実行した場合と同様にフォルダを作成して、各種ファイル形式をアップロードして、実行可能か、テストします。
modules/auxiliary/scanner/http/http_put.rbを元に作成しました。
テストは、php,txt,html,shtmlしかしていません。

Msf::Exploit::Remote::HttpClient

send_request_cgi

このメッソドを使用して、httpリクエストを送信します。
res = send_request_cgi(opts = {}, timeout = 20, disconnect = true)

Options Hash (opts):

Content-Type ('ctype' => String)
ヘッダー値(POSTリクエストのデフォルト値):application/x-www-form-urlencoded
URI ('encode_params' => Bool)
GETまたはPOST変数(名前と値)をエンコードします。デフォルト:true
GET ('vars_get' => Hash)
クエリ文字列に変換されるハッシュとしての変数
POST ('vars_post' => Hash)
POSTデータに変換されるハッシュとしての変数
POST ('vars_form_data' => Hash)
POSTデータに変換されるハッシュとしての変数
User-Agent ('agent' => String)
ヘッダー値
Connection ('connection' => String)
ヘッダー値
Cookie ('cookie' => String)
ヘッダー値
HTTP ('data' => String)
データ(一部のメソッドでのみ有用。RFC2616を参照)
URI ('encode' => Bool)
指定されたURIをエンコードします。デフォルト: false
HTTP ('headers' => Hash)
ヘッダーをハッシュで指定する、例: { "X-MyHeader" => "value" }
HTTP ('method' => String)
リクエストで使用するメソッド。RFC2616で定義されている標準メソッドに限定されない。デフォルト:GET
protocol, ('proto' => String)
デフォルト: HTTP
raw ('query' => String)
クエリ文字列をそのまま指定する。
Raw ('raw_headers' => String)
HTTPヘッダーをそのまま指定する。改行コード(\r\n)も必要
the ('uri' => String)
リクエストするURI
version ('version' => String)
プロトコルのデフォルト値: 1.1
Host ('vhost' => String)
ヘッダー値

HTTPメソッド

PROPFIND

指定したURIが示す資源の属性を取得します。具体的には、要求する属性をクライアントがWebサーバに送信すると、サーバはそれに対応した属性値を返します。また、その資源の属性全てを取得することも出来ます。
davtest.plでは、GETを送る前にPROPFINDを送っているようです。
最初、PROPFINDで、フォルダやファイルの存在を確認していましたが、nginxのWebDavでは、デフォルトでは、PROPFINDに対応していないようですので、GETで確認するようにしました。
以下のヘッダーを、raw_headerで追加しています。

Depth: 0

データとして

<?xml version="1.0" encoding="utf-8"?>
<D:propfind xmlns:D="DAV:">
<D:allprop/>
</D:propfind>

が必要です。
多分、すべてのプロパティを取得するということだと思います。

MKCOL

指定したURIの場所に新たな資源を作成します。
ファイルをアップロードするためのフォルダを作成します。

GET

通常のGETコマンド。
アップロードフォルダの存在確認とアップロードしたアプリの動作確認をします。

PUT

Webサーバー上のファイルをHTTPのPUTメソッドを用いて、ローカルのファイルで上書き更新、または新規作成します。
dataとして、ファイルの内容を引き渡すことで、path名のファイルを作成します。

プログラム

davtest.rb
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'securerandom'
require 'date'

class MetasploitModule < Msf::Auxiliary
  include Msf::Exploit::Remote::HttpClient
  include Msf::Auxiliary::WmapScanDir
  include Msf::Auxiliary::Scanner
  include Msf::Auxiliary::Report

  def initialize
    super(
      'Name' => 'HTTP Writable Path PUT File Testing',
      'Description' => %q{
        This module tests whether the web server's Path supports WebDAV via PUT and
        GET HTTP requests.

        Please configure the target host and Path.
      },
      'Author' => [
        'tsuko5963 [at] gmail.com'
      ],
      'License' => MSF_LICENSE
    )

    register_options(
      [
        OptString.new('PATH', [true, "Path to test", '/'])
      ]
    )
  end

  #
  # Do a PROFIND request to the server. Function returns the HTTP response.
  #
  def propaty_find(path, data, ip)
    begin
      res = send_request_cgi(
        {
          'uri' => path,
          'method' => 'PROPFIND',
          'ctype' => 'text/xml',
          'raw_headers' => "Depth: 0\r\n",
          'data' => data,
        }, 20
      )
    rescue ::Exception => e
      print_error("#{ip}: Error: #{e.to_s}")
      return nil
    end

    return res 
  end

  #
  # Do a MKCOL request to the server. Function returns the HTTP response.
  #
  def make_collection(path, ip)
    begin
      res = send_request_cgi(
        {
          'uri' => path,
          'method' => 'MKCOL',
        }, 20
      )
    rescue ::Exception => e
      print_error("#{ip}: Error: #{e.to_s}")
      return nil
    end

    return res 
  end

  #
  # Send a normal HTTP GET request. Function returns the HTTP response.
  #
  def do_get(path, ip)
    begin
      res = send_request_cgi(
        {
          'uri' => path,
          'method' => 'GET',
        }, 20
      )
    rescue ::Exception => e
      print_error("#{ip}: Error: #{e.to_s}")
      return nil
    end

    return res 
  end

  #
  # Do a PUT request to the server.  Function returns the HTTP response.
  #
  def do_put(path, data, ip)
    begin
      res = send_request_cgi(
        {
          'uri' => normalize_uri(path),
          'method' => 'PUT',
          'ctype' => 'text/plain',
          'data' => data,
        }, 20
      )
    rescue ::Exception => e
      print_error("#{ip}: Error: #{e.to_s}")
      return nil
    end

    return res
  end

  #
  # Main function for the module.
  #
  def run_host(ip)
    test_txt = {
      php: {content: '<?php print 7.8 * 6.4;?>', execmatch: '49.92', put: false, exec: false},
      txt: {content: 'TXT put via davtest', execmatch: 'TXT put via davtest', put: false, exec: false},
      html: {content: 'HTML put via davtest<br />', execmatch: 'HTML put via davtest', put: false, exec: false},
      asp: {content: '<html><body><% response.write (7.8 * 6.4) %>', execmatch: '49.92', put: false, exec: false},
      aspx: {content: '<html><body><% response.write (7.8 * 6.4) %>', execmatch: '49.92', put: false, exec: false},
      cfm: {content: '<cfscript>WriteOutput(7.8*6.4);</cfscript>', execmatch: '49.92', put: false, exec: false},
      cgi: {content: "#!/usr/bin/perl\nprint 'Content-Type: text/html\n\r\n\r' . 7.8 * 6.4;", execmatch: '49.92', put: false, exec: false},
      jhtml: {content: '<%= System.out.println(7.8 * 6.4); %>', execmatch: '49.92', put: false, exec: false},
      jsp: {content: '<%= System.out.println(7.8 * 6.4); %>', execmatch: '49.92', put: false, exec: false},
      pl: {content: "#!/usr/bin/perl\nprint 'Content-Type: text/html\n\r\n\r' . 7.8 * 6.4;", execmatch: '49.92', put: false, exec: false},
      shtml: {content: '<p><!--#config timefmt="%Y-%m-%d" --><!--#echo var="DATE_LOCAL" --></p>', execmatch: Date.today.to_s, put: false, exec: false},
    }
    
    dir_create = false
    path = datastore['PATH']
    #Add a leading '/' if one is not already present.
    if path[0, 1] != '/'
      path = '/' + path
    end
    #Add a '/' if there is no '/' at the end of the path.
    if path[-1, 1] != '/'
      path += '/'
    end

    puts "********************************************************\n Testing DAV connection"
    # Find Property of the upload directory
    # data = '<?xml version="1.0" encoding="utf-8"?><D:propfind xmlns:D="DAV:"><D:allprop/></D:propfind>'
    # res = propaty_find(path, data, ip)
    data = ""
    res = do_get(path, ip)
    if res && res.code.to_s == '200'
      print_good("OPEN\t\tSUCCEED:\t\t#{(ssl ? 'https' : 'http')}://#{ip}:#{rport}#{path}")
      sid = [*('a'..'z'), *('A'..'Z'), *('0'..'9'), '_'].sample(rand(5..15)).join
      newdir = "DavTestDir_#{sid}"
      new_url = "#{path}#{newdir}"
      puts "********************************************************\nNOTE\tRandom string for this session: #{newdir}"
      puts "********************************************************\n Creating directory"
      data = ""
      res = make_collection(new_url, ip)
      if res && res.code.to_s == '201'
        print_good("MKCOL\t\tSUCCEED:\t\tCreated #{(ssl ? 'https' : 'http')}://#{ip}:#{rport}#{new_url}")
        dir_create = true
        puts "********************************************************\n Sending test files"
  
        test_txt.each do |k, v|
          # Upload file
          res = do_put("#{new_url}/davtest_#{sid}.#{k}", v[:content], ip)
          if res && res.code.to_s == '201'
            print_good("PUT\t#{k}\tSUCCEED:\t#{(ssl ? 'https' : 'http')}://#{ip}:#{rport}#{new_url}/davtest_#{sid}.#{k}")
            v[:put] = 'true'
          else
            print_err("PUT\t#{k}\tFAIL")
          end
        end
        puts "********************************************************\n Checking for test files execution"
        test_txt.each do |k, v|
          # Execute file
          res = do_get("#{new_url}/davtest_#{sid}.#{k}", ip)
          if res && res.code.to_s == '200' && res.body.include?(v[:execmatch])

            print_good("EXEC\t#{k}\tSUCCEED:\t#{(ssl ? 'https' : 'http')}://#{ip}:#{rport}#{new_url}/davtest_#{sid}.#{k}")
            v[:exec] = 'true'
          else
            print_error("EXEC\t#{k}\tFAIL")
          end
        end
      else
        print_error("MKCOL\t\tFAIL")
      end
    else
      print_error("OPEN\t\tFAIL:\t\t#{(ssl ? 'https' : 'http')}://#{ip}:#{rport}#{path}")
    end
    puts "********************************************************\n Summary:"
    if dir_create
      puts "Created: #{(ssl ? 'https' : 'http')}://#{ip}:#{rport}#{new_url}"
    end
    test_txt.each do |k, v|
      if v[:put]
        puts "PUT File: #{(ssl ? 'https' : 'http')}://#{ip}:#{rport}#{new_url}/davtest_#{sid}.#{k}"
      end
    end
    test_txt.each do |k, v|
      if v[:exec]
        puts "Executes: #{(ssl ? 'https' : 'http')}://#{ip}:#{rport}#{new_url}/davtest_#{sid}.#{k}"
      end
    end
  end
end

インストール

~/.msf4/modules/auxiliary/scanner/httpにdavtest.rbで保存します。

実行する

Metasploitable3のuploadsフォルダをテストします。
Metasploitを起動します。
モジュールを検索します。

[msf](Jobs:0 Agents:0) >> search davtest

Matching Modules
================

   #  Name                            Disclosure Date  Rank    Check  Description
   -  ----                            ---------------  ----    -----  -----------
   0  auxiliary/scanner/http/davtest  .                normal  No     HTTP Writable Path PUT File Testing


Interact with a module by name or index. For example info 0, use 0 or use auxiliary/scanner/http/davtest

モジュールを選択します。

[msf](Jobs:0 Agents:0) >> use 0

オプションを確認します。

[msf](Jobs:0 Agents:0) auxiliary(scanner/http/davtest) >> show options

Module options (auxiliary/scanner/http/davtest):

   Name     Current Setting  Required  Description
   ----     ---------------  --------  -----------
   PATH     /                yes       Path to test
   Proxies                   no        A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: socks4, socks5, socks5h, http, sapni
   RHOSTS                    yes       The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
   RPORT    80               yes       The target port (TCP)
   SSL      false            no        Negotiate SSL/TLS for outgoing connections
   THREADS  1                yes       The number of concurrent threads (max one per host)
   VHOST                     no        HTTP server virtual host


View the full module info with the info, or info -d command.

オプションを設定します。

[msf](Jobs:0 Agents:0) auxiliary(scanner/http/davtest) >> set RHOSTS 192.168.56.200
RHOSTS => 192.168.56.200
[msf](Jobs:0 Agents:0) auxiliary(scanner/http/davtest) >> set PATH uploads
PATH => uploads

実行します。

[msf](Jobs:0 Agents:0) auxiliary(scanner/http/davtest) >> run
********************************************************
 Testing DAV connection
[+] OPEN                SUCCEED:                http://192.168.56.200:80/uploads/
********************************************************
NOTE    Random string for this session: DavTestDir_8h0UM
********************************************************
 Creating directory
[+] MKCOL               SUCCEED:                Created http://192.168.56.200:80/uploads/DavTestDir_8h0UM
********************************************************
 Sending test files
[+] PUT php     SUCCEED:        http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.php
[+] PUT txt     SUCCEED:        http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.txt
[+] PUT html    SUCCEED:        http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.html
[+] PUT asp     SUCCEED:        http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.asp
[+] PUT aspx    SUCCEED:        http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.aspx
[+] PUT cfm     SUCCEED:        http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.cfm
[+] PUT cgi     SUCCEED:        http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.cgi
[+] PUT jhtml   SUCCEED:        http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.jhtml
[+] PUT jsp     SUCCEED:        http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.jsp
[+] PUT pl      SUCCEED:        http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.pl
[+] PUT shtml   SUCCEED:        http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.shtml
********************************************************
 Checking for test files execution
[+] EXEC        php     SUCCEED:        http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.php
[+] EXEC        txt     SUCCEED:        http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.txt
[+] EXEC        html    SUCCEED:        http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.html
[-] EXEC        asp     FAIL
[-] EXEC        aspx    FAIL
[-] EXEC        cfm     FAIL
[-] EXEC        cgi     FAIL
[-] EXEC        jhtml   FAIL
[-] EXEC        jsp     FAIL
[-] EXEC        pl      FAIL
[-] EXEC        shtml   FAIL
********************************************************
 Summary:
Created: http://192.168.56.200:80/uploads/DavTestDir_8h0UM
PUT File: http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.php
PUT File: http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.txt
PUT File: http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.html
PUT File: http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.asp
PUT File: http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.aspx
PUT File: http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.cfm
PUT File: http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.cgi
PUT File: http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.jhtml
PUT File: http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.jsp
PUT File: http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.pl
PUT File: http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.shtml
Executes: http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.php
Executes: http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.txt
Executes: http://192.168.56.200:80/uploads/DavTestDir_8h0UM/davtest_8h0UM.html
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?