# frozen_string_literal: true # # ronin-web-server - A custom Ruby web server based on Sinatra. # # Copyright (c) 2006-2023 Hal Brodigan (postmodern.mod3 at gmail.com) # # ronin-web-server is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ronin-web-server is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with ronin-web-server. If not, see . # require 'ronin/support/network/asn' require 'ipaddr' module Ronin module Web module Server # # Defines Sinatra routing conditions. # # @api semipublic # module Conditions # # Adds {ClassMethods} to the class. # # @param [Class] base # The application base class that is including {Conditions}. # # @api private # def self.included(base) base.extend ClassMethods end # # Class methods to be added to the application base class. # module ClassMethods protected # # Condition to match the client IP Address that sent the request. # # @param [IPAddr, String, Proc, #===] matcher # The IP address or range of addresses to match against. # # @example Only allow the exact IP: # get '/path', client_ip: '10.1.1.1' do # # ... # end # # @example Allow all IPs from the IP range: # get '/path', client_ip: IPAddr.new('10.1.1.1/24') do # # ... # end # def client_ip(matcher) condition { matcher === request.ip } end # # Condition to match the AS number of the client's IP address. # # @param [Integer] number # The AS number to match. # # @example # get '/path', asn: 13335 do # # ... # end # def asn(number) condition do if (record = Support::Network::ASN.query(request.ip)) record.number == number end end end # # Condition to match the country code of the ASN information for the # client's IP address. # # @param [String] code # The two letter country code to match for. # # @example # get '/path', country_code: 'US' do # # ... # end # def country_code(code) condition do if (record = Support::Network::ASN.query(request.ip)) record.country_code == country_code end end end # # Condition to match the company/ISP name of the ASN information for # the client's IP address. # # @param [String] name # The name of the company/ISP that the ASN is assigned to. # # @example # get '/path', asn_name: 'CLOUDFLARENET' do # # ... # end # def asn_name(name) condition do if (record = Support::Network::ASN.query(request.ip)) record.name == name end end end # # Condition for matching the `Host` header. # # @param [Regexp, String, Proc, #===] matcher # The host to match against. # # @example Match the exact `Host` header: # get '/path', host: 'example.com' do # # ... # end # # @example Match any `Host` header ending in `.example.com`: # get '/path', host: /\.example\.com$/ do # # ... # end # def host(matcher) condition { matcher === request.host } end # # Condition to match the `Referer` header of the request. # # @param [Regexp, String, Proc, #===] matcher # Regular expression or exact `Referer` header to match against. # # @example Match the exact `Referer` URI: # get '/path', referer: 'https://example.com/signin' do # # ... # end # # @example Match any `Referer` URI matching the Regexp: # get '/path', referer: /^http:\/\// do # # ... # end # def referer(matcher) condition do if (referer = request.referer) matcher === referer end end end alias referrer referer # # Condition to match the `User-Agent` header of the request. # # @param [Regexp, String, Proc, #===] matcher # Regular expression, exact String, Proc, or any other object which # defines an `#===` method. # # @example Match any `User-Agent` with `Intel Mac OSX` in it: # get '/path', user_agent: /Intel Mac OSX/ do # # ... # end # def user_agent(matcher) condition do if (user_agent = request.user_agent) matcher === user_agent end end end # # Condition to match the browser name from the `User-Agent` header of # the request. # # @param [:chrome, :firefox, Regexp, String, Proc, #===] matcher # Regular expression, exact String, Proc, or any other object which # defines an `#===` method. # # @example Match the exact browser name: # get '/path', browser: "Foo" do # # ... # end # # @example Match any browser name matching the Regexp: # get '/path', browser: /googlebot/i do # # ... # end # # @example Match all Chrome browsers: # get '/path', browser: :chrome do # # ... # end # # @example Match all Firefox browsers: # get '/path', browser: :firefox do # # ... # end # def browser(matcher) case matcher when :chrome condition { request.browser == 'Chrome' } when :firefox condition { request.browser == 'Firefox' } else condition do if (browser = request.browser) matcher === browser end end end end # # Condition to match the browser vendor from the `User-Agent` header # of the request. # # @param [Regexp, String, Proc, #===] matcher # Regular expression, exact String, Proc, or any other object which # defines an `#===` method. # # @example Match the browser vendor: # get '/path', browser_vendor: 'Google' do # # ... # end # def browser_vendor(matcher) condition do if (browser_vendor = request.browser_vendor) matcher === browser_vendor end end end # # Condition to match the browser version from the `User-Agent` header # of the request. # # @param [Array, Set, # Regexp, String, Proc, #===] matcher # Regular expression, exact String, Proc, or any other object which # defines an `#===` method. # # @example Match an exact version of Chrome: # get '/path', browser: :chrome, browser_version: '99.100.4844.27' do # # ... # end # # @example Match all Chrome versions in the 99.x version family: # get '/path', browser: :chrome, browser_version: /^99\./ do # # ... # end # # @example Match versions of Chrome with known vulnerabilities: # vuln_versions = File.readlines('chrome_versions.txt', chomp: true) # # get '/path', browser: :chrome, browser_version: vuln_versions do # # ... # end # def browser_version(matcher) case matcher when Array, Set condition do if (browser_version = request.browser_version) matcher.include?(browser_version) end end else condition do if (browser_version = request.browser_version) matcher === browser_version end end end end # # Condition to match the device type of the `User-Agent` header of # the request. # # @param [Array<:pc, :smartphone, :mobilephone, :appliance, :crawler>, # :pc, :smartphone, :mobilephone, :appliance, :crawler, # Proc, #===] matcher # Array of device type Symbols, the exact devicde type Symbol, # Proc, or any other object which defines an `#===` method. # # @example Match a specific device type: # get '/path', device_type: :crawler do # halt 404 # end # # @example Match multiple device types: # get '/path', device_type: [:smartphone, :appliance] do # # ... # end # def device_type(matcher) condition do if (device_type = request.device_type) case matcher when Array then matcher.include?(device_type) else matcher === device_type end end end end # # Condition to match the OS from the `User-Agent` header of the # request. # # @param [:android, :ios, :linux, :windows, # Regexp, String, Proc, #===] matcher # Regular expression, exact String, Proc, or any other object which # defines an `#===` method. # # @example Match all Android devices: # get '/path', os: :android do # # ... # end # # @example Match all iOS devices: # get '/path', os: :ios do # # ... # end # # @example Match all Linux systems: # get '/path', os: :linux do # # ... # end # # @example Match all Windows systems: # get '/path', os: :windows do # # ... # end # # @example Match a specific OS: # get '/path', os: 'Windows 10' do # # ... # end # # @example Match any OS that matches the Regexp: # get '/path', os: /^Windows (?:7|8|10)/ do # # ... # end # def os(matcher) case matcher when :android condition { request.from_android_os? } when :ios condition { request.from_ios? } when :linux condition { request.os == 'Linux' } when :windows condition do if (os = request.os) os.start_with?('Windows') end end else condition do if (os = request.os) matcher === os end end end end # # Condition to match the OS version from the `User-Agent` header of # the request. # # @param [Array, Set, # Regexp, String, Proc, #===] matcher # Regular expression, exact String, Proc, or any other object which # defines an `#===` method. # # @example Match a specific Android OS version: # get '/path', os: :android, os_version: '8.1.0' do # # ... # end # # @example Match all Android OS versions that match a Regexp: # get '/path', os: :android, os_version: /^8\.1\./ do # # ... # end # # @example Match versions of Android with known vulnerabilities: # vuln_versions = File.readlines('android_versions.txt', chomp: true) # # get '/path', os: :android, os_version: vuln_versions do # # ... # end # def os_version(matcher) case matcher when Array, Set condition do if (os_version = request.os_version) matcher.include?(os_version) end end else condition do if (os_version = request.os_version) matcher === os_version end end end end end end end end end