diff --git a/README.md b/README.md index 32f3d4a..3f97696 100644 --- a/README.md +++ b/README.md @@ -316,6 +316,7 @@ end * [ronin-repos] ~> 0.1 * [ronin-nmap] ~> 0.1 * [ronin-web-spider] ~> 0.2 +* [ronin-web-browser] ~> 0.1 ## Install @@ -392,3 +393,4 @@ along with ronin-recon. If not, see . [ronin-masscan]: https://github.com/ronin-rb/ronin-masscan#readme [ronin-nmap]: https://github.com/ronin-rb/ronin-nmap#readme [ronin-web-spider]: https://github.com/ronin-rb/ronin-web-spider#readme +[ronin-web-browser]: https://github.com/ronin-rb/ronin-web-browser#readme diff --git a/gemspec.yml b/gemspec.yml index 5534f80..76284fd 100644 --- a/gemspec.yml +++ b/gemspec.yml @@ -51,6 +51,7 @@ dependencies: ronin-repos: ~> 0.1 ronin-nmap: ~> 0.1 ronin-web-spider: ~> 0.2 + ronin-web-browser: ~> 0.1 development_dependencies: bundler: ~> 2.0 diff --git a/lib/ronin/recon/builtin/web/screenshot.rb b/lib/ronin/recon/builtin/web/screenshot.rb new file mode 100644 index 0000000..9bb554d --- /dev/null +++ b/lib/ronin/recon/builtin/web/screenshot.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true +# +# ronin-recon - A micro-framework and tool for performing reconnaissance. +# +# Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) +# +# ronin-recon 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-recon 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-recon. If not, see . +# + +require 'ronin/recon/worker' + +require 'ronin/web/browser' + +module Ronin + module Recon + module Web + # + # A recon worker that takes a screenshot of a page. + # + # @since 0.2.0 + # + class Screenshot < Worker + + register 'web/screenshot' + + summary 'Visits a website and takes a screenshot of it' + description <<~DESC + Visits a website and takes a screenshot of it. + DESC + + accepts URL + outputs nil + concurrency 1 + + param :output_dir, String, required: true, + desc: 'The directory you want to save the screenshot to.' + + param :format, Enum[:png, :jpg], required: true, + default: :png, + desc: 'The screenshot format.' + + # The Web::Browser instance + # + # @return [Web::Browser] + # + # @api private + attr_reader :browser + + # + # Initializes the `web/screenshot` worker. + # + # @param [Hash{Symbol => Object}] kwargs + # Additional keyword arguments. + # + # @api private + # + def initialize(**kwargs) + super(**kwargs) + + @browser = Ronin::Web::Browser.new + end + + # + # Visits a website and takes a screenshot of it. + # + # @param [Values::URL] url + # The URL of the website you want to screenshot. + # + def process(url) + browser.go_to(url) + + path = path_for(browser.page.url) + FileUtils.mkdir_p(File.dirname(path)) + + browser.screenshot(path: path) + end + + # + # Generates the file path for a given URL. + # + # @param [String] url + # The given url. + # + # @return [String] + # The relative file path that represents the URL. + # + def path_for(url) + page_url = URI(url) + path = File.join(params[:output_dir], page_url.host, page_url.request_uri) + path << 'index' if path.end_with?('/') + path << ".#{params[:format]}" + end + end + end + end +end diff --git a/spec/builtin/web/screenshot_spec.rb b/spec/builtin/web/screenshot_spec.rb new file mode 100644 index 0000000..093cb3a --- /dev/null +++ b/spec/builtin/web/screenshot_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' +require 'ronin/recon/builtin/web/screenshot' + +require 'webmock/rspec' + +describe Ronin::Recon::Web::Screenshot do + let(:dir) { Dir.mktmpdir('test-ronin-recon-web-screenshot') } + + subject { described_class.new(params: { output_dir: dir }) } + + before do + WebMock.disable_net_connect!(allow_localhost: true) + end + + describe "#process" do + let(:url) { Ronin::Recon::Values::URL.new('https://www.example.com') } + let(:path) { File.join(dir,"www.example.com","index.png") } + + before do + stub_request(:get, 'https://www.example.com') + .to_return(status: 200, body: "") + end + + it "must visit a website and take a screenshot of it" do + subject.process(url) + + expect(File.exist?(path)).to be(true) + end + end + + describe "#path_for" do + context "when url ends with '/'" do + let(:url) { 'https://www.example.com/' } + let(:expected_path) { File.join(dir,'www.example.com','index.png') } + + it "must add 'index' to the returned path" do + expect(subject.path_for(url)).to eq(expected_path) + end + end + + context "when url does not ends with '/'" do + let(:url) { 'https://www.example.com/foo/bar.php' } + let(:expected_path) { File.join(dir, 'www.example.com','foo','bar.php.png') } + + it "must return path" do + expect(subject.path_for(url)).to eq(expected_path) + end + end + end +end