ronin-web-server 0.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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