moby.rb 1.0.0 → 2.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 +4 -4
- data/CHANGELOG +120 -0
- data/Gemfile +4 -0
- data/bin/moby +24 -27
- data/lib/Moby/Backends/MechanizeBackend.rb +1 -1
- data/lib/Moby/Backends/SeleniumBackend.rb +28 -22
- data/lib/Moby/Backends.rb +20 -0
- data/lib/Moby/Configuration.rb +17 -11
- data/lib/Moby/VERSION.rb +1 -1
- data/lib/Moby.rb +15 -160
- data/{moby.gemspec → moby.rb.gemspec} +6 -5
- data/test/Moby/Backends/MechanizeBackend_test.rb +83 -0
- data/test/Moby/Backends/SeleniumBackend_test.rb +44 -0
- data/test/Moby/Backends_test.rb +27 -0
- data/test/Moby/Configuration_test.rb +134 -0
- data/test/Moby/RandomInputGenerator_test.rb +37 -0
- data/test/Moby/VERSION_test.rb +2 -2
- data/test/moby_test.rb +6 -282
- data/test/test_helper.rb +15 -0
- metadata +26 -8
- data/CHANGES.txt +0 -32
- data/lib/File/self.collect.rb +0 -10
- data/lib/Thoran/File/SelfCollect/self.collect.rb +0 -22
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6f4bd8e1d75dd3b141595bf678fb267890c5554a26999b147485bda0eaac40f7
|
|
4
|
+
data.tar.gz: c49e5c7240d47a06656b91c1108dacdd1016248d86d1b5eb4843beecb092fa33
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '029f6229354cdc1746db60befce2a266092c647dbd861c72871ad04b0abda2f46aa7568465a21b5c75543442456d03fd9979cce96a433fbc2a6ffe3898444fe7'
|
|
7
|
+
data.tar.gz: 8329d384ab5ff2ce15563cd07ddcda4ddecc97458558ecac9a3199607cb7dfdc240e56646cbaf479ee6bba9dda049da15abf0fbb24bd0adb7088da33fc8425a9
|
data/CHANGELOG
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# CHANGELOG
|
|
2
|
+
|
|
3
|
+
## 20260811
|
|
4
|
+
|
|
5
|
+
2.0.0: Move the Mechanize engine into a pluggable backend and add a Selenium backend alongside it.
|
|
6
|
+
|
|
7
|
+
1. ~ lib/Moby.rb: the monolithic Moby was the whole Mechanize engine; it becomes a thin facade that builds a Configuration, a RandomInputGenerator and the chosen backend, and delegates #counter_phish to it. The submission loop, the page, form and field finding, and the random credential generation all move out into the classes below.
|
|
8
|
+
2. + lib/Moby/Backends/MechanizeBackend.rb: the Mechanize functionality, moved out of Moby and behind a backend interface, driven by Configuration and RandomInputGenerator.
|
|
9
|
+
3. + lib/Moby/Backends/SeleniumBackend.rb: a second backend driving a real headless browser (Chrome or Firefox), for forms that filter non-browser clients so the Mechanize submissions never land.
|
|
10
|
+
4. + lib/Moby/Backends.rb: Moby::Backends.for, which chooses the backend from the configuration: :mechanize (the default) or :selenium.
|
|
11
|
+
5. + lib/Moby/Configuration.rb: Moby::Configuration, a settings object for a run (url, backend, browser, form and field identification, username generation, iterations, delay, word list).
|
|
12
|
+
6. + lib/Moby/RandomInputGenerator.rb: Moby::RandomInputGenerator, the fake-credential generation (random username and password, optionally email-shaped), moved out of Moby.
|
|
13
|
+
7. + lib/Selenium/ and lib/Thoran/Selenium/: Selenium::WebDriver::Driver#attempt (retry a block) and #element_present?, used by the Selenium backend.
|
|
14
|
+
8. ~ bin/moby: builds a Configuration and runs the selected backend through the facade; + --backend, + --browser, + --iterations (moby runs until interrupted when it is omitted), + --delay; and the previously inert --username-prefilled is wired through, so only the password is submitted when the username is already on the form.
|
|
15
|
+
9. - lib/File/self.collect.rb, - lib/Thoran/File/SelfCollect/self.collect.rb: File.collect was used only by the old Moby; RandomInputGenerator reads the word list with File.readlines.
|
|
16
|
+
10. ~ test/moby_test.rb: it exercised the removed Moby monolith; rewritten to test the Moby facade (backend selection). + test/Moby/Configuration_test.rb, RandomInputGenerator_test.rb, Backends_test.rb, Backends/MechanizeBackend_test.rb and Backends/SeleniumBackend_test.rb.
|
|
17
|
+
11. ~ moby.rb.gemspec: + selenium-webdriver (~> 4).
|
|
18
|
+
12. ~ moby.rb.gemspec: required_ruby_version: /2.5.0/3.3.0/, which selenium-webdriver 4 requires.
|
|
19
|
+
13. ~ Gemfile: + ostruct, which switches.rb requires and which is no longer a default gem as of Ruby 4.0.
|
|
20
|
+
14. ~ lib/Moby/VERSION.rb: VERSION: /1.0.1/2.0.0/
|
|
21
|
+
15. ~ test/Moby/VERSION_test.rb: /1.0.1/2.0.0/
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## 20260807
|
|
25
|
+
|
|
26
|
+
1.0.1: Add a CHANGELOG in the form changelogger writes, and retire CHANGES.txt.
|
|
27
|
+
|
|
28
|
+
1. + CHANGELOG, in the form changelogger writes: a dated heading per version, the version and its summary on the first line of the body, then the numbered items, newest first — the form the other repositories already use. Versions 0.8.0 to 1.0.0 are carried across from CHANGES.txt; 0.7.0 to 0.7.4 are recovered from the git-importall revisions, each of which carries a version and a date of its own. Nothing before 0.7.0 is recorded: the 2006 series stamps every version with the one creation date, and the revisions between it and 0.7.0 carry a date but no version, so neither a reliable date nor a reliable version can be had for them.
|
|
29
|
+
2. - CHANGES.txt. The CHANGELOG holds that record now, and keeping both invites them to disagree. Its changelog_uri and file list in moby.rb.gemspec are pointed at CHANGELOG in its place.
|
|
30
|
+
3. ~ lib/Moby/VERSION.rb: VERSION: /1.0.0/1.0.1/
|
|
31
|
+
4. ~ test/Moby/VERSION_test.rb: /1.0.0/1.0.1/
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## 20260108
|
|
35
|
+
|
|
36
|
+
1.0.0: Ensure that tests are in place and that version handling is working properly.
|
|
37
|
+
|
|
38
|
+
1. + tests
|
|
39
|
+
2. + moby.rb.gemspec
|
|
40
|
+
3. ~ lib/moby.rb: - Moby::VERSION_STRING
|
|
41
|
+
4. ~ lib/moby.rb: Fix finding username and password fields by name. It was returning the name of the field and not the field object.
|
|
42
|
+
5. + lib/Moby/VERSION.rb
|
|
43
|
+
6. ~ bin/moby: + --version flag
|
|
44
|
+
7. ~ README.md: Updated installation and usage.
|
|
45
|
+
8. ~ CHANGES.txt: Reorganised in descending order and added dates.
|
|
46
|
+
9. ~ LICENSE.txt: Updated the years to include the year this was started: /2026/2006-2026/
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## 20260105
|
|
50
|
+
|
|
51
|
+
0.9.1: Fix syntax errors in README.md.
|
|
52
|
+
|
|
53
|
+
1. ~ README.md: Fix syntax errors.
|
|
54
|
+
2. ~ lib/Moby.rb: VERSION_STRING: /0.9.0/0.9.1/
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
## 20240605
|
|
58
|
+
|
|
59
|
+
0.9.0: Merge of 2019's 0.8.0 and 2023's 0.8.1.
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
## 20231206
|
|
63
|
+
|
|
64
|
+
0.8.1: Fix when username or password field number is specified or nothing is specified.
|
|
65
|
+
|
|
66
|
+
1. ~ lib/Whale: ~ set_username_field(): Fix when username field number is specified or nothing is specified.
|
|
67
|
+
2. ~ lib/Whale: ~ set_password_field(): Fix when password field number is specified or nothing is specified.
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
## 20231119
|
|
71
|
+
|
|
72
|
+
0.8.0: An accidental fork of 0.7.0 in 2023.
|
|
73
|
+
|
|
74
|
+
1. + README.md
|
|
75
|
+
2. + CHANGES.txt
|
|
76
|
+
3. + Gemfile
|
|
77
|
+
4. ~ bin/whale: ~ main(): - args (unnecessary)
|
|
78
|
+
5. ~ bin/whale VERSION_STRING --> lib/Whale::VERSION_STRING
|
|
79
|
+
6. ~ bin/whale: ~ switches(): use Whale::VERSION_STRING
|
|
80
|
+
7. ~ lib/Whale.rb: general tidying
|
|
81
|
+
8. + lib/File/self.collect.rb
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
## 20190820
|
|
85
|
+
|
|
86
|
+
0.7.4: Rescue Net::OpenTimeout in the submission loop.
|
|
87
|
+
|
|
88
|
+
1. ~ counter_phish(): + rescue Net::OpenTimeout.
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
## 20190819
|
|
92
|
+
|
|
93
|
+
0.7.3: Rescue Net::HTTP::Persistent::Error in place of EOFError.
|
|
94
|
+
|
|
95
|
+
1. ~ counter_phish(): rescue Net::HTTP::Persistent::Error in place of EOFError.
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
## 20190819
|
|
99
|
+
|
|
100
|
+
0.7.2: Set field values on the field objects rather than through the setter methods.
|
|
101
|
+
|
|
102
|
+
1. ~ counter_phish(): set the user agent and field values inline — mechanize.user_agent_alias, username_field.value, password_field.value — in place of set_user_agent()/set_username_field()/set_password_field().
|
|
103
|
+
2. ~ username(): use the username_is_email_address? predicate, its default nil in place of false.
|
|
104
|
+
3. ~ user_agent: attr_writer in place of attr_accessor.
|
|
105
|
+
4. ~ counter_phish(): verbose reports username_field.value:password_field.value.
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
## 20190819
|
|
109
|
+
|
|
110
|
+
0.7.1: Assign to the field's value rather than replacing the field object.
|
|
111
|
+
|
|
112
|
+
1. ~ set_username_field()/set_password_field(): set the field's value in place of replacing the field object at form.fields[n].
|
|
113
|
+
2. ~ counter_phish(): + rescue EOFError; + pp page in debug.
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
## 20190815
|
|
117
|
+
|
|
118
|
+
0.7.0: The earliest recoverable version.
|
|
119
|
+
|
|
120
|
+
1. whale floods a phishing form with fake credentials: counter_phish() loads the page, then loops submitting a generated username and password, each built from random dictionary words and TLDs. Recorded as an inventory of the first cleanly-versioned revision; the revision before it carries no version and a different structure, and is not reconstructed here.
|
data/Gemfile
CHANGED
data/bin/moby
CHANGED
|
@@ -11,12 +11,16 @@ require 'switches.rb'
|
|
|
11
11
|
|
|
12
12
|
def switches
|
|
13
13
|
Switches.new do |s|
|
|
14
|
-
s.banner = 'Usage:
|
|
14
|
+
s.banner = 'Usage: moby <url> and any of the following options...'
|
|
15
15
|
s.set(:a, :user_agent){'What webclient I should pretend to be.'}
|
|
16
|
+
s.set(:b, :backend){'Which backend to use: mechanize (default) or selenium.'}
|
|
17
|
+
s.set(:browser){'Which browser the selenium backend should drive: chrome (default) or firefox.'}
|
|
16
18
|
s.set(:debug?){'Show debugging information.'}
|
|
19
|
+
s.set(:delay){'Seconds to wait between submissions.'}
|
|
17
20
|
s.set(:e?, :username_is_email_address?){'Automatically generate a username which is in the form of an email address.'}
|
|
18
21
|
s.set(:f, :form, :form_name){'If left empty, the first form on the page will be used.'}
|
|
19
|
-
s.set(:help){'Usage:
|
|
22
|
+
s.set(:help){'Usage: moby <url> and any of the following options...'}
|
|
23
|
+
s.set(:iterations){'How many submissions to make. If left empty, moby runs until interrupted.'}
|
|
20
24
|
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
25
|
s.set(:pass_field, :password_field, :pass_field_name, :password_field_name){'The name of the password field.'}
|
|
22
26
|
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.'}
|
|
@@ -24,7 +28,7 @@ def switches
|
|
|
24
28
|
s.set(:user_field, :username_field, :user_field_name, :username_field_name){'The name of the username field.'}
|
|
25
29
|
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
30
|
s.set(:username_hostname){'The hostname to be used when the username is an email address.'}
|
|
27
|
-
s.set(:username_prefilled){'
|
|
31
|
+
s.set(:username_prefilled?){'The username is already filled in on the form, so only submit the password.'}
|
|
28
32
|
s.set(:v?, :verbose?){'Outputs each submission as it goes.'}
|
|
29
33
|
s.perform(:version) do
|
|
30
34
|
puts "moby #{Moby::VERSION}"
|
|
@@ -35,31 +39,24 @@ end
|
|
|
35
39
|
|
|
36
40
|
def main
|
|
37
41
|
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
42
|
moby = Moby.new(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
43
|
+
backend: args[:backend],
|
|
44
|
+
browser: args[:browser],
|
|
45
|
+
debug: args[:debug?],
|
|
46
|
+
delay: args[:delay],
|
|
47
|
+
form_name: args[:form_name],
|
|
48
|
+
form_number: args[:form_number],
|
|
49
|
+
iterations: args[:iterations],
|
|
50
|
+
password_field_name: args[:password_field_name],
|
|
51
|
+
password_field_number: args[:password_field_number],
|
|
52
|
+
url: args[:url],
|
|
53
|
+
user_agent: args[:user_agent],
|
|
54
|
+
username_field_name: args[:username_field_name],
|
|
55
|
+
username_field_number: args[:username_field_number],
|
|
56
|
+
username_hostname: args[:username_hostname],
|
|
57
|
+
username_is_email_address: args[:username_is_email_address?],
|
|
58
|
+
username_prefilled: args[:username_prefilled?],
|
|
59
|
+
verbose: args[:verbose?]
|
|
63
60
|
)
|
|
64
61
|
moby.counter_phish
|
|
65
62
|
end
|
|
@@ -80,7 +80,7 @@ class Moby
|
|
|
80
80
|
def fill_and_submit_login_form
|
|
81
81
|
puts "Filling with: #{username}:#{password}" if @config.verbose?
|
|
82
82
|
agent.user_agent_alias = @config.user_agent
|
|
83
|
-
username_field.value = username
|
|
83
|
+
username_field.value = username unless @config.username_prefilled?
|
|
84
84
|
password_field.value = password
|
|
85
85
|
pp page if @config.debug?
|
|
86
86
|
result = agent.submit(form)
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
require 'selenium-webdriver'
|
|
5
5
|
|
|
6
|
-
require_relative '
|
|
7
|
-
require_relative '
|
|
6
|
+
require_relative '../../Selenium/WebDriver/Driver/Attempt/attempt'
|
|
7
|
+
require_relative '../../Selenium/WebDriver/SearchContext/ElementPresentQ/element_presentQ'
|
|
8
8
|
|
|
9
9
|
class Moby
|
|
10
10
|
module Backends
|
|
@@ -26,24 +26,28 @@ class Moby
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def driver
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
29
|
+
@driver ||= (
|
|
30
|
+
puts "Setting up #{@config.browser} driver..." if @config.verbose
|
|
31
|
+
driver =
|
|
32
|
+
case @config.browser.to_sym
|
|
33
|
+
when :chrome
|
|
34
|
+
options = Selenium::WebDriver::Chrome::Options.new
|
|
35
|
+
options.add_argument('--headless') unless @config.debug
|
|
36
|
+
options.add_argument('--disable-gpu')
|
|
37
|
+
options.add_argument('--no-sandbox')
|
|
38
|
+
options.add_argument("user-agent=#{@config.user_agent}") if @config.user_agent
|
|
39
|
+
Selenium::WebDriver.for(:chrome, options: options)
|
|
40
|
+
when :firefox
|
|
41
|
+
options = Selenium::WebDriver::Firefox::Options.new
|
|
42
|
+
options.add_argument('--headless') unless @config.debug
|
|
43
|
+
options.add_preference('general.useragent.override', @config.user_agent) if @config.user_agent
|
|
44
|
+
Selenium::WebDriver.for(:firefox, options: options)
|
|
45
|
+
else
|
|
46
|
+
Selenium::WebDriver.for(@config.browser)
|
|
47
|
+
end
|
|
48
|
+
puts "Driver setup complete." if @config.verbose?
|
|
49
|
+
driver
|
|
50
|
+
)
|
|
47
51
|
end
|
|
48
52
|
alias_method :set_up_driver, :driver
|
|
49
53
|
|
|
@@ -105,8 +109,10 @@ class Moby
|
|
|
105
109
|
driver.navigate.to(@config.url)
|
|
106
110
|
wait_for_page_load
|
|
107
111
|
driver.attempt do
|
|
108
|
-
|
|
109
|
-
|
|
112
|
+
unless @config.username_prefilled?
|
|
113
|
+
username_field.clear
|
|
114
|
+
username_field.send_keys(username)
|
|
115
|
+
end
|
|
110
116
|
password_field.clear
|
|
111
117
|
password_field.send_keys(password)
|
|
112
118
|
submit_form(form)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# lib/Moby/Backends.rb
|
|
2
|
+
# Moby::Backends
|
|
3
|
+
|
|
4
|
+
require_relative './Backends/MechanizeBackend'
|
|
5
|
+
require_relative './Backends/SeleniumBackend'
|
|
6
|
+
|
|
7
|
+
class Moby
|
|
8
|
+
module Backends
|
|
9
|
+
def self.for(config:, random_input_generator:)
|
|
10
|
+
case config.backend
|
|
11
|
+
when :mechanize
|
|
12
|
+
MechanizeBackend.new(config: config, random_input_generator: random_input_generator)
|
|
13
|
+
when :selenium
|
|
14
|
+
SeleniumBackend.new(config: config, random_input_generator: random_input_generator)
|
|
15
|
+
else
|
|
16
|
+
raise "Unknown backend: #{config.backend.inspect}."
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/Moby/Configuration.rb
CHANGED
|
@@ -21,6 +21,7 @@ class Moby
|
|
|
21
21
|
:password_field_number,
|
|
22
22
|
:username_hostname,
|
|
23
23
|
:username_is_email_address,
|
|
24
|
+
:username_prefilled,
|
|
24
25
|
:iterations,
|
|
25
26
|
:delay,
|
|
26
27
|
:word_list_path
|
|
@@ -58,6 +59,10 @@ class Moby
|
|
|
58
59
|
@username_is_email_address || @username_hostname || @username_field_name == 'email'
|
|
59
60
|
end
|
|
60
61
|
|
|
62
|
+
def username_prefilled?
|
|
63
|
+
@username_prefilled
|
|
64
|
+
end
|
|
65
|
+
|
|
61
66
|
def debug?
|
|
62
67
|
@debug
|
|
63
68
|
end
|
|
@@ -73,36 +78,37 @@ class Moby
|
|
|
73
78
|
private
|
|
74
79
|
|
|
75
80
|
# @param url [String] The target URL
|
|
76
|
-
# @param backend [Symbol] :mechanize or :selenium (default: :
|
|
81
|
+
# @param backend [Symbol] :mechanize or :selenium (default: :mechanize)
|
|
77
82
|
# @param browser [Symbol] :chrome or :firefox (default: :chrome)
|
|
78
83
|
|
|
79
|
-
def initialize(url:, backend: :
|
|
84
|
+
def initialize(url:, backend: :mechanize, browser: :chrome, **args)
|
|
80
85
|
@url = url
|
|
81
|
-
@backend = backend.to_sym
|
|
86
|
+
@backend = (backend || :mechanize).to_sym
|
|
82
87
|
|
|
83
88
|
# Browser configuration (for Selenium)
|
|
84
|
-
@browser = browser.to_sym
|
|
89
|
+
@browser = (browser || :chrome).to_sym
|
|
85
90
|
@user_agent = args[:user_agent]
|
|
86
91
|
|
|
87
92
|
# Form identification
|
|
88
93
|
@form_name = args[:form_name]
|
|
89
|
-
@form_number = args[:form_number] || 0
|
|
94
|
+
@form_number = (args[:form_number] || 0).to_i
|
|
90
95
|
|
|
91
96
|
# Field identification
|
|
92
|
-
@username_field_name = args[:username_field_name]
|
|
93
|
-
@username_field_number = args[:username_field_number] || 0
|
|
94
|
-
@password_field_name = args[:password_field_name]
|
|
95
|
-
@password_field_number = args[:password_field_number] || 1
|
|
97
|
+
@username_field_name = args[:username_field_name] || 'username'
|
|
98
|
+
@username_field_number = (args[:username_field_number] || 0).to_i
|
|
99
|
+
@password_field_name = args[:password_field_name] || 'password'
|
|
100
|
+
@password_field_number = (args[:password_field_number] || 1).to_i
|
|
96
101
|
|
|
97
102
|
# Username configuration
|
|
98
103
|
@username_hostname = args[:username_hostname]
|
|
99
104
|
@username_is_email_address = args[:username_is_email_address] || false
|
|
105
|
+
@username_prefilled = args[:username_prefilled] || false
|
|
100
106
|
|
|
101
107
|
# Runtime configuration
|
|
102
108
|
@debug = args[:debug] || false
|
|
103
109
|
@verbose = args[:verbose] || false
|
|
104
|
-
@iterations = args[:iterations]
|
|
105
|
-
@delay = args[:delay] || 0
|
|
110
|
+
@iterations = args[:iterations] && args[:iterations].to_i
|
|
111
|
+
@delay = (args[:delay] || 0).to_i
|
|
106
112
|
@word_list_path = args[:word_list_path] || '/usr/share/dict/words'
|
|
107
113
|
end
|
|
108
114
|
|
data/lib/Moby/VERSION.rb
CHANGED
data/lib/Moby.rb
CHANGED
|
@@ -1,179 +1,34 @@
|
|
|
1
1
|
# Moby.rb
|
|
2
2
|
# Moby
|
|
3
3
|
|
|
4
|
-
require 'mechanize'
|
|
5
|
-
require 'pp'
|
|
6
|
-
|
|
7
|
-
require_relative './File/self.collect'
|
|
8
4
|
require_relative './Moby/VERSION'
|
|
5
|
+
require_relative './Moby/Configuration'
|
|
6
|
+
require_relative './Moby/RandomInputGenerator'
|
|
7
|
+
require_relative './Moby/Backends'
|
|
9
8
|
|
|
10
9
|
class Moby
|
|
11
|
-
TLD = %w{com net org edu int mil gov arpa biz aero name coop info pro museum}
|
|
12
|
-
|
|
13
|
-
attr_accessor\
|
|
14
|
-
:debug,
|
|
15
|
-
:form_name,
|
|
16
|
-
:form_number,
|
|
17
|
-
:password_field_name,
|
|
18
|
-
:password_field_number,
|
|
19
|
-
:url,
|
|
20
|
-
:username_field_name,
|
|
21
|
-
:username_field_number,
|
|
22
|
-
:username_hostname,
|
|
23
|
-
:username_is_email_address,
|
|
24
|
-
:verbose
|
|
25
|
-
|
|
26
|
-
attr_writer\
|
|
27
|
-
:user_agent
|
|
28
|
-
|
|
29
|
-
def initialize(
|
|
30
|
-
debug: false,
|
|
31
|
-
form_name: nil,
|
|
32
|
-
form_number: 0,
|
|
33
|
-
password_field_name: 'password',
|
|
34
|
-
password_field_number: 1,
|
|
35
|
-
url: nil,
|
|
36
|
-
user_agent: nil,
|
|
37
|
-
username_field_name: 'username',
|
|
38
|
-
username_field_number: 0,
|
|
39
|
-
username_hostname: nil,
|
|
40
|
-
username_is_email_address: false,
|
|
41
|
-
verbose: false
|
|
42
|
-
)
|
|
43
|
-
@debug = debug
|
|
44
|
-
@form_name = form_name
|
|
45
|
-
@form_number = form_number.to_i
|
|
46
|
-
@password_field_name = password_field_name
|
|
47
|
-
@password_field_number = password_field_number.to_i
|
|
48
|
-
@url = url
|
|
49
|
-
@user_agent = user_agent
|
|
50
|
-
@username_field_name = username_field_name
|
|
51
|
-
@username_field_number = username_field_number.to_i
|
|
52
|
-
@username_hostname = username_hostname
|
|
53
|
-
@username_is_email_address = username_is_email_address
|
|
54
|
-
@verbose = verbose
|
|
55
|
-
end
|
|
56
|
-
|
|
57
10
|
def counter_phish
|
|
58
|
-
|
|
59
|
-
puts "Moby session begun #{start_time}."
|
|
60
|
-
submission_count = 0
|
|
61
|
-
begin
|
|
62
|
-
loop do
|
|
63
|
-
mechanize.user_agent_alias = user_agent
|
|
64
|
-
username_field.value = username
|
|
65
|
-
password_field.value = password
|
|
66
|
-
pp page if @debug
|
|
67
|
-
result = mechanize.submit(form)
|
|
68
|
-
pp result if @debug
|
|
69
|
-
submission_count += 1
|
|
70
|
-
puts "#{submission_count} #{username}:#{password}" if @verbose
|
|
71
|
-
rescue Net::HTTP::Persistent::Error
|
|
72
|
-
puts "\n\nNet::HTTP::Persistent::Error rescued.\n\n" if @verbose
|
|
73
|
-
rescue Net::OpenTimeout
|
|
74
|
-
puts "\n\nNet::OpenTimeout rescued.\n\n" if @verbose
|
|
75
|
-
end
|
|
76
|
-
ensure
|
|
77
|
-
finish_time = Time.now
|
|
78
|
-
time_delta_in_minutes = (finish_time - start_time) / 60
|
|
79
|
-
submissions_per_minute = submission_count / time_delta_in_minutes
|
|
80
|
-
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."
|
|
81
|
-
end
|
|
11
|
+
backend.counter_phish
|
|
82
12
|
end
|
|
83
13
|
|
|
84
14
|
private
|
|
85
15
|
|
|
86
|
-
def
|
|
87
|
-
@
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def words(filename = '/usr/share/dict/words')
|
|
91
|
-
@words ||= File.collect(filename){|line| line.chomp}
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def random_word
|
|
95
|
-
words[rand(words.size)]
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def random_TLD
|
|
99
|
-
TLD[rand(TLD.size)]
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def random_user_agent
|
|
103
|
-
agent_aliases = Mechanize::AGENT_ALIASES
|
|
104
|
-
agent_aliases.delete('Mechanize')
|
|
105
|
-
agent_aliases_keys = agent_aliases.keys
|
|
106
|
-
agent_aliases_keys[rand(agent_aliases.size)]
|
|
16
|
+
def initialize(**args)
|
|
17
|
+
@configuration = Configuration.new(**args)
|
|
107
18
|
end
|
|
108
19
|
|
|
109
|
-
def
|
|
110
|
-
@
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if md
|
|
114
|
-
puts 'meta_refresh_url found' if @debug
|
|
115
|
-
page = mechanize.get(md[1])
|
|
116
|
-
end
|
|
117
|
-
pp page if @debug
|
|
118
|
-
page
|
|
20
|
+
def backend
|
|
21
|
+
@backend ||= Backends.for(
|
|
22
|
+
config: @configuration,
|
|
23
|
+
random_input_generator: random_input_generator
|
|
119
24
|
)
|
|
120
25
|
end
|
|
121
26
|
|
|
122
|
-
def
|
|
123
|
-
@
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
page.forms[@form_number]
|
|
128
|
-
else
|
|
129
|
-
page.forms[0]
|
|
130
|
-
end
|
|
27
|
+
def random_input_generator
|
|
28
|
+
@random_input_generator ||= RandomInputGenerator.new(
|
|
29
|
+
word_list_path: @configuration.word_list_path,
|
|
30
|
+
username_hostname: @configuration.username_hostname,
|
|
31
|
+
username_is_email_address: @configuration.username_is_email_address?
|
|
131
32
|
)
|
|
132
33
|
end
|
|
133
|
-
|
|
134
|
-
def username
|
|
135
|
-
if username_is_email_address?
|
|
136
|
-
if username_hostname
|
|
137
|
-
"#{random_word}@#{@username_hostname}"
|
|
138
|
-
else
|
|
139
|
-
"#{random_word}@#{random_word}.#{random_TLD}"
|
|
140
|
-
end
|
|
141
|
-
else
|
|
142
|
-
random_word
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def password
|
|
147
|
-
random_word
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def user_agent
|
|
151
|
-
@user_agent || random_user_agent
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def username_field
|
|
155
|
-
if @username_field_name
|
|
156
|
-
form.field(@username_field_name)
|
|
157
|
-
elsif @username_field_number
|
|
158
|
-
form.fields[@username_field_number]
|
|
159
|
-
else
|
|
160
|
-
form.fields[0]
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def password_field
|
|
165
|
-
if @password_field_name
|
|
166
|
-
form.field(@password_field_name)
|
|
167
|
-
elsif @password_field_number
|
|
168
|
-
form.fields[@password_field_number]
|
|
169
|
-
else
|
|
170
|
-
form.fields[1]
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
# predicate methods
|
|
175
|
-
|
|
176
|
-
def username_is_email_address?
|
|
177
|
-
@username_is_email_address || @username_hostname || username_field.name == 'email'
|
|
178
|
-
end
|
|
179
34
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# moby.gemspec
|
|
1
|
+
# moby.rb.gemspec
|
|
2
2
|
|
|
3
3
|
require_relative './lib/Moby/VERSION'
|
|
4
4
|
|
|
@@ -16,9 +16,10 @@ Gem::Specification.new do |spec|
|
|
|
16
16
|
spec.homepage = 'https://github.com/thoran/moby'
|
|
17
17
|
spec.license = 'MIT'
|
|
18
18
|
|
|
19
|
-
spec.required_ruby_version = ">=
|
|
19
|
+
spec.required_ruby_version = ">= 3.3.0"
|
|
20
20
|
|
|
21
21
|
spec.add_dependency('mechanize', '~> 2')
|
|
22
|
+
spec.add_dependency('selenium-webdriver', '~> 4')
|
|
22
23
|
spec.add_dependency('switches.rb', '~> 0')
|
|
23
24
|
|
|
24
25
|
spec.add_development_dependency('minitest')
|
|
@@ -30,10 +31,10 @@ Gem::Specification.new do |spec|
|
|
|
30
31
|
'bin/*',
|
|
31
32
|
'lib/**/*.rb',
|
|
32
33
|
'test/**/*.rb',
|
|
33
|
-
'
|
|
34
|
+
'CHANGELOG',
|
|
34
35
|
'Gemfile',
|
|
35
36
|
'LICENSE.txt',
|
|
36
|
-
'moby.gemspec',
|
|
37
|
+
'moby.rb.gemspec',
|
|
37
38
|
'Rakefile',
|
|
38
39
|
'README.md',
|
|
39
40
|
]
|
|
@@ -44,7 +45,7 @@ Gem::Specification.new do |spec|
|
|
|
44
45
|
|
|
45
46
|
spec.metadata = {
|
|
46
47
|
"bug_tracker_uri" => "https://github.com/thoran/moby/issues",
|
|
47
|
-
"changelog_uri" => "https://github.com/thoran/moby/blob/main/
|
|
48
|
+
"changelog_uri" => "https://github.com/thoran/moby/blob/main/CHANGELOG",
|
|
48
49
|
"source_code_uri" => "https://github.com/thoran/moby",
|
|
49
50
|
"documentation_uri" => "https://github.com/thoran/moby/blob/main/README.md"
|
|
50
51
|
}
|