msfrpc-simple 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,6 +3,7 @@ require 'msfrpc-client'
3
3
  require 'features/framework'
4
4
  require 'features/pro'
5
5
  require 'module_mapper'
6
+ require 'timeout'
6
7
  require 'logger'
7
8
 
8
9
  module Msf
@@ -13,30 +14,26 @@ module Msf
13
14
  include Msf::RPC::Simple::Features::Framework
14
15
  include Msf::RPC::Simple::Features::Pro
15
16
 
16
- attr_accessor :options
17
+ #attr_accessor :options
17
18
 
18
19
  # Public: Create a simple client object.
19
20
  #
20
- # opts - hash of options to include in our initial connection.
21
+ # user_options - hash of options to include in our initial connection.
21
22
  # project - project name we want to use for this connection.
22
23
  #
23
24
  # Returns nothing.
24
25
  def initialize(user_options)
25
26
 
26
- # db username
27
- # db password
28
-
29
- #
30
- # Merge our project in, and set this as the project we'll
31
- # use going forward.
32
- #
27
+ # configure default options
33
28
  @options = {
34
29
  :project => user_options[:project] || "default",
35
30
  :port => user_options[:project] || 55553,
36
31
  :user => user_options[:rpc_user],
37
32
  :pass => user_options[:rpc_pass],
33
+ :db_host => user_options[:db_host] || "localhost",
38
34
  :db_user => user_options[:db_user],
39
- :db_pass => user_options[:db_pass]
35
+ :db_pass => user_options[:db_pass],
36
+ :db => user_options[:db_name] || "msf"
40
37
  }
41
38
 
42
39
  @options.merge!(user_options)
@@ -46,18 +43,108 @@ module Msf
46
43
  #
47
44
  @client = Msf::RPC::Client.new(@options)
48
45
 
46
+ #
47
+ # Connect to the database based on the included options
48
+ #
49
+ _connect_database
50
+
51
+ #
52
+ # Add a new workspace
53
+ #
54
+ @workspace_name = Time.now.utc.to_s.gsub(" ","_").gsub(":","_")
55
+ _create_workspace
56
+
49
57
  #
50
58
  # Create a logger
51
59
  #
52
60
  #@logger = Msf::RPC::Simple::Logger.new
53
61
  end
54
62
 
63
+ # Public: Creates and retuns an xml report
55
64
  #
56
- #
65
+ # This method is ugly for a number of reasons, but there doesn't
66
+ # appear to be a way to be notified when the command is completed
67
+ # nor when the
68
+ #
57
69
  #
70
+ # returns a valid xml string
71
+ def create_report
72
+ report_path = "/tmp/metasploit_#{@workspace_name}.xml"
73
+
74
+ # Create the report using the db_export command
75
+ _send_command("db_export #{report_path}\n")
76
+
77
+ # We've sent the command, so let's sit back and wait for th
78
+ # output to hit the disk.
79
+ begin
80
+ xml_string = ""
81
+ status = Timeout::timeout(240) {
82
+ # We don't know when the file is going to show up, so
83
+ # wait for it...
84
+ until File.exists? report_path do
85
+ sleep 1
86
+ end
87
+
88
+ # Read and clean up the file when it exists...
89
+ until xml_string.include? "</MetasploitV4>" do
90
+ sleep 5
91
+ xml_string = File.read(report_path)
92
+ end
93
+
94
+ File.delete(report_path)
95
+ }
96
+ rescue Timeout::Error
97
+ xml_string = "<MetasploitV4></MetasploitV4>"
98
+ end
99
+
100
+ xml_string
101
+ end
102
+
103
+ def cleanup
104
+ #_send_command("workspace -d #{@workspace_name}")
105
+ _send_command("db_disconnect")
106
+ end
107
+
58
108
  def connected?
59
109
  return true if @client.call("core.version")
60
110
  end
