moby.rb 1.0.1 → 2.0.1

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.
@@ -0,0 +1,27 @@
1
+ # test/Moby/Backends_test.rb
2
+
3
+ require_relative '../test_helper'
4
+
5
+ describe Moby::Backends do
6
+ let(:url){'https://phishing.example.com/login'}
7
+ let(:generator){Moby::RandomInputGenerator.new(word_list_path: TestFixtures::WORD_LIST_PATH)}
8
+
9
+ def backend_for(**config_args)
10
+ config = Moby::Configuration.new(url: url, **config_args)
11
+ Moby::Backends.for(config: config, random_input_generator: generator)
12
+ end
13
+
14
+ describe ".for" do
15
+ it "returns a MechanizeBackend for the mechanize configuration" do
16
+ _(backend_for).must_be_instance_of(Moby::Backends::MechanizeBackend)
17
+ end
18
+
19
+ it "returns a SeleniumBackend for the selenium configuration" do
20
+ _(backend_for(backend: :selenium)).must_be_instance_of(Moby::Backends::SeleniumBackend)
21
+ end
22
+
23
+ it "raises for an unknown backend" do
24
+ _(proc{backend_for(backend: :ftp)}).must_raise(RuntimeError)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,134 @@
1
+ # test/Moby/Configuration_test.rb
2
+
3
+ require_relative '../test_helper'
4
+
5
+ describe Moby::Configuration do
6
+ let(:url){'https://phishing.example.com/login'}
7
+
8
+ describe "defaults" do
9
+ let(:config){Moby::Configuration.new(url: url)}
10
+
11
+ it "keeps the url" do
12
+ _(config.url).must_equal(url)
13
+ end
14
+
15
+ it "defaults the backend to mechanize" do
16
+ _(config.backend).must_equal(:mechanize)
17
+ _(config.mechanize?).must_equal(true)
18
+ _(config.selenium?).must_equal(false)
19
+ end
20
+
21
+ it "defaults the browser to chrome" do
22
+ _(config.browser).must_equal(:chrome)
23
+ end
24
+
25
+ it "defaults form_number to 0" do
26
+ _(config.form_number).must_equal(0)
27
+ end
28
+
29
+ it "defaults the field names to username and password" do
30
+ _(config.username_field_name).must_equal('username')
31
+ _(config.password_field_name).must_equal('password')
32
+ end
33
+
34
+ it "defaults the field numbers to 0 and 1" do
35
+ _(config.username_field_number).must_equal(0)
36
+ _(config.password_field_number).must_equal(1)
37
+ end
38
+
39
+ it "runs indefinitely by default" do
40
+ _(config.iterations).must_be_nil
41
+ _(config.fixed_number_of_submissions?).must_equal(false)
42
+ end
43
+
44
+ it "defaults the delay to 0" do
45
+ _(config.delay).must_equal(0)
46
+ end
47
+
48
+ it "is not prefilled, email, verbose or debug by default" do
49
+ _(config.username_prefilled?).must_equal(false)
50
+ _(config.username_is_email_address?).must_equal(false)
51
+ _(config.verbose?).must_equal(false)
52
+ _(config.debug?).must_equal(false)
53
+ end
54
+ end
55
+
56
+ describe "backend and browser selection" do
57
+ it "selects selenium" do
58
+ _(Moby::Configuration.new(url: url, backend: :selenium).selenium?).must_equal(true)
59
+ end
60
+
61
+ it "coerces a string backend to a symbol" do
62
+ _(Moby::Configuration.new(url: url, backend: 'selenium').backend).must_equal(:selenium)
63
+ end
64
+
65
+ it "falls back to mechanize when the backend is nil" do
66
+ _(Moby::Configuration.new(url: url, backend: nil).backend).must_equal(:mechanize)
67
+ end
68
+
69
+ it "falls back to chrome when the browser is nil" do
70
+ _(Moby::Configuration.new(url: url, browser: nil).browser).must_equal(:chrome)
71
+ end
72
+ end
73
+
74
+ describe "coercion of string inputs" do
75
+ let(:config) do
76
+ Moby::Configuration.new(
77
+ url: url,
78
+ form_number: '2',
79
+ username_field_number: '3',
80
+ password_field_number: '4',
81
+ delay: '5',
82
+ iterations: '6'
83
+ )
84
+ end
85
+
86
+ it "coerces the numeric switches to integers" do
87
+ _(config.form_number).must_equal(2)
88
+ _(config.username_field_number).must_equal(3)
89
+ _(config.password_field_number).must_equal(4)
90
+ _(config.delay).must_equal(5)
91
+ _(config.iterations).must_equal(6)
92
+ end
93
+
94
+ it "treats a given iterations count as a fixed number of submissions" do
95
+ _(config.fixed_number_of_submissions?).must_equal(true)
96
+ end
97
+ end
98
+
99
+ describe "#username_is_email_address?" do
100
+ it "is true when set explicitly" do
101
+ _(Moby::Configuration.new(url: url, username_is_email_address: true).username_is_email_address?).must_equal(true)
102
+ end
103
+
104
+ it "is truthy when a username hostname is given" do
105
+ _(Moby::Configuration.new(url: url, username_hostname: 'example.com').username_is_email_address?).wont_equal(false)
106
+ end
107
+
108
+ it "is truthy when the username field is named email" do
109
+ _(Moby::Configuration.new(url: url, username_field_name: 'email').username_is_email_address?).wont_equal(false)
110
+ end
111
+ end
112
+
113
+ describe "#user_agent" do
114
+ it "returns a random agent when not set" do
115
+ agent = Moby::Configuration.new(url: url).user_agent
116
+ _(agent).must_be_kind_of(String)
117
+ _(agent).wont_equal('Mechanize')
118
+ end
119
+
120
+ it "returns the set agent" do
121
+ _(Moby::Configuration.new(url: url, user_agent: 'CustomAgent').user_agent).must_equal('CustomAgent')
122
+ end
123
+ end
124
+
125
+ describe "#using_form_name?" do
126
+ it "is false without a form name" do
127
+ _(Moby::Configuration.new(url: url).using_form_name?).must_equal(false)
128
+ end
129
+
130
+ it "is true with a form name" do
131
+ _(Moby::Configuration.new(url: url, form_name: 'login').using_form_name?).must_equal(true)
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,37 @@
1
+ # test/Moby/RandomInputGenerator_test.rb
2
+
3
+ require_relative '../test_helper'
4
+
5
+ describe Moby::RandomInputGenerator do
6
+ let(:word_list_path){TestFixtures::WORD_LIST_PATH}
7
+
8
+ def generator(**args)
9
+ Moby::RandomInputGenerator.new(word_list_path: word_list_path, **args)
10
+ end
11
+
12
+ describe "#random_username" do
13
+ it "returns a word from the list by default" do
14
+ _(TestFixtures::WORDS).must_include(generator.random_username)
15
+ end
16
+
17
+ it "returns an email address when username_is_email_address is true" do
18
+ _(generator(username_is_email_address: true).random_username).must_match(/\A[^@]+@[^@]+\z/)
19
+ end
20
+
21
+ it "uses the given hostname for the email address" do
22
+ username = generator(username_hostname: 'example.com', username_is_email_address: true).random_username
23
+ _(username).must_match(/@example\.com\z/)
24
+ end
25
+
26
+ it "generates more than one distinct username" do
27
+ usernames = 20.times.map{generator.random_username}
28
+ _(usernames.uniq.length).must_be(:>, 1)
29
+ end
30
+ end
31
+
32
+ describe "#random_password" do
33
+ it "returns a word from the list" do
34
+ _(TestFixtures::WORDS).must_include(generator.random_password)
35
+ end
36
+ end
37
+ end
@@ -16,8 +16,8 @@ describe Moby do
16
16
  _(Moby::VERSION).must_match(/\d+\.\d+\.\d+/)
