ronin-web-server 0.1.0.beta1

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,443 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # ronin-web-server - A custom Ruby web server based on Sinatra.
4
+ #
5
+ # Copyright (c) 2006-2022 Hal Brodigan (postmodern.mod3 at gmail.com)
6
+ #
7
+ # ronin-web-server is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Lesser General Public License as published
9
+ # by the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # ronin-web-server is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public License
18
+ # along with ronin-web-server. If not, see <https://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require 'ronin/support/network/asn'
22
+
23
+ require 'ipaddr'
24
+
25
+ module Ronin
26
+ module Web
27
+ module Server
28
+ #
29
+ # Defines Sinatra routing conditions.
30
+ #
31
+ # @api semipublic
32
+ #
33
+ module Conditions
34
+ #
35
+ # Adds {ClassMethods} to the class.
36
+ #
37
+ # @param [Class] base
38
+ # The application base class that is including {Conditions}.
39
+ #
40
+ # @api private
41
+ #
42
+ def self.included(base)
43
+ base.extend ClassMethods
44
+ end
45
+
46
+ #
47
+ # Class methods to be added to the application base class.
48
+ #
49
+ module ClassMethods
50
+ protected
51
+
52
+ #
53
+ # Condition to match the client IP Address that sent the request.
54
+ #
55
+ # @param [IPAddr, String, Proc, #===] matcher
56
+ # The IP address or range of addresses to match against.
57
+ #
58
+ # @example Only allow the exact IP:
59
+ # get '/path', client_ip: '10.1.1.1' do
60
+ # # ...
61
+ # end
62
+ #
63
+ # @example Allow all IPs from the IP range:
64
+ # get '/path', client_ip: IPAddr.new('10.1.1.1/24') do
65
+ # # ...
66
+ # end
67
+ #
68
+ def client_ip(matcher)
69
+ condition { matcher === request.ip }
70
+ end
71
+
72
+ #
73
+ # Condition to match the AS number of the client's IP address.
74
+ #
75
+ # @param [Integer] number
76
+ # The AS number to match.
77
+ #
78
+ # @example
79
+ # get '/path', asn: 13335 do
80
+ # # ...
81
+ # end
82
+ #
83
+ def asn(number)
84
+ condition do
85
+ if (record = Support::Network::ASN.query(request.ip))
86
+ record.number == number
87
+ end
88
+ end
89
+ end
90
+
91
+ #
92
+ # Condition to match the country code of the ASN information for the
93
+ # client's IP address.
94
+ #
95
+ # @param [String] code
96
+ # The two letter country code to match for.
97
+ #
98
+ # @example
99
+ # get '/path', country_code: 'US' do
100
+ # # ...
101
+ # end
102
+ #
103
+ def country_code(code)
104
+ condition do
105
+ if (record = Support::Network::ASN.query(request.ip))
106
+ record.country_code == country_code
107
+ end
108
+ end
109
+ end
110
+
111
+ #
112
+ # Condition to match the company/ISP name of the ASN information for
113
+ # the client's IP address.
114
+ #
115
+ # @param [String] name
116
+ # The name of the company/ISP that the ASN is assigned to.
117
+ #
118
+ # @example
119
+ # get '/path', asn_name: 'CLOUDFLARENET' do
120
+ # # ...
121
+ # end
122
+ #
123
+ def asn_name(name)
124
+ condition do
125
+ if (record = Support::Network::ASN.query(request.ip))
126
+ record.name == name
127
+ end
128
+ end
129
+ end
130
+
131
+ #
132
+ # Condition for matching the `Host` header.
133
+ #
134
+ # @param [Regexp, String, Proc, #===] matcher
135
+ # The host to match against.
136
+ #
137
+ # @example Match the exact `Host` header:
138
+ # get '/path', host: 'example.com' do
139
+ # # ...
140
+ # end
141
+ #
142
+ # @example Match any `Host` header ending in `.example.com`:
143
+ # get '/path', host: /\.example\.com$/ do
144
+ # # ...
145
+ # end
146
+ #
147
+ def host(matcher)
148
+ condition { matcher === request.host }
149
+ end
150
+
151
+ #
152
+ # Condition to match the `Referer` header of the request.
153
+ #
154
+ # @param [Regexp, String, Proc, #===] matcher
155
+ # Regular expression or exact `Referer` header to match against.
156
+ #
157
+ # @example Match the exact `Referer` URI:
158
+ # get '/path', referer: 'https://example.com/signin' do
159
+ # # ...
160
+ # end
161
+ #
162
+ # @example Match any `Referer` URI matching the Regexp:
163
+ # get '/path', referer: /^http:\/\// do
164
+ # # ...
165
+ # end
166
+ #
167
+ def referer(matcher)
168
+ condition do
169
+ if (referer = request.referer)
170
+ matcher === referer
171
+ end
172
+ end
173
+ end
174
+
175
+ alias referrer referer
176
+
177
+ #
178
+ # Condition to match the `User-Agent` header of the request.
179
+ #
180
+ # @param [Regexp, String, Proc, #===] matcher
181
+ # Regular expression, exact String, Proc, or any other object which
182
+ # defines an `#===` method.
183
+ #
184
+ # @example Match any `User-Agent` with `Intel Mac OSX` in it:
185
+ # get '/path', user_agent: /Intel Mac OSX/ do
186
+ # # ...
187
+ # end
188
+ #
189
+ def user_agent(matcher)
190
+ condition do
191
+ if (user_agent = request.user_agent)
192
+ matcher === user_agent
193
+ end
194
+ end
195
+ end
196
+
197
+ #
198
+ # Condition to match the browser name from the `User-Agent` header of
199
+ # the request.
200
+ #
201
+ # @param [:chrome, :firefox, Regexp, String, Proc, #===] matcher
202
+ # Regular expression, exact String, Proc, or any other object which
203
+ # defines an `#===` method.
204
+ #
205
+ # @example Match the exact browser name:
206
+ # get '/path', browser: "Foo" do
207
+ # # ...
208
+ # end
209
+ #
210
+ # @example Match any browser name matching the Regexp:
211
+ # get '/path', browser: /googlebot/i do
212
+ # # ...
213
+ # end
214
+ #
215
+ # @example Match all Chrome browsers:
216
+ # get '/path', browser: :chrome do
217
+ # # ...
218
+ # end
219
+ #
220
+ # @example Match all Firefox browsers:
221
+ # get '/path', browser: :firefox do
222
+ # # ...
223
+ # end
224
+ #
225
+ def browser(matcher)
226
+ case matcher
227
+ when :chrome
228
+ condition { request.browser == 'Chrome' }
229
+ when :firefox
230
+ condition { request.browser == 'Firefox' }
231
+ else
232
+ condition do
233
+ if (browser = request.browser)
234
+ matcher === browser
235
+ end
236
+ end
237
+ end
238
+ end
239
+
240
+ #
241
+ # Condition to match the browser vendor from the `User-Agent` header
242
+ # of the request.
243
+ #
244
+ # @param [Regexp, String, Proc, #===] matcher
245
+ # Regular expression, exact String, Proc, or any other object which
246
+ # defines an `#===` method.
247
+ #
248
+ # @example Match the browser vendor:
249
+ # get '/path', browser_vendor: 'Google' do
250
+ # # ...
251
+ # end
252
+ #
253
+ def browser_vendor(matcher)
254
+ condition do
255
+ if (browser_vendor = request.browser_vendor)
256
+ matcher === browser_vendor
257
+ end
258
+ end
259
+ end
260
+
261
+ #
262
+ # Condition to match the browser version from the `User-Agent` header
263
+ # of the request.
264
+ #
265
+ # @param [Array<String>, Set<String>,
266
+ # Regexp, String, Proc, #===] matcher
267
+ # Regular expression, exact String, Proc, or any other object which
268
+ # defines an `#===` method.
269
+ #
270
+ # @example Match an exact version of Chrome:
271
+ # get '/path', browser: :chrome, browser_version: '99.100.4844.27' do
272
+ # # ...
273
+ # end
274
+ #
275
+ # @example Match all Chrome versions in the 99.x version family:
276
+ # get '/path', browser: :chrome, browser_version: /^99\./ do
277
+ # # ...
278
+ # end
279
+ #
280
+ # @example Match versions of Chrome with known vulnerabilities:
281
+ # vuln_versions = File.readlines('chrome_versions.txt', chomp: true)
282
+ #
283
+ # get '/path', browser: :chrome, browser_version: vuln_versions do
284
+ # # ...
285
+ # end
286
+ #
287
+ def browser_version(matcher)
288
+ case matcher
289
+ when Array, Set
290
+ condition do
291
+ if (browser_version = request.browser_version)
292
+ matcher.include?(browser_version)
293
+ end
294
+ end
295
+ else
296
+ condition do
297
+ if (browser_version = request.browser_version)
298
+ matcher === browser_version
299
+ end
300
+ end
301
+ end
302
+ end
303
+
304
+ #
305
+ # Condition to match the device type of the `User-Agent` header of
306
+ # the request.
307
+ #
308
+ # @param [Array<:pc, :smartphone, :mobilephone, :appliance, :crawler>,
309
+ # :pc, :smartphone, :mobilephone, :appliance, :crawler,
310
+ # Proc, #===] matcher
311
+ # Array of device type Symbols, the exact devicde type Symbol,
312
+ # Proc, or any other object which defines an `#===` method.
313
+ #
314
+ # @example Match a specific device type:
315
+ # get '/path', device_type: :crawler do
316
+ # halt 404
317
+ # end
318
+ #
319
+ # @example Match multiple device types:
320
+ # get '/path', device_type: [:smartphone, :appliance] do
321
+ # # ...
322
+ # end
323
+ #
324
+ def device_type(matcher)
325
+ condition do
326
+ if (device_type = request.device_type)
327
+ case matcher
328
+ when Array then matcher.include?(device_type)
329
+ else matcher === device_type
330
+ end
331
+ end
332
+ end
333
+ end
334
+
335
+ #
336
+ # Condition to match the OS from the `User-Agent` header of the
337
+ # request.
338
+ #
339
+ # @param [:android, :ios, :linux, :windows,
340
+ # Regexp, String, Proc, #===] matcher
341
+ # Regular expression, exact String, Proc, or any other object which
342
+ # defines an `#===` method.
343
+ #
344
+ # @example Match all Android devices:
345
+ # get '/path', os: :android do
346
+ # # ...
347
+ # end
348
+ #
349
+ # @example Match all iOS devices:
350
+ # get '/path', os: :ios do
351
+ # # ...
352
+ # end
353
+ #
354
+ # @example Match all Linux systems:
355
+ # get '/path', os: :linux do
356
+ # # ...
357
+ # end
358
+ #
359
+ # @example Match all Windows systems:
360
+ # get '/path', os: :windows do
361
+ # # ...
362
+ # end
363
+ #
364
+ # @example Match a specific OS:
365
+ # get '/path', os: 'Windows 10' do
366
+ # # ...
367
+ # end
368
+ #
369
+ # @example Match any OS that matches the Regexp:
370
+ # get '/path', os: /^Windows (?:7|8|10)/ do
371
+ # # ...
372
+ # end
373
+ #
374
+ def os(matcher)
375
+ case matcher
376
+ when :android
377
+ condition { request.from_android_os? }
378
+ when :ios
379
+ condition { request.from_ios? }
380
+ when :linux
381
+ condition { request.os == 'Linux' }
382
+ when :windows
383
+ condition do
384
+ if (os = request.os)
385
+ os.start_with?('Windows')
386
+ end
387
+ end
388
+ else
389
+ condition do
390
+ if (os = request.os)
391
+ matcher === os
392
+ end
393
+ end
394
+ end
395
+ end
396
+
397
+ #
398
+ # Condition to match the OS version from the `User-Agent` header of
399
+ # the request.
400
+ #
401
+ # @param [Array<String>, Set<String>,
402
+ # Regexp, String, Proc, #===] matcher
403
+ # Regular expression, exact String, Proc, or any other object which
404
+ # defines an `#===` method.
405
+ #
406
+ # @example Match a specific Android OS version:
407
+ # get '/path', os: :android, os_version: '8.1.0' do
408
+ # # ...
409
+ # end
410
+ #
411
+ # @example Match all Android OS versions that match a Regexp:
412
+ # get '/path', os: :android, os_version: /^8\.1\./ do
413
+ # # ...
414
+ # end
415
+ #
416
+ # @example Match versions of Android with known vulnerabilities:
417
+ # vuln_versions = File.readlines('android_versions.txt', chomp: true)
418
+ #
419
+ # get '/path', os: :android, os_version: vuln_versions do
420
+ # # ...
421
+ # end
422
+ #
423
+ def os_version(matcher)
424
+ case matcher
425
+ when Array, Set
426
+ condition do
427
+ if (os_version = request.os_version)
428
+ matcher.include?(os_version)
429
+ end
430
+ end
431
+ else
432
+ condition do
433
+ if (os_version = request.os_version)
434
+ matcher === os_version
435
+ end
436
+ end
437
+ end
438
+ end
439
+ end
440
+ end
441
+ end
442
+ end
443
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # ronin-web-server - A custom Ruby web server based on Sinatra.
4
+ #
5
+ # Copyright (c) 2006-2022 Hal Brodigan (postmodern.mod3 at gmail.com)
6
+ #
7
+ # ronin-web-server is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Lesser General Public License as published
9
+ # by the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # ronin-web-server is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public License
18
+ # along with ronin-web-server. If not, see <https://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require 'sinatra/base'
22
+ require 'rack/utils'
23
+
24
+ module Ronin
25
+ module Web
26
+ module Server
27
+ #
28
+ # Provides Sinatra routing and helper methods.
29
+ #
30
+ module Helpers
31
+
32
+ include Rack::Utils
33
+ include Sinatra::Helpers
34
+
35
+ alias h escape_html
36
+ alias file send_file
37
+
38
+ #
39
+ # Returns the MIME type for a path.
40
+ #
41
+ # @param [String] path
42
+ # The path to determine the MIME type for.
43
+ #
44
+ # @return [String]
45
+ # The MIME type for the path.
46
+ #
47
+ # @api public
48
+ #
49
+ def mime_type_for(path)
50
+ mime_type(File.extname(path))
51
+ end
52
+
53
+ #
54
+ # Sets the `Content-Type` for the file.
55
+ #
56
+ # @param [String] path
57
+ # The path to determine the `Content-Type` for.
58
+ #
59
+ # @api public
60
+ #
61
+ def content_type_for(path)
62
+ content_type mime_type_for(path)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # ronin-web-server - A custom Ruby web server based on Sinatra.
4
+ #
5
+ # Copyright (c) 2006-2022 Hal Brodigan (postmodern.mod3 at gmail.com)
6
+ #
7
+ # ronin-web-server is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Lesser General Public License as published
9
+ # by the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # ronin-web-server is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public License
18
+ # along with ronin-web-server. If not, see <https://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require 'sinatra/base'
22
+
23
+ module Ronin
24
+ module Web
25
+ module Server
26
+ #
27
+ # Convenience class that represents requests.
28
+ #
29
+ # @see http://rubydoc.info/gems/rack/Rack/Request
30
+ #
31
+ class Request < Sinatra::Request
32
+
33
+ alias client_ip ip
34
+
35
+ #
36
+ # Returns the remote IP address and port for the request.
37
+ #
38
+ # @return [String]
39
+ # The IP address and port number.
40
+ #
41
+ # @api semipublic
42
+ #
43
+ def ip_with_port
44
+ if env.has_key?('REMOTE_PORT')
45
+ "#{ip}:#{env['REMOTE_PORT']}"
46
+ else
47
+ ip
48
+ end
49
+ end
50
+
51
+ #
52
+ # The HTTP Headers for the request.
53
+ #
54
+ # @return [Hash{String => String}]
55
+ # The HTTP Headers of the request.
56
+ #
57
+ # @api public
58
+ #
59
+ def headers
60
+ headers = {}
61
+
62
+ env.each do |name,value|
63
+ if name =~ /^HTTP_/
64
+ header_words = name[5..].split('_')
65
+ header_words.each(&:capitalize!)
66
+ header_name = header_words.join('-')
67
+
68
+ headers[header_name] = value
69
+ end
70
+ end
71
+
72
+ return headers
73
+ end
74
+
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # ronin-web-server - A custom Ruby web server based on Sinatra.
4
+ #
5
+ # Copyright (c) 2006-2022 Hal Brodigan (postmodern.mod3 at gmail.com)
6
+ #
7
+ # ronin-web-server is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Lesser General Public License as published
9
+ # by the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # ronin-web-server is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public License
18
+ # along with ronin-web-server. If not, see <https://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require 'sinatra/base'
22
+
23
+ module Ronin
24
+ module Web
25
+ module Server
26
+ #
27
+ # Convenience class that represents responses.
28
+ #
29
+ # @see http://rubydoc.info/gems/rack/Rack/Response
30
+ #
31
+ class Response < Sinatra::Response
32
+
33
+ end
34
+ end
35
+ end
36
+ end