moby.rb 1.0.1 → 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 +31 -0
- data/Gemfile +4 -0
- data/bin/moby +24 -27
- data/lib/Moby/Backends/MechanizeBackend.rb +157 -0
- data/lib/Moby/Backends/SeleniumBackend.rb +242 -0
- data/lib/Moby/Backends.rb +20 -0
- data/lib/Moby/Configuration.rb +119 -0
- data/lib/Moby/RandomInputGenerator.rb +49 -0
- data/lib/Moby/VERSION.rb +1 -1
- data/lib/Moby.rb +15 -160
- data/lib/Selenium/WebDriver/Driver/Attempt/attempt.rb +18 -0
- data/lib/Selenium/WebDriver/SearchContext/ElementPresentQ/element_presentQ.rb +13 -0
- data/lib/Thoran/Selenium/WebDriver/Driver/Attempt/attempt.rb +53 -0
- data/lib/Thoran/Selenium/WebDriver/SearchContext/ElementPresentQ/element_presentQ.rb +39 -0
- data/moby.rb.gemspec +2 -1
- 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 +31 -5
- 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
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
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
|
+
|
|
3
24
|
## 20260807
|
|
4
25
|
|
|
5
26
|
1.0.1: Add a CHANGELOG in the form changelogger writes, and retire CHANGES.txt.
|
|
@@ -9,6 +30,7 @@
|
|
|
9
30
|
3. ~ lib/Moby/VERSION.rb: VERSION: /1.0.0/1.0.1/
|
|
10
31
|
4. ~ test/Moby/VERSION_test.rb: /1.0.0/1.0.1/
|
|
11
32
|
|
|
33
|
+
|
|
12
34
|
## 20260108
|
|
13
35
|
|
|
14
36
|
1.0.0: Ensure that tests are in place and that version handling is working properly.
|
|
@@ -23,6 +45,7 @@
|
|
|
23
45
|
8. ~ CHANGES.txt: Reorganised in descending order and added dates.
|
|
24
46
|
9. ~ LICENSE.txt: Updated the years to include the year this was started: /2026/2006-2026/
|
|
25
47
|
|
|
48
|
+
|
|
26
49
|
## 20260105
|
|
27
50
|
|
|
28
51
|
0.9.1: Fix syntax errors in README.md.
|
|
@@ -30,10 +53,12 @@
|
|
|
30
53
|
1. ~ README.md: Fix syntax errors.
|
|
31
54
|
2. ~ lib/Moby.rb: VERSION_STRING: /0.9.0/0.9.1/
|
|
32
55
|
|
|
56
|
+
|
|
33
57
|
## 20240605
|
|
34
58
|
|
|
35
59
|
0.9.0: Merge of 2019's 0.8.0 and 2023's 0.8.1.
|
|
36
60
|
|
|
61
|
+
|
|
37
62
|
## 20231206
|
|
38
63
|
|
|
39
64
|
0.8.1: Fix when username or password field number is specified or nothing is specified.
|
|
@@ -41,6 +66,7 @@
|
|
|
41
66
|
1. ~ lib/Whale: ~ set_username_field(): Fix when username field number is specified or nothing is specified.
|
|
42
67
|
2. ~ lib/Whale: ~ set_password_field(): Fix when password field number is specified or nothing is specified.
|
|
43
68
|
|
|
69
|
+
|
|
44
70
|
## 20231119
|
|
45
71
|
|
|
46
72
|
0.8.0: An accidental fork of 0.7.0 in 2023.
|
|
@@ -54,18 +80,21 @@
|
|
|
54
80
|
7. ~ lib/Whale.rb: general tidying
|
|
55
81
|
8. + lib/File/self.collect.rb
|
|
56
82
|
|
|
83
|
+
|
|
57
84
|
## 20190820
|
|
58
85
|
|
|
59
86
|
0.7.4: Rescue Net::OpenTimeout in the submission loop.
|
|
60
87
|
|
|
61
88
|
1. ~ counter_phish(): + rescue Net::OpenTimeout.
|
|
62
89
|
|
|
90
|
+
|
|
63
91
|
## 20190819
|
|
64
92
|
|
|
65
93
|
0.7.3: Rescue Net::HTTP::Persistent::Error in place of EOFError.
|
|
66
94
|
|
|
67
95
|
1. ~ counter_phish(): rescue Net::HTTP::Persistent::Error in place of EOFError.
|
|
68
96
|
|
|
97
|
+
|
|
69
98
|
## 20190819
|
|
70
99
|
|
|
71
100
|
0.7.2: Set field values on the field objects rather than through the setter methods.
|
|
@@ -75,6 +104,7 @@
|
|
|
75
104
|
3. ~ user_agent: attr_writer in place of attr_accessor.
|
|
76
105
|
4. ~ counter_phish(): verbose reports username_field.value:password_field.value.
|
|
77
106
|
|
|
107
|
+
|
|
78
108
|
## 20190819
|
|
79
109
|
|
|
80
110
|
0.7.1: Assign to the field's value rather than replacing the field object.
|
|
@@ -82,6 +112,7 @@
|
|
|
82
112
|
1. ~ set_username_field()/set_password_field(): set the field's value in place of replacing the field object at form.fields[n].
|
|
83
113
|
2. ~ counter_phish(): + rescue EOFError; + pp page in debug.
|
|
84
114
|
|
|
115
|
+
|
|
85
116
|
## 20190815
|
|
86
117
|
|
|
87
118
|
0.7.0: The earliest recoverable version.
|
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
|
|
@@ -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 unless @config.username_prefilled?
|
|
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
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# lib/Moby/Backends/SeleniumBackend.rb
|
|
2
|
+
# Moby::Backends::SeleniumBackend
|
|
3
|
+
|
|
4
|
+
require 'selenium-webdriver'
|
|
5
|
+
|
|
6
|
+
require_relative '../../Selenium/WebDriver/Driver/Attempt/attempt'
|
|
7
|
+
require_relative '../../Selenium/WebDriver/SearchContext/ElementPresentQ/element_presentQ'
|
|
8
|
+
|
|
9
|
+
class Moby
|
|
10
|
+
module Backends
|
|
11
|
+
class SeleniumBackend
|
|
12
|
+
def counter_phish
|
|
13
|
+
set_up_driver
|
|
14
|
+
navigate_to_target
|
|
15
|
+
repeatedly_fill_and_submit_login_form
|
|
16
|
+
ensure
|
|
17
|
+
cleanup_driver unless @config.debug?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def initialize(config:, random_input_generator:)
|
|
23
|
+
@config = config
|
|
24
|
+
@random_input_generator = random_input_generator
|
|
25
|
+
@driver = nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def driver
|
|
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
|
+
)
|
|
51
|
+
end
|
|
52
|
+
alias_method :set_up_driver, :driver
|
|
53
|
+
|
|
54
|
+
def navigate_to_target
|
|
55
|
+
puts "Navigating to #{@config.url}..." if @config.verbose?
|
|
56
|
+
@driver.attempt do
|
|
57
|
+
@driver.get(@config.url)
|
|
58
|
+
wait_for_page_load
|
|
59
|
+
end
|
|
60
|
+
puts "Page loaded successfully." if @config.verbose?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def wait_for_page_load
|
|
64
|
+
wait = Selenium::WebDriver::Wait.new(timeout: 10)
|
|
65
|
+
wait.until do
|
|
66
|
+
@driver.execute_script('return document.readyState') == 'complete'
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def form
|
|
71
|
+
@form ||= (
|
|
72
|
+
if @config.using_form_name?
|
|
73
|
+
find_form_by_name
|
|
74
|
+
else
|
|
75
|
+
find_form_by_number
|
|
76
|
+
end
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def repeatedly_fill_and_submit_login_form
|
|
81
|
+
start_time = Time.now
|
|
82
|
+
puts "moby session begun #{start_time}."
|
|
83
|
+
submission_count = 0
|
|
84
|
+
begin
|
|
85
|
+
if @config.fixed_number_of_submissions?
|
|
86
|
+
@config.iterations.times do |iteration|
|
|
87
|
+
fill_and_submit_login_form
|
|
88
|
+
submission_count += 1
|
|
89
|
+
puts "Submission #{submission_count} of #{@config.iterations}." if @config.verbose?
|
|
90
|
+
end
|
|
91
|
+
puts "Successfully completed #{@config.iterations} submissions." if @config.verbose?
|
|
92
|
+
else
|
|
93
|
+
loop do
|
|
94
|
+
fill_and_submit_login_form
|
|
95
|
+
submission_count += 1
|
|
96
|
+
puts "#{submission_count} #{username}:#{password}" if @config.verbose?
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
ensure
|
|
100
|
+
finish_time = Time.now
|
|
101
|
+
time_delta_in_minutes = (finish_time - start_time) / 60
|
|
102
|
+
submissions_per_minute = submission_count / time_delta_in_minutes
|
|
103
|
+
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."
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def fill_and_submit_login_form
|
|
108
|
+
puts "Filling with: #{username}:#{password}" if @config.verbose?
|
|
109
|
+
driver.navigate.to(@config.url)
|
|
110
|
+
wait_for_page_load
|
|
111
|
+
driver.attempt do
|
|
112
|
+
unless @config.username_prefilled?
|
|
113
|
+
username_field.clear
|
|
114
|
+
username_field.send_keys(username)
|
|
115
|
+
end
|
|
116
|
+
password_field.clear
|
|
117
|
+
password_field.send_keys(password)
|
|
118
|
+
submit_form(form)
|
|
119
|
+
end
|
|
120
|
+
sleep @config.delay if @config.delay > 0
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def find_form_by_name
|
|
124
|
+
name = @config.form_name
|
|
125
|
+
selectors = [
|
|
126
|
+
[:name, name],
|
|
127
|
+
[:id, name],
|
|
128
|
+
[:css, "form[name='#{name}']"],
|
|
129
|
+
[:css, "form##{name}"]
|
|
130
|
+
]
|
|
131
|
+
selectors.each do |type, selector|
|
|
132
|
+
if @driver.element_present?(type, selector)
|
|
133
|
+
return @driver.find_element(type, selector)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
raise("Could not find a form with the name or id of #{@config.form_name}.")
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def find_form_by_number
|
|
140
|
+
index = @config.form_number
|
|
141
|
+
forms = @driver.find_elements(:tag_name, 'form')
|
|
142
|
+
return forms[index] if forms[index]
|
|
143
|
+
raise("Could not find a form at index #{@config.form_number}.")
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def username
|
|
147
|
+
@random_input_generator.random_username
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def password
|
|
151
|
+
@random_input_generator.random_password
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def username_field
|
|
155
|
+
@username_field ||= (
|
|
156
|
+
if @config.using_username_field_name?
|
|
157
|
+
find_username_field_by_name
|
|
158
|
+
else
|
|
159
|
+
find_username_field_by_number
|
|
160
|
+
end
|
|
161
|
+
)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def password_field
|
|
165
|
+
@password_field ||= (
|
|
166
|
+
if @config.using_password_field_name?
|
|
167
|
+
find_password_field_by_name
|
|
168
|
+
else
|
|
169
|
+
find_password_field_by_number
|
|
170
|
+
end
|
|
171
|
+
)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def find_username_field_by_name
|
|
175
|
+
name = @config.username_field_name
|
|
176
|
+
selectors = [
|
|
177
|
+
[:name, name],
|
|
178
|
+
[:id, name],
|
|
179
|
+
[:css, "input[name='#{name}']"],
|
|
180
|
+
[:css, "input##{name}"]
|
|
181
|
+
]
|
|
182
|
+
selectors.each do |type, selector|
|
|
183
|
+
begin
|
|
184
|
+
element = form.find_element(type, selector)
|
|
185
|
+
return element if element
|
|
186
|
+
rescue Selenium::WebDriver::Error::NoSuchElementError
|
|
187
|
+
next
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
raise("Could not find username field: #{@config.username_field_name}")
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def find_username_field_by_number
|
|
194
|
+
index = @config.username_field_number
|
|
195
|
+
inputs = form.find_elements(:tag_name, 'input')
|
|
196
|
+
inputs[index] ||
|
|
197
|
+
raise("Could not find username field at index #{index}")
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def find_password_field_by_name
|
|
201
|
+
name = @config.password_field_name
|
|
202
|
+
selectors = [
|
|
203
|
+
[:name, name],
|
|
204
|
+
[:id, name],
|
|
205
|
+
[:css, "input[name='#{name}']"],
|
|
206
|
+
[:css, "input##{name}"]
|
|
207
|
+
]
|
|
208
|
+
selectors.each do |type, selector|
|
|
209
|
+
begin
|
|
210
|
+
element = form.find_element(type, selector)
|
|
211
|
+
return element if element
|
|
212
|
+
rescue Selenium::WebDriver::Error::NoSuchElementError
|
|
213
|
+
next
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
raise("Could not find password field: #{@config.password_field_name}")
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def find_password_field_by_number
|
|
220
|
+
index = @config.password_field_number
|
|
221
|
+
inputs = form.find_elements(:tag_name, 'input')
|
|
222
|
+
inputs[index] ||
|
|
223
|
+
raise("Could not find password field at index #{index}")
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def submit_form(form)
|
|
227
|
+
begin
|
|
228
|
+
submit_button = form.find_element(:css, "input[type='submit'], button[type='submit'], button")
|
|
229
|
+
submit_button.click
|
|
230
|
+
rescue
|
|
231
|
+
form.submit
|
|
232
|
+
end
|
|
233
|
+
sleep 0.5
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def cleanup_driver
|
|
237
|
+
@driver.quit if @driver
|
|
238
|
+
puts "Driver closed." if @config.verbose?
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
@@ -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
|