17
17
  end
18
18
 
19
- it "version is 1.0.1" do
20
- _(Moby::VERSION).must_equal('1.0.1')
19
+ it "version is 2.0.1" do
20
+ _(Moby::VERSION).must_equal('2.0.1')
21
21
  end
22
22
  end
23
23
  end
@@ -0,0 +1,27 @@
1
+ # test/gemspec_test.rb
2
+
3
+ require_relative './test_helper'
4
+
5
+ describe 'moby.rb.gemspec' do
6
+ let(:spec){Gem::Specification.load(File.expand_path('../moby.rb.gemspec', __dir__))}
7
+
8
+ it "is a valid specification" do
9
+ _(spec.validate).must_equal(true)
10
+ end
11
+
12
+ it "does not pin a date" do
13
+ _(spec.date).must_equal(Gem::Specification.new.date)
14
+ end
15
+
16
+ it "takes its version from Moby::VERSION" do
17
+ _(spec.version.to_s).must_equal(Moby::VERSION)
18
+ end
19
+
20
+ it "declares its runtime dependencies" do
21
+ _(spec.runtime_dependencies.map(&:name).sort).must_equal(%w{mechanize selenium-webdriver switches.rb})
22
+ end
23
+
24
+ it "declares its development dependencies" do
25
+ _(spec.development_dependencies.map(&:name).sort).must_equal(%w{minitest minitest-spec-context rake webmock})
26
+ end
27
+ end
data/test/moby_test.rb CHANGED
@@ -4,292 +4,16 @@ require_relative './test_helper'
4
4
 
