diff --git a/lib/ronin/cli/commands/http.rb b/lib/ronin/cli/commands/http.rb index 9acb8c888..f1dab5003 100644 --- a/lib/ronin/cli/commands/http.rb +++ b/lib/ronin/cli/commands/http.rb @@ -20,6 +20,7 @@ require 'ronin/cli/printing/http' require 'ronin/cli/http_shell' require 'ronin/support/network/http' +require 'ronin/support/network/http/cookie' require 'command_kit/options/verbose' require 'addressable/uri' @@ -60,6 +61,8 @@ module Commands # -u chrome-linux|chrome-macos|chrome-windows|chrome-iphone|chrome-ipad|chrome-android|firefox-linux|firefox-macos|firefox-windows|firefox-iphone|firefox-ipad|firefox-android|safari-macos|safari-iphone|safari-ipad|edge, # --user-agent The User-Agent to use # -H, --header "NAME: VALUE" Adds a header to the request + # -C, --cookie COOKIE Sets the Cookie header + # -c, --cookie-param NAME=VALUE Sets an additional cookie param # -B, --body STRING The request body # -F, --body-file FILE Sends the file as the request body # -f, --form-data NAME=VALUE Adds a value to the form data @@ -199,6 +202,35 @@ class Http < ValueProcessorCommand @headers[name] = value end + option :cookie, short: '-C', + value: { + type: String, + usage: 'COOKIE' + }, + desc: 'Sets the Cookie header' do |cookie| + cookie = Support::Network::HTTP::Cookie.parse(cookie) + + if @cookie + @cookie.merge!(cookie) + else + @cookie = cookie + end + end + + option :cookie_param, short: '-c', + value: { + type: /[^\s=]+=\w+/, + usage: 'NAME=VALUE' + }, + desc: 'Sets an additional cookie param' do |param| + name, value = param.split('=',2) + + # lazy initialize the cookie + @cookie ||= Support::Network::HTTP::Cookie.new + + @cookie[name] = value + end + option :body, short: '-B', value: { type: String, @@ -262,6 +294,11 @@ class Http < ValueProcessorCommand # @return [Hash{String => String}] attr_reader :headers + # The optional `Cookie` header to send. + # + # @return [Ronin::Support::Network::HTTP::Cookie, nil] + attr_reader :cookie + # Optional `User-agent` string to use. # # @return [String, nil] @@ -294,6 +331,7 @@ def initialize(**kwargs) @proxy = nil @http_method = :get @headers = {} + @cookie = nil @user_agent = nil @query_params = {} @form_data = {} @@ -347,6 +385,7 @@ def process_value(url) begin Support::Network::HTTP.request( @http_method, uri, proxy: @proxy, + cookie: @cookie, user_agent: @user_agent, query_params: @query_params, headers: @headers, diff --git a/man/ronin-http.1.md b/man/ronin-http.1.md index cb5e3bdf8..1866ccd4f 100644 --- a/man/ronin-http.1.md +++ b/man/ronin-http.1.md @@ -84,6 +84,12 @@ Send HTTP requests or spawn an interactive HTTP shell. `-H`, `--header` "*NAME*: *VALUE*" Adds a header to the request. +`-C`, `--cookie` *COOKIE* + Sets the raw `Cookie` header. + +`-c`, `--cookie-param` *NAME*`=`*VALUE* + Sets an additional `Cookie` param using the given *NAME* and *VALUE*. + `-B`, `--body` *STRING* The request body. diff --git a/spec/cli/commands/http_spec.rb b/spec/cli/commands/http_spec.rb index e0d5a009f..8948cd86f 100644 --- a/spec/cli/commands/http_spec.rb +++ b/spec/cli/commands/http_spec.rb @@ -24,6 +24,10 @@ expect(subject.user_agent).to be(nil) end + it "must default #cookie to nil" do + expect(subject.cookie).to be(nil) + end + it "must default #query_params to an empty Hash" do expect(subject.query_params).to eq({}) end @@ -38,6 +42,96 @@ subject { described_class.new(stdout: stdout) } describe "#option_parser" do + context "when the '--cookie \"...\"' option is parsed" do + let(:cookie_name1) { 'a' } + let(:cookie_value1) { '1' } + let(:cookie_name2) { 'b' } + let(:cookie_value2) { '2' } + let(:cookie) do + "#{cookie_name1}=#{cookie_value1}; #{cookie_name2}=#{cookie_value2}" + end + + let(:argv) { ['--cookie', cookie] } + + before { subject.option_parser.parse(argv) } + + it "must set #cookie to the parsed Ronin::Support::Network::HTTP::Cookie" do + expect(subject.cookie).to be_kind_of(Ronin::Support::Network::HTTP::Cookie) + expect(subject.cookie.to_h).to eq( + { + cookie_name1 => cookie_value1, + cookie_name2 => cookie_value2 + } + ) + end + + context "when #cookie is already set" do + let(:cookie_name3) { 'c' } + let(:cookie_value3) { '3' } + let(:cookie_name4) { 'a' } + let(:cookie_value4) { 'x' } + let(:cookie2) do + "#{cookie_name3}=#{cookie_value3}; #{cookie_name4}=#{cookie_value4}" + end + + let(:argv) { ['--cookie', cookie, '--cookie', cookie2] } + + it "must merged the parsed cookie params into #cookie" do + expect(subject.cookie).to be_kind_of(Ronin::Support::Network::HTTP::Cookie) + expect(subject.cookie.to_h).to eq( + { + cookie_name2 => cookie_value2, + cookie_name3 => cookie_value3, + cookie_name4 => cookie_value4 + } + ) + end + end + end + + context "when the '--cookie-param name=value' option is parsed" do + let(:cookie_name) { 'a' } + let(:cookie_value) { '1' } + + let(:argv) { ['--cookie-param', "#{cookie_name}=#{cookie_value}"] } + + before { subject.option_parser.parse(argv) } + + it "must set #cookie to a Ronin::Support::Network::HTTP::Cookie containing the parsed name and param" do + expect(subject.cookie).to be_kind_of(Ronin::Support::Network::HTTP::Cookie) + expect(subject.cookie.to_h).to eq( + { + cookie_name => cookie_value + } + ) + end + + context "when #cookie is already set" do + let(:cookie_name2) { 'b' } + let(:cookie_value2) { '2' } + let(:cookie_name3) { 'a' } + let(:cookie_value3) { 'x' } + + let(:argv) do + [ + '--cookie-param', "#{cookie_name}=#{cookie_value}", + '--cookie-param', "#{cookie_name2}=#{cookie_value2}", + '--cookie-param', "#{cookie_name3}=#{cookie_value3}" + ] + end + + it "must merged the parsed cookie params into #cookie" do + expect(subject.cookie).to be_kind_of(Ronin::Support::Network::HTTP::Cookie) + expect(subject.cookie.to_h).to eq( + { + cookie_name2 => cookie_value2, + cookie_name3 => cookie_value3 + } + ) + end + end + end + context "when --shell is given a non-http URL" do it do expect { @@ -123,6 +217,7 @@ expect(Ronin::Support::Network::HTTP).to receive(:request).with( subject.http_method, uri, proxy: subject.proxy, user_agent: subject.user_agent, + cookie: subject.cookie, query_params: subject.query_params, headers: subject.headers, body: subject.body,