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.
@@ -0,0 +1,39 @@
1
+ # Thoran/Selenium/WebDriver/SearchContext/ElementPresentQ/element_presentQ.rb
2
+ # Thoran::Selenium::WebDriver::SearchContext::ElementPresentQ.element_present?
3
+
4
+ # 20200418
5
+ # 0.0.0
6
+
7
+ # Examples:
8
+ # 1. driver.element_present?(:xpath, '//a[@id="submit"]')
9
+ # 2. driver.element_present?(:id, @logout_button_id)
10
+
11
+ # Changes:
12
+ # 1. + Thoran namespace.
13
+
14
+ module Thoran
15
+ module Selenium
16
+ module WebDriver
17
+ module SearchContext
18
+ module ElementPresentQ
19
+
20
+ def element_present?(selector_type, selector)
21
+ bridge.find_element_by(selector_type, selector)
22
+ true
23
+ rescue ::Selenium::WebDriver::Error::NoSuchElementError
24
+ false
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ module Selenium
34
+ module WebDriver
35
+ class Driver
36
+ include Thoran::Selenium::WebDriver::SearchContext::ElementPresentQ
37
+ end
38
+ end
39
+ end
data/moby.gemspec ADDED
@@ -0,0 +1,51 @@
1
+ # moby.gemspec
2
+
3
+ require_relative './lib/Moby/VERSION'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'moby.rb'
7
+
8
+ spec.version = Moby::VERSION
9
+ spec.date = '2026-01-08'
10
+
11
+ spec.summary = "Moby is a credentials poisoning tool which floods phishing forms with fake credentials."
12
+ spec.description = "Sometimes when they go fishing, they get a whale and it sinks their boat. Moby is a counter-phishing tool that floods phishing websites with fake login credentials, making harvested data useless."
13
+
14
+ spec.author = 'thoran'
15
+ spec.email = 'code@thoran.com'
16
+ spec.homepage = 'https://github.com/thoran/moby'
17
+ spec.license = 'MIT'
18
+
19
+ spec.required_ruby_version = ">= 2.5.0"
20
+
21
+ spec.add_dependency('mechanize', '~> 2')
22
+ spec.add_dependency('switches.rb', '~> 0')
23
+
24
+ spec.add_development_dependency('minitest')
25
+ spec.add_development_dependency('minitest-spec-context')
26
+ spec.add_development_dependency('rake')
27
+ spec.add_development_dependency('webmock')
28
+
29
+ spec.files = Dir[
30
+ 'bin/*',
31
+ 'lib/**/*.rb',
32
+ 'test/**/*.rb',
33
+ 'CHANGES.txt',
34
+ 'Gemfile',
35
+ 'LICENSE.txt',
36
+ 'moby.gemspec',
37
+ 'Rakefile',
38
+ 'README.md',
39
+ ]
40
+
41
+ spec.bindir = 'bin'
42
+ spec.executables = ['moby']
43
+ spec.require_paths = ['lib']
44
+
45
+ spec.metadata = {
46
+ "bug_tracker_uri" => "https://github.com/thoran/moby/issues",
47
+ "changelog_uri" => "https://github.com/thoran/moby/blob/main/CHANGES.txt",
48
+ "source_code_uri" => "https://github.com/thoran/moby",
49
+ "documentation_uri" => "https://github.com/thoran/moby/blob/main/README.md"
50
+ }
51
+ end
@@ -0,0 +1,23 @@
1
+ # test/Moby/VERSION_test.rb
2
+
3
+ require_relative '../test_helper'
4
+
5
+ describe Moby do
6
+ describe "VERSION" do
7
+ it "has VERSION constant" do
8
+ _(defined?(Moby::VERSION)).wont_be_nil
9
+ end
10
+
11
+ it "version is a string" do
12
+ _(Moby::VERSION).must_be_kind_of(String)
13
+ end
14
+
15
+ it "version follows semver pattern" do
16
+ _(Moby::VERSION).must_match(/\d+\.\d+\.\d+/)
17
+ end
18
+
19
+ it "version is 1.0.0" do
20
+ _(Moby::VERSION).must_equal('1.0.0')
21
+ end
22
+ end
23
+ end
data/test/moby_test.rb ADDED
@@ -0,0 +1,295 @@
1
+ # test/moby_test.rb
2
+
3
+ require_relative './test_helper'
4
+
5
+ describe Moby do
6
+ let(:url){'https://phishing.example.com/login'}
7
+ let(:moby){Moby.new(url: url)}
8
+
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
246
+ end
247
+
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
255
+ end
256
+
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
294
+ end
295
+ end
@@ -0,0 +1,36 @@
1
+ # test/test_helper.rb
2
+
3
+ require 'minitest/autorun'
4
+ require 'minitest-spec-context'
5
+ require 'webmock/minitest'
6
+
7
+ require_relative '../lib/moby'
8
+
9
+ module TestFixtures
10
+ SIMPLE_LOGIN_FORM = <<~HTML
11
+ <html>
12
+ <body>
13
+ <form name="login" action="/login" method="post">
14
+ <input type="text" name="username" />
15
+ <input type="password" name="password" />
16
+ <input type="submit" value="Login" />
17
+ </form>
18
+ </body>
19
+ </html>
20
+ HTML
21
+
22
+ MULTIPLE_FORMS = <<~HTML
23
+ <html>
24
+ <body>
25
+ <form name="search">
26
+ <input type="text" name="q" />
27
+ </form>
28
+ <form id="login_form">
29
+ <input type="email" name="username" id="user_email" />
30
+ <input type="password" name="password" id="user_password" />
31
+ <button type="submit">Submit</button>
32
+ </form>
33
+ </body>
34
+ </html>
35
+ HTML
36
+ end
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: moby.rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - thoran
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2026-01-08 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: mechanize
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2'
26
+ - !ruby/object:Gem::Dependency
27
+ name: switches.rb
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: minitest
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: minitest-spec-context
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rake
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: webmock
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ description: Sometimes when they go fishing, they get a whale and it sinks their boat.
97
+ Moby is a counter-phishing tool that floods phishing websites with fake login credentials,
98
+ making harvested data useless.
99
+ email: code@thoran.com
100
+ executables:
101
+ - moby
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - CHANGES.txt
106
+ - Gemfile
107
+ - LICENSE.txt
108
+ - README.md
109
+ - Rakefile
110
+ - bin/moby
111
+ - lib/File/self.collect.rb
112
+ - lib/Moby.rb
113
+ - lib/Moby/Backends/MechanizeBackend.rb
114
+ - lib/Moby/Backends/SeleniumBackend.rb
115
+ - lib/Moby/Configuration.rb
116
+ - lib/Moby/RandomInputGenerator.rb
117
+ - lib/Moby/VERSION.rb
118
+ - lib/Selenium/WebDriver/Driver/Attempt/attempt.rb
119
+ - lib/Selenium/WebDriver/SearchContext/ElementPresentQ/element_presentQ.rb
120
+ - lib/Thoran/File/SelfCollect/self.collect.rb
121
+ - lib/Thoran/Selenium/WebDriver/Driver/Attempt/attempt.rb
122
+ - lib/Thoran/Selenium/WebDriver/SearchContext/ElementPresentQ/element_presentQ.rb
123
+ - moby.gemspec
124
+ - test/Moby/VERSION_test.rb
125
+ - test/moby_test.rb
126
+ - test/test_helper.rb
127
+ homepage: https://github.com/thoran/moby
128
+ licenses:
129
+ - MIT
130
+ metadata:
131
+ bug_tracker_uri: https://github.com/thoran/moby/issues
132
+ changelog_uri: https://github.com/thoran/moby/blob/main/CHANGES.txt
133
+ source_code_uri: https://github.com/thoran/moby
134
+ documentation_uri: https://github.com/thoran/moby/blob/main/README.md
135
+ rdoc_options: []
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: 2.5.0
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ requirements: []
149
+ rubygems_version: 4.0.3
150
+ specification_version: 4
151
+ summary: Moby is a credentials poisoning tool which floods phishing forms with fake
152
+ credentials.
153
+ test_files: []