5
5
  describe Moby do
6
6
  let(:url){'https://phishing.example.com/login'}
7
- let(:moby){Moby.new(url: url)}
8
7
 
9
- before do
10
- WebMock.disable_net_connect!
11
-
12
- # Stub the login page
13
- stub_request(:get, url)
14
- .to_return(
15
- status: 200,
16
- body: TestFixtures::SIMPLE_LOGIN_FORM,
17
- headers: {'Content-Type' => 'text/html'}
18
- )
19
-
20
- # Stub form submission
21
- stub_request(:post, 'https://phishing.example.com/login')
22
- .to_return(status: 200, body: 'Success')
23
- end
24
-
25
- after do
26
- WebMock.reset!
27
- WebMock.allow_net_connect!
28
- end
29
-
30
- describe "initialization" do
31
- it "accepts url parameter" do
32
- _(moby.url).must_equal(url)
33
- end
34
-
35
- it "sets default form_number to 0" do
36
- _(moby.form_number).must_equal(0)
37
- end
38
-
39
- it "sets default username_field_name to 'username'" do
40
- _(moby.username_field_name).must_equal('username')
41
- end
42
-
43
- it "sets default password_field_name to 'password'" do
44
- _(moby.password_field_name).must_equal('password')
45
- end
46
-
47
- it "sets default username_field_number to 0" do
48
- _(moby.username_field_number).must_equal(0)
49
- end
50
-
51
- it "sets default password_field_number to 1" do
52
- _(moby.password_field_number).must_equal(1)
53
- end
54
-
55
- it "accepts form_name parameter" do
56
- m = Moby.new(url: url, form_name: 'login')
57
-
58
- _(m.form_name).must_equal('login')
59
- end
60
-
61
- it "accepts username_hostname parameter" do
62
- m = Moby.new(url: url, username_hostname: 'example.com')
63
-
64
- _(m.username_hostname).must_equal('example.com')
65
- end
66
-
67
- it "accepts username_is_email_address parameter" do
68
- m = Moby.new(url: url, username_is_email_address: true)
69
-
70
- _(m.username_is_email_address).must_equal(true)
71
- end
72
- end
73
-
74
- describe "mechanize setup" do
75
- it "creates Mechanize instance lazily" do
76
- mech = moby.instance_variable_get(:@mechanize)
77
-
78
- _(mech).must_be_nil
79
-
80
- moby.send(:mechanize)
81
- mech = moby.instance_variable_get(:@mechanize)
82
-
83
- _(mech).must_be_instance_of(Mechanize)
84
- end
85
-
86
- it "reuses same mechanize instance" do
87
- mech1 = moby.send(:mechanize)
88
- mech2 = moby.send(:mechanize)
89
-
90
- _(mech1).must_be_same_as(mech2)
91
- end
92
- end
93
-
94
- describe "page loading" do
95
- it "loads the target URL" do
96
- page = moby.send(:page)
97
-
98
- _(page).must_be_instance_of(Mechanize::Page)
99
- assert_requested(:get, url, times: 1)
100
- end
101
-
102
- it "reuses loaded page" do
103
- page1 = moby.send(:page)
104
- page2 = moby.send(:page)
105
-
106
- _(page1).must_be_same_as(page2)
107
- assert_requested(:get, url, times: 1)
108
- end
109
-
110
- it "handles meta refresh redirects" do
111
- redirect_body = '<html><head><meta http-equiv="Refresh" content="0; url=https://phishing.example.com/real-login"></head></html>'
112
-
113
- stub_request(:get, url)
114
- .to_return(status: 200, body: redirect_body, headers: {'Content-Type' => 'text/html'})
115
-
116
- stub_request(:get, 'https://phishing.example.com/real-login')
117
- .to_return(status: 200, body: TestFixtures::SIMPLE_LOGIN_FORM, headers: {'Content-Type' => 'text/html'})
118
-
119
- m = Moby.new(url: url)
120
- page = m.send(:page)
121
-
122
- assert_requested(:get, url, times: 1)
123
- assert_requested(:get, 'https://phishing.example.com/real-login', times: 1)
124
- end
125
- end
126
-
127
- describe "form finding" do
128
- describe "finding by name" do
129
- let(:moby) do
130
- Moby.new(url: url, form_name: 'login')
131
- end
132
-
133
- it "finds form by name when form_name is set" do
134
- moby.send(:page)
135
- form = moby.send(:form)
136
-
137
- _(form).must_be_instance_of(Mechanize::Form)
138
- _(form.name).must_equal('login')
139
- end
140
- end
141
-
142
- describe "finding by number" do
143
- let(:moby) do
144
- Moby.new(url: 'https://multi.example.com/page', form_number: 1)
145
- end
146
-
147
- it "finds form by number when form_number is set" do
148
- stub_request(:get, 'https://multi.example.com/page')
149
- .to_return(
150
- status: 200,
151
- body: TestFixtures::MULTIPLE_FORMS,
152
- headers: {'Content-Type' => 'text/html'}
153
- )
154
- moby.send(:page)
155
- form = moby.send(:form)
156
-
157
- _(form).must_be_instance_of(Mechanize::Form)
158
- _(form.node['id']).must_equal('login_form')
159
- end
160
- end
161
-
162
- describe "deafult form" do
163
- it "defaults to first form" do
164
- moby.send(:page)
165
- form = moby.send(:form)
166
-
167
- _(form).must_be_instance_of(Mechanize::Form)
168
- end
169
- end
170
- end
171
-
172
- describe "field finding" do
173
- describe "finding by name" do
174
- let(:moby) do
175
- Moby.new(url: url, form_name: 'login', username_field_name: 'username', password_field_name: 'password')
176
- end
177
-
178
- it "finds username field by name" do
179
- moby.send(:page)
180
- moby.send(:form)
181
- field = moby.send(:username_field)
182
-
183
- _(field.name).must_equal('username')
184
- end
185
-
186
- it "finds password field by name" do
187
- moby.send(:page)
188
- moby.send(:form)
189
- field = moby.send(:password_field)
190
-
191
- _(field.name).must_equal('password')
192
- end
193
- end
194
-
195
- describe "finding by number" do
196
- let(:moby) do
197
- Moby.new(url: url, username_field_number: 0, password_field_number: 1)
198
- end
199
-
200
- it "finds username field by number" do
201
- moby.send(:page)
202
- moby.send(:form)
203
- field = moby.send(:username_field)
204
-
205
- _(field).wont_be_nil
206
- end
207
-
208
- it "finds password field by number" do
209
- moby.send(:page)
210
- moby.send(:form)
211
- field = moby.send(:password_field)
212
-
213
- _(field).wont_be_nil
214
- end
215
- end
216
- end
217
-
218
- describe "#username" do
219
- it "generates random username" do
220
- username = moby.send(:username)
221
-
222
- _(username).must_be_kind_of(String)
223
- _(username.length).must_be(:>, 0)
224
- end
225
-
226
- it "generates email address when username_is_email_address is true" do
227
- m = Moby.new(url: url, username_is_email_address: true)
228
- username = m.send(:username)
229
-
230
- _(username).must_match(/@/)
231
- end
232
-
233
- it "generates email with custom hostname" do
234
- m = Moby.new(url: url, username_hostname: 'example.com')
235
- username = m.send(:username)
236
-
237
- _(username).must_match(/@example\.com$/)
238
- end
239
-
240
-
241
- it "generates different usernames" do
242
- usernames = 10.times.map{moby.send(:username)}
243
-
244
- _(usernames.uniq.length).must_be(:>, 1)
245
- end
8
+ it "selects the mechanize backend by default" do
9
+ _(Moby.new(url: url).send(:backend)).must_be_instance_of(Moby::Backends::MechanizeBackend)
246
10
  end
247
11
 
248
- describe "#password" do
249
- it "generates random password" do
250
- password = moby.send(:password)
251
-
252
- _(password).must_be_kind_of(String)
253
- _(password.length).must_be(:>, 0)
254
- end
12
+ it "selects the selenium backend when configured" do
13
+ _(Moby.new(url: url, backend: :selenium).send(:backend)).must_be_instance_of(Moby::Backends::SeleniumBackend)
255
14
  end
256
15
 
257
- describe "#user_agent" do
258
- it "returns random user agent when not set" do
259
- agent = moby.send(:user_agent)
260
-
261
- _(agent).must_be_kind_of(String)
262
- _(agent).wont_equal('Mechanize')
263
- end
264
-
265
- it "returns set user agent when provided" do
266
- m = Moby.new(url: url, user_agent: 'CustomAgent')
267
-
268
- _(m.send(:user_agent)).must_equal('CustomAgent')
269
- end
270
-
271
- it "generates different random agents" do
272
- agents = 10.times.map{Moby.new(url: url).send(:user_agent)}
273
-
274
- _(agents.uniq.length).must_be(:>, 1)
275
- end
276
- end
277
-
278
- describe "#username_is_email_address?" do
279
- it "returns true when username_is_email_address is set" do
280
- m = Moby.new(url: url, username_is_email_address: true)
281
-
282
- _(m.send(:username_is_email_address?)).must_equal(true)
283
- end
284
-
285
- it "returns true when username_hostname is set" do
286
- m = Moby.new(url: url, username_hostname: 'example.com')
287
-
288
- _(m.send(:username_is_email_address?)).wont_be_nil
289
- end
290
-
291
- it "returns false by default" do
292
- _(moby.send(:username_field_name)).must_equal('username')
293
- end
16
+ it "responds to counter_phish" do
17
+ _(Moby.new(url: url)).must_respond_to(:counter_phish)
294
18
  end
295
19
  end
data/test/test_helper.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require 'minitest/autorun'
4
4
  require 'minitest-spec-context'
5
5
  require 'webmock/minitest'
6
+ require 'tempfile'
6
7
 
7
8
  require_relative '../lib/moby'
8
9
 
@@ -33,4 +34,18 @@ module TestFixtures
33
34
  </body>
34
35
  </html>
35
36
  HTML
37
+
38
+ WORDS = %w{
39
+ alpha bravo charlie delta echo foxtrot golf hotel india juliet
40
+ kilo lima mike november oscar papa quebec romeo sierra tango
41
+ uniform victor whiskey xray yankee zulu apple banana cherry date
42
+ elder fig grape honey iris jasmine kiwi lemon mango nutmeg
43
+ }.freeze
44
+
45
+ WORD_LIST_FILE = Tempfile.new('moby_words').tap do |file|
46
+ file.write(WORDS.join("\n") << "\n")
47
+ file.flush
48
+ end
49
+
50
+ WORD_LIST_PATH = WORD_LIST_FILE.path
36
51
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: moby.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-01-08 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: mechanize
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '2'
26
+ - !ruby/object:Gem::Dependency
27
+ name: selenium-webdriver
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '4'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '4'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: switches.rb
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -108,12 +122,25 @@ files:
108
122
  - README.md
109
123
  - Rakefile
110
124
  - bin/moby
111
- - lib/File/self.collect.rb
112
125
  - lib/Moby.rb
126
+ - lib/Moby/Backends.rb
127
+ - lib/Moby/Backends/MechanizeBackend.rb
128
+ - lib/Moby/Backends/SeleniumBackend.rb
129
+ - lib/Moby/Configuration.rb
130
+ - lib/Moby/RandomInputGenerator.rb
113
131
  - lib/Moby/VERSION.rb
114
- - lib/Thoran/File/SelfCollect/self.collect.rb
132
+ - lib/Selenium/WebDriver/Driver/Attempt/attempt.rb
133
+ - lib/Selenium/WebDriver/SearchContext/ElementPresentQ/element_presentQ.rb
134
+ - lib/Thoran/Selenium/WebDriver/Driver/Attempt/attempt.rb
135
+ - lib/Thoran/Selenium/WebDriver/SearchContext/ElementPresentQ/element_presentQ.rb
115
136
  - moby.rb.gemspec
137
+ - test/Moby/Backends/MechanizeBackend_test.rb
138
+ - test/Moby/Backends/SeleniumBackend_test.rb
139
+ - test/Moby/Backends_test.rb
140
+ - test/Moby/Configuration_test.rb
141
+ - test/Moby/RandomInputGenerator_test.rb
116
142
  - test/Moby/VERSION_test.rb
143
+ - test/gemspec_test.rb
117
144
  - test/moby_test.rb
118
145
  - test/test_helper.rb
119
146
  homepage: https://github.com/thoran/moby
@@ -131,14 +158,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
131
158
  requirements:
132
159
  - - ">="
133
160
  - !ruby/object:Gem::Version
134
- version: 2.5.0
161
+ version: 3.3.0
135
162
  required_rubygems_version: !ruby/object:Gem::Requirement
136
163
  requirements:
137
164
  - - ">="
138
165
  - !ruby/object:Gem::Version
139
166
  version: '0'
140
167
  requirements: []
141
- rubygems_version: 4.0.17
168
+ rubygems_version: 4.0.18
142
169
  specification_version: 4
143
170
  summary: Moby is a credentials poisoning tool which floods phishing forms with fake
144
171
  credentials.
@@ -1,10 +0,0 @@
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'
@@ -1,22 +0,0 @@
1
- # Thoran/File/SelfCollect/self.collect.rb
2
- # Thoran::File::SelfCollect.collect.rb
3
-
4
- # 20190821
5
- # 0.2.0
6
-
7
- # Changes:
8
- # 1. + Thoran::File::SelfCollect namespace.
9
-
10
- module Thoran
11
- module File
12
- module SelfCollect
13
-
14
- def collect(filename, &block)
15
- open(filename, 'r').collect(&block)
16
- end
17
-
18
- end
19
- end
20
- end
21
-
22
- File.extend(Thoran::File::SelfCollect)