moby.rb 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 59fc23e9713b37a55d2e866bc1109b525dd188f04136092b05ae0b8f2a860d10
4
+ data.tar.gz: 6945ecfdea1a8f3437bae309c1d9b0e20dbe31d3a28006699e145e9dd57abec9
5
+ SHA512:
6
+ metadata.gz: f909d78a0a23c40143f3ed9d876a80b5fdd1f963670cbaea39d5f5ad42224ee0458adf1a095c9734c65e95e122b6ba9b69ea65fe95e860edf03c12442c2eab65
7
+ data.tar.gz: 1eb639787025eccdcc74517655651eef1e0ef2d142015abaad3523eab31dcc298f33e1a5bf7afb66735b281856261a49f03f499792eca4f7ad8c93c6f89036e1
data/CHANGES.txt ADDED
@@ -0,0 +1,32 @@
1
+ 1.0.0 - 2026-01-08
2
+ Ensure that tests are in place and that version handling is working properly.
3
+ + tests
4
+ + moby.gemspec
5
+ ~ lib/moby.rb: - Moby::VERSION_STRING
6
+ ~ lib/moby.rb: Fix finding username and password fields by name. It was returning the name of the field and not the field object.
7
+ + lib/Moby/VERSION.rb
8
+ ~ bin/moby: + --version flag
9
+ ~ README.md: Updated installation and usage.
10
+ ~ CHANGES.txt: Reorganised in descending order and added dates.
11
+ ~ LICENSE.txt: Updated the years to include the year this was started: /2026/2006-2026/
12
+
13
+ 0.9.1 - 2026-01-05
14
+ ~ README.md: Fix syntax errors.
15
+ ~ lib/Moby.rb: VERSION_STRING: /0.9.0/0.9.1/
16
+
17
+ 0.9.0 - 2024-06-05
18
+ Merged the 2019 0.8.0 version of moby with the 2023 0.8.1 version of whale.
19
+
20
+ 0.8.1 - 2023-12-06
21
+ ~ lib/Whale: ~ set_username_field(): Fix when username field number is specified or nothing is specified.
22
+ ~ lib/Whale: ~ set_password_field(): Fix when password field number is specified or nothing is specified.
23
+
24
+ 0.8.0 - 2023-11-19
25
+ + README.md
26
+ + CHANGES.txt
27
+ + Gemfile
28
+ ~ bin/whale: ~ main(): - args (unnecessary)
29
+ ~ bin/whale VERSION_STRING --> lib/Whale::VERSION_STRING
30
+ ~ bin/whale: ~ switches(): use Whale::VERSION_STRING
31
+ ~ lib/Whale.rb: general tidying
32
+ + lib/File/self.collect.rb
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2006-2026 thoran
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,172 @@
1
+ # moby
2
+
3
+ ![Version](https://img.shields.io/badge/version-1.0.0-green.svg)
4
+ ![Status](https://img.shields.io/badge/status-stable-brightgreen.svg)
5
+
6
+ ## Description
7
+
8
+ "Sometimes when they go fishing, they get a whale and it sinks their boat."
9
+
10
+ Moby is a credentials poisoning tool which floods phishing forms with fake credentials. When phishing operations collect data, the overwhelming volume of fake credentials makes the collected data worthless.
11
+
12
+ Use responsibly. Only target confirmed phishing sites.
13
+
14
+ ## Installation
15
+
16
+ ### Prerequisites
17
+ 1. Some version of Ruby.
18
+ 2. \*nix, so that it has access to /usr/share/dict/words.
19
+
20
+ ### 1. via RubyGems
21
+ ```shell
22
+ $ gem install moby.rb
23
+ ```
24
+
25
+ ### 2. via Bundler
26
+ Add to your Gemfile:
27
+ ```ruby
28
+ gem 'moby.rb'
29
+ ```
30
+
31
+ Then run:
32
+ ```shell
33
+ $ bundle install
34
+ ```
35
+
36
+ ### 3. via Homebrew
37
+ ```shell
38
+ $ brew tap thoran/tap
39
+ $ brew install thoran/tap/moby
40
+ ```
41
+
42
+ ### 4. via git
43
+ ```shell
44
+ $ git clone git@github.com:thoran/moby.git
45
+ $ cd moby
46
+ $ bundle install
47
+ $ rake test
48
+ ```
49
+
50
+ ## Usage
51
+
52
+ ### From the command line
53
+
54
+ #### 1. Specify form name and field names
55
+ ```shell
56
+ $ moby --url https://phishing.site --form_name form-name --username_field_name login --password_field_name password
57
+ ```
58
+
59
+ #### 2. Specify form name and field names with short switches
60
+ This is the same as above, but using alternate switches:
61
+
62
+ ```shell
63
+ $ moby --url https://phishing.site --f form-name --user_field login --pass_field password
64
+ ```
65
+
66
+ #### 3. Specify form number and field numbers
67
+ This selects the second form on the page (using zero-based indexing) and the first and second fields:
68
+
69
+ ```shell
70
+ $ moby --url https://phishing.site --form_number 1 --username_field_number 0 --password_field_number 1
71
+ ```
72
+ #### 4. Specify form number and field numbers with short switches
73
+ This selects the second form on the page (using zero-based indexing) and the first and second fields:
74
+
75
+ ```shell
76
+ $ moby --url https://phishing.site --form_number 1 --user_field_number 0 --pass_field_number 1
77
+ ```
78
+
79
+ #### 5. Specify nothing but the URL
80
+ ```shell
81
+ $ moby --url https://phishing.site
82
+ ```
83
+ This is equivalent to:
84
+
85
+ ```shell
86
+ $ moby --url https://phishing.site --form_number 0 --username_field_number 0 --password_field_number 1
87
+ ```
88
+
89
+ #### 6. With other options
90
+ ```shell
91
+ $ moby --url https://phishing.site --debug --user_agent 'Mozilla/5.0' --username_hostname mydomain.com --username_is_email_address --verbose
92
+ ```
93
+
94
+ Note that `username_hostname` is useful only if `username_is_email_address` is true.
95
+
96
+ ### From within Ruby
97
+
98
+ #### 1. Specify form name and field names
99
+ ```ruby
100
+ require 'moby'
101
+
102
+ moby = Moby.new(
103
+ url: 'https://phishing.site',
104
+ form_name: 'form-name',
105
+ username_field_name: 'login',
106
+ password_field_name: 'password'
107
+ )
108
+ moby.counter_phish
109
+ ```
110
+
111
+ #### 2. Specify form number and field numbers
112
+ This selects the second form on the page (using zero-based indexing) and uses the first and second fields therein:
113
+
114
+ ```ruby
115
+ require 'moby'
116
+
117
+ moby = Moby.new(
118
+ url: 'https://phishing.site',
119
+ form_number: 1,
120
+ username_field_number: 0,
121
+ password_field_number: 1,
122
+ )
123
+ moby.counter_phish
124
+ ```
125
+
126
+ #### 3. Specify nothing but the URL
127
+ ```ruby
128
+ require 'moby'
129
+
130
+ moby = Moby.new(
131
+ url: 'https://phishing.site',
132
+ )
133
+ moby.counter_phish
134
+ ```
135
+ This is equivalent to:
136
+
137
+ ```ruby
138
+ require 'moby'
139
+
140
+ moby = Moby.new(
141
+ url: 'https://phishing.site',
142
+ form_number: 0,
143
+ username_field_number: 0,
144
+ password_field_number: 1,
145
+ )
146
+ moby.counter_phish
147
+ ```
148
+
149
+ #### 4. With other options
150
+ ```ruby
151
+ require 'moby'
152
+
153
+ moby = Moby.new(
154
+ url: 'https://phishing.site',
155
+ debug: true,
156
+ user_agent: 'Mozilla/5.0',
157
+ username_hostname: 'mydomain.com', # Only useful if username_is_email_address is true.
158
+ username_is_email_address: true,
159
+ verbose: true
160
+ )
161
+ moby.counter_phish
162
+ ```
163
+
164
+ Note that there's no equivalent short names for constructor arguments.
165
+
166
+ ## Contributing
167
+
168
+ 1. Fork it: `https://github.com/thoran/moby/fork`
169
+ 2. Create your feature branch: `git checkout -b my-new-feature`
170
+ 3. Commit your changes: `git commit -am 'Add some feature'`
171
+ 4. Push to the branch: `git push origin my-new-feature`
172
+ 5. Create a new pull request
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ # Rakefile
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.libs << 'lib'
7
+ t.libs << 'test'
8
+ t.test_files = FileList['test/**/*_test.rb']
9
+ t.verbose = true
10
+ t.warning = false
11
+ end
12
+
13
+ task default: :test
14
+
15
+ desc "Run tests"
16
+ task :spec => :test
17
+
18
+ desc "Show version"
19
+ task :version do
20
+ require_relative './lib/Moby/VERSION'
21
+ puts "moby #{Moby::VERSION}"
22
+ end
data/bin/moby ADDED
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby
2
+ # moby
3
+
4
+ # moby is a credentials poisoning tool which floods phishing forms with fake credentials.
5
+
6
+ lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
8
+
9
+ require 'Moby'
10
+ require 'switches.rb'
11
+
12
+ def switches
13
+ Switches.new do |s|
14
+ s.banner = 'Usage: whale <url> and any of the following options...'
15
+ s.set(:a, :user_agent){'What webclient I should pretend to be.'}
16
+ s.set(:debug?){'Show debugging information.'}
17
+ s.set(:e?, :username_is_email_address?){'Automatically generate a username which is in the form of an email address.'}
18
+ s.set(:f, :form, :form_name){'If left empty, the first form on the page will be used.'}
19
+ s.set(:help){'Usage: whale <url> and any of the following options...'}
20
+ s.set(:n, :form_number){'This will set the number of the form in order of appearance on the page. Use zero-based indexing.'}
21
+ s.set(:pass_field, :password_field, :pass_field_name, :password_field_name){'The name of the password field.'}
22
+ s.set(:pass_field_number, :password_field_number){'The position of the password field in relation to the form it is attached to. Use zero-based indexing.'}
23
+ s.set(:url){'The location of the phishing form.'}
24
+ s.set(:user_field, :username_field, :user_field_name, :username_field_name){'The name of the username field.'}
25
+ s.set(:user_field_number, :username_field_number){'The position of the username field in relation to the form it is attached to. Use zero-based indexing.'}
26
+ s.set(:username_hostname){'The hostname to be used when the username is an email address.'}
27
+ s.set(:username_prefilled){'No need to supply an email address in the form.'}
28
+ s.set(:v?, :verbose?){'Outputs each submission as it goes.'}
29
+ s.perform(:version) do
30
+ puts "moby #{Moby::VERSION}"
31
+ exit
32
+ end
33
+ end
34
+ end
35
+
36
+ def main
37
+ args = switches.to_h
38
+ debug = args[:debug?]
39
+ form_name = args[:form_name]
40
+ form_number = args[:form_number]
41
+ password_field_name = args[:password_field_name]
42
+ password_field_number = args[:password_field_number]
43
+ url = args[:url]
44
+ user_agent = args[:user_agent]
45
+ username_field_name = args[:username_field_name]
46
+ username_field_number = args[:username_field_number]
47
+ username_hostname = args[:username_hostname]
48
+ username_is_email_address = args[:username_is_email_address?]
49
+ verbose = args[:verbose?]
50
+ moby = Moby.new(
51
+ debug: debug,
52
+ form_name: form_name,
53
+ form_number: form_number,
54
+ password_field_name: password_field_name,
55
+ password_field_number: password_field_number,
56
+ url: url,
57
+ user_agent: user_agent,
58
+ username_field_name: username_field_name,
59
+ username_field_number: username_field_number,
60
+ username_hostname: username_hostname,
61
+ username_is_email_address: username_is_email_address,
62
+ verbose: verbose
63
+ )
64
+ moby.counter_phish
65
+ end
66
+
67
+ main
@@ -0,0 +1,10 @@
1
+ # File/self.collect.rb
2
+ # File.collect
3
+
4
+ # 20200111
5
+ # 0.2.1
6
+
7
+ # Changes:
8
+ # 1. + Thoran::File::SelfCollect namespace.
9
+
10
+ require 'Thoran/File/SelfCollect/self.collect.rb'
@@ -0,0 +1,157 @@
1
+ # lib/moby/backends/mechanize_backend.rb
2
+ # Moby::Backends::MechanizeBackend
3
+
4
+ require 'mechanize'
5
+ require 'pp'
6
+
7
+ class Moby
8
+ module Backends
9
+ class MechanizeBackend
10
+ def counter_phish
11
+ set_up_agent
12
+ load_page
13
+ repeatedly_fill_and_submit_login_form
14
+ end
15
+
16
+ private
17
+
18
+ def initialize(config:, random_input_generator:)
19
+ @config = config
20
+ @random_input_generator = random_input_generator
21
+ end
22
+
23
+ def agent
24
+ @agent ||= (
25
+ puts "Setting up Mechanize agent..." if @config.verbose?
26
+ agent = Mechanize.new
27
+ puts "Agent setup complete." if @config.verbose?
28
+ agent
29
+ )
30
+ end
31
+ alias_method :set_up_agent, :agent
32
+
33
+ def page
34
+ @page ||= (
35
+ puts "Navigating to #{@config.url}..." if @config.verbose?
36
+ page = agent.get(@config.url)
37
+ puts "Page loaded successfully." if @config.verbose?
38
+ page
39
+ )
40
+ end
41
+ alias_method :load_page, :page
42
+
43
+ def form
44
+ @form ||= (
45
+ if @config.using_form_name?
46
+ find_form_by_name
47
+ else
48
+ find_form_by_number
49
+ end
50
+ )
51
+ end
52
+
53
+ def repeatedly_fill_and_submit_login_form
54
+ start_time = Time.now
55
+ puts "moby session begun #{start_time}."
56
+ submission_count = 0
57
+ begin
58
+ if @config.fixed_number_of_submissions?
59
+ @config.iterations.times do |iteration|
60
+ fill_and_submit_login_form
61
+ submission_count += 1
62
+ puts "Iteration #{submission_count} of #{@config.iterations}." if @config.verbose?
63
+ end
64
+ puts "Successfully completed #{@config.iterations} submissions." if @config.verbose?
65
+ else
66
+ loop do
67
+ fill_and_submit_login_form
68
+ submission_count += 1
69
+ puts "#{submission_count} #{username}:#{password}" if @config.verbose?
70
+ end
71
+ end
72
+ ensure
73
+ finish_time = Time.now
74
+ time_delta_in_minutes = (finish_time - start_time) / 60
75
+ submissions_per_minute = submission_count / time_delta_in_minutes
76
+ puts "moby session terminated #{finish_time} with #{submission_count} counter-phishes served in #{time_delta_in_minutes} minutes for an average of #{submissions_per_minute} submissions per minute."
77
+ end
78
+ end
79
+
80
+ def fill_and_submit_login_form
81
+ puts "Filling with: #{username}:#{password}" if @config.verbose?
82
+ agent.user_agent_alias = @config.user_agent
83
+ username_field.value = username
84
+ password_field.value = password
85
+ pp page if @config.debug?
86
+ result = agent.submit(form)
87
+ pp result if @config.debug?
88
+ sleep @config.delay if @config.delay > 0
89
+ rescue Net::HTTP::Persistent::Error
90
+ puts "\n\nNet::HTTP::Persistent::Error rescued.\n\n" if @config.verbose?
91
+ rescue Net::OpenTimeout
92
+ puts "\n\nNet::OpenTimeout rescued.\n\n" if @config.verbose?
93
+ end
94
+
95
+ def find_form_by_name
96
+ page.form_with(name: @config.form_name) ||
97
+ page.form_with(id: @config.form_name) ||
98
+ raise("Could not find a form with the name or id of #{@config.form_name}.")
99
+ end
100
+
101
+ def find_form_by_number
102
+ page.forms[@config.form_number] ||
103
+ raise("Could not find a form at index #{@config.form_number}.")
104
+ end
105
+
106
+ def username
107
+ @random_input_generator.random_username
108
+ end
109
+
110
+ def password
111
+ @random_input_generator.random_password
112
+ end
113
+
114
+ def username_field
115
+ @username_field ||=(
116
+ if @config.using_username_field_name?
117
+ find_username_field_by_name
118
+ else
119
+ find_username_field_by_number
120
+ end
121
+ )
122
+ end
123
+
124
+ def password_field
125
+ @password_field ||= (
126
+ if @config.using_password_field_name?
127
+ find_password_field_by_name
128
+ else
129
+ find_password_field_by_number
130
+ end
131
+ )
132
+ end
133
+
134
+ def find_username_field_by_name
135
+ form.field_with(name: @config.username_field_name) ||
136
+ form.field_with(id: @config.username_field_name) ||
137
+ raise("Could not find username field: #{@config.username_field_name}")
138
+ end
139
+
140
+ def find_username_field_by_number
141
+ form.fields[@config.username_field_number] ||
142
+ raise("Could not find username field at index #{@config.username_field_number}")
143
+ end
144
+
145
+ def find_password_field_by_name
146
+ form.field_with(name: @config.password_field_name) ||
147
+ form.field_with(id: @config.password_field_name) ||
148
+ raise("Could not find password field: #{@config.password_field_name}.")
149
+ end
150
+
151
+ def find_password_field_by_number
152
+ form.fields[@config.password_field_number] ||
153
+ raise("Could not find password field at index: #{@config.password_field_number}.")
154
+ end
155
+ end
156
+ end
157
+ end