# frozen_string_literal: true # # ronin-web - A collection of useful web helper methods and commands. # # Copyright (c) 2006-2023 Hal Brodigan (postmodern.mod3 at gmail.com) # # ronin-web is free software: you can redistribute it and/or modify # it under the terms of the GNU 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 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with ronin-web. If not, see . # require 'ronin/web/cli/command' require 'ronin/core/cli/logging' require 'ronin/web/server/reverse_proxy' module Ronin module Web class CLI module Commands # # Starts a HTTP proxy server. # # ## Usage # # ronin-web reverse-proxy [options] [--host HOST] [--port PORT] # # ## Options # # -H, --host HOST Host to listen on (Default: localhost) # -p, --port PORT Port to listen on (Default: 8080) # -b, --show-body Print the request and response bodies # --rewrite-requests /REGEXP/:REPLACE # Rewrite request bodies # --rewrite-responses /REGEXP/:REPLACE # Rewrite response bodies # -h, --help Print help information # # @api private # class ReverseProxy < Command include Core::CLI::Logging command_name 'reverse-proxy' usage '[options] [--host HOST] [--port PORT]' option :host, short: '-H', value: { type: String, usage: 'HOST', default: 'localhost' }, desc: 'Host to listen on' option :port, short: '-p', value: { type: Integer, usage: 'PORT', default: 8080 }, desc: 'Port to listen on' option :show_body, short: '-b', desc: 'Print the request and response bodies' option :rewrite_requests, value: { type: String, usage: '/REGEXP/:REPLACE' }, desc: 'Rewrite request bodies' do |str| @rewrite_requests << parse_rewrite_rule(str) end option :rewrite_responses, value: { type: String, usage: '/REGEXP/:REPLACE' }, desc: 'Rewrite response bodies' do |str| @rewrite_responses << parse_rewrite_rule(str) end description 'Starts a HTTP proxy server' man_page 'ronin-web-reverse-proxy.1' # # Initializes the `reverse-proxy` command. # # @param [Hash{Symbol => Object}] kwargs # Additional keyword arguments. # def initialize(**kwargs) super(**kwargs) @rewrite_requests = [] @rewrite_responses = [] end # # Runs the `ronin-web reverse-proxy` command. # def run proxy = Ronin::Web::Server::ReverseProxy.new do |proxy| proxy.on_request do |request| puts "[#{request.ip} -> #{request.host_with_port}] #{request.request_method} #{request.url}" request.headers.each do |name,value| puts "> #{name}: #{value}" end puts unless @rewrite_requests.empty? request.body = rewrite_body(request.body,@rewrite_requests) end print_body(request.body) if options[:show_body] end proxy.on_response do |response| puts "< HTTP/1.1 #{response.status}" response.headers.each do |name,value| puts "< #{name}: #{value}" end puts unless @rewrite_responses.empty? response.body = rewrite_body(response.body,@rewrite_responses) end print_body(response.body) if options[:show_body] end end log_info "Starting proxy server on #{options[:host]}:#{options[:port]} ..." begin proxy.run!(host: options[:host], port: options[:port]) rescue Errno::EADDRINUSE => error log_error(error.message) exit(1) end log_info "shutting down ..." end # # Prints a request or response body. # # @param [IO, StringIO, Array, String] body # The request/response body to print. May be a IO/StringIO object, # an Array of Strings, or a String. # def print_body(body) case body when StringIO, IO body.each_line do |line| puts line end body.rewind else puts body end end # # Parses a rewrite rule. # # @param [String] value # # @return [(Regexp, String), (String, String)] # def parse_rewrite_rule(value) if (index = value.rindex('/:')) regexp = Regexp.new(value[1...index]) replace = value[(index + 2)..] return [regexp, replace] elsif (index = value.rindex(':')) string = value[0...index] replace = value[(index + 1)..] return [string, replace] end end # # Rewrites a request or response body. # # @param [IO, StringIO, Array, String] body # # @return [String] # def rewrite_body(body,rules) body = case body when StringIO, IO then body.read when Array then body.join else body.to_s end rules.each do |(pattern,replace)| body.gsub!(pattern,replace) end return body end end end end end end