111
+
112
+ private
113
+
114
+ def _connect_database
115
+ _send_command("db_connect #{@options[:db_user]}:#{@options[:db_pass]}@#{@options[:db_host]}/#{@options[:db]}")
116
+ end
117
+
118
+ def _create_workspace
119
+ _send_command("workspace -a #{@workspace_name}")
120
+ end
121
+
122
+ def _send_command(command)
123
+ # Create the console and get its id
124
+ console = @client.call("console.create")
125
+
126
+ # Do an initial read / discard to pull out any info on the console
127
+ # then write the command to the console
128
+ @client.call("console.read", console["id"])
129
+ @client.call("console.write", console["id"], "#{command}\n")
130
+
131
+ # Initial read
132
+ output_string = ""
133
+ output = @client.call("console.read", console["id"])
134
+
135
+ # Read until finished
136
+ while (output["busy"] == true) do
137
+ output_string += "#{output['data']}"
138
+ output = @client.call("console.read", console["id"])
139
+ output_string = "Error" if output["result"] == "failure"
140
+ end
141
+
142
+ # Clean up console
143
+ #@client.call("console.destroy", console["id"])
144
+
145
+ output_string
146
+ end
147
+
61
148
  end
62
149
  end
63
150
  end
@@ -3,116 +3,12 @@ module Msf
3
3
  module Simple
4
4
  module Features
5
5
  module Framework
6
-
7
- def create_report
8
-
9
- # Create the console and get its id
10
- console = @client.call("console.create")
11
- console_id = console["id"]
12
-
13
- # Do an initial read / discard to pull out the banner
14
- @client.call("console.read", console_id)
15
-
16
- # Move to the context of our module
17
- @client.call("console.write", console_id, "db_connect #{self.options[:db_user]}:#{self.options[:db_pass]}@localhost/msf3\n")
18
- @client.call("console.write", console_id, "db_export /tmp/metasploit.xml\n")
19
-
20
- # do an initial read of the module's output
21
- output = @client.call("console.read", console_id)
22
- output_string = "#{output['data']}"
23
-
24
- return "Module Error" if output["result"] == "failure"
25
-
26
- until (output["busy"] == false) do
27
- output = @client.call("console.read", console_id)
28
- output_string += "#{output['data']}"
29
- return "Module Error" if output["result"] == "failure"
30
- end
31
-
32
- # Clean up
33
- @client.call("console.destroy", console_id)
34
-
35
- File.open("/tmp/metasploit.xml").read
36
- end
37
-
38
- #
39
- # This module simply runs a module
40
- #
41
- def execute_module_and_return_output(options)
42
-
43
- module_name = options[:module_name]
44
- #module_options = options[:module_options]
45
- module_option_string = options[:module_option_string]
46
-
47
- # split up the module name into type / name
48
- module_type = module_name.split("/").first
49
- raise "Error, bad module name" unless ["exploit", "auxiliary", "post", "encoder", "nop"].include? module_type
50
-
51
- #module_options["TARGET"] = 0 unless module_options["TARGET"]
52
-
53
- #info = @client.call("module.execute", module_type, module_name, module_options)
54
- #@client.call("job.info", info["job_id"])
55
-
56
- # The module output will be not available when run this way; to
57
- # capture the result of the print_* commands, you have to set the
58
- # output driver of the module to something you can read from (Buffer,
59
- # File, etc). For your use case, the best bet is to run the module
60
- # via the Console API instead of module.execute, and use that to read
61
- # the output from the console itself, which provides buffer output for you.
62
-
63
- # Create the console and get its id
64
- console = @client.call("console.create")
65
- console_id = console["id"]
66
-
67
- # Do an initial read / discard to pull out the banner
68
- @client.call("console.read", console_id)
69
-
70
- # Move to the context of our module
71
- @client.call("console.write", console_id, "use #{module_name}\n")
72
-
73
- # Set up the module's datastore
74
- module_option_string.split(",").each do |module_option|
75
- @client.call "console.write", console_id, "set #{module_option}\n"
76
- end
77
-
78
- # Do an another read / discard to pull out the option confirmation
79
- @client.call("console.read", console_id)
80
-
81
- # Depending on the module_type, kick off the module
82
- if module_type == "auxiliary"
83
- @client.call "console.write", console_id, "run\n"
84
- elsif module_type == "exploit"
85
- @client.call "console.write", console_id, "exploit\n"
86
- else
87
- return "Unsupported"
88
- end
89
-
90
- # do an initial read of the module's output
91
- module_output = @client.call("console.read", console_id)
92
- module_output_data_string = "#{module_output['data']}"
93
-
94
- return "Module Error" if module_output["result"] == "failure"
95
-
96
- until (module_output["busy"] == false) do
97
- module_output = @client.call("console.read", console_id)
98
- module_output_data_string += "#{module_output['data']}"
99
- return "Module Error" if module_output["result"] == "failure"
100
- end
101
-
102
- # Clean up
103
- @client.call("console.destroy", console_id)
104
-
105
- module_output_data_string
106
- end
107
-
108
6
 
109
7
  #
110
8
  # This module runs a number of discovery modules
111
9
  #
112
- def discover_host(host)
10
+ def discover_range(range)
113
11
 
114
- #
115
- #
116
12
  # Other Potential options
117
13
  # - auxiliary/scanner/smb/pipe_auditor
118
14
  # - auxiliary/scanner/smb/pipe_dcerpc_auditor
@@ -120,45 +16,45 @@ module Msf
120
16
  # - auxiliary/scanner/smb/smb_enumusers
121
17
  modules_and_options = [
122
18
  {:module_name => "auxiliary/scanner/http/http_version",
123
- :module_option_string => "RHOSTS #{host}" },
19
+ :module_option_string => "RHOSTS #{range}" },
124
20
  #{:module_name => "auxiliary/scanner/http/cert",
125
- # :module_option_string => "RHOSTS #{host}" },
21
+ # :module_option_string => "RHOSTS #{range}" },
126
22
  {:module_name => "auxiliary/scanner/ftp/ftp_version",
127
- :module_option_string => "RHOSTS #{host}" },
23
+ :module_option_string => "RHOSTS #{range}" },
128
24
  {:module_name => "auxiliary/scanner/h323/h323_version",
129
- :module_option_string => "RHOSTS #{host}" },
25
+ :module_option_string => "RHOSTS #{range}" },
130
26
  {:module_name => "auxiliary/scanner/imap/imap_version",
131
- :module_option_string => "RHOSTS #{host}" },
27
+ :module_option_string => "RHOSTS #{range}" },
132
28
  #{:module_name => "auxiliary/scanner/portscan/syn",
133
- #:module_option_string => "RHOSTS #{host}" },
29
+ #:module_option_string => "RHOSTS #{range}" },
134
30
  #{:module_name => "auxiliary/scanner/portscan/tcp",
135
- #:module_option_string => "RHOSTS #{host}" },
31
+ #:module_option_string => "RHOSTS #{range}" },
136
32
  #{:module_name => "auxiliary/scanner/lotus/lotus_domino_version",
137
- #:module_option_string => "RHOSTS #{host}" },
33
+ #:module_option_string => "RHOSTS #{range}" },
138
34
  {:module_name => "auxiliary/scanner/mysql/mysql_version",
139
- :module_option_string => "RHOSTS #{host}" },
35
+ :module_option_string => "RHOSTS #{range}" },
140
36
  #{:module_name => "auxiliary/scanner/netbios/nbname",
141
- #:module_option_string => "RHOSTS #{host}" },
37
+ #:module_option_string => "RHOSTS #{range}" },
142
38
  #{:module_name => "auxiliary/scanner/netbios/nbname_probe",
143
- #:module_option_string => "RHOSTS #{host}" },
39
+ #:module_option_string => "RHOSTS #{range}" },
144
40
  #{:module_name => "auxiliary/scanner/pcanywhere/pcanywhere_tcp",
145
- #:module_option_string => "RHOSTS #{host}" },
41
+ #:module_option_string => "RHOSTS #{range}" },
146
42
  #{:module_name => "auxiliary/scanner/pcanywhere/pcanywhere_udp",
147
- #:module_option_string => "RHOSTS #{host}" },
43
+ #:module_option_string => "RHOSTS #{range}" },
148
44
  {:module_name => "auxiliary/scanner/pop3/pop3_version",
149
- :module_option_string => "RHOSTS #{host}" },
45
+ :module_option_string => "RHOSTS #{range}" },
150
46
  {:module_name => "auxiliary/scanner/postgres/postgres_version",
151
- :module_option_string => "RHOSTS #{host}" },
47
+ :module_option_string => "RHOSTS #{range}" },
152
48
  {:module_name => "auxiliary/scanner/smb/smb_version",
153
- :module_option_string => "RHOSTS #{host}" },
49
+ :module_option_string => "RHOSTS #{range}" },
154
50
  {:module_name => "auxiliary/scanner/snmp/snmp_enum",
155
- :module_option_string => "RHOSTS #{host}" },
51
+ :module_option_string => "RHOSTS #{range}" },
156
52
  {:module_name => "auxiliary/scanner/ssh/ssh_version",
157
- :module_option_string => "RHOSTS #{host}" },
53
+ :module_option_string => "RHOSTS #{range}" },
158
54
  {:module_name => "auxiliary/scanner/telnet/telnet_version",
159
- :module_option_string => "RHOSTS #{host}" },
55
+ :module_option_string => "RHOSTS #{range}" },
160
56
  #{:module_name => "auxiliary/scanner/vmware/vmauthd_version",
161
- #:module_option_string => "RHOSTS #{host}" },
57
+ #:module_option_string => "RHOSTS #{range}" },
162
58
  ]
163
59
 
164
60
  # This is a naive and horrible way of doing it, but let's just knock
@@ -182,29 +78,29 @@ module Msf
182
78
  #
183
79
  # This module runs a number of _login modules
184
80
  #
185
- def bruteforce_host(host)
81
+ def bruteforce_range(range)
186
82
 
187
83
  modules_and_options = [
188
84
  {:module_name => "auxiliary/scanner/ftp/ftp_login",
189
- :module_option_string => "RHOSTS #{host}" },
85
+ :module_option_string => "RHOSTS #{range}" },
190
86
  {:module_name => "auxiliary/scanner/http/http_login",
191
- :module_option_string => "RHOSTS #{host}" },
87
+ :module_option_string => "RHOSTS #{range}" },
192
88
  {:module_name => "auxiliary/scanner/smb/smb_login",
193
- :module_option_string => "RHOSTS #{host}" },
89
+ :module_option_string => "RHOSTS #{range}" },
194
90
  {:module_name => "auxiliary/scanner/mssql/mssql_login",
195
- :module_option_string => "RHOSTS #{host}" },
91
+ :module_option_string => "RHOSTS #{range}" },
196
92
  {:module_name => "auxiliary/scanner/mysql/mysql_login",
197
- :module_option_string => "RHOSTS #{host}" },
93
+ :module_option_string => "RHOSTS #{range}" },
198
94
  {:module_name => "auxiliary/scanner/pop3/pop3_login",
199
- :module_option_string => "RHOSTS #{host}" },
95
+ :module_option_string => "RHOSTS #{range}" },
200
96
  {:module_name => "auxiliary/scanner/smb/smb_login",
201
- :module_option_string => "RHOSTS #{host}" },
97
+ :module_option_string => "RHOSTS #{range}" },
202
98
  {:module_name => "auxiliary/scanner/snmp/snmp_login",
203
- :module_option_string => "RHOSTS #{host}" },
99
+ :module_option_string => "RHOSTS #{range}" },
204
100
  {:module_name => "auxiliary/scanner/ssh/ssh_login",
205
- :module_option_string => "RHOSTS #{host}" },
101
+ :module_option_string => "RHOSTS #{range}" },
206
102
  {:module_name => "auxiliary/scanner/telnet/telnet_login",
207
- :module_option_string => "RHOSTS #{host}" },
103
+ :module_option_string => "RHOSTS #{range}" },
208
104
  ]
209
105
 
210
106
  # This is a naive and horrible way of doing it, but let's just knock
@@ -228,10 +124,88 @@ module Msf
228
124
  #
229
125
  # This module runs a number of exploit modules
230
126
  #
231
- def exploit_host(host)
127
+ def exploit_range(range)
232
128
  return "Not Implemented"
233
129
  end
234
130
 
131
+ #
132
+ # This module simply runs a module
133
+ #
134
+ def execute_module_and_return_output(options)
135
+ module_name = options[:module_name]
136
+ #module_options = options[:module_options]
137
+ module_option_string = options[:module_option_string]
138
+
139
+ # split up the module name into type / name
140
+ module_type = module_name.split("/").first
141
+ raise "Error, bad module name" unless ["exploit", "auxiliary", "post", "encoder", "nop"].include? module_type
142
+
143
+ #module_options["TARGET"] = 0 unless module_options["TARGET"]
144
+
145
+ #info = @client.call("module.execute", module_type, module_name, module_options)
146
+ #@client.call("job.info", info["job_id"])
147
+
148
+ # The module output will be not available when run this way; to
149
+ # capture the result of the print_* commands, you have to set the
150
+ # output driver of the module to something you can read from (Buffer,
151
+ # File, etc). For your use case, the best bet is to run the module
152
+ # via the Console API instead of module.execute, and use that to read
153
+ # the output from the console itself, which provides buffer output for you.
154
+
155
+ # Create the console and get its id
156
+ console = @client.call("console.create")
157
+ console_id = console["id"]
158
+
159
+ # Do an initial read / discard to pull out the banner
160
+ @client.call("console.read", console_id)
161
+
162
+ # Move to the context of our module
163
+ @client.call("console.write", console_id, "use #{module_name}\n")
164
+
165
+ # Set up the module's datastore
166
+ module_option_string.split(",").each do |module_option|
167
+ @client.call "console.write", console_id, "set #{module_option}\n"
168
+ @client.call("console.read", console_id)
169
+ end
170
+
171
+ # Ugh, this is horrible, but the read call is currently racey
172
+ 5.times do
173
+ @client.call("console.read", console_id)
174
+ end
175
+
176
+ # Depending on the module_type, kick off the module
177
+ if module_type == "auxiliary"
178
+ @client.call "console.write", console_id, "run\n"
179
+ elsif module_type == "exploit"
180
+ @client.call "console.write", console_id, "exploit\n"
181
+ else
182
+ return "Unsupported"
183
+ end
184
+
185
+ # do an initial read of the module's output
186
+ module_output = @client.call("console.read", console_id)
187
+ module_output_data_string = "#{module_output['data']}"
188
+
189
+ return "Module Error" if module_output["result"] == "failure"
190
+
191
+ while module_output["busy"] do
192
+ module_output = @client.call("console.read", console_id)
193
+ module_output_data_string += "#{module_output['data']}"
194
+ return "Module Error" if module_output["result"] == "failure"
195
+ end
196
+
197
+ # Ugh, this is horrible, but the read call is currently racey
198
+ 5.times do
199
+ module_output = @client.call("console.read", console_id)
200
+ module_output_data_string += "#{module_output['data']}"
201
+ end
202
+
203
+ # Clean up
204
+ @client.call("console.destroy", console_id)
205
+
206
+ module_output_data_string
207
+ end
208
+
235
209
  end
236
210
  end
237
211
  end
@@ -1,7 +1,7 @@
1
1
  module Msf
2
2
  module RPC
3
3
  module Simple
4
- VERSION = "0.0.4"
4
+ VERSION = "0.0.5"
5
5
  end
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: msfrpc-simple
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: