├── .ruby-version ├── .rspec ├── spec ├── fixtures │ ├── wordlist.txt │ ├── config │ │ ├── does_not_contain_a_hash.yml │ │ ├── invalid_params.yml │ │ ├── invalid_workers.yml │ │ ├── invalid_concurrency.yml │ │ ├── invalid_concurrency_sub_key.yml │ │ ├── invalid_params_sub_value.yml │ │ ├── invalid_concurrency_sub_value.yml │ │ ├── invalid_params_sub_key.yml │ │ ├── invalid_params_sub_sub_key.yml │ │ ├── invalid_workers_params.yml │ │ ├── with_concurrency.yml │ │ ├── invalid_workers_concurrency.yml │ │ ├── with_workers.yml │ │ └── with_params.yml │ ├── input_file.txt │ ├── config.yml │ ├── test_worker.rb │ └── certs │ │ ├── other.crt │ │ └── example.crt ├── builtin │ ├── dns │ │ ├── fixtures │ │ │ ├── no_suffixes.dat │ │ │ └── public_domains.dat │ │ ├── lookup_spec.rb │ │ ├── srv_enum_spec.rb │ │ ├── nameservers_spec.rb │ │ ├── subdomain_enum_spec.rb │ │ ├── mailservers_spec.rb │ │ ├── reverse_lookup_spec.rb │ │ └── suffix_enum_spec.rb │ ├── web │ │ ├── fixtures │ │ │ └── dir_enum_wordlist.txt │ │ └── email_addresses_spec.rb │ ├── ssl │ │ ├── ip_range_enum_spec.rb │ │ ├── cert_grab_spec.rb │ │ └── cert_enum_spec.rb │ ├── net │ │ └── service_id_spec.rb │ └── api │ │ └── crt_sh_spec.rb ├── spec_helper.rb ├── web_worker_spec.rb ├── dns_worker_spec.rb ├── cli │ ├── commands │ │ ├── irb_spec.rb │ │ ├── man_page_example.rb │ │ ├── completion_spec.rb │ │ ├── workers_spec.rb │ │ └── test_spec.rb │ ├── ruby_shell_spec.rb │ ├── command_spec.rb │ └── debug_option_spec.rb ├── exceptions_spec.rb ├── output_formats │ ├── pdf_spec.rb │ ├── png_spec.rb │ ├── svg_spec.rb │ └── graphviz_format_spec.rb ├── values │ ├── mailserver_spec.rb │ ├── nameserver_spec.rb │ ├── email_address_spec.rb │ └── ip_spec.rb ├── input_file_spec.rb ├── value_spec.rb ├── graph_spec.rb └── output_formats_spec.rb ├── .document ├── .yardopts ├── data ├── completions │ └── ronin-recon.yml └── templates │ └── worker.rb.erb ├── .gitignore ├── bin └── ronin-recon ├── man ├── ronin-recon-workers.1.md ├── ronin-recon-irb.1.md ├── ronin-recon-worker.1.md ├── ronin-recon-test.1.md ├── ronin-recon.1.md ├── ronin-recon-new.1.md ├── ronin-recon-completion.1.md └── ronin-recon-run.1.md ├── examples └── recon.rb ├── lib └── ronin │ ├── recon │ ├── mixins.rb │ ├── version.rb │ ├── web_worker.rb │ ├── root.rb │ ├── registry.rb │ ├── message │ │ ├── shutdown.rb │ │ ├── worker_started.rb │ │ ├── worker_stopped.rb │ │ ├── job_started.rb │ │ ├── job_completed.rb │ │ ├── job_failed.rb │ │ └── value.rb │ ├── dns_worker.rb │ ├── cli │ │ ├── command.rb │ │ ├── debug_option.rb │ │ ├── ruby_shell.rb │ │ ├── commands │ │ │ ├── irb.rb │ │ │ ├── completion.rb │ │ │ ├── workers.rb │ │ │ └── worker.rb │ │ └── worker_command.rb │ ├── values.rb │ ├── output_formats │ │ ├── pdf.rb │ │ ├── png.rb │ │ ├── svg.rb │ │ ├── graph_format.rb │ │ ├── dir.rb │ │ └── graphviz_format.rb │ ├── exceptions.rb │ ├── builtin.rb │ ├── cli.rb │ ├── output_formats.rb │ ├── values │ │ ├── mailserver.rb │ │ ├── nameserver.rb │ │ ├── domain.rb │ │ ├── email_address.rb │ │ └── ip.rb │ ├── mixins │ │ └── http.rb │ ├── builtin │ │ ├── dns │ │ │ ├── nameservers.rb │ │ │ ├── reverse_lookup.rb │ │ │ ├── mailservers.rb │ │ │ ├── lookup.rb │ │ │ └── subdomain_enum.rb │ │ ├── net │ │ │ ├── ip_range_enum.rb │ │ │ ├── service_id.rb │ │ │ └── port_scan.rb │ │ ├── web │ │ │ ├── email_addresses.rb │ │ │ └── spider.rb │ │ ├── ssl │ │ │ └── cert_grab.rb │ │ └── api │ │ │ └── crt_sh.rb │ ├── input_file.rb │ ├── value_status.rb │ ├── value.rb │ └── graph.rb │ └── recon.rb ├── .rubocop.yml ├── scripts └── setup ├── ChangeLog.md ├── .github └── workflows │ └── ruby.yml ├── Gemfile ├── gemspec.yml ├── Rakefile └── ronin-recon.gemspec /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-3.3 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --colour --format documentation 2 | -------------------------------------------------------------------------------- /spec/fixtures/wordlist.txt: -------------------------------------------------------------------------------- 1 | test 2 | www 3 | localhost -------------------------------------------------------------------------------- /spec/builtin/dns/fixtures/no_suffixes.dat: -------------------------------------------------------------------------------- 1 | com 2 | nom.ag 3 | ai -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | lib/**/*.rb 2 | - 3 | ChangeLog.md 4 | COPYING.txt 5 | -------------------------------------------------------------------------------- /spec/fixtures/config/does_not_contain_a_hash.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - 42 3 | -------------------------------------------------------------------------------- /spec/fixtures/config/invalid_params.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :params: foo 3 | -------------------------------------------------------------------------------- /spec/fixtures/config/invalid_workers.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :workers: foo 3 | -------------------------------------------------------------------------------- /spec/fixtures/config/invalid_concurrency.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :concurrency: foo 3 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup markdown --title 'Ronin::Recon Documentation' --protected 2 | -------------------------------------------------------------------------------- /spec/fixtures/config/invalid_concurrency_sub_key.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :concurrency: 3 | :foo: 42 4 | -------------------------------------------------------------------------------- /spec/fixtures/config/invalid_params_sub_value.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :params: 3 | test_worker: foo 4 | -------------------------------------------------------------------------------- /spec/fixtures/config/invalid_concurrency_sub_value.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :concurrency: 3 | test/worker: foo 4 | -------------------------------------------------------------------------------- /spec/fixtures/config/invalid_params_sub_key.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :params: 3 | :foo: 4 | a: 1 5 | b: 2 6 | -------------------------------------------------------------------------------- /spec/fixtures/config/invalid_params_sub_sub_key.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :params: 3 | test/worker: 4 | foo: 42 5 | -------------------------------------------------------------------------------- /spec/fixtures/config/invalid_workers_params.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :workers: 3 | dns/foo: 4 | :params: 42 5 | -------------------------------------------------------------------------------- /spec/fixtures/config/with_concurrency.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :concurrency: 3 | test/worker1: 3 4 | test/worker2: 42 5 | -------------------------------------------------------------------------------- /spec/builtin/web/fixtures/dir_enum_wordlist.txt: -------------------------------------------------------------------------------- 1 | foo 2 | admin 3 | bar 4 | downloads 5 | baz 6 | secret 7 | qux 8 | -------------------------------------------------------------------------------- /spec/fixtures/config/invalid_workers_concurrency.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :workers: 3 | dns/foo: 4 | :concurrency: foo 5 | -------------------------------------------------------------------------------- /spec/fixtures/config/with_workers.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :workers: 3 | - test/worker1 4 | - test/worker2 5 | - test/worker3 6 | -------------------------------------------------------------------------------- /spec/fixtures/input_file.txt: -------------------------------------------------------------------------------- 1 | 1.2.3.4 2 | 1.2.3.4/24 3 | example.com 4 | www.example.com 5 | *.example.com 6 | https://example.com 7 | -------------------------------------------------------------------------------- /spec/fixtures/config/with_params.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :params: 3 | test/worker1: 4 | :foo: a 5 | :bar: b 6 | test/worker2: 7 | :foo: x 8 | :bar: y 9 | -------------------------------------------------------------------------------- /spec/builtin/dns/fixtures/public_domains.dat: -------------------------------------------------------------------------------- 1 | // ===BEGIN ICANN DOMAINS=== 2 | com.zm 3 | org.za 4 | in.th 5 | vn 6 | mil.al 7 | net.al 8 | org.al 9 | union.aero 10 | com -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'simplecov' 3 | 4 | SimpleCov.start 5 | 6 | RSpec.configure do |specs| 7 | specs.filter_run_excluding :network 8 | end 9 | -------------------------------------------------------------------------------- /spec/fixtures/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :workers: 3 | - dns/lookup 4 | - dns/reverse_lookup 5 | - dns/nameservers 6 | - dns/mailservers 7 | - dns/subdomain_enum 8 | - net/port_scan 9 | - net/service_id 10 | - web/spider 11 | -------------------------------------------------------------------------------- /data/completions/ronin-recon.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ronin-recon worker: &worker_ids 3 | - $(ronin-recon workers) 4 | ronin-recon run*-w: *worker_ids 5 | ronin-recon run*-e: *worker_ids 6 | ronin-recon run*-d: *worker_ids 7 | ronin-recon test: *worker_ids 8 | -------------------------------------------------------------------------------- /spec/web_worker_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/web_worker' 3 | 4 | describe Ronin::Recon::WebWorker do 5 | it "must include Mixins::HTTP" do 6 | expect(described_class).to include(Ronin::Recon::Mixins::HTTP) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /data/completions/ronin-recon 3 | /doc 4 | /pkg 5 | /man/*.[1-9] 6 | /vendor/bundle 7 | /Gemfile.lock 8 | /.bundle 9 | /.yardoc 10 | .DS_Store 11 | *.db 12 | *.log 13 | *.swp 14 | *~ 15 | /*.txt 16 | /*.csv 17 | /*.xml 18 | /*.dot 19 | /*.svg 20 | /data/wordlists 21 | -------------------------------------------------------------------------------- /spec/dns_worker_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/dns_worker' 3 | 4 | describe Ronin::Recon::DNSWorker do 5 | it "must include Mixins::DNS" do 6 | expect(described_class).to include(Ronin::Recon::Mixins::DNS) 7 | end 8 | 9 | it "must set intensity to :passive" do 10 | expect(described_class.intensity).to be(:passive) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/cli/commands/irb_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/cli/commands/irb' 3 | 4 | require_relative 'man_page_example' 5 | 6 | describe Ronin::Recon::CLI::Commands::Irb do 7 | include_examples "man_page" 8 | 9 | describe "#run" do 10 | it "must call CLI::RubyShell.start" do 11 | expect(Ronin::Recon::CLI::RubyShell).to receive(:start) 12 | 13 | subject.run 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /bin/ronin-recon: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | root = File.expand_path(File.join(__dir__,'..')) 5 | if File.file?(File.join(root,'Gemfile.lock')) 6 | Dir.chdir(root) do 7 | require 'bundler/setup' 8 | rescue LoadError => e 9 | warn e.message 10 | warn "Run `gem install bundler` to install Bundler" 11 | exit(-1) 12 | end 13 | end 14 | 15 | require 'ronin/recon/cli' 16 | Ronin::Recon::CLI.start 17 | -------------------------------------------------------------------------------- /spec/exceptions_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/exceptions' 3 | 4 | describe Ronin::Recon::UnknownValue do 5 | it { expect(described_class).to be < Ronin::Recon::Exception } 6 | end 7 | 8 | describe Ronin::Recon::InvalidConfig do 9 | it { expect(described_class).to be < Ronin::Recon::Exception } 10 | end 11 | 12 | describe Ronin::Recon::InvalidConfigFile do 13 | it { expect(described_class).to be < Ronin::Recon::InvalidConfig } 14 | end 15 | -------------------------------------------------------------------------------- /spec/cli/ruby_shell_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/cli/ruby_shell' 3 | 4 | describe Ronin::Recon::CLI::RubyShell do 5 | describe "#initialize" do 6 | it "must default #name to 'ronin-recon'" do 7 | expect(subject.name).to eq('ronin-recon') 8 | end 9 | 10 | it "must default context: to Ronin::Recon" do 11 | expect(subject.context).to be_a(Object) 12 | expect(subject.context).to be_kind_of(Ronin::Recon) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/cli/command_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/cli/command' 3 | 4 | describe Ronin::Recon::CLI::Command do 5 | it { expect(described_class).to be < Ronin::Core::CLI::Command } 6 | 7 | it "must set .man_dir" do 8 | expect(described_class.man_dir).to eq(File.join(Ronin::Recon::ROOT,'man')) 9 | end 10 | 11 | it "must set .bug_report_rul" do 12 | expect(described_class.bug_report_url).to eq('https://github.com/ronin-rb/ronin-recon/issues/new') 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/cli/commands/man_page_example.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | 3 | RSpec.shared_examples_for "man_page" do 4 | describe "man_page" do 5 | subject { described_class } 6 | 7 | it "must define a man page" do 8 | expect(subject.man_page).to_not be(nil) 9 | end 10 | 11 | it "must map to a markdown man page" do 12 | man_page_md_path = File.join(subject.man_dir,"#{subject.man_page}.md") 13 | 14 | expect(File.file?(man_page_md_path)).to be(true) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/output_formats/pdf_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/output_formats/pdf' 3 | 4 | require 'stringio' 5 | 6 | describe Ronin::Recon::OutputFormats::PDF do 7 | it "must inherit from Ronin::Core::OutputFormats::GraphvizFormat" do 8 | expect(described_class).to be < Ronin::Recon::OutputFormats::GraphvizFormat 9 | end 10 | 11 | let(:io) { StringIO.new } 12 | 13 | subject { described_class.new(io) } 14 | 15 | describe "#format" do 16 | it "must return :pdf" do 17 | expect(subject.format).to eq(:pdf) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/output_formats/png_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/output_formats/png' 3 | 4 | require 'stringio' 5 | 6 | describe Ronin::Recon::OutputFormats::PNG do 7 | it "must inherit from Ronin::Core::OutputFormats::GraphvizFormat" do 8 | expect(described_class).to be < Ronin::Recon::OutputFormats::GraphvizFormat 9 | end 10 | 11 | let(:io) { StringIO.new } 12 | 13 | subject { described_class.new(io) } 14 | 15 | describe "#format" do 16 | it "must return :png" do 17 | expect(subject.format).to eq(:png) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/output_formats/svg_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/output_formats/svg' 3 | 4 | require 'stringio' 5 | 6 | describe Ronin::Recon::OutputFormats::SVG do 7 | it "must inherit from Ronin::Core::OutputFormats::GraphvizFormat" do 8 | expect(described_class).to be < Ronin::Recon::OutputFormats::GraphvizFormat 9 | end 10 | 11 | let(:io) { StringIO.new } 12 | 13 | subject { described_class.new(io) } 14 | 15 | describe "#format" do 16 | it "must return :svg" do 17 | expect(subject.format).to eq(:svg) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /man/ronin-recon-workers.1.md: -------------------------------------------------------------------------------- 1 | # ronin-recon-workers 1 "2023-05-01" Ronin "User Manuals" 2 | 3 | ## NAME 4 | 5 | ronin-recon-workers - Lists the available recon workers 6 | 7 | ## SYNOPSIS 8 | 9 | `ronin-recon workers` [*options*] *DIR* 10 | 11 | ## DESCRIPTION 12 | 13 | Lists available recon workers. 14 | 15 | ## ARGUMENTS 16 | 17 | *DIR* 18 | : The optional directory to list workers from. 19 | 20 | ## OPTIONS 21 | 22 | `-h`, `--help` 23 | : Print help information 24 | 25 | ## AUTHOR 26 | 27 | Postmodern 28 | 29 | ## SEE ALSO 30 | 31 | [ronin-recon-worker](ronin-recon-worker.1.md) [ronin-recon-run](ronin-recon-run.1.md) -------------------------------------------------------------------------------- /spec/values/mailserver_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/values/mailserver' 3 | 4 | describe Ronin::Recon::Values::Mailserver do 5 | let(:name) { 'aspmx.l.google.com' } 6 | 7 | subject { described_class.new(name) } 8 | 9 | describe "#as_json" do 10 | it "must return a Hash containing the type: and name: attributes" do 11 | expect(subject.as_json).to eq({type: :mailserver, name: name}) 12 | end 13 | end 14 | 15 | describe ".value_type" do 16 | subject { described_class } 17 | 18 | it "must return :mailserver " do 19 | expect(subject.value_type).to be(:mailserver) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/values/nameserver_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/values/nameserver' 3 | 4 | describe Ronin::Recon::Values::Nameserver do 5 | let(:name) { 'a.iana-servers.net.' } 6 | 7 | subject { described_class.new(name) } 8 | 9 | describe "#as_json" do 10 | it "must return a Hash containing the type: and name: attributes" do 11 | expect(subject.as_json).to eq({type: :nameserver, name: name}) 12 | end 13 | end 14 | 15 | describe ".value_type" do 16 | subject { described_class } 17 | 18 | it "must return :nameserver" do 19 | expect(subject.value_type).to be(:nameserver) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /man/ronin-recon-irb.1.md: -------------------------------------------------------------------------------- 1 | # ronin-recon-irb 1 "2023-02-01" Ronin Recon "User Manuals" 2 | 3 | ## NAME 4 | 5 | ronin-recon-irb - Starts an interactive Ruby shell with ronin-recon loaded 6 | 7 | ## SYNOPSIS 8 | 9 | `ronin-recon irb` [*options*] 10 | 11 | ## DESCRIPTION 12 | 13 | Starts an interactive Ruby shell with `ronin/recon` loaded. 14 | 15 | ## OPTIONS 16 | 17 | `-h`, `--help` 18 | : Print help information 19 | 20 | ## AUTHOR 21 | 22 | Postmodern 23 | 24 | ## SEE ALSO 25 | 26 | [ronin-recon-workers](ronin-recon-workers.1.md) [ronin-recon-worker](ronin-recon-worker.1.md) [ronin-recon-run](ronin-recon-run.1.md) [ronin-recon-test](ronin-recon-test.1.md) 27 | -------------------------------------------------------------------------------- /man/ronin-recon-worker.1.md: -------------------------------------------------------------------------------- 1 | # ronin-recon-worker 1 "2023-05-01" Ronin "User Manuals" 2 | 3 | ## NAME 4 | 5 | ronin-recon-worker - Prints information about a recon worker 6 | 7 | ## SYNOPSIS 8 | 9 | `ronin-recon worker` [*options*] {`--file` *FILE* \| *NAME*} 10 | 11 | ## DESCRIPTION 12 | 13 | Prints information about a recon worker. 14 | 15 | ## ARGUMENTS 16 | 17 | *NAME* 18 | : The name of the recon worker to load. 19 | 20 | ## OPTIONS 21 | 22 | `-f`, `--file` *FILE* 23 | : Optionally loads the recon worker from the file. 24 | 25 | `-h`, `--help` 26 | : Print help information 27 | 28 | ## AUTHOR 29 | 30 | Postmodern 31 | 32 | ## SEE ALSO 33 | 34 | [ronin-recon-workers](ronin-recon-workers.1.md) [ronin-recon-run](ronin-recon-run.1.md) -------------------------------------------------------------------------------- /spec/fixtures/test_worker.rb: -------------------------------------------------------------------------------- 1 | require 'ronin/recon/worker' 2 | 3 | module Ronin 4 | module Recon 5 | class TestWorker < Worker 6 | 7 | register 'test_worker' 8 | 9 | summary 'Test worker' 10 | description <<~DESC 11 | This is a test worker. 12 | DESC 13 | author 'Postmodern', email: 'postmodern.mod3@gmail.com' 14 | 15 | accepts Domain 16 | outputs Host 17 | 18 | intensity :passive 19 | 20 | param :prefix, default: 'test', 21 | desc: 'Example param' 22 | 23 | def process(value) 24 | prefix = params[:prefix] 25 | 26 | yield Host.new("#{prefix}1.#{value}") 27 | yield Host.new("#{prefix}2.#{value}") 28 | end 29 | 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/cli/commands/completion_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/cli/commands/completion' 3 | 4 | require_relative 'man_page_example' 5 | 6 | describe Ronin::Recon::CLI::Commands::Completion do 7 | it "must inherit from Ronin::Core::CLI::CompletionCommand" do 8 | expect(described_class).to be < Ronin::Core::CLI::CompletionCommand 9 | end 10 | 11 | it "must set completion_file" do 12 | expect(described_class.completion_file).to eq( 13 | File.join(Ronin::Recon::ROOT,'data','completions','ronin-recon') 14 | ) 15 | end 16 | 17 | it "must set man_dir" do 18 | expect(described_class.man_dir).to_not be(nil) 19 | expect(File.directory?(described_class.man_dir)).to be(true) 20 | end 21 | 22 | include_examples "man_page" 23 | end 24 | -------------------------------------------------------------------------------- /examples/recon.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'bundler/setup' 5 | require 'ronin/recon/engine' 6 | 7 | domain = Ronin::Recon::Values::Domain.new('example.com') 8 | 9 | Ronin::Recon::Engine.run([domain], max_depth: 3) do |engine| 10 | engine.on(:value) do |value,parent| 11 | case value 12 | when Ronin::Recon::Values::Domain 13 | puts ">>> Found new domain #{value} for #{parent}" 14 | when Ronin::Recon::Values::Nameserver 15 | puts ">>> Found new nameserver #{value} for #{parent}" 16 | when Ronin::Recon::Values::Mailserver 17 | puts ">>> Found new mailserver #{value} for #{parent}" 18 | when Ronin::Recon::Values::Host 19 | puts ">>> Found new host #{value} for #{parent}" 20 | when Ronin::Recon::Values::IP 21 | puts ">>> Found new IP address #{value} for #{parent}" 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/cli/debug_option_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/cli/debug_option' 3 | require 'ronin/recon/cli/command' 4 | 5 | describe Ronin::Recon::CLI::DebugOption do 6 | module TestDebugOption 7 | class TestCommand < Ronin::Recon::CLI::Command 8 | include Ronin::Recon::CLI::DebugOption 9 | end 10 | end 11 | 12 | let(:command_class) { TestDebugOption::TestCommand } 13 | subject { command_class.new } 14 | 15 | describe "options" do 16 | before { subject.option_parser.parse(argv) } 17 | 18 | describe "when the '--debug' option is given" do 19 | let(:argv) { %w[--debug] } 20 | 21 | it "must set Console.logger.level to Console::Logger::DEBUG" do 22 | expect(Console.logger.level).to be(Console::Logger::DEBUG) 23 | end 24 | 25 | after { Console.logger.level = Console::Logger::INFO } 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/cli/commands/workers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/cli/commands/workers' 3 | 4 | describe Ronin::Recon::CLI::Commands::Workers do 5 | describe "#run" do 6 | it "must list all worker IDs" do 7 | expect { 8 | subject.run 9 | }.to output( 10 | Ronin::Recon.list_files.map { |id| 11 | " #{id}" 12 | }.join($/) + $/ 13 | ).to_stdout 14 | end 15 | 16 | context "when given a directory argument" do 17 | let(:dir) { 'dns' } 18 | 19 | it "must only list workers that exist within that directory" do 20 | expect { 21 | subject.run(dir) 22 | }.to output( 23 | Ronin::Recon.list_files.select { |id| 24 | id.start_with?("#{dir}/") 25 | }.map { |id| 26 | " #{id}" 27 | }.join($/) + $/ 28 | ).to_stdout 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/builtin/ssl/ip_range_enum_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/builtin/net/ip_range_enum' 3 | 4 | describe Ronin::Recon::Net::IPRangeEnum do 5 | describe "#process" do 6 | context "when there are ips within the range" do 7 | let(:ip_range) { Ronin::Recon::Values::IPRange.new('192.168.0.1/30') } 8 | let(:addresses) do 9 | [ 10 | "192.168.0.0", 11 | "192.168.0.1", 12 | "192.168.0.2", 13 | "192.168.0.3" 14 | ] 15 | end 16 | 17 | it "must yield each value" do 18 | yielded_values = [] 19 | 20 | subject.process(ip_range) do |value| 21 | yielded_values << value 22 | end 23 | 24 | expect(yielded_values).to_not be_empty 25 | expect(yielded_values).to all(be_kind_of(Ronin::Recon::Values::IP)) 26 | expect(yielded_values.map(&:address)).to match_array(addresses) 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/ronin/recon/mixins.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative 'mixins/dns' 22 | -------------------------------------------------------------------------------- /lib/ronin/recon.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require 'ronin/recon/engine' 22 | require 'ronin/recon/version' 23 | -------------------------------------------------------------------------------- /lib/ronin/recon/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | module Ronin 22 | module Recon 23 | # ronin-recon version 24 | VERSION = '0.1.1' 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/builtin/dns/lookup_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/builtin/dns/lookup' 3 | 4 | describe Ronin::Recon::DNS::Lookup do 5 | describe "#process", :network do 6 | context "when there are ip addresses for the host" do 7 | let(:host) { Ronin::Recon::Values::Host.new('www.example.com') } 8 | let(:ip) { Ronin::Recon::Values::IP.new('93.184.215.14', host: 'www.example.com') } 9 | 10 | it "must yield IP values" do 11 | yielded_values = [] 12 | 13 | Async do 14 | subject.process(host) do |value| 15 | yielded_values << value 16 | end 17 | end 18 | 19 | expect(yielded_values).to eq([ip]) 20 | end 21 | end 22 | 23 | context "when there is no ip address for the host" do 24 | let(:host) { Ronin::Recon::Values::Host.new('doesnotexist.example.com') } 25 | 26 | it "must not yield anything" do 27 | expect { |b| 28 | Async do 29 | subject.process(host,&b) 30 | end 31 | }.not_to yield_control 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | NewCops: disable 3 | SuggestExtensions: false 4 | TargetRubyVersion: 3.1 5 | 6 | inherit_gem: 7 | rubocop-ronin: rubocop.yml 8 | 9 | # 10 | # ronin-recon specific exceptions 11 | # 12 | 13 | # Empty classes 14 | Lint/EmptyClass: 15 | Exclude: 16 | - 'lib/ronin/recon/value.rb' 17 | - 'lib/ronin/recon/message/shutdown.rb' 18 | 19 | # Allow Value classes to not call super() in their initialize 20 | Lint/MissingSuper: 21 | Exclude: 22 | - 'lib/ronin/recon/values/*.rb' 23 | 24 | # Worker#initialize exists for documentation purposes 25 | Lint/UselessMethodDefinition: 26 | Exclude: 27 | - 'lib/ronin/recon/worker.rb' 28 | 29 | # until is better suited for waiting until the Engine is "empty" 30 | Style/WhileUntilModifier: 31 | Exclude: 32 | - 'lib/ronin/recon/engine.rb' 33 | 34 | # the Values #=== methods need to compare the other value's class 35 | Style/ClassEqualityComparison: 36 | Exclude: 37 | - 'lib/ronin/recon/values/*.rb' 38 | 39 | # must use #=== to compare values 40 | Style/CaseEquality: 41 | Exclude: 42 | - 'lib/ronin/recon/scope.rb' 43 | - 'lib/ronin/recon/values/ip_range.rb' 44 | - 'spec/**/*_spec.rb' 45 | -------------------------------------------------------------------------------- /scripts/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # Prints a log message. 5 | # 6 | function log() 7 | { 8 | if [[ -t 1 ]]; then 9 | echo -e "\x1b[1m\x1b[32m>>>\x1b[0m \x1b[1m$1\x1b[0m" 10 | else 11 | echo ">>> $1" 12 | fi 13 | } 14 | 15 | # 16 | # Prints a warn message. 17 | # 18 | function warn() 19 | { 20 | if [[ -t 1 ]]; then 21 | echo -e "\x1b[1m\x1b[33m***\x1b[0m \x1b[1m$1\x1b[0m" >&2 22 | else 23 | echo "*** $1" >&2 24 | fi 25 | } 26 | 27 | # 28 | # Prints an error message. 29 | # 30 | function error() 31 | { 32 | if [[ -t 1 ]]; then 33 | echo -e "\x1b[1m\x1b[31m!!!\x1b[0m \x1b[1m$1\x1b[0m" >&2 34 | else 35 | echo "!!! $1" >&2 36 | fi 37 | } 38 | 39 | # 40 | # Prints an error message and exists with -1. 41 | # 42 | function fail() 43 | { 44 | error "$@" 45 | exit -1 46 | } 47 | 48 | # default to installing gems into vendor/bundle 49 | if [[ ! -f .bundle/config ]]; then 50 | bundle config set --local path vendor/bundle >/dev/null || \ 51 | fail "Failed to run 'bundle config'" 52 | fi 53 | 54 | log "Installing gems ..." 55 | bundle install || fail "Failed to run 'bundle install'!" 56 | 57 | log "Setting up the project ..." 58 | bundle exec rake setup || "Failed to run 'rake setup'!" 59 | -------------------------------------------------------------------------------- /lib/ronin/recon/web_worker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative 'worker' 22 | require_relative 'mixins/http' 23 | 24 | module Ronin 25 | module Recon 26 | # 27 | # Base class for all web related workers. 28 | # 29 | class WebWorker < Worker 30 | 31 | include Mixins::HTTP 32 | 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/ronin/recon/root.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | module Ronin 22 | module Recon 23 | # Path to `ronin-recon` root directory. 24 | # 25 | # @api private 26 | ROOT = File.expand_path(File.join(__dir__,'..','..','..')) 27 | 28 | # Path to wordlists directory 29 | # 30 | # @api private 31 | WORDLISTS_DIR = File.join(ROOT, "data", "wordlists") 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/ronin/recon/registry.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require 'ronin/core/class_registry' 22 | require 'ronin/repos/class_dir' 23 | 24 | module Ronin 25 | # 26 | # Namespace for various worker classes. 27 | # 28 | module Recon 29 | include Core::ClassRegistry 30 | include Repos::ClassDir 31 | 32 | class_dir "#{__dir__}/builtin" 33 | repo_class_dir 'recon' 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/ronin/recon/message/shutdown.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | module Ronin 22 | module Recon 23 | module Message 24 | # 25 | # Tells all workers to shutdown. 26 | # 27 | # @api private 28 | # 29 | class Shutdown 30 | end 31 | 32 | # The shutdown message. 33 | # 34 | # @api private 35 | SHUTDOWN = Shutdown.new.freeze 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/builtin/dns/srv_enum_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/builtin/dns/srv_enum' 3 | 4 | describe Ronin::Recon::DNS::SRVEnum do 5 | describe "#process", :network do 6 | context "when there are hosts in the domain" do 7 | let(:domain) { Ronin::Recon::Values::Domain.new("gmail.com") } 8 | let(:hosts) do 9 | %w[ 10 | imap.gmail.com 11 | smtp.gmail.com 12 | pop.gmail.com 13 | calendar.google.com 14 | calendar.google.com 15 | ] 16 | end 17 | 18 | it "must yield Host values" do 19 | yielded_values = [] 20 | 21 | subject.process(domain) do |value| 22 | yielded_values << value 23 | end 24 | 25 | expect(yielded_values).to_not be_empty 26 | expect(yielded_values).to all(be_kind_of(Ronin::Recon::Values::Host)) 27 | expect(yielded_values.map(&:name)).to match_array(hosts) 28 | end 29 | end 30 | 31 | context "when there is no host in the domain" do 32 | let(:domain) { Ronin::Recon::Values::Domain.new("example.invalid") } 33 | 34 | it "must not yield anything" do 35 | expect { |b| 36 | subject.process(domain,&b) 37 | }.to_not yield_control 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/ronin/recon/dns_worker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative 'worker' 22 | require_relative 'mixins/dns' 23 | 24 | require 'ronin/support/network/dns' 25 | 26 | module Ronin 27 | module Recon 28 | # 29 | # Base class for all DNS related workers. 30 | # 31 | # @api public 32 | # 33 | class DNSWorker < Worker 34 | 35 | include Mixins::DNS 36 | 37 | intensity :passive 38 | 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/builtin/dns/nameservers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/builtin/dns/nameservers' 3 | 4 | describe Ronin::Recon::DNS::Nameservers do 5 | describe "#process", :network do 6 | context "when there are nameservers for the domain" do 7 | let(:domain) { Ronin::Recon::Values::Domain.new('example.com') } 8 | let(:nameservers) do 9 | %w[ 10 | a.iana-servers.net 11 | b.iana-servers.net 12 | ] 13 | end 14 | 15 | it "must yield Nameserver values" do 16 | yielded_values = [] 17 | 18 | Async do 19 | subject.process(domain) do |value| 20 | yielded_values << value 21 | end 22 | end 23 | 24 | expect(yielded_values).to_not be_empty 25 | expect(yielded_values).to all(be_kind_of(Ronin::Recon::Values::Nameserver)) 26 | expect(yielded_values.map(&:name).map(&:to_s)).to match_array(nameservers) 27 | end 28 | end 29 | 30 | context "when there is no nameserver for the domain" do 31 | let(:domain) { Ronin::Recon::Values::Domain.new('localhost') } 32 | 33 | it "must not yield anything" do 34 | expect { |b| 35 | Async do 36 | subject.process(domain,&b) 37 | end 38 | }.not_to yield_control 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | ### 0.1.0 / 2024-07-22 2 | 3 | * Initial release: 4 | * Uses asynchronous I/O and fibers. 5 | * Supports defining recon modules as plain old Ruby class. 6 | * Provides built-in recon workers for: 7 | * IP range enumeration. 8 | * DNS lookup of host-names. 9 | * Querying nameservers. 10 | * Querying mailservers. 11 | * DNS reverse lookup of IP addresses. 12 | * DNS SRV record enumeration. 13 | * DNS subdomain enumeration. 14 | * Service/port scanning with `nmap`. 15 | * Enumerates the Common Name (`CN`) and `subjectAltName`s within all SSL/TLS 16 | certificates. 17 | * Web spidering. 18 | * HTTP directory enumeration. 19 | * Supports loading additional recon modules from Ruby files or from installed 20 | [3rd-party git repositories][ronin-repos]. 21 | * Builds a network graph of all discovered assets. 22 | * Provides a simple CLI for listing workers or performing recon. 23 | * Supports many different output file formats: 24 | * TXT 25 | * CSV 26 | * JSON 27 | * [NDJSON](http://ndjson.org/) 28 | * [GraphViz][graphviz] 29 | * DOT 30 | * SVG 31 | * PNG 32 | * PDF 33 | * Supports automatically saving recon results into [ronin-db]. 34 | 35 | [graphviz]: https://graphviz.org/ 36 | [ronin-repos]: https://github.com/ronin-rb/ronin-repos#readme 37 | -------------------------------------------------------------------------------- /man/ronin-recon-test.1.md: -------------------------------------------------------------------------------- 1 | # ronin-recon-test 1 "2023-05-01" Ronin "User Manuals" 2 | 3 | ## NAME 4 | 5 | ronin-recon-test - Loads an individual worker and tests it 6 | 7 | ## SYNOPSIS 8 | 9 | `ronin-recon test` [*options*] {`--file` *FILE* \| *NAME*} {*IP* \| *IP-range* \| *DOMAIN* \| *HOST* \| *WILDCARD* \| *WEBSITE*} 10 | 11 | ## DESCRIPTION 12 | 13 | Loads an individual worker and tests it with an input value.. 14 | 15 | ## ARGUMENTS 16 | 17 | *NAME* 18 | : The name of the recon worker to load. 19 | 20 | *IP* 21 | : An IP address to recon (ex: `192.168.1.1`). 22 | 23 | *IP-range* 24 | : A CIDR IP range to recon (ex: `192.168.1.0/24`). 25 | 26 | *DOMAIN* 27 | : A top-level domain name to recon (ex: `example.com`). 28 | 29 | *HOST* 30 | : A sub-domain to recon (ex: `www.example.com`). 31 | 32 | *WILDCARD* 33 | : A wildcard host name (ex: `*.example.com`). 34 | 35 | *WEBSITE* 36 | : A website base URL to recon (ex: `https://example.com`). 37 | 38 | ## OPTIONS 39 | 40 | `-f`, `--file` *FILE* 41 | : Optionally loads the recon worker from the file. 42 | 43 | `-D`, `--debug` 44 | : Enables debugging output. 45 | 46 | `-h`, `--help` 47 | : Print help information 48 | 49 | ## AUTHOR 50 | 51 | Postmodern 52 | 53 | ## SEE ALSO 54 | 55 | [ronin-recon-workers](ronin-recon-workers.1.md) [ronin-recon-run](ronin-recon-run.1.md) -------------------------------------------------------------------------------- /lib/ronin/recon/cli/command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative '../root' 22 | 23 | require 'ronin/core/cli/command' 24 | 25 | module Ronin 26 | module Recon 27 | class CLI 28 | # 29 | # Base command for all `ronin-recon` commands. 30 | # 31 | class Command < Core::CLI::Command 32 | 33 | man_dir File.join(ROOT,'man') 34 | 35 | bug_report_url 'https://github.com/ronin-rb/ronin-recon/issues/new' 36 | 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /.github/workflows/ruby.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | tests: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | ruby: 12 | - '3.2' 13 | - '3.3' 14 | - '3.4' 15 | # - jruby 16 | # - truffleruby 17 | name: Ruby ${{ matrix.ruby }} 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Set up Ruby 21 | uses: ruby/setup-ruby@v1 22 | with: 23 | ruby-version: ${{ matrix.ruby }} 24 | bundler-cache: true 25 | bundler: latest 26 | - name: Install dependencies 27 | run: | 28 | sudo apt update -y && \ 29 | sudo apt install -y --no-install-recommends --no-install-suggests graphviz 30 | - name: Install dependencies 31 | run: bundle install --jobs 4 --retry 3 32 | - name: Run tests 33 | run: bundle exec rake test 34 | 35 | # rubocop linting 36 | rubocop: 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v4 40 | - name: Set up Ruby 41 | uses: ruby/setup-ruby@v1 42 | with: 43 | ruby-version: 3.0 44 | - name: Install dependencies 45 | run: bundle install --jobs 4 --retry 3 46 | - name: Run rubocop 47 | run: bundle exec rubocop --parallel 48 | -------------------------------------------------------------------------------- /lib/ronin/recon/values.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative 'values/host' 22 | require_relative 'values/nameserver' 23 | require_relative 'values/mailserver' 24 | require_relative 'values/domain' 25 | require_relative 'values/wildcard' 26 | require_relative 'values/ip' 27 | require_relative 'values/ip_range' 28 | require_relative 'values/open_port' 29 | require_relative 'values/email_address' 30 | require_relative 'values/cert' 31 | require_relative 'values/website' 32 | require_relative 'values/url' 33 | -------------------------------------------------------------------------------- /spec/builtin/dns/subdomain_enum_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/builtin/dns/subdomain_enum' 3 | 4 | describe Ronin::Recon::DNS::SubdomainEnum do 5 | let(:fixtures_dir) { File.expand_path(File.join(__dir__,'..','..','fixtures')) } 6 | let(:wordlist_path) { File.join(fixtures_dir, 'wordlist.txt') } 7 | 8 | subject do 9 | described_class.new(params: {wordlist: wordlist_path}) 10 | end 11 | 12 | context "#process", :network do 13 | context "when there is a host for the domain" do 14 | let(:domain) { Ronin::Recon::Values::Domain.new('example.com') } 15 | let(:hosts) { ["www.example.com"] } 16 | 17 | it "must yield Host" do 18 | yielded_values = [] 19 | 20 | subject.process(domain) do |value| 21 | yielded_values << value 22 | end 23 | 24 | expect(yielded_values).to_not be_empty 25 | expect(yielded_values).to all(be_kind_of(Ronin::Recon::Values::Host)) 26 | expect(yielded_values.map(&:name).map(&:to_s)).to eq(hosts) 27 | end 28 | end 29 | 30 | context "when there is no host for the domain" do 31 | let(:domain) { Ronin::Recon::Values::Domain.new('foo.invalid') } 32 | 33 | it "must not yield anything" do 34 | expect { |b| 35 | subject.process(domain,&b) 36 | }.not_to yield_control 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/ronin/recon/output_formats/pdf.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative 'graphviz_format' 22 | 23 | module Ronin 24 | module Recon 25 | module OutputFormats 26 | # 27 | # Represents a GraphViz PDF (`.pdf`) output format. 28 | # 29 | class PDF < GraphvizFormat 30 | 31 | # 32 | # The desired GraphViz output format. 33 | # 34 | # @return [:pdf] 35 | # 36 | def format 37 | :pdf 38 | end 39 | 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/ronin/recon/output_formats/png.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative 'graphviz_format' 22 | 23 | module Ronin 24 | module Recon 25 | module OutputFormats 26 | # 27 | # Represents a GraphViz PNG (`.png`) output format. 28 | # 29 | class PNG < GraphvizFormat 30 | 31 | # 32 | # The desired GraphViz output format. 33 | # 34 | # @return [:png] 35 | # 36 | def format 37 | :png 38 | end 39 | 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/ronin/recon/output_formats/svg.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative 'graphviz_format' 22 | 23 | module Ronin 24 | module Recon 25 | module OutputFormats 26 | # 27 | # Represents a GraphViz SVG (`.svg`) output format. 28 | # 29 | class SVG < GraphvizFormat 30 | 31 | # 32 | # The desired GraphViz output format. 33 | # 34 | # @return [:svg] 35 | # 36 | def format 37 | :svg 38 | end 39 | 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/builtin/ssl/cert_grab_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/builtin/ssl/cert_grab' 3 | 4 | describe Ronin::Recon::SSL::CertGrab do 5 | describe "#process", :network do 6 | context "when there are certificates in the open port" do 7 | let(:port) { Ronin::Recon::Values::OpenPort.new("93.184.216.34", 443, service: 'http', ssl: true) } 8 | let(:fixtures_dir) { File.join(__dir__,'..','..','fixtures','certs') } 9 | let(:cert_path) { File.join(fixtures_dir,'example.crt') } 10 | let(:cert) { Ronin::Support::Crypto::Cert.load_file(cert_path) } 11 | 12 | it "must yield Cert" do 13 | yielded_values = [] 14 | 15 | Async do 16 | subject.process(port) do |value| 17 | yielded_values << value 18 | end 19 | end 20 | 21 | expect(yielded_values.size).to eq(1) 22 | expect(yielded_values[0]).to be_kind_of(Ronin::Recon::Values::Cert) 23 | expect(yielded_values[0].cert).to eq(cert) 24 | end 25 | end 26 | 27 | context "when there is no certificate in the open port" do 28 | let(:port) { Ronin::Recon::Values::OpenPort.new("192.168.0.1", 80) } 29 | 30 | it "must not yield anything" do 31 | expect { |b| 32 | Async do 33 | subject.process(port,&b) 34 | end 35 | }.to_not yield_control 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/ronin/recon/exceptions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # Copyright (c) 2006-2023 Hal Brodigan (postmodern.mod3 at gmail.com) 4 | # 5 | # ronin-support is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Lesser General Public License as published 7 | # by the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # ronin-support is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with ronin-support. If not, see . 17 | # 18 | 19 | module Ronin 20 | module Recon 21 | # 22 | # Base class for all {Ronin::Recon} exceptions. 23 | # 24 | class Exception < RuntimeError 25 | end 26 | 27 | # 28 | # Indicates that a value string could not be parsed. 29 | # 30 | class UnknownValue < Exception 31 | end 32 | 33 | # 34 | # Indicates invalid YAML configuration. 35 | # 36 | class InvalidConfig < Exception 37 | end 38 | 39 | # 40 | # Indicates an invalid YAML configuration file. 41 | # 42 | class InvalidConfigFile < InvalidConfig 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /man/ronin-recon.1.md: -------------------------------------------------------------------------------- 1 | # ronin-recon 1 "2024-01-01" Ronin Recon "User Manuals" 2 | 3 | ## NAME 4 | 5 | ronin-recon - A micro-framework and tool for performing reconnaissance. 6 | 7 | ## SYNOPSIS 8 | 9 | `ronin-recon` [*options*] [*COMMAND* [...]] 10 | 11 | ## DESCRIPTION 12 | 13 | Runs a `ronin-recon` *COMMAND*. 14 | 15 | ## ARGUMENTS 16 | 17 | *COMMAND* 18 | : The `ronin-recon` command to execute. 19 | 20 | ## OPTIONS 21 | 22 | `-V`, `--version` 23 | : Prints the `ronin-recon` version and exits. 24 | 25 | `-h`, `--help` 26 | : Print help information 27 | 28 | ## COMMANDS 29 | 30 | *completion* 31 | : Manages the shell completion rules for `ronin-recon`. 32 | 33 | *help* 34 | : Lists available commands or shows help about a specific command. 35 | 36 | *irb* 37 | : Starts an interactive Ruby shell with ronin-recon loaded. 38 | 39 | *new* 40 | : Creates a new recon worker file. 41 | 42 | *run* 43 | : Runs the recon engine with one or more initial values. 44 | 45 | *test* 46 | : Loads an individual worker and tests it. 47 | 48 | *worker* 49 | : Prints information about a recon worker. 50 | 51 | *workers* 52 | : Lists the available recon workers. 53 | 54 | ## AUTHOR 55 | 56 | Postmodern 57 | 58 | ## SEE ALSO 59 | 60 | [ronin-recon-completion](ronin-recon-completion.1.md) [ronin-recon-new](ronin-recon-new.1.md) [ronin-recon-test](ronin-recon-test.1.md) [ronin-recon-worker](ronin-recon-worker.1.md) [ronin-recon-workers](ronin-recon-workers.1.md) 61 | -------------------------------------------------------------------------------- /spec/builtin/dns/mailservers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/builtin/dns/mailservers' 3 | 4 | describe Ronin::Recon::DNS::Mailservers do 5 | describe "#process", :network do 6 | context "when there are mailservers for the domain" do 7 | let(:domain) { Ronin::Recon::Values::Domain.new('gmail.com') } 8 | let(:mailservers) do 9 | %w[ 10 | alt1.gmail-smtp-in.l.google.com 11 | alt2.gmail-smtp-in.l.google.com 12 | alt3.gmail-smtp-in.l.google.com 13 | gmail-smtp-in.l.google.com 14 | alt4.gmail-smtp-in.l.google.com 15 | ] 16 | end 17 | 18 | it "must yield Mailserver values" do 19 | yielded_values = [] 20 | 21 | Async do 22 | subject.process(domain) do |value| 23 | yielded_values << value 24 | end 25 | end 26 | 27 | expect(yielded_values).to_not be_empty 28 | expect(yielded_values).to all(be_kind_of(Ronin::Recon::Values::Mailserver)) 29 | expect(yielded_values.map(&:name).map(&:to_s)).to match_array(mailservers) 30 | end 31 | end 32 | 33 | context "when there is no mailserver for the domain" do 34 | let(:domain) { Ronin::Recon::Values::Domain.new('localhost') } 35 | 36 | it "must not yield anything" do 37 | expect { |b| 38 | Async do 39 | subject.process(domain,&b) 40 | end 41 | }.not_to yield_control 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/builtin/dns/reverse_lookup_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/builtin/dns/reverse_lookup' 3 | 4 | describe Ronin::Recon::DNS::ReverseLookup do 5 | describe "#process", :network do 6 | context "when there the IP address has a PTR record back to a host name" do 7 | let(:ip) { Ronin::Recon::Values::IP.new('1.1.1.1') } 8 | let(:host) { Ronin::Recon::Values::Host.new('one.one.one.one') } 9 | 10 | it "must yield a Host value" do 11 | yielded_values = [] 12 | 13 | Async do 14 | subject.process(ip) do |value| 15 | yielded_values << value 16 | end 17 | end 18 | 19 | expect(yielded_values).to eq([host]) 20 | end 21 | 22 | context "but the IP value is already has a #host" do 23 | let(:ip) do 24 | Ronin::Recon::Values::IP.new('1.1.1.1', host: 'one.one.one.one') 25 | end 26 | 27 | it "must not yield any values" do 28 | expect { |b| 29 | Async do 30 | subject.process(ip,&b) 31 | end 32 | }.not_to yield_control 33 | end 34 | end 35 | end 36 | 37 | context "but the IP address has no PTR records" do 38 | let(:ip) { Ronin::Recon::Values::IP.new('93.184.216.34') } 39 | 40 | it "must not yield anything" do 41 | expect { |b| 42 | Async do 43 | subject.process(ip,&b) 44 | end 45 | }.not_to yield_control 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/ronin/recon/builtin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative 'builtin/dns/lookup' 22 | require_relative 'builtin/dns/reverse_lookup' 23 | require_relative 'builtin/dns/mailservers' 24 | require_relative 'builtin/dns/nameservers' 25 | require_relative 'builtin/dns/subdomain_enum' 26 | require_relative 'builtin/dns/suffix_enum' 27 | require_relative 'builtin/dns/srv_enum' 28 | require_relative 'builtin/net/ip_range_enum' 29 | require_relative 'builtin/net/port_scan' 30 | require_relative 'builtin/net/service_id' 31 | require_relative 'builtin/ssl/cert_grab' 32 | require_relative 'builtin/ssl/cert_enum' 33 | require_relative 'builtin/web/spider' 34 | require_relative 'builtin/web/dir_enum' 35 | -------------------------------------------------------------------------------- /lib/ronin/recon/message/worker_started.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | module Ronin 22 | module Recon 23 | module Message 24 | # 25 | # Indicates that a worker has started. 26 | # 27 | # @api prviate 28 | # 29 | class WorkerStarted 30 | 31 | # The worker object. 32 | # 33 | # @return [Worker] 34 | attr_reader :worker 35 | 36 | # 37 | # Initializes the message. 38 | # 39 | # @param [Worker] worker 40 | # The worker object that started. 41 | # 42 | def initialize(worker) 43 | @worker = worker 44 | 45 | freeze 46 | end 47 | 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/ronin/recon/message/worker_stopped.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | module Ronin 22 | module Recon 23 | module Message 24 | # 25 | # Indicates that a worker has stopped. 26 | # 27 | # @api private 28 | # 29 | class WorkerStopped 30 | 31 | # The worker object. 32 | # 33 | # @return [Worker] 34 | attr_reader :worker 35 | 36 | # 37 | # Initializes the message. 38 | # 39 | # @param [Worker] worker 40 | # The worker object that has stopped. 41 | # 42 | def initialize(worker) 43 | @worker = worker 44 | 45 | freeze 46 | end 47 | 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/ronin/recon/cli.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require 'ronin/core/cli/help/banner' 22 | 23 | require 'command_kit/commands' 24 | require 'command_kit/commands/auto_load' 25 | require 'command_kit/options/version' 26 | 27 | require_relative 'version' 28 | 29 | module Ronin 30 | module Recon 31 | # 32 | # The `ronin-recon` command-line interface (CLI). 33 | # 34 | # @api private 35 | # 36 | class CLI 37 | 38 | include CommandKit::Commands 39 | include CommandKit::Commands::AutoLoad.new( 40 | dir: "#{__dir__}/cli/commands", 41 | namespace: "#{self}::Commands" 42 | ) 43 | include CommandKit::Options::Version 44 | include Core::CLI::Help::Banner 45 | 46 | command_name 'ronin-recon' 47 | version Ronin::Recon::VERSION 48 | 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/ronin/recon/output_formats/graph_format.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | module Ronin 22 | module Recon 23 | module OutputFormats 24 | # 25 | # Indicates that an output format can contain graph information. 26 | # 27 | module GraphFormat 28 | # 29 | # Appends a value and it's parent value to the GraphViz DOT output 30 | # stream. 31 | # 32 | # @param [Value] value 33 | # The value to append. 34 | # 35 | # @param [Value] parent 36 | # The parent value of the given value. 37 | # 38 | # @return [self] 39 | # 40 | # @abstract 41 | # 42 | def []=(value,parent) 43 | raise(NotImplementedError,"#{self.class}#[]= was not implemented") 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/ronin/recon/cli/debug_option.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require 'console' 22 | 23 | module Ronin 24 | module Recon 25 | class CLI 26 | # 27 | # Adds a `-D,--debug` option to the command that enables debugging output. 28 | # 29 | module DebugOption 30 | # 31 | # Adds the `-D,--debug` option to the including command class. 32 | # 33 | # @param [Class] command 34 | # The command class which is including {DebugOption}. 35 | # 36 | def self.included(command) 37 | command.option :debug, short: '-D', 38 | desc: 'Enable debugging output' do 39 | Console.logger.level = Console::Logger::DEBUG 40 | end 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/builtin/dns/suffix_enum_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/builtin/dns/suffix_enum' 3 | 4 | RSpec.describe Ronin::Recon::DNS::SuffixEnum do 5 | let(:fixtures_dir) { File.join(__dir__,'fixtures') } 6 | 7 | describe '#process', :network do 8 | let(:suffix_list) { Ronin::Support::Network::PublicSuffix::List.load_file(suffix_path) } 9 | 10 | before do 11 | allow(Ronin::Support::Network::PublicSuffix).to receive(:list).and_return(suffix_list) 12 | end 13 | 14 | context 'when there is a domain with a different suffix' do 15 | let(:domain) { Ronin::Recon::Values::Domain.new('example.com') } 16 | let(:suffix_path) { File.join(fixtures_dir,'public_domains.dat') } 17 | let(:domains_names) do 18 | [ 19 | "example.com.zm", 20 | "example.org.za", 21 | "example.in.th", 22 | "example.vn" 23 | ] 24 | end 25 | 26 | it 'must yield Values::Domain for each found' do 27 | yielded_values = [] 28 | 29 | subject.process(domain) do |value| 30 | yielded_values << value 31 | end 32 | 33 | expect(yielded_values).to_not be_empty 34 | expect(yielded_values).to all(be_kind_of(Ronin::Recon::Values::Domain)) 35 | expect(yielded_values.map(&:name)).to match_array(domains_names) 36 | end 37 | end 38 | 39 | context 'when there is no domain with a different suffix' do 40 | let(:domain) { Ronin::Recon::Values::Domain.new('no_suffixes.com') } 41 | let(:suffix_path) { File.join(fixtures_dir,'no_suffixes.dat') } 42 | 43 | it 'must not yield anything' do 44 | expect { |b| 45 | subject.process(domain, &b) 46 | }.not_to yield_control 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /man/ronin-recon-new.1.md: -------------------------------------------------------------------------------- 1 | # ronin-recon-new 1 "May 2022" Ronin "User Manuals" 2 | 3 | ## NAME 4 | 5 | ronin-recon-new - Creates a new recon worker file 6 | 7 | ## SYNOPSIS 8 | 9 | `ronin-recon new` [*options*] *PATH* 10 | 11 | ## DESCRIPTION 12 | 13 | Generates a new recon worker file. 14 | 15 | ## ARGUMENTS 16 | 17 | *PATH* 18 | : The path to the new recon worker file to generate. 19 | 20 | ## OPTIONS 21 | 22 | `-t`, `--type` `worker`\|`dns`\|`web` 23 | : The type of recon worker to generate. 24 | 25 | `-a`, `--author` *NAME* 26 | : The name of the author. Defaults to the configured git author name or the 27 | `USERNAME` environment variable. 28 | 29 | `-e`, `--author-email` *EMAIL* 30 | : The email address of the author. Defaults to the configured git author email. 31 | 32 | `-S`, `--summary` *TEXT* 33 | : The summary text for the new recon worker. 34 | 35 | `-D`, `--description` *TEXT* 36 | : The description text for the new recon worker. 37 | 38 | `-R`, `--reference` *URL* 39 | : Adds a reference URL to the new recon worker. 40 | 41 | `-A`, `--accepts` `cert`\|`domain|email_address|host|ip_range|ip|mailserver|nameserver|open_port|url|website|wildcard` 42 | : The value type(s) that the recon worker accepts. 43 | 44 | `-O`, `--outputs` `cert`\|`domain|email_address|host|ip_range|ip|mailserver|nameserver|open_port|url|website|wildcard` 45 | : The value type(s) that the recon worker outputs. 46 | 47 | `-I`, `--intensity` `passive`\|`active`\|`aggressive` 48 | : The intensity of the recon worker. 49 | 50 | `-h`, `--help` 51 | : Print help information 52 | 53 | ## AUTHOR 54 | 55 | Postmodern 56 | 57 | ## SEE ALSO 58 | 59 | [ronin-payloads-workers](ronin-payloads-workers.1.md) [ronin-payloads-worker](ronin-payloads-worker.1.md) [ronin-payloads-test](ronin-payloads-test.1.md) 60 | -------------------------------------------------------------------------------- /lib/ronin/recon/message/job_started.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | module Ronin 22 | module Recon 23 | module Message 24 | # 25 | # Indicates that a worker has started a job. 26 | # 27 | # @api private 28 | # 29 | class JobStarted 30 | 31 | # The worker object. 32 | # 33 | # @return [Worker] 34 | attr_reader :worker 35 | 36 | # The input value object. 37 | # 38 | # @return [Value] 39 | attr_reader :value 40 | 41 | # 42 | # Initializes the message. 43 | # 44 | # @param [Worker] worker 45 | # The worker object. 46 | # 47 | # @param [Value] value 48 | # The input value object. 49 | # 50 | def initialize(worker,value) 51 | @worker = worker 52 | @value = value 53 | 54 | freeze 55 | end 56 | 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/ronin/recon/cli/ruby_shell.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require 'ronin/core/cli/ruby_shell' 22 | 23 | module Ronin 24 | module Recon 25 | class CLI 26 | # 27 | # The interactive Ruby shell for {Ronin::Recon}. 28 | # 29 | class RubyShell < Core::CLI::RubyShell 30 | 31 | # 32 | # Initializes the `ronin-recon` Ruby shell. 33 | # 34 | # @param [String] name 35 | # The name of the IRB shell. 36 | # 37 | # @param [Object] context 38 | # Custom context to launch IRB from within. 39 | # 40 | # @param [Hash{Symbol => Object}] kwargs 41 | # Additional keyword arguments for 42 | # `Ronin::Core::CLI::RubyShell#initialize`. 43 | # 44 | def initialize(name: 'ronin-recon', context: Recon, **kwargs) 45 | super(name: name, context: context, **kwargs) 46 | end 47 | 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/ronin/recon/message/job_completed.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | module Ronin 22 | module Recon 23 | module Message 24 | # 25 | # Indicates that a recon job has been completed. 26 | # 27 | # @api private 28 | # 29 | class JobCompleted 30 | 31 | # The worker object. 32 | # 33 | # @return [Worker] 34 | attr_reader :worker 35 | 36 | # The input value object. 37 | # 38 | # @return [Value] 39 | attr_reader :value 40 | 41 | # 42 | # Initializes the message. 43 | # 44 | # @param [Worker] worker 45 | # The worker object. 46 | # 47 | # @param [Value] value 48 | # The input value for the job. 49 | # 50 | def initialize(worker,value) 51 | @worker = worker 52 | @value = value 53 | 54 | freeze 55 | end 56 | 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/ronin/recon/output_formats.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative 'output_formats/dir' 22 | require_relative 'output_formats/dot' 23 | require_relative 'output_formats/svg' 24 | require_relative 'output_formats/png' 25 | require_relative 'output_formats/pdf' 26 | 27 | require 'ronin/core/output_formats' 28 | 29 | module Ronin 30 | module Recon 31 | # 32 | # Contains the supported output formats for saving {Ronin::Recon::Values} 33 | # object to output files. 34 | # 35 | module OutputFormats 36 | include Core::OutputFormats 37 | 38 | register :txt, '.txt', TXT 39 | register :csv, '.csv', CSV 40 | register :json, '.json', JSON 41 | register :ndjson, '.ndjson', NDJSON 42 | register :dir, '', Dir 43 | register :dot, '.dot', Dot 44 | register :svg, '.svg', SVG 45 | register :png, '.png', PNG 46 | register :pdf, '.pdf', PDF 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/ronin/recon/cli/commands/irb.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative '../command' 22 | require_relative '../ruby_shell' 23 | 24 | module Ronin 25 | module Recon 26 | class CLI 27 | module Commands 28 | # 29 | # Starts an interactive Ruby shell with `ronin-recon` loaded. 30 | # 31 | # ## Usage 32 | # 33 | # ronin-recon irb [options] 34 | # 35 | # ## Options 36 | # 37 | # -h, --help Print help information 38 | # 39 | class Irb < Command 40 | 41 | description "Starts an interactive Ruby shell with ronin-recon loaded" 42 | 43 | man_page 'ronin-recon-irb.1' 44 | 45 | # 46 | # Runs the `ronin-recon irb` command. 47 | # 48 | def run 49 | require 'ronin/recon' 50 | CLI::RubyShell.start 51 | end 52 | 53 | end 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /data/templates/worker.rb.erb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S ronin-recon test -f 2 | 3 | require 'ronin/recon/<%= @worker_type[:file] -%>' 4 | 5 | module Ronin 6 | module Recon 7 | class <%= @class_name -%> < <%= @worker_type[:class] %> 8 | 9 | register '<%= @file_name -%>' 10 | 11 | <%- if @author_name -%> 12 | <%- if @author_email -%> 13 | author <%= @author_name.inspect %>, email: <%= @author_email.inspect %> 14 | <%- else -%> 15 | author <%= @author_name.inspect %> 16 | <%- end -%> 17 | <%- else -%> 18 | author "FIX ME", email: "FIXME@example.com" 19 | <%- end -%> 20 | <%- if @summary -%> 21 | summary <%= @summary.inspect %> 22 | <%- else -%> 23 | summary "FIX ME" 24 | <%- end -%> 25 | <%- if @description -%> 26 | description <<~DESC 27 | <%= @description %> 28 | DESC 29 | <%- else -%> 30 | description <<~DESC 31 | FIX ME 32 | DESC 33 | <%- end -%> 34 | <%- unless @references.empty? -%> 35 | references [ 36 | <%- @references.each_with_index do |url,index| -%> 37 | <%= url.inspect -%><% if index < @references.length-1 %>,<% end %> 38 | <%- end -%> 39 | ] 40 | <%- else -%> 41 | # references [ 42 | # "https://...", 43 | # "https://..." 44 | # ] 45 | <%- end -%> 46 | 47 | <%- unless @accepts.empty? -%> 48 | accepts <%= @accepts.join(', ') %> 49 | <%- else -%> 50 | accepts FIXME 51 | <%- end -%> 52 | <%- unless @outputs.empty? -%> 53 | outputs <%= @outputs.join(', ') %> 54 | <%- else -%> 55 | outputs FIXME 56 | <%- end -%> 57 | <%- if @intensity -%> 58 | intensity <%= @intensity.inspect %> 59 | <%- end -%> 60 | 61 | def process(value) 62 | # ... 63 | end 64 | 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/ronin/recon/values/mailserver.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative 'host' 22 | 23 | module Ronin 24 | module Recon 25 | module Values 26 | # 27 | # Represents a discovered mailserver. 28 | # 29 | # @api public 30 | # 31 | class Mailserver < Host 32 | 33 | # 34 | # Coerces the mailserver value into JSON. 35 | # 36 | # @return [Hash{Symbol => Object}] 37 | # The Ruby Hash that will be converted into JSON. 38 | # 39 | def as_json 40 | {type: :mailserver, name: @name} 41 | end 42 | 43 | # 44 | # Returns the type or kind of recon value. 45 | # 46 | # @return [:mailserver] 47 | # 48 | # @note 49 | # This is used internally to map a recon value class to a printable 50 | # type. 51 | # 52 | # @api private 53 | # 54 | def self.value_type 55 | :mailserver 56 | end 57 | 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/ronin/recon/values/nameserver.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative 'host' 22 | 23 | module Ronin 24 | module Recon 25 | module Values 26 | # 27 | # Represents a discovered DNS nameserver. 28 | # 29 | # @api public 30 | # 31 | class Nameserver < Host 32 | 33 | # 34 | # Coerces the nameserver value into JSON. 35 | # 36 | # @return [Hash{Symbol => Object}] 37 | # The Ruby Hash that will be converted into JSON. 38 | # 39 | def as_json 40 | {type: :nameserver, name: @name} 41 | end 42 | 43 | # 44 | # Returns the type or kind of recon value. 45 | # 46 | # @return [:nameserver] 47 | # 48 | # @note 49 | # This is used internally to map a recon value class to a printable 50 | # type. 51 | # 52 | # @api private 53 | # 54 | def self.value_type 55 | :nameserver 56 | end 57 | 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/ronin/recon/mixins/http.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require 'async/http' 22 | require 'set' 23 | 24 | module Ronin 25 | module Recon 26 | module Mixins 27 | # 28 | # Mixin which adds methods for performing async HTTP requests. 29 | # 30 | # @api public 31 | # 32 | module HTTP 33 | # HTTP status codes that indicate a valid route. 34 | VALID_STATUS_CODES = Set[ 35 | 200, # OK 36 | 201, # Created 37 | 202, # Accepted 38 | 203, # Non-Authoritative Information 39 | 204, # No Content 40 | 205, # Reset Content 41 | 206, # Partial Content 42 | 207, # Multi-Status 43 | 208, # Already Reported 44 | 226, # IM Used 45 | 405, # Method Not Allowed 46 | 409, # Conflict 47 | 415, # Unsupported Media Type 48 | 422, # Unprocessable Content 49 | 423, # Locked 50 | 424, # Failed Dependency 51 | 451, # Unavailable For Legal Reasons 52 | 500 # Internal Server Error 53 | ] 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/input_file_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/input_file' 3 | 4 | describe Ronin::Recon::InputFile do 5 | let(:fixtures_dir) { File.join(__dir__,'fixtures') } 6 | let(:path) { File.join(fixtures_dir,'input_file.txt') } 7 | 8 | subject { described_class.new(path) } 9 | 10 | describe "#initialize" do 11 | it "must set #path" do 12 | expect(subject.path).to eq(path) 13 | end 14 | end 15 | 16 | describe ".open" do 17 | subject { described_class.open(path) } 18 | 19 | it "must create a new InputFile with the given path" do 20 | expect(subject).to be_kind_of(described_class) 21 | expect(subject.path).to eq(path) 22 | end 23 | end 24 | 25 | describe "#each" do 26 | context "when given a block" do 27 | it "must parse each line of the input file and yield each value" do 28 | expect { |b| 29 | subject.each(&b) 30 | }.to yield_successive_args( 31 | Ronin::Recon::Values::IP.new('1.2.3.4'), 32 | Ronin::Recon::Values::IPRange.new('1.2.3.4/24'), 33 | Ronin::Recon::Values::Domain.new('example.com'), 34 | Ronin::Recon::Values::Host.new('www.example.com'), 35 | Ronin::Recon::Values::Wildcard.new('*.example.com'), 36 | Ronin::Recon::Values::Website.parse('https://example.com') 37 | ) 38 | end 39 | end 40 | 41 | context "when no block is given" do 42 | it "must an Enumerator object for the method" do 43 | expect(subject.each.to_a).to eq( 44 | [ 45 | Ronin::Recon::Values::IP.new('1.2.3.4'), 46 | Ronin::Recon::Values::IPRange.new('1.2.3.4/24'), 47 | Ronin::Recon::Values::Domain.new('example.com'), 48 | Ronin::Recon::Values::Host.new('www.example.com'), 49 | Ronin::Recon::Values::Wildcard.new('*.example.com'), 50 | Ronin::Recon::Values::Website.parse('https://example.com') 51 | ] 52 | ) 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | 7 | platform :jruby do 8 | gem 'jruby-openssl', '~> 0.7' 9 | end 10 | 11 | # gem 'wordlist', '~> 1.0', github: 'postmodern/wordlist', 12 | # branch: 'main' 13 | 14 | # Ronin dependencies 15 | # gem 'ronin-support', '~> 1.1', github: 'ronin-rb/ronin-support', 16 | # branch: 'main' 17 | 18 | # gem 'ronin-core', '~> 0.2', github: 'ronin-rb/ronin-core', 19 | # branch: 'main' 20 | 21 | # gem 'ronin-repos', '~> 0.1', github: 'ronin-rb/ronin-repos', 22 | # branch: 'main' 23 | 24 | # gem 'ronin-db-activerecord', '~> 0.2', github: 'ronin-rb/ronin-db-activerecord', 25 | # branch: 'main' 26 | 27 | # gem 'ronin-db', '~> 0.2', github: 'ronin-rb/ronin-db', 28 | # branch: 'main' 29 | 30 | # gem 'ronin-nmap', '~> 0.1', github: 'ronin-rb/ronin-nmap', 31 | # branch: 'main' 32 | 33 | # gem 'ronin-web-spider', '~> 0.2', github: 'ronin-rb/ronin-web-spider', 34 | # branch: 'main' 35 | 36 | group :development do 37 | gem 'rake' 38 | gem 'rubygems-tasks', '~> 0.2' 39 | 40 | gem 'rspec', '~> 3.0' 41 | gem 'simplecov', '~> 0.20' 42 | gem 'sinatra', '~> 3.0' 43 | gem 'webmock', '~> 3.0' 44 | 45 | gem 'kramdown', '~> 2.0' 46 | gem 'kramdown-man', '~> 1.0' 47 | 48 | gem 'redcarpet', platform: :mri 49 | gem 'yard', '~> 0.9' 50 | gem 'yard-spellcheck', require: false 51 | 52 | gem 'dead_end', require: false 53 | gem 'sord', require: false, platform: :mri 54 | gem 'stackprof', require: false, platform: :mri 55 | gem 'rubocop', require: false, platform: :mri 56 | gem 'rubocop-ronin', require: false, platform: :mri 57 | 58 | gem 'command_kit-completion', '~> 0.2', require: false 59 | end 60 | -------------------------------------------------------------------------------- /spec/values/email_address_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/values/email_address' 3 | 4 | describe Ronin::Recon::Values::EmailAddress do 5 | let(:address) { 'john.smith@example.com' } 6 | 7 | subject { described_class.new(address) } 8 | 9 | describe "#initialize" do 10 | it "must set #address" do 11 | expect(subject.address).to eq(address) 12 | end 13 | end 14 | 15 | describe "#eql?" do 16 | context "when given an email address object" do 17 | context "and the other email address object has the same #address" do 18 | let(:other) { described_class.new(address) } 19 | 20 | it "must return true" do 21 | expect(subject.eql?(other)).to be(true) 22 | end 23 | end 24 | 25 | context "but the other email address object has a different #address" do 26 | let(:other) { described_class.new('127.0.0.1') } 27 | 28 | it "must return true" do 29 | expect(subject.eql?(other)).to be(false) 30 | end 31 | end 32 | end 33 | 34 | context "when given a non-email address object" do 35 | let(:other) { Object.new } 36 | 37 | it "must return false" do 38 | expect(subject.eql?(other)).to be(false) 39 | end 40 | end 41 | end 42 | 43 | describe "#hash" do 44 | it "must return the #hash of an Array containing the class and the #address" do 45 | expect(subject.hash).to eq([described_class, address].hash) 46 | end 47 | end 48 | 49 | describe "#to_s" do 50 | it "must return the #address" do 51 | expect(subject.to_s).to eq(address) 52 | end 53 | end 54 | 55 | describe "#as_json" do 56 | it "must return a Hash containing the type: and address: attributes" do 57 | expect(subject.as_json).to eq({type: :email_address, address: address}) 58 | end 59 | end 60 | 61 | describe ".value_type" do 62 | subject { described_class } 63 | 64 | it "must return :email_address" do 65 | expect(subject.value_type).to be(:email_address) 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/ronin/recon/builtin/dns/nameservers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative '../../dns_worker' 22 | 23 | module Ronin 24 | module Recon 25 | module DNS 26 | # 27 | # Finds the nameservers associated with a domaim. 28 | # 29 | class Nameservers < DNSWorker 30 | 31 | register 'dns/nameservers' 32 | 33 | summary 'Looks up the nameservers of a domain' 34 | description <<~DESC 35 | Queries the nameservers (NS records) for a domain name. 36 | DESC 37 | 38 | accepts Domain 39 | outputs Nameserver 40 | 41 | # 42 | # Looks up the nameservers of a given domain. 43 | # 44 | # @param [Values::Domain] domain 45 | # The domain value. 46 | # 47 | # @yield [nameserver] 48 | # The discovered nameservers will be yielded. 49 | # 50 | # @yieldparam [Values::Nameserver] nameserver 51 | # 52 | def process(domain) 53 | dns_get_nameservers(domain.name).each do |nameserver| 54 | yield Nameserver.new(nameserver.chomp('.')) 55 | end 56 | end 57 | 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/fixtures/certs/other.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFbTCCA1WgAwIBAgIBADANBgkqhkiG9w0BAQsFADBqMRIwEAYDVQQDDAlsb2Nh 3 | bGhvc3QxEjAQBgNVBAsMCVRlc3QgRGVwdDESMBAGA1UECgwJVGVzdCBDby4uMRIw 4 | EAYDVQQHDAlUZXN0IENpdHkxCzAJBgNVBAgMAlhYMQswCQYDVQQGEwJVUzAeFw0y 5 | MjA2MDMwNjU1NTBaFw0yMzA2MDMwNjU1NTBaMGoxEjAQBgNVBAMMCWxvY2FsaG9z 6 | dDESMBAGA1UECwwJVGVzdCBEZXB0MRIwEAYDVQQKDAlUZXN0IENvLi4xEjAQBgNV 7 | BAcMCVRlc3QgQ2l0eTELMAkGA1UECAwCWFgxCzAJBgNVBAYTAlVTMIICIjANBgkq 8 | hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqLS904GJqasfdClyPU/ySEFzDLuIFeEJ 9 | HVpVU6JhxXZRbZmBERLh153u+giYxLpeXeKvNGpA7kyPQsrtYQ5iK/rTQ6nlTUDO 10 | hSsGMPvwSk1a2mRFgrb1yg/pm/2FbZeNnFBPMEdOiH/QIX0/sJvlfs5hi1wfCC+4 11 | HrOKyglDNfHeM2DMU3Uq22eoWX9SruiGfpekGgbvtPN3Xf/YGcdaEawKcNNqrpcc 12 | cznGBWy10RjlgredDDYKBfs0tXS5YBJudnN+/Dv/XqN80jHY6nSVV0vLXYtPPT5s 13 | tCcS5+NM11RfUq4LlM9n50u6Xy7sW0z89UPRXBgfYw+eSLK94EG2Zn3WGE96gS1z 14 | pAwand3+8ML/QFo9DKgbFAL4DwTH/tQY7uTEQl2pw4QheLvjW2A9ty2xfYS/0laq 15 | H11v8MPf63RxHqqALJGk1zt6kc5C3UQpi+peXGX+c10kFjoqmvqY18R0jNljayX4 16 | 8T81xMfiO8kiPlcUEEyDKWWCeN+UbZNx0vdiUKBrdpEKX1SfBDGILbkPRkwmGzvf 17 | jtsMTNjjqMr77qwDKfdb0wdN5O8EoSpI5SH0Cgn3wTuxbeYDAVJfFYimHRMAvulu 18 | L2JZeFQaC9gl0CCg22lJL0FkXv/3+KzMaoJaQ88oT3DrCCbcgXsGk0F/jRhpo6Bf 19 | 19/0kXI66iUCAwEAAaMeMBwwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0G 20 | CSqGSIb3DQEBCwUAA4ICAQAmprxoSOB9w37YXY1qN+hK5DfXfMxa9ORfQ8U9MoJQ 21 | WS34R4DXp/kq0iTYzwUKrFScONgbq5exw+boy6EvoO2zjdZLhlXPSbeQdbEYUQzy 22 | 6IyPpoX3G++vtRh0Nd8NZhL+RhlKymCeZOtPhgT+UDJYIztTZZhTUuYGzK9KJJAn 23 | d4TghUN/PCJvVfq3ImUWmBFavPuMPV5Hct2YCVkjP2fXPv5RKc/xpGIgEb7UZLN2 24 | 0iymLF+/dnESdf/Yj8833LRrdOZBMa9E6hGrr3apX3Tg6fZVFE6SlxhretMao6H6 25 | lR0f6ZXhigqu0vb8DuKxIzQX0uECG6kQLeFuWuwRJepdn2wdShsHyqG/iNaGlzfX 26 | Ij7897BRbCENLJUFN9ovtvBgYWtVmQMDWpuGlRBUgkhuS0B6KQzAGoNxeQVgjH8r 27 | zipq+wjjtzbL3/iwnB/AFBaYj3YF+/mddJiO+EpRVep9FHsHCHnJQbkqL6rQd3MV 28 | 7NKE2bOr8ZCPXsp8l3yHTSx23e/v5nYe3Tizwmrimyo5nPakBS5mc6HmOEUETecZ 29 | 8fOHSBUg3pCZU3a7P9V/EAyf/Qqkwuc/nyoBYbWAfqeCivSfH3CVuvyhocSzjBvt 30 | 2XJlNtHoFuSZ5v+Z7GpFSGl0Cjm631ECuiP+VQkcC45/JVHl+s7xjEMBMLHUKRln 31 | yA== 32 | -----END CERTIFICATE----- 33 | -------------------------------------------------------------------------------- /gemspec.yml: -------------------------------------------------------------------------------- 1 | name: ronin-recon 2 | summary: A micro-framework and tool for performing reconnaissance. 3 | description: | 4 | ronin-recon is a micro-framework and tool for performing reconnaissance. 5 | ronin-recon uses multiple workers which process different data types 6 | (IP, host, URL, etc) and produce new values. ronin-recon contains built-in 7 | recon workers and supports loading additional 3rd-party workers from Ruby 8 | files or 3rd-party git repositories. ronin-recon has a unique queue design 9 | and uses asynchronous I/O to maximize efficiency. ronin-recon can lookup 10 | IPs addresses, nameservers, mailservers, bruteforce sub-domains, port scan 11 | IPs, discover services, and spider websites. 12 | 13 | license: LGPL-3.0-or-later 14 | authors: Postmodern 15 | email: postmodern.mod3@gmail.com 16 | homepage: https://ronin-rb.dev/ 17 | has_yard: true 18 | 19 | metadata: 20 | documentation_uri: https://ronin-rb.dev/docs/ronin-recon 21 | source_code_uri: https://github.com/ronin-rb/ronin-recon 22 | bug_tracker_uri: https://github.com/ronin-rb/ronin-recon/issues 23 | changelog_uri: https://github.com/ronin-rb/ronin-recon/blob/main/ChangeLog.md 24 | rubygems_mfa_required: 'true' 25 | 26 | generated_files: 27 | - data/completions/ronin-recon 28 | - man/ronin-recon.1 29 | - man/ronin-recon-completion.1 30 | - man/ronin-recon-irb.1 31 | - man/ronin-recon-new.1 32 | - man/ronin-recon-workers.1 33 | - man/ronin-recon-worker.1 34 | - man/ronin-recon-test.1 35 | - man/ronin-recon-run.1 36 | - data/wordlists/subdomains-1000.txt.gz 37 | - data/wordlists/raft-small-directories.txt.gz 38 | 39 | required_ruby_version: ">= 3.2.0" 40 | 41 | dependencies: 42 | csv: ~> 3.0 43 | thread-local: ~> 1.0 44 | io-endpoint: ~> 0.15 45 | async-dns: ~> 1.0 46 | async-http: ~> 0.60 47 | wordlist: ~> 1.0, >= 1.0.3 48 | # Ronin dependencies: 49 | ronin-support: ~> 1.1 50 | ronin-core: ~> 0.2 51 | ronin-db: ~> 0.2 52 | ronin-repos: ~> 0.1 53 | ronin-nmap: ~> 0.1 54 | ronin-web-spider: ~> 0.2 55 | 56 | development_dependencies: 57 | bundler: ~> 2.0 58 | -------------------------------------------------------------------------------- /lib/ronin/recon/message/job_failed.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | module Ronin 22 | module Recon 23 | module Message 24 | # 25 | # Indicates that a job encountered an exception. 26 | # 27 | # @api private 28 | # 29 | class JobFailed 30 | 31 | # The worker object. 32 | # 33 | # @return [Worker] 34 | attr_reader :worker 35 | 36 | # The input value object. 37 | # 38 | # @return [Value] 39 | attr_reader :value 40 | 41 | # The exception. 42 | # 43 | # @return [StandardError] 44 | attr_reader :exception 45 | 46 | # 47 | # Initializes the message. 48 | # 49 | # @param [Worker] worker 50 | # The worker object. 51 | # 52 | # @param [Value] value 53 | # The input value object. 54 | # 55 | # @param [StandardError] exception 56 | # The exception object. 57 | # 58 | def initialize(worker,value,exception) 59 | @worker = worker 60 | @value = value 61 | @exception = exception 62 | 63 | freeze 64 | end 65 | 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/ronin/recon/builtin/net/ip_range_enum.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative '../../worker' 22 | 23 | require 'ronin/support/network/ip_range' 24 | 25 | module Ronin 26 | module Recon 27 | module Net 28 | # 29 | # A recon worker that enumerates every IP address within an IP range. 30 | # 31 | class IPRangeEnum < Worker 32 | 33 | register 'net/ip_range_enum' 34 | 35 | summary 'Enumerates the IP addresses in an IP range' 36 | 37 | description <<~DESC 38 | Enumerates over every IP address in a CIDR IP range. 39 | DESC 40 | 41 | accepts IPRange 42 | outputs IP 43 | intensity :passive 44 | 45 | # 46 | # Enumerates an IP range. 47 | # 48 | # @param [Values::IPRange] ip_range 49 | # The IP range value. 50 | # 51 | # @yield [ip] 52 | # Each IP value within the IP range will be yielded. 53 | # 54 | # @yieldparam [Values::IP] ip 55 | # An IP value. 56 | # 57 | def process(ip_range) 58 | ip_range.range.each do |address| 59 | yield IP.new(address) 60 | end 61 | end 62 | 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/ronin/recon/builtin/dns/reverse_lookup.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative '../../dns_worker' 22 | 23 | module Ronin 24 | module Recon 25 | module DNS 26 | # 27 | # Performs reverse DNS lookup on an IP address and finds it's host name. 28 | # 29 | class ReverseLookup < DNSWorker 30 | 31 | register 'dns/reverse_lookup' 32 | 33 | summary 'Reverse looks up an IP address' 34 | description <<~DESC 35 | Reverse looks up an IP address and return the host names associated 36 | with the IP address. 37 | DESC 38 | 39 | accepts IP 40 | outputs Host 41 | 42 | # 43 | # Reverse DNS looks up an IP address and finds it's host name. 44 | # 45 | # @param [Values::IP] ip 46 | # 47 | # @yield [host] 48 | # 49 | # @yieldparam [Values::Host] host 50 | # 51 | def process(ip) 52 | unless ip.host 53 | # NOTE: only query IP addresses not associated with a hostname 54 | dns_get_ptr_names(ip.address).each do |host_name| 55 | yield Host.new(host_name.chomp('.')) 56 | end 57 | end 58 | end 59 | 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /spec/value_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/value' 3 | 4 | describe Ronin::Recon::Value do 5 | describe ".value_type" do 6 | subject { described_class } 7 | 8 | it do 9 | expect { 10 | subject.value_type 11 | }.to raise_error(NotImplementedError,"#{described_class}.value_type was not defined") 12 | end 13 | end 14 | 15 | describe "#as_json" do 16 | subject { described_class.new } 17 | 18 | it do 19 | expect { 20 | subject.as_json 21 | }.to raise_error(NotImplementedError,"#{described_class}#as_json was not implemented") 22 | end 23 | end 24 | 25 | describe "#to_s" do 26 | subject { described_class.new } 27 | 28 | it do 29 | expect { 30 | subject.to_s 31 | }.to raise_error(NotImplementedError,"#{described_class}#to_s was not implemented") 32 | end 33 | end 34 | 35 | module TestValue 36 | class TestValue < Ronin::Recon::Value 37 | attr_reader :a, :b 38 | 39 | def initialize 40 | @a = 1 41 | @b = 2 42 | end 43 | 44 | def eql?(other) 45 | self.class == other.class && 46 | @a == other.a && 47 | @b == other.b 48 | end 49 | 50 | def as_json 51 | { 52 | type: :test, 53 | a: @a, 54 | b: @b 55 | } 56 | end 57 | 58 | def to_s 59 | "test" 60 | end 61 | 62 | def self.value_type 63 | :test 64 | end 65 | end 66 | end 67 | 68 | let(:value_class) { TestValue::TestValue } 69 | subject { value_class.new } 70 | 71 | describe "#==" do 72 | it "must call #eql?" do 73 | expect(subject == value_class.new).to be(true) 74 | end 75 | end 76 | 77 | describe "#to_json" do 78 | it "must call #as_json and convert it to JSON" do 79 | expect(subject.to_json).to eq(subject.as_json.to_json) 80 | end 81 | end 82 | 83 | describe "#to_csv" do 84 | it "must return the .value_type with the #to_s value as two CSV columns" do 85 | expect(subject.to_csv).to eq("#{value_class.value_type},#{subject}\n") 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/ronin/recon/builtin/dns/mailservers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative '../../dns_worker' 22 | 23 | module Ronin 24 | module Recon 25 | module DNS 26 | # 27 | # Finds the mailservers for the domain. 28 | # 29 | class Mailservers < DNSWorker 30 | 31 | register 'dns/mailservers' 32 | 33 | summary 'Looks up the mailservers of a domain' 34 | description <<~DESC 35 | Queries the mailservers (MX records) for a domain name. 36 | DESC 37 | 38 | accepts Domain 39 | outputs Mailserver 40 | 41 | # 42 | # Finds the mailservers for the given domain. 43 | # 44 | # @param [Values::Domain] domain 45 | # The given domain value. 46 | # 47 | # @yield [mailserver] 48 | # Each discovered mailserver will be yielded. 49 | # 50 | # @yieldparam [Values::Mailserver] mailserver 51 | # A discovered mailserver. 52 | # 53 | def process(domain) 54 | dns_get_mailservers(domain.name).each do |mailserver| 55 | unless mailserver == '.' 56 | yield Mailserver.new(mailserver.chomp('.')) 57 | end 58 | end 59 | end 60 | 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/ronin/recon/builtin/dns/lookup.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative '../../dns_worker' 22 | 23 | module Ronin 24 | module Recon 25 | module DNS 26 | # 27 | # Looks up the IP address of a host name, domain, nameserver, or 28 | # mailserver. 29 | # 30 | class Lookup < DNSWorker 31 | 32 | register 'dns/lookup' 33 | 34 | summary 'Looks up the IPs of a host-name' 35 | description <<~DESC 36 | Resolves the IP addresses of domains, host names, nameservers, 37 | and mailservers. 38 | DESC 39 | 40 | accepts Domain, Host, Nameserver, Mailserver 41 | outputs IP 42 | 43 | # 44 | # Resolves the IP address for the given host. 45 | # 46 | # @param [Values::Host, Values::Domain, Values::Nameserver, Values::Mailserver] host 47 | # The host name to resolve. 48 | # 49 | # @yield [ip] 50 | # 51 | # @yieldparam [Values::IP] ip 52 | # An IP address for the host. 53 | # 54 | def process(host) 55 | addresses = dns_get_addresses(host.name) 56 | 57 | addresses.each do |address| 58 | yield IP.new(address, host: host.name) 59 | end 60 | end 61 | 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/values/ip_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/values/ip' 3 | 4 | describe Ronin::Recon::Values::IP do 5 | let(:address) { '93.184.216.34' } 6 | let(:host) { 'example.com' } 7 | 8 | subject { described_class.new(address) } 9 | 10 | describe "#initialize" do 11 | it "must set #address" do 12 | expect(subject.address).to eq(address) 13 | end 14 | 15 | context "when initialized with the host: keyword argument" do 16 | subject { described_class.new(address, host: host) } 17 | 18 | it "must also set #host" do 19 | expect(subject.host).to eq(host) 20 | end 21 | end 22 | end 23 | 24 | describe "#eql?" do 25 | context "when given an IP object" do 26 | context "and the other IP object has the same #address" do 27 | let(:other) { described_class.new(address) } 28 | 29 | it "must return true" do 30 | expect(subject.eql?(other)).to be(true) 31 | end 32 | end 33 | 34 | context "but the other IP object has a different #address" do 35 | let(:other) { described_class.new('127.0.0.1') } 36 | 37 | it "must return true" do 38 | expect(subject.eql?(other)).to be(false) 39 | end 40 | end 41 | end 42 | 43 | context "when given a non-IP object" do 44 | let(:other) { Object.new } 45 | 46 | it "must return false" do 47 | expect(subject.eql?(other)).to be(false) 48 | end 49 | end 50 | end 51 | 52 | describe "#hash" do 53 | it "must return the #hash of an Array containing the class and the #address" do 54 | expect(subject.hash).to eq([described_class, address].hash) 55 | end 56 | end 57 | 58 | describe "#to_s" do 59 | it "must return the #address" do 60 | expect(subject.to_s).to eq(address) 61 | end 62 | end 63 | 64 | describe "#as_json" do 65 | it "must return a Hash containing the type: and address: attributes" do 66 | expect(subject.as_json).to eq({type: :ip, address: address}) 67 | end 68 | end 69 | 70 | describe ".value_type" do 71 | subject { described_class } 72 | 73 | it "must return :ip" do 74 | expect(subject.value_type).to be(:ip) 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /man/ronin-recon-completion.1.md: -------------------------------------------------------------------------------- 1 | # ronin-recon-completion 1 "2024-01-01" Ronin Recon "User Manuals" 2 | 3 | ## NAME 4 | 5 | ronin-recon-completion - Manages shell completion rules for `ronin-recon` 6 | 7 | ## SYNOPSIS 8 | 9 | `ronin-recon completion` [*options*] 10 | 11 | ## DESCRIPTION 12 | 13 | The `ronin-recon completion` command can print, install, or uninstall shell 14 | completion rules for the `ronin-recon` command. 15 | 16 | Supports installing completion rules for Bash or Zsh shells. 17 | Completion rules for the Fish shell is currently not supported. 18 | 19 | ### ZSH SUPPORT 20 | 21 | Zsh users will have to add the following lines to their `~/.zshrc` file in 22 | order to enable Zsh's Bash completion compatibility layer: 23 | 24 | autoload -Uz +X compinit && compinit 25 | autoload -Uz +X bashcompinit && bashcompinit 26 | 27 | ## OPTIONS 28 | 29 | `--print` 30 | : Prints the shell completion file. 31 | 32 | `--install` 33 | : Installs the shell completion file. 34 | 35 | `--uninstall` 36 | : Uninstalls the shell completion file. 37 | 38 | `-h`, `--help` 39 | : Prints help information. 40 | 41 | ## ENVIRONMENT 42 | 43 | *PREFIX* 44 | : Specifies the root prefix for the file system. 45 | 46 | *HOME* 47 | : Specifies the home directory of the user. Ronin will search for the 48 | `~/.cache/ronin-recon` cache directory within the home directory. 49 | 50 | *XDG_DATA_HOME* 51 | : Specifies the data directory to use. Defaults to `$HOME/.local/share`. 52 | 53 | ## FILES 54 | 55 | `~/.local/share/bash-completion/completions/` 56 | : The user-local installation directory for Bash completion files. 57 | 58 | `/usr/local/share/bash-completion/completions/` 59 | : The system-wide installation directory for Bash completions files. 60 | 61 | `/usr/local/share/zsh/site-functions/` 62 | : The installation directory for Zsh completion files. 63 | 64 | ## EXAMPLES 65 | 66 | `ronin-recon completion --print` 67 | : Prints the shell completion rules instead of installing them. 68 | 69 | `ronin-recon completion --install` 70 | : Installs the shell completion rules for `ronin-recon`. 71 | 72 | `ronin-recon completion --uninstall` 73 | : Uninstalls the shell completion rules for `ronin-recon`. 74 | 75 | ## AUTHOR 76 | 77 | Postmodern 78 | 79 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | require 'bundler' 5 | rescue LoadError => e 6 | warn e.message 7 | warn "Run `gem install bundler` to install Bundler" 8 | exit(-1) 9 | end 10 | 11 | begin 12 | Bundler.setup(:development) 13 | rescue Bundler::BundlerError => e 14 | warn e.message 15 | warn "Run `bundle install` to install missing gems" 16 | exit e.status_code 17 | end 18 | 19 | require 'rake' 20 | 21 | require 'rubygems/tasks' 22 | Gem::Tasks.new(sign: {checksum: true, pgp: true}) 23 | 24 | require 'rspec/core/rake_task' 25 | RSpec::Core::RakeTask.new 26 | 27 | namespace :spec do 28 | RSpec::Core::RakeTask.new(:network) do |t| 29 | t.rspec_opts = '--tag network' 30 | end 31 | end 32 | 33 | task :test => :spec 34 | task :default => :spec 35 | 36 | require 'yard' 37 | YARD::Rake::YardocTask.new 38 | task :docs => :yard 39 | 40 | require 'kramdown/man/task' 41 | Kramdown::Man::Task.new 42 | 43 | directory 'data/wordlists' 44 | 45 | file 'data/wordlists/subdomains-1000.txt' => 'data/wordlists' do 46 | sh 'wget -O data/wordlists/subdomains-1000.txt https://raw.githubusercontent.com/rbsec/dnscan/master/subdomains-1000.txt' 47 | end 48 | 49 | file 'data/wordlists/subdomains-1000.txt.gz' => 'data/wordlists/subdomains-1000.txt' do 50 | sh 'gzip -f data/wordlists/subdomains-1000.txt' 51 | end 52 | 53 | file 'data/wordlists/raft-small-directories.txt' => 'data/wordlists' do 54 | sh 'wget -O data/wordlists/raft-small-directories.txt https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/Web-Content/raft-small-directories.txt' 55 | end 56 | 57 | file 'data/wordlists/raft-small-directories.txt.gz' => 'data/wordlists/raft-small-directories.txt' do 58 | sh 'gzip -f data/wordlists/raft-small-directories.txt' 59 | end 60 | 61 | desc 'Generate built-in wordlists' 62 | task :wordlists => %w[ 63 | data/wordlists/subdomains-1000.txt.gz 64 | data/wordlists/raft-small-directories.txt.gz 65 | ] 66 | 67 | require 'command_kit/completion/task' 68 | CommandKit::Completion::Task.new( 69 | class_file: 'ronin/recon/cli', 70 | class_name: 'Ronin::Recon::CLI', 71 | input_file: 'data/completions/ronin-recon.yml', 72 | output_file: 'data/completions/ronin-recon' 73 | ) 74 | 75 | task :setup => %w[wordlists man command_kit:completion] 76 | -------------------------------------------------------------------------------- /lib/ronin/recon/cli/commands/completion.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative '../../root' 22 | 23 | require 'ronin/core/cli/completion_command' 24 | 25 | module Ronin 26 | module Recon 27 | class CLI 28 | module Commands 29 | # 30 | # Manages the shell completion rules for `ronin-recon`. 31 | # 32 | # ## Usage 33 | # 34 | # ronin-recon completion [options] 35 | # 36 | # ## Options 37 | # 38 | # --print Prints the shell completion file 39 | # --install Installs the shell completion file 40 | # --uninstall Uninstalls the shell completion file 41 | # -h, --help Print help information 42 | # 43 | # ## Examples 44 | # 45 | # ronin-recon completion --print 46 | # ronin-recon completion --install 47 | # ronin-recon completion --uninstall 48 | # 49 | class Completion < Core::CLI::CompletionCommand 50 | 51 | completion_file File.join(ROOT,'data','completions','ronin-recon') 52 | 53 | man_dir File.join(ROOT,'man') 54 | man_page 'ronin-recon-completion.1' 55 | 56 | description 'Manages the shell completion rules for ronin-recon' 57 | 58 | end 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/builtin/ssl/cert_enum_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/builtin/ssl/cert_enum' 3 | 4 | describe Ronin::Recon::SSL::CertEnum do 5 | describe "#process" do 6 | context "when there are values in cert" do 7 | context "with subject alt names" do 8 | let(:fixtures_dir) { File.expand_path(File.join(__dir__,'..','..','fixtures')) } 9 | let(:cert_path) { File.join(fixtures_dir,'certs','example.crt') } 10 | let(:cert) { Ronin::Support::Crypto::Cert.load_file(cert_path) } 11 | let(:expected) do 12 | %w[ 13 | www.example.org 14 | www.example.org 15 | www.example.com 16 | www.example.edu 17 | www.example.net 18 | example.org 19 | example.com 20 | example.edu 21 | example.net 22 | ] 23 | end 24 | 25 | it "must yield Values::Domain or Values::Host found in subject and subject_alt_names" do 26 | yielded_values = [] 27 | 28 | subject.process(cert) do |value| 29 | yielded_values << value 30 | end 31 | 32 | expect(yielded_values).to_not be_empty 33 | expect(yielded_values).to all(be_kind_of(Ronin::Recon::Values::Domain).or(be_kind_of(Ronin::Recon::Values::Host))) 34 | expect(yielded_values.map(&:name)).to match_array(expected) 35 | end 36 | end 37 | 38 | context "without subject alt names" do 39 | let(:cert) { Ronin::Support::Crypto::Cert.new } 40 | let(:x509_subject) { OpenSSL::X509::Name.new } 41 | 42 | before do 43 | x509_subject.add_entry("CN", "example.com") 44 | x509_subject.add_entry("O", "Example Co.") 45 | x509_subject.add_entry("C", "US") 46 | cert.subject = x509_subject 47 | end 48 | 49 | it "must yield Values::Domain found in subject only" do 50 | yielded_values = [] 51 | 52 | subject.process(cert) do |value| 53 | yielded_values << value 54 | end 55 | 56 | expect(yielded_values.length).to eq(1) 57 | expect(yielded_values[0]).to be_kind_of(Ronin::Recon::Values::Domain) 58 | expect(yielded_values[0].name).to eq("example.com") 59 | end 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /ronin-recon.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'yaml' 4 | 5 | Gem::Specification.new do |gem| 6 | gemspec = YAML.load_file('gemspec.yml') 7 | 8 | gem.name = gemspec.fetch('name') 9 | gem.version = gemspec.fetch('version') do 10 | lib_dir = File.join(File.dirname(__FILE__),'lib') 11 | $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir) 12 | 13 | require 'ronin/recon/version' 14 | Ronin::Recon::VERSION 15 | end 16 | 17 | gem.summary = gemspec['summary'] 18 | gem.description = gemspec['description'] 19 | gem.licenses = Array(gemspec['license']) 20 | gem.authors = Array(gemspec['authors']) 21 | gem.email = gemspec['email'] 22 | gem.homepage = gemspec['homepage'] 23 | gem.metadata = gemspec['metadata'] if gemspec['metadata'] 24 | 25 | glob = ->(patterns) { gem.files & Dir[*patterns] } 26 | 27 | gem.files = `git ls-files`.split($/) 28 | gem.files = glob[gemspec['files']] if gemspec['files'] 29 | gem.files += Array(gemspec['generated_files']) 30 | # exclude test files from the packages gem 31 | gem.files -= glob[gemspec['test_files'] || 'spec/{**/}*'] 32 | 33 | gem.executables = gemspec.fetch('executables') do 34 | glob['bin/*'].map { |path| File.basename(path) } 35 | end 36 | 37 | gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb'] 38 | gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}'] 39 | 40 | gem.require_paths = Array(gemspec.fetch('require_paths') { 41 | %w[ext lib].select { |dir| File.directory?(dir) } 42 | }) 43 | 44 | gem.requirements = gemspec['requirements'] 45 | gem.required_ruby_version = gemspec['required_ruby_version'] 46 | gem.required_rubygems_version = gemspec['required_rubygems_version'] 47 | gem.post_install_message = gemspec['post_install_message'] 48 | 49 | split = ->(string) { string.split(/,\s*/) } 50 | 51 | if gemspec['dependencies'] 52 | gemspec['dependencies'].each do |name,versions| 53 | gem.add_dependency(name,split[versions]) 54 | end 55 | end 56 | 57 | if gemspec['development_dependencies'] 58 | gemspec['development_dependencies'].each do |name,versions| 59 | gem.add_development_dependency(name,split[versions]) 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/output_formats/graphviz_format_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/output_formats/graphviz_format' 3 | require 'ronin/recon/values/host' 4 | require 'ronin/recon/values/domain' 5 | 6 | require 'stringio' 7 | 8 | describe Ronin::Recon::OutputFormats::GraphvizFormat do 9 | it "must inherit from Ronin::Core::OutputFormats::OutputFile" do 10 | expect(described_class).to be < Ronin::Core::OutputFormats::OutputFile 11 | end 12 | 13 | it "must include Ronin::Core::OutputFormats::GraphFormat" do 14 | expect(described_class).to include(Ronin::Recon::OutputFormats::GraphFormat) 15 | end 16 | 17 | module TestGraphvizFormat 18 | class SVG < Ronin::Recon::OutputFormats::GraphvizFormat 19 | 20 | def format 21 | :svg 22 | end 23 | 24 | end 25 | end 26 | 27 | let(:output_format_class) { TestGraphvizFormat::SVG } 28 | let(:io) { StringIO.new } 29 | 30 | subject { output_format_class.new(io) } 31 | 32 | let(:value) { Ronin::Recon::Values::Host.new('www.example.com') } 33 | let(:parent) { Ronin::Recon::Values::Domain.new('example.com') } 34 | 35 | describe "#<<" do 36 | it "must call #<< on #dot_output with the given value" do 37 | expect(subject.dot_output).to receive(:<<).with(value) 38 | 39 | subject << value 40 | end 41 | end 42 | 43 | describe "#[]=" do 44 | it "must call #[]= on #dot_output with the given value and parent value" do 45 | expect(subject.dot_output).to receive(:[]=).with(value,parent) 46 | 47 | subject[value] = parent 48 | end 49 | end 50 | 51 | describe "#close" do 52 | before do 53 | subject << parent 54 | subject << value 55 | 56 | subject[value] = parent 57 | 58 | subject.close 59 | end 60 | 61 | it "must create a .dot output file" do 62 | expect(File.file?(subject.dot_file.path)).to be(true) 63 | expect(File.read(subject.dot_file.path)).to eq( 64 | <<~DOT 65 | digraph { 66 | \t"#{parent}" [label="Domain\\n#{parent}"] 67 | \t"#{value}" [label="Host\\n#{value}"] 68 | \t"#{parent}" -> "#{value}" 69 | } 70 | DOT 71 | ) 72 | end 73 | 74 | it "must also generate the GraphViz output file from the .dot file" do 75 | expect(io.string).to start_with(%{\n}) 76 | expect(io.string).to end_with(%{\n}) 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/ronin/recon/input_file.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative 'value/parser' 22 | 23 | module Ronin 24 | module Recon 25 | # 26 | # Represents an input file of values to recon. 27 | # 28 | class InputFile 29 | 30 | # The path to the input file. 31 | # 32 | # @return [String] 33 | attr_reader :path 34 | 35 | # 36 | # Initializes the input file. 37 | # 38 | # @param [String] path 39 | # The input file path. 40 | # 41 | def initialize(path) 42 | @path = path 43 | end 44 | 45 | # 46 | # Opens the input file. 47 | # 48 | # @param [String] path 49 | # The input file path. 50 | # 51 | # @see #initialize 52 | # 53 | def self.open(path) 54 | new(path) 55 | end 56 | 57 | # 58 | # Enumerates over every value in the input file. 59 | # 60 | # @yield [value] 61 | # If a block is given, it will be passed each parsed value. 62 | # 63 | # @yieldparam [Values::Domain, Values::Host, Values::IP, Values::IPRange, Values::Website] value 64 | # A parsed value from the input file. 65 | # 66 | # @return [Enumerator] 67 | # If no block is given, then an Enumerator will be returned. 68 | # 69 | def each 70 | return enum_for unless block_given? 71 | 72 | File.open(@path) do |file| 73 | file.each_line(chomp: true) do |line| 74 | yield Value.parse(line) 75 | end 76 | end 77 | end 78 | 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/ronin/recon/message/value.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | module Ronin 22 | module Recon 23 | module Message 24 | # 25 | # Represents either an input or output value. 26 | # 27 | # @api private 28 | # 29 | class Value 30 | 31 | # The value object. 32 | # 33 | # @return [Value] 34 | attr_reader :value 35 | 36 | # The associated parent value. 37 | # 38 | # @return [Value, nil] 39 | attr_reader :parent 40 | 41 | # The depth of the value in relation to the input value. 42 | # 43 | # @return [Integer] 44 | attr_reader :depth 45 | 46 | # The ID of the recon worker which produced the value. 47 | # 48 | # @return [Worker, nil] 49 | attr_reader :worker 50 | 51 | # 52 | # Initializes the recon value. 53 | # 54 | # @param [Value] value 55 | # The value object. 56 | # 57 | # @param [Value, nil] parent 58 | # The associated parent value. 59 | # 60 | # @param [Integer] depth 61 | # The depth of the value object. 62 | # 63 | # @param [Worker, nil] worker 64 | # The worker object, if the value object is an output value produced 65 | # by the worker object. 66 | # 67 | def initialize(value, parent: nil, depth: 0, worker: nil) 68 | @value = value 69 | @parent = parent 70 | @depth = depth 71 | @worker = worker 72 | 73 | freeze 74 | end 75 | 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/ronin/recon/builtin/web/email_addresses.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative '../../web_worker' 22 | require_relative 'spider' 23 | 24 | require 'ronin/support/text/patterns' 25 | 26 | module Ronin 27 | module Recon 28 | module Web 29 | # 30 | # A recon worker that returns email addresses found on website. 31 | # 32 | class EmailAddresses < WebWorker 33 | 34 | register 'web/email_addresses' 35 | 36 | summary 'Extracts emails from a website' 37 | 38 | description <<~DESC 39 | Extracts all emails from a website. 40 | DESC 41 | 42 | accepts URL 43 | outputs EmailAddress 44 | intensity :passive 45 | 46 | # 47 | # Extract email addresses found in the pages body. 48 | # 49 | # @param [Values::URL] url 50 | # The URL of the page to extract email addresses from. 51 | # 52 | # @yield [email] 53 | # Each email address found on the page will be yielded. 54 | # 55 | # @yieldparam [Values::EmailAddress] email 56 | # Email address found on the page. 57 | # 58 | def process(url) 59 | if (body = url.body) 60 | if body.encoding == Encoding::ASCII_8BIT 61 | # forcibly convert and scrub binary data into UTF-8 data 62 | body = body.dup 63 | body.force_encoding(Encoding::UTF_8) 64 | body.scrub! 65 | end 66 | 67 | body.scan(Support::Text::Patterns::EMAIL_ADDRESS) do |email| 68 | yield EmailAddress.new(email) 69 | end 70 | end 71 | end 72 | 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/ronin/recon/cli/commands/workers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative '../command' 22 | require_relative '../../registry' 23 | 24 | module Ronin 25 | module Recon 26 | class CLI 27 | module Commands 28 | # 29 | # Lists the available recon workers. 30 | # 31 | # ## Usage 32 | # 33 | # ronin-recon help [options] 34 | # 35 | # ## Options 36 | # 37 | # -h, --help Print help information 38 | # 39 | # ## Arguments 40 | # 41 | # [COMMAND] Command name to lookup 42 | # 43 | class Workers < Command 44 | 45 | usage '[options] [DIR]' 46 | 47 | argument :dir, required: false, 48 | desc: 'The optional recon worker directory to list' 49 | 50 | description 'Lists the available recon workers' 51 | 52 | man_page 'ronin-workers-list.1' 53 | 54 | # 55 | # Runs the `ronin-recon workers` command. 56 | # 57 | # @param [String, nil] dir 58 | # The optional recon worker directory to list. 59 | # 60 | def run(dir=nil) 61 | files = if dir 62 | dir = "#{dir}/" unless dir.end_with?('/') 63 | 64 | Ronin::Recon.list_files.select do |file| 65 | file.start_with?(dir) 66 | end 67 | else 68 | Ronin::Recon.list_files 69 | end 70 | 71 | files.each do |file| 72 | puts " #{file}" 73 | end 74 | end 75 | 76 | end 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/ronin/recon/builtin/net/service_id.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative '../../worker' 22 | 23 | module Ronin 24 | module Recon 25 | module Net 26 | # 27 | # A recon worker that identifies services on open ports. 28 | # 29 | class ServiceID < Worker 30 | 31 | register 'net/service_id' 32 | 33 | summary 'Identifies services running on open ports' 34 | 35 | description <<~DESC 36 | Identifies various services that are running on open ports. 37 | DESC 38 | 39 | accepts OpenPort 40 | outputs Nameserver, Mailserver, Website 41 | intensity :passive 42 | 43 | # 44 | # Identifies the service running on an open port. 45 | # 46 | # @param [Values::OpenPort] open_port 47 | # The given open port. 48 | # 49 | # @yield [new_value] 50 | # The identified service will be yielded. 51 | # 52 | # @yieldparam [Values::Nameserver, Values::Mailserver, Values::Website] new_value 53 | # A discovered nameserver, mailserver, or website. 54 | # 55 | def process(open_port) 56 | case open_port.service 57 | when 'domain' 58 | yield Nameserver.new(open_port.host) 59 | when 'smtp' 60 | yield Mailserver.new(open_port.host) 61 | when 'http' 62 | if open_port.ssl? 63 | yield Website.https(open_port.host,open_port.number) 64 | else 65 | yield Website.http(open_port.host,open_port.number) 66 | end 67 | when 'https', 'https-alt' 68 | yield Website.https(open_port.host,open_port.number) 69 | end 70 | end 71 | 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/ronin/recon/value_status.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | module Ronin 22 | module Recon 23 | # 24 | # Represents the status of every value across all queues and workers. 25 | # 26 | class ValueStatus 27 | 28 | attr_reader :values 29 | 30 | def initialize 31 | @values = {} 32 | end 33 | 34 | # 35 | # Records that a value was enqueued for the given worker class. 36 | # 37 | # @param [Value] value 38 | # 39 | # @param [Class] worker_class 40 | # 41 | def value_enqueued(worker_class,value) 42 | (@values[value] ||= {})[worker_class] = :enqueued 43 | end 44 | 45 | # 46 | # Records that a worker has dequeued the value and started processing it. 47 | # 48 | # @param [Value] value 49 | # 50 | # @param [Class] worker_class 51 | # 52 | def job_started(worker_class,value) 53 | (@values[value] ||= {})[worker_class] = :working 54 | end 55 | 56 | # 57 | # Records that a worker has completed processing the value. 58 | # 59 | # @param [Value] value 60 | # 61 | # @param [Class] worker_class 62 | # 63 | def job_completed(worker_class,value) 64 | if (worker_statuses = @values[value]) 65 | worker_statuses.delete(worker_class) 66 | 67 | if worker_statuses.empty? 68 | @values.delete(value) 69 | end 70 | end 71 | end 72 | 73 | alias job_failed job_completed 74 | 75 | # 76 | # Determines if there are no more values within the queue or being 77 | # processed by any of the workers. 78 | # 79 | # @return [Boolean] 80 | # 81 | def empty? 82 | @values.empty? 83 | end 84 | 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /spec/fixtures/certs/example.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIHbjCCBlagAwIBAgIQB1vO8waJyK3fE+Ua9K/hhzANBgkqhkiG9w0BAQsFADBZ 3 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMTMwMQYDVQQDEypE 4 | aWdpQ2VydCBHbG9iYWwgRzIgVExTIFJTQSBTSEEyNTYgMjAyMCBDQTEwHhcNMjQw 5 | MTMwMDAwMDAwWhcNMjUwMzAxMjM1OTU5WjCBljELMAkGA1UEBhMCVVMxEzARBgNV 6 | BAgTCkNhbGlmb3JuaWExFDASBgNVBAcTC0xvcyBBbmdlbGVzMUIwQAYDVQQKDDlJ 7 | bnRlcm5ldMKgQ29ycG9yYXRpb27CoGZvcsKgQXNzaWduZWTCoE5hbWVzwqBhbmTC 8 | oE51bWJlcnMxGDAWBgNVBAMTD3d3dy5leGFtcGxlLm9yZzCCASIwDQYJKoZIhvcN 9 | AQEBBQADggEPADCCAQoCggEBAIaFD7sO+cpf2fXgCjIsM9mqDgcpqC8IrXi9wga/ 10 | 9y0rpqcnPVOmTMNLsid3INbBVEm4CNr5cKlh9rJJnWlX2vttJDRyLkfwBD+dsVvi 11 | vGYxWTLmqX6/1LDUZPVrynv/cltemtg/1Aay88jcj2ZaRoRmqBgVeacIzgU8+zmJ 12 | 7236TnFSe7fkoKSclsBhPaQKcE3Djs1uszJs8sdECQTdoFX9I6UgeLKFXtg7rRf/ 13 | hcW5dI0zubhXbrW8aWXbCzySVZn0c7RkJMpnTCiZzNxnPXnHFpwr5quqqjVyN/aB 14 | KkjoP04Zmr+eRqoyk/+lslq0sS8eaYSSHbC5ja/yMWyVhvMCAwEAAaOCA/IwggPu 15 | MB8GA1UdIwQYMBaAFHSFgMBmx9833s+9KTeqAx2+7c0XMB0GA1UdDgQWBBRM/tAS 16 | TS4hz2v68vK4TEkCHTGRijCBgQYDVR0RBHoweIIPd3d3LmV4YW1wbGUub3Jnggtl 17 | eGFtcGxlLm5ldIILZXhhbXBsZS5lZHWCC2V4YW1wbGUuY29tggtleGFtcGxlLm9y 18 | Z4IPd3d3LmV4YW1wbGUuY29tgg93d3cuZXhhbXBsZS5lZHWCD3d3dy5leGFtcGxl 19 | Lm5ldDA+BgNVHSAENzA1MDMGBmeBDAECAjApMCcGCCsGAQUFBwIBFhtodHRwOi8v 20 | d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQG 21 | CCsGAQUFBwMBBggrBgEFBQcDAjCBnwYDVR0fBIGXMIGUMEigRqBEhkJodHRwOi8v 22 | Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxHMlRMU1JTQVNIQTI1NjIw 23 | MjBDQTEtMS5jcmwwSKBGoESGQmh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdp 24 | Q2VydEdsb2JhbEcyVExTUlNBU0hBMjU2MjAyMENBMS0xLmNybDCBhwYIKwYBBQUH 25 | AQEEezB5MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wUQYI 26 | KwYBBQUHMAKGRWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEds 27 | b2JhbEcyVExTUlNBU0hBMjU2MjAyMENBMS0xLmNydDAMBgNVHRMBAf8EAjAAMIIB 28 | fQYKKwYBBAHWeQIEAgSCAW0EggFpAWcAdABOdaMnXJoQwzhbbNTfP1LrHfDgjhuN 29 | acCx+mSxYpo53wAAAY1b0vxkAAAEAwBFMEMCH0BRCgxPbBBVxhcWZ26a8JCe83P1 30 | JZ6wmv56GsVcyMACIDgpMbEo5HJITTRPnoyT4mG8cLrWjEvhchUdEcWUuk1TAHYA 31 | fVkeEuF4KnscYWd8Xv340IdcFKBOlZ65Ay/ZDowuebgAAAGNW9L8MAAABAMARzBF 32 | AiBdv5Z3pZFbfgoM3tGpCTM3ZxBMQsxBRSdTS6d8d2NAcwIhALLoCT9mTMN9OyFz 33 | IBV5MkXVLyuTf2OAzAOa7d8x2H6XAHcA5tIxY0B3jMEQQQbXcbnOwdJA9paEhvu6 34 | hzId/R43jlAAAAGNW9L8XwAABAMASDBGAiEA4Koh/VizdQU1tjZ2E2VGgWSXXkwn 35 | QmiYhmAeKcVLHeACIQD7JIGFsdGol7kss2pe4lYrCgPVc+iGZkuqnj26hqhr0TAN 36 | BgkqhkiG9w0BAQsFAAOCAQEABOFuAj4N4yNG9OOWNQWTNSICC4Rd4nOG1HRP/Bsn 37 | rz7KrcPORtb6D+Jx+Q0amhO31QhIvVBYs14gY4Ypyj7MzHgm4VmPXcqLvEkxb2G9 38 | Qv9hYuEiNSQmm1fr5QAN/0AzbEbCM3cImLJ69kP5bUjfv/76KB57is8tYf9sh5ik 39 | LGKauxCM/zRIcGa3bXLDafk5S2g5Vr2hs230d/NGW1wZrE+zdGuMxfGJzJP+DAFv 40 | iBfcQnFg4+1zMEKcqS87oniOyG+60RMM0MdejBD7AS43m9us96Gsun/4kufLQUTI 41 | FfnzxLutUV++3seshgefQOy5C/ayi8y1VTNmujPCxPCi6Q== 42 | -----END CERTIFICATE----- 43 | -------------------------------------------------------------------------------- /lib/ronin/recon/builtin/ssl/cert_grab.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative '../../worker' 22 | 23 | require 'io/endpoint' 24 | 25 | module Ronin 26 | module Recon 27 | module SSL 28 | # 29 | # A recon worker that grabs the SSL/TLS certificate from open ports that 30 | # use SSL/TLS. 31 | # 32 | class CertGrab < Worker 33 | 34 | register 'ssl/cert_grab' 35 | 36 | summary 'Fetches the SSL/TLS certificate from an open port' 37 | 38 | description <<~DESC 39 | Grabs and decodes the X509 SSL/TLS peer certificate from an open port 40 | which supports SSL/TLS. 41 | DESC 42 | 43 | accepts OpenPort 44 | outputs Cert 45 | 46 | # 47 | # Grabs the TLS certificate from the open port, if it supports SSL/TLS. 48 | # 49 | # @param [Values::OpenPort] open_port 50 | # The open port value to check. 51 | # 52 | # @yield [cert] 53 | # If the open port supports SSL/TLS, then a certificate value will be 54 | # yielded. 55 | # 56 | # @yieldparam [Values::Cert] cert 57 | # The grabbed certificate value. 58 | # 59 | def process(open_port) 60 | if open_port.ssl? 61 | context = OpenSSL::SSL::SSLContext.new 62 | 63 | context.verify_mode = OpenSSL::SSL::VERIFY_NONE 64 | 65 | address = open_port.address 66 | port = open_port.number 67 | endpoint = IO::Endpoint.ssl(address,port, ssl_context: context) 68 | 69 | begin 70 | endpoint.connect do |socket| 71 | peer_cert = socket.peer_cert 72 | 73 | yield Cert.new(peer_cert) 74 | end 75 | rescue OpenSSL::SSL::SSLError 76 | # abort if we cannot successfully establish a SSL/TLS connection 77 | end 78 | end 79 | end 80 | 81 | end 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /lib/ronin/recon/builtin/net/port_scan.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative '../../worker' 22 | 23 | require 'ronin/nmap' 24 | 25 | module Ronin 26 | module Recon 27 | module Net 28 | # 29 | # A recon worker that performs a nmap port scan. 30 | # 31 | class PortScan < Worker 32 | 33 | register 'net/port_scan' 34 | 35 | summary 'Scans an IP for open ports' 36 | 37 | description <<~DESC 38 | Performs a nmap port scan of the given IP and retruns the open 39 | ports and their services. 40 | DESC 41 | 42 | accepts IP 43 | outputs OpenPort 44 | concurrency 1 # prevents overloading the network interface 45 | 46 | param :ports, String, desc: 'Optional port list to scan' 47 | 48 | # 49 | # Performs an nmap port scan on the given IP value. 50 | # 51 | # @param [Values::IP] ip 52 | # The given IP to scan. 53 | # 54 | # @yield [new_value] 55 | # The discovered open ports will be yielded. 56 | # 57 | # @yieldparam [Values::OpenPort] new_value 58 | # A discovered open port. 59 | # 60 | def process(ip) 61 | xml = Nmap.scan(ip.address, verbose: true, 62 | service_scan: true, 63 | ports: params[:ports]) 64 | 65 | address = ip.address 66 | host = ip.host || xml.host.to_s 67 | 68 | xml.host.open_ports.each do |open_port| 69 | number = open_port.number 70 | protocol = open_port.protocol 71 | service = open_port.service 72 | 73 | yield OpenPort.new( 74 | address,number, host: host, 75 | protocol: protocol, 76 | service: service && service.name, 77 | ssl: service && service.ssl? 78 | ) 79 | end 80 | end 81 | 82 | end 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/ronin/recon/values/domain.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative 'host' 22 | require_relative 'ip' 23 | require_relative 'website' 24 | require_relative 'url' 25 | require_relative 'email_address' 26 | 27 | module Ronin 28 | module Recon 29 | module Values 30 | # 31 | # Represents a domain name (ex: `example.com`). 32 | # 33 | class Domain < Host 34 | 35 | # 36 | # Case equality method used for fuzzy matching. 37 | # 38 | # @param [Value] other 39 | # The other value to compare. 40 | # 41 | # @return [Boolean] 42 | # Imdicates whether the other value is either a {Domain} and has the 43 | # same domain name, or a {Host}, {IP}, {Website}, {URL} with the same 44 | # domain name. 45 | # 46 | def ===(other) 47 | case other 48 | when Domain 49 | @name == other.name 50 | when Host 51 | other.name.end_with?(".#{@name}") 52 | when IP, Website, URL 53 | if (other_host = other.host) 54 | other_host == @name || other_host.end_with?(".#{@name}") 55 | end 56 | when EmailAddress 57 | other.address.end_with?("@#{@name}") || 58 | other.address.end_with?(".#{@name}") 59 | else 60 | false 61 | end 62 | end 63 | 64 | # 65 | # Coerces the domain value into JSON. 66 | # 67 | # @return [Hash{Symbol => Object}] 68 | # The Ruby Hash that will be converted into JSON. 69 | # 70 | def as_json 71 | {type: :domain, name: @name} 72 | end 73 | 74 | # 75 | # Returns the type or kind of recon value. 76 | # 77 | # @return [:domain] 78 | # 79 | # @note 80 | # This is used internally to map a recon value class to a printable 81 | # type. 82 | # 83 | # @api private 84 | # 85 | def self.value_type 86 | :domain 87 | end 88 | 89 | end 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /spec/cli/commands/test_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/cli/commands/test' 3 | 4 | require 'fixtures/test_worker' 5 | 6 | describe Ronin::Recon::CLI::Commands::Test do 7 | describe "#run" do 8 | let(:name) { 'test_worker' } 9 | let(:value) { 'example.com' } 10 | 11 | context "when given a worker name" do 12 | it "must load the worker and run it with the given value" do 13 | expect { 14 | subject.run(name,value) 15 | }.to output( 16 | <<~OUTPUT 17 | test1.#{value} 18 | test2.#{value} 19 | OUTPUT 20 | ).to_stdout 21 | end 22 | end 23 | 24 | context "when the '--file FILE' option instead of a worker name argument" do 25 | let(:fixtures_dir) { File.join(__dir__,'..','..','fixtures') } 26 | let(:path) { File.join(fixtures_dir,'test_worker.rb') } 27 | 28 | before { subject.option_parser.parse(['--file', path]) } 29 | 30 | it "must load the worker from the file and run it with the given value" do 31 | expect { 32 | subject.run(value) 33 | }.to output( 34 | <<~OUTPUT 35 | test1.#{value} 36 | test2.#{value} 37 | OUTPUT 38 | ).to_stdout 39 | end 40 | end 41 | 42 | context "when a '--param NAME=VALUE' option is given" do 43 | let(:prefix) { 'foo' } 44 | 45 | before { subject.option_parser.parse(['--param', "prefix=#{prefix}"]) } 46 | 47 | it "must parse the params and initialize the worker with them" do 48 | expect { 49 | subject.run(name,value) 50 | }.to output( 51 | <<~OUTPUT 52 | #{prefix}1.#{value} 53 | #{prefix}2.#{value} 54 | OUTPUT 55 | ).to_stdout 56 | end 57 | end 58 | 59 | context "when given an unknown input value" do 60 | let(:value) { 'bad' } 61 | 62 | it "must print an error message and exit with -1" do 63 | expect { 64 | subject.run(name,value) 65 | }.to output( 66 | <<~ERROR 67 | #{subject.command_name}: unrecognized recon value: #{value.inspect} 68 | #{subject.command_name}: must be a domain value 69 | ERROR 70 | ).to_stderr.and(raise_error(SystemExit) { |error| 71 | expect(error.status).to eq(-1) 72 | }) 73 | end 74 | end 75 | 76 | context "when given an unacceptable input value" do 77 | let(:value) { '192.168.1.1' } 78 | 79 | it "must print an error message and exit with 1" do 80 | expect { 81 | subject.run(name,value) 82 | }.to output( 83 | <<~ERROR 84 | #{subject.command_name}: worker #{name.inspect} does not accept IP address values 85 | #{subject.command_name}: must be a domain value 86 | ERROR 87 | ).to_stderr.and(raise_error(SystemExit) { |error| 88 | expect(error.status).to eq(1) 89 | }) 90 | end 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /lib/ronin/recon/output_formats/dir.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative '../values' 22 | 23 | require 'ronin/core/output_formats/output_dir' 24 | require 'set' 25 | 26 | module Ronin 27 | module Recon 28 | module OutputFormats 29 | # 30 | # Represents an output directory. 31 | # 32 | class Dir < Core::OutputFormats::OutputDir 33 | 34 | # The opened filenames and files within the output directory. 35 | # 36 | # @return [Hash{Class => File}] 37 | attr_reader :files 38 | 39 | # Mapping of value classes to file names. 40 | VALUE_FILE_NAMES = { 41 | Values::Domain => 'domains.txt', 42 | Values::Mailserver => 'mailservers.txt', 43 | Values::Nameserver => 'nameservers.txt', 44 | Values::Host => 'hosts.txt', 45 | Values::Wildcard => 'wildcards.txt', 46 | Values::IP => 'ips.txt', 47 | Values::IPRange => 'ip_ranges.txt', 48 | Values::OpenPort => 'open_ports.txt', 49 | Values::Cert => 'certs.txt', 50 | Values::EmailAddress => 'email_addresses.txt', 51 | Values::URL => 'urls.txt', 52 | Values::Website => 'websites.txt' 53 | } 54 | 55 | # 56 | # Initializes the list output format. 57 | # 58 | # @param [String] path 59 | # The output file path. 60 | # 61 | def initialize(path) 62 | super(path) 63 | 64 | @files = VALUE_FILE_NAMES.transform_values do |file_name| 65 | File.open(File.join(@path,file_name),'w') 66 | end 67 | end 68 | 69 | # 70 | # Writes a new value to it's specific file. 71 | # 72 | # @param [Value] value 73 | # The value to write. 74 | # 75 | def <<(value) 76 | file = @files.fetch(value.class) do 77 | raise(NotImplementedError,"unsupported value class: #{value.inspect}") 78 | end 79 | 80 | file.puts(value) 81 | file.flush 82 | end 83 | 84 | # 85 | # Closes the output files. 86 | # 87 | def close 88 | @files.each_value(&:close) 89 | end 90 | 91 | end 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /lib/ronin/recon/value.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require 'json' 22 | require 'csv' 23 | 24 | module Ronin 25 | module Recon 26 | # 27 | # Base class for all {Values} classes. 28 | # 29 | class Value 30 | 31 | # 32 | # Returns the type or kind of recon value. 33 | # 34 | # @return [Symbol] 35 | # 36 | # @note 37 | # This is used internally to map a recon value class to a printable 38 | # type. 39 | # 40 | # @abstract 41 | # 42 | # @api private 43 | # 44 | def self.value_type 45 | raise(NotImplementedError,"#{self}.value_type was not defined") 46 | end 47 | 48 | # 49 | # Compares the value to another value. 50 | # 51 | # @param [Object] other 52 | # The other value. 53 | # 54 | # @return [Boolean] 55 | # Indicates that the value matches the other value. 56 | # 57 | def ==(other) 58 | eql?(other) 59 | end 60 | 61 | # 62 | # Coerces the value into JSON. 63 | # 64 | # @return [Hash{Symbol => Object}] 65 | # The Ruby Hash that will be converted into JSON. 66 | # 67 | # @abstract 68 | # 69 | def as_json 70 | raise(NotImplementedError,"#{self.class}#as_json was not implemented") 71 | end 72 | 73 | # 74 | # Converts the value to a String. 75 | # 76 | # @return [String] 77 | # The string value of the value. 78 | # 79 | # @abstract 80 | # 81 | def to_s 82 | raise(NotImplementedError,"#{self.class}#to_s was not implemented") 83 | end 84 | 85 | # 86 | # Converts the value into JSON. 87 | # 88 | # @param [Array] args 89 | # Additional arguments for `Hash#to_json`. 90 | # 91 | # @return [String] 92 | # The raw JSON string. 93 | # 94 | def to_json(*args) 95 | as_json.to_json(*args) 96 | end 97 | 98 | # 99 | # Converts the value to a CSV row. 100 | # 101 | # @return [String] 102 | # The CSV row. 103 | # 104 | def to_csv 105 | CSV.generate_line([self.class.value_type,to_s]) 106 | end 107 | 108 | end 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /lib/ronin/recon/builtin/web/spider.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative '../../web_worker' 22 | 23 | require 'ronin/web/spider' 24 | 25 | module Ronin 26 | module Recon 27 | module Web 28 | # 29 | # A recon worker that spiders a website. 30 | # 31 | class Spider < WebWorker 32 | 33 | register 'web/spider' 34 | 35 | summary 'Spiders a website and finds every URL' 36 | 37 | description <<~DESC 38 | Spiders a website and finds every URL. 39 | 40 | * Visits every `a`, `iframe`, `frame`, `link`, and `script` URL. 41 | * Extracts paths from JavaScript. 42 | * Extracts URLs from JavaScript. 43 | DESC 44 | 45 | accepts Website 46 | outputs URL 47 | 48 | # 49 | # Spiders a website and yields every spidered URL. 50 | # 51 | # @param [Values::Website] website 52 | # The website value to start spidering. 53 | # 54 | # @yield [url] 55 | # Every spidered URL will be yielded. 56 | # 57 | # @yieldparam [Values::URL] url 58 | # A URL visited by the spider. 59 | # 60 | def process(website) 61 | base_uri = website.to_uri 62 | 63 | Ronin::Web::Spider.site(base_uri) do |agent| 64 | agent.every_page do |page| 65 | if VALID_STATUS_CODES.include?(page.code) 66 | yield URL.new(page.url, status: page.code, 67 | headers: page.headers, 68 | body: page.body) 69 | end 70 | end 71 | 72 | agent.every_javascript_url_string do |url,page| 73 | uri = URI.parse(url) 74 | 75 | case uri 76 | when URI::HTTP 77 | agent.enqueue(uri) 78 | end 79 | rescue URI::InvalidURIError 80 | # ignore invalid URIs 81 | end 82 | 83 | agent.every_javascript_path_string do |path,page| 84 | if (uri = page.to_absolute(path)) 85 | case uri 86 | when URI::HTTP 87 | agent.enqueue(uri) 88 | end 89 | end 90 | end 91 | end 92 | end 93 | 94 | end 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /spec/graph_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/graph' 3 | require 'ronin/recon/values/ip' 4 | 5 | describe Ronin::Recon::Graph do 6 | subject { described_class.new } 7 | let(:value1) { Ronin::Recon::Values::IP.new('192.168.0.1') } 8 | let(:value2) { Ronin::Recon::Values::IP.new('192.168.1.1') } 9 | 10 | describe "#initialize" do 11 | it "must initialize #nodes and #edges" do 12 | expect(subject.nodes).to eq(Set.new) 13 | expect(subject.edges).to eq({}) 14 | end 15 | end 16 | 17 | describe "#add_node" do 18 | context "when value node was successfully added" do 19 | it "must return true" do 20 | expect(subject.add_node(value1)).to be(true) 21 | end 22 | 23 | it "must contain node" do 24 | subject.add_node(value1) 25 | 26 | expect(subject.nodes.size).to eq(1) 27 | end 28 | end 29 | 30 | context "when value node was already added" do 31 | it "must return false" do 32 | subject.add_node(value1) 33 | 34 | expect(subject.add_node(value1)).to be(false) 35 | end 36 | end 37 | end 38 | 39 | describe "#add_edge" do 40 | context "when node value was successfully added" do 41 | it "must return true" do 42 | expect(subject.add_edge(value1,value2)).to be(true) 43 | end 44 | end 45 | 46 | context "when node value was already added" do 47 | it "must return false" do 48 | subject.add_edge(value1,value2) 49 | 50 | expect(subject.add_edge(value1,value2)).to be(false) 51 | end 52 | end 53 | end 54 | 55 | describe "#include?" do 56 | context "when value exists in the graph" do 57 | it "must return true" do 58 | subject.add_node(value1) 59 | 60 | expect(subject.include?(value1)).to be(true) 61 | end 62 | end 63 | 64 | context "when value does not exists in the graph" do 65 | it "must return false" do 66 | expect(subject.include?(value1)).to be(false) 67 | end 68 | end 69 | end 70 | 71 | describe "#[]" do 72 | context "when node value exists in the graph" do 73 | context "and has no edges" do 74 | it "returns empty Set" do 75 | subject.add_node(value1) 76 | 77 | expect(subject[value1]).to be(nil) 78 | end 79 | end 80 | 81 | context "and has edges to other nodes" do 82 | it "returns no-empty Set" do 83 | subject.add_edge(value1,value2) 84 | 85 | expect(subject[value1]).to eq(Set.new([value2])) 86 | end 87 | end 88 | end 89 | 90 | context "when node value does not exists in the graph" do 91 | it "must return nil" do 92 | expect(subject[value1]).to be(nil) 93 | end 94 | end 95 | end 96 | 97 | describe "#empty?" do 98 | context "when the graph does not have any nodes" do 99 | it "must return true" do 100 | expect(subject.empty?).to be(true) 101 | end 102 | end 103 | 104 | context "when the graph does have nodes" do 105 | before { subject.add_node(value1) } 106 | 107 | it "must return false" do 108 | expect(subject.empty?).to be(false) 109 | end 110 | end 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /spec/builtin/web/email_addresses_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/builtin/web/email_addresses' 3 | require 'ronin/recon/values/url' 4 | 5 | describe Ronin::Recon::Web::EmailAddresses do 6 | describe "#process" do 7 | context "when URL #body exists" do 8 | context "and email is present" do 9 | let(:email_address1) { 'example1@example.com' } 10 | let(:email_address2) { 'example2@example.com' } 11 | let(:body) do 12 | <<~HTML 13 | 14 | 15 |

#{email_address1}

16 |

#{email_address2}

17 | 18 | 19 | HTML 20 | end 21 | let(:url) { Ronin::Recon::Values::URL.new("example.com", body: body) } 22 | let(:expected_emails) do 23 | [ 24 | Ronin::Recon::Values::EmailAddress.new(email_address1), 25 | Ronin::Recon::Values::EmailAddress.new(email_address2) 26 | ] 27 | end 28 | 29 | it "must return array of EmailAddresses" do 30 | yielded_values = [] 31 | 32 | subject.process(url) do |value| 33 | yielded_values << value 34 | end 35 | 36 | expect(yielded_values).to eq(expected_emails) 37 | end 38 | 39 | context "but the URL #body is binary data" do 40 | let(:body) { super().encode(Encoding::ASCII_8BIT) } 41 | 42 | it "must convert the #body into a UTF-8 String" do 43 | yielded_values = [] 44 | 45 | subject.process(url) do |value| 46 | yielded_values << value 47 | end 48 | 49 | expect(yielded_values).to eq(expected_emails) 50 | end 51 | 52 | context "and it contains invalid UTF-8 byte-sequences" do 53 | let(:body) do 54 | "\xfe\xff#{email_address1}\xfe\xff#{email_address2}\xfe\xff".b 55 | end 56 | 57 | it "must ignore any invalid UTF-8 byte sequences and only yield email address values" do 58 | yielded_values = [] 59 | 60 | subject.process(url) do |value| 61 | yielded_values << value 62 | end 63 | 64 | expect(yielded_values).to eq(expected_emails) 65 | end 66 | end 67 | end 68 | end 69 | 70 | context "and email is not present" do 71 | let(:body) do 72 | <<~HTML 73 | 74 | 75 |

without email

76 | 77 | 78 | HTML 79 | end 80 | let(:url) { Ronin::Recon::Values::URL.new("example.com", body: body) } 81 | 82 | it "must return empty array" do 83 | yielded_values = [] 84 | 85 | subject.process(url) do |value| 86 | yielded_values << value 87 | end 88 | 89 | expect(yielded_values).to be_empty 90 | end 91 | end 92 | end 93 | 94 | context "when url body is nil" do 95 | let(:url) { Ronin::Recon::Values::URL.new("example.com") } 96 | 97 | it "must return nil" do 98 | expect { |b| 99 | subject.process(url,&b) 100 | }.to_not yield_control 101 | end 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /man/ronin-recon-run.1.md: -------------------------------------------------------------------------------- 1 | # ronin-recon-run 1 "2023-05-01" Ronin Recon "User Manuals" 2 | 3 | ## NAME 4 | 5 | ronin-recon-run - Runs the recon engine with one or more initial values 6 | 7 | ## SYNOPSIS 8 | 9 | `ronin-recon run` [*options*] {*IP* \| *IP-range* \| *DOMAIN* \| *HOST* \| *WILDCARD* \| *WEBSITE*} ... 10 | 11 | ## DESCRIPTION 12 | 13 | Runs the recon engine with one or more initial values. 14 | 15 | ## ARGUMENTS 16 | 17 | *IP* 18 | : An IP address to recon (ex: `192.168.1.1`). 19 | 20 | *IP-range* 21 | : A CIDR IP range to recon (ex: `192.168.1.0/24`). 22 | 23 | *DOMAIN* 24 | : A top-level domain name to recon (ex: `example.com`). 25 | 26 | *HOST* 27 | : A sub-domain to recon (ex: `www.example.com`). 28 | 29 | *WILDCARD* 30 | : A wildcard host name (ex: `*.example.com`). 31 | 32 | *WEBSITE* 33 | : A website base URL to recon (ex: `https://example.com`). 34 | 35 | ## OPTIONS 36 | 37 | `-D`, `--debug` 38 | : Enables debugging output. 39 | 40 | `-C`, `--config-file` *FILE* 41 | : Loads the `ronin-recon` configuration file. If not specified, then 42 | `~/.config/ronin-recon/config.yml` will be loaded instead. 43 | 44 | `-w`, `--worker` *WORKER* 45 | : Explicitly uses the specified worker instead of the default set of workers. 46 | 47 | `-e`, `--enable` *WORKER* 48 | : Enables the worker in addition to the default set of workers. 49 | 50 | `-d`, `--disable` *WORKER* 51 | : Disables the worker from the default set of workers. 52 | 53 | `--worker-file` *FILE* 54 | : Loads a custom worker from the specified `.rb` file. 55 | 56 | `-p`, `--param` *WORKER*`.`*NAME*`=`*VALUE* 57 | : Sets a param value for the given worker. 58 | 59 | `-c`, `--concurrency` *WORKER*`=`*NUM* 60 | : Overrides the concurrency for the given worker. 61 | 62 | `--max-depth` *NUM* 63 | : The maximum recon depth. Defaults to depth of `10` if the option is not 64 | specified. 65 | 66 | `-o`, `--output` *FILE* 67 | : The output file to write results to. 68 | 69 | `-F`, `--output-format` `txt`\|`list`\|`csv`\|`json`\|`ndjson`\|`dot`\|`svg`\|`png`\|`pdf` 70 | : The output format. If not specified, the output format will be inferred from 71 | the `--output` *FILE* extension. 72 | 73 | `--import` 74 | : Imports each newly discovered value into the Ronin database. 75 | 76 | `-I`, `--ignore` *VALUE* 77 | : The value to ignore from the result. 78 | 79 | `-h`, `--help` 80 | : Print help information 81 | 82 | ## EXAMPLES 83 | 84 | Run the recon engine on a single domain: 85 | 86 | $ ronin-recon run example.com 87 | 88 | Run the recon engine on a single host-name: 89 | 90 | $ ronin-recon run www.example.com 91 | 92 | Run the recon engine on a single IP address: 93 | 94 | $ ronin-recon run 1.1.1.1 95 | 96 | Run the recon engine on an IP range: 97 | 98 | $ ronin-recon run 1.1.1.1/24 99 | 100 | Run the recon engine on multiple targets: 101 | 102 | $ ronin-recon run example1.com example2.com secret.foo.example1.com \ 103 | secret.bar.example2.com 1.1.1.1/24 104 | 105 | Run the recon engine and ignore specific hosts, IPs, URLs, etc.: 106 | 107 | $ ronin-recon run --ignore staging.example.com example.com 108 | 109 | ## AUTHOR 110 | 111 | Postmodern 112 | 113 | ## SEE ALSO 114 | 115 | [ronin-recon-workers](ronin-recon-workers.1.md) [ronin-recon-worker](ronin-recon-worker.1.md) [ronin-recon-test](ronin-recon-test.1.md) 116 | -------------------------------------------------------------------------------- /lib/ronin/recon/values/email_address.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative '../value' 22 | 23 | module Ronin 24 | module Recon 25 | module Values 26 | # 27 | # Represents an email address. 28 | # 29 | # @api public 30 | # 31 | class EmailAddress < Value 32 | 33 | # The email address. 34 | # 35 | # @return [String] 36 | attr_reader :address 37 | 38 | # 39 | # Initializes the email address object. 40 | # 41 | # @param [String] address 42 | # The email address. 43 | # 44 | def initialize(address) 45 | @address = address 46 | end 47 | 48 | # 49 | # Compares the value to another value. 50 | # 51 | # @param [Value] other 52 | # The other value to compare. 53 | # 54 | # @return [Boolean] 55 | # Indicates whether the other value is a kind of {EmailAddress} and 56 | # has the same address. 57 | # 58 | def eql?(other) 59 | other.kind_of?(self.class) && @address == other.address 60 | end 61 | 62 | alias === eql? 63 | 64 | # 65 | # The "hash" value for the email address. 66 | # 67 | # @return [Integer] 68 | # The hash value derived from the class and the {#address}. 69 | # 70 | def hash 71 | [self.class, @address].hash 72 | end 73 | 74 | # 75 | # Converts the email address object to a String. 76 | # 77 | # @return [String] 78 | # The email address. 79 | # 80 | def to_s 81 | @address.to_s 82 | end 83 | 84 | alias to_str to_s 85 | 86 | # 87 | # Coerces the email address value into JSON. 88 | # 89 | # @return [Hash{Symbol => Object}] 90 | # The Ruby Hash that will be converted into JSON. 91 | # 92 | def as_json 93 | {type: :email_address, address: @address} 94 | end 95 | 96 | # 97 | # Returns the type or kind of recon value. 98 | # 99 | # @return [:email_address] 100 | # 101 | # @note 102 | # This is used internally to map a recon value class to a printable 103 | # type. 104 | # 105 | # @api private 106 | # 107 | def self.value_type 108 | :email_address 109 | end 110 | 111 | end 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /lib/ronin/recon/output_formats/graphviz_format.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative 'graph_format' 22 | require_relative 'dot' 23 | 24 | require 'ronin/core/output_formats/output_file' 25 | require 'tempfile' 26 | 27 | module Ronin 28 | module Recon 29 | module OutputFormats 30 | # 31 | # Represents a GraphViz output format. 32 | # 33 | class GraphvizFormat < Core::OutputFormats::OutputFile 34 | 35 | include GraphFormat 36 | 37 | # The `.dot` output file. 38 | # 39 | # @return [Tempfile] 40 | attr_reader :dot_file 41 | 42 | # The DOT output format. 43 | # 44 | # @return [Dot] 45 | attr_reader :dot_output 46 | 47 | # 48 | # Initializes the GraphViz output format. 49 | # 50 | # @param [IO] io 51 | # The output stream to write to. 52 | # 53 | def initialize(io) 54 | super(io) 55 | 56 | @dot_file = Tempfile.new(['ronin-recon',"#{format}"]) 57 | @dot_output = Dot.new(@dot_file) 58 | end 59 | 60 | # 61 | # The desired GraphViz output format. 62 | # 63 | # @return [Symbol] 64 | # The output format name. 65 | # 66 | # @abstract 67 | # 68 | def format 69 | raise(NotImplementedError,"#{self.class}#format was not defined!") 70 | end 71 | 72 | # 73 | # Writes a value to the GraphViz output stream as a node declaration. 74 | # 75 | # @param [Value] value 76 | # The value object to write. 77 | # 78 | def <<(value) 79 | @dot_output << value 80 | end 81 | 82 | # 83 | # Appends a value and it's parent value to the GraphViz output stream. 84 | # 85 | # @param [Value] value 86 | # The value to append. 87 | # 88 | # @param [Value] parent 89 | # The parent value of the given value. 90 | # 91 | # @return [self] 92 | # 93 | def []=(value,parent) 94 | @dot_output[value] = parent 95 | return self 96 | end 97 | 98 | # 99 | # Closes and generates the GraphViz output file. 100 | # 101 | def close 102 | @dot_output.close 103 | 104 | IO.popen(['dot',"-T#{format}",@dot_file.path]) do |dot_io| 105 | # relay the `dot` output to the output stream. 106 | @io.write(dot_io.readpartial(4096)) until dot_io.eof? 107 | end 108 | 109 | super 110 | end 111 | 112 | end 113 | end 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /lib/ronin/recon/values/ip.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative '../value' 22 | 23 | module Ronin 24 | module Recon 25 | module Values 26 | # 27 | # Represents an IP address. 28 | # 29 | # @api public 30 | # 31 | class IP < Value 32 | 33 | # The IP address. 34 | # 35 | # @return [String] 36 | attr_reader :address 37 | 38 | # The optional parent host name. 39 | # 40 | # @return [String, nil] 41 | attr_reader :host 42 | 43 | # 44 | # Initializes the IP object. 45 | # 46 | # @param [String] address 47 | # The IP address. 48 | # 49 | # @param [String, nil] host 50 | # The host name for the IP address. 51 | # 52 | def initialize(address, host: nil) 53 | @address = address 54 | @host = host 55 | end 56 | 57 | # 58 | # Compares the value to another value. 59 | # 60 | # @param [Value] other 61 | # The other value to compare. 62 | # 63 | # @return [Boolean] 64 | # Indicates whether the other value is a kind of {IP} and has the 65 | # same address. 66 | # 67 | def eql?(other) 68 | other.kind_of?(self.class) && @address == other.address 69 | end 70 | 71 | alias === eql? 72 | 73 | # 74 | # The "hash" value for the IP address. 75 | # 76 | # @return [Integer] 77 | # The hash value derived from the class and the {#address}. 78 | # 79 | def hash 80 | [self.class, @address].hash 81 | end 82 | 83 | # 84 | # Converts the IP object to a String. 85 | # 86 | # @return [String] 87 | # The IP address. 88 | # 89 | def to_s 90 | @address.to_s 91 | end 92 | 93 | alias to_str to_s 94 | 95 | # 96 | # Coerces the IP value into JSON. 97 | # 98 | # @return [Hash{Symbol => Object}] 99 | # The Ruby Hash that will be converted into JSON. 100 | # 101 | def as_json 102 | {type: :ip, address: @address} 103 | end 104 | 105 | # 106 | # Returns the type or kind of recon value. 107 | # 108 | # @return [:ip] 109 | # 110 | # @note 111 | # This is used internally to map a recon value class to a printable 112 | # type. 113 | # 114 | # @api private 115 | # 116 | def self.value_type 117 | :ip 118 | end 119 | 120 | end 121 | end 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /spec/builtin/net/service_id_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/builtin/net/service_id' 3 | 4 | RSpec.describe Ronin::Recon::Net::ServiceID do 5 | describe '#process' do 6 | context 'when nameserver is running on given port' do 7 | let(:port) { Ronin::Recon::Values::OpenPort.new("93.184.216.34", 53, service: 'domain') } 8 | 9 | it 'must yield Values::Nameserver' do 10 | yielded_value = nil 11 | 12 | subject.process(port) do |value| 13 | yielded_value = value 14 | end 15 | 16 | expect(yielded_value).to be_kind_of(Ronin::Recon::Values::Nameserver) 17 | expect(yielded_value.name).to eq(port.host) 18 | end 19 | end 20 | 21 | context 'when mailserver is running on given port' do 22 | let(:port) { Ronin::Recon::Values::OpenPort.new("93.184.216.34", 25, service: 'smtp') } 23 | 24 | it 'must yield Values::Mailserver' do 25 | yielded_value = nil 26 | 27 | subject.process(port) do |value| 28 | yielded_value = value 29 | end 30 | 31 | expect(yielded_value).to be_kind_of(Ronin::Recon::Values::Mailserver) 32 | expect(yielded_value.name).to eq(port.host) 33 | end 34 | end 35 | 36 | context 'when http website is running on given port' do 37 | let(:port) { Ronin::Recon::Values::OpenPort.new("93.184.216.34", 443, service: 'http') } 38 | 39 | it 'must yield Values::Website with http schema' do 40 | yielded_value = nil 41 | 42 | subject.process(port) do |value| 43 | yielded_value = value 44 | end 45 | 46 | expect(yielded_value).to be_kind_of(Ronin::Recon::Values::Website) 47 | expect(yielded_value.scheme).to eq('http') 48 | expect(yielded_value.host).to eq(port.host) 49 | end 50 | 51 | context 'but it also has ssl enabled' do 52 | let(:port) { Ronin::Recon::Values::OpenPort.new("93.184.216.34", 80, service: 'http', ssl: true) } 53 | 54 | it 'must yield Values::Website with https schema' do 55 | yielded_value = nil 56 | 57 | subject.process(port) do |value| 58 | yielded_value = value 59 | end 60 | 61 | expect(yielded_value).to be_kind_of(Ronin::Recon::Values::Website) 62 | expect(yielded_value.scheme).to eq('https') 63 | expect(yielded_value.host).to eq(port.host) 64 | end 65 | end 66 | end 67 | 68 | context 'when https website is running on given port' do 69 | let(:port) { Ronin::Recon::Values::OpenPort.new("93.184.216.34", 443, service: 'https', ssl: true) } 70 | 71 | it 'must yield Values::Website' do 72 | yielded_value = nil 73 | 74 | subject.process(port) do |value| 75 | yielded_value = value 76 | end 77 | 78 | expect(yielded_value).to be_kind_of(Ronin::Recon::Values::Website) 79 | expect(yielded_value.scheme).to eq('https') 80 | expect(yielded_value.host).to eq(port.host) 81 | end 82 | end 83 | 84 | context 'when https-alt service is running on given port' do 85 | let(:port) { Ronin::Recon::Values::OpenPort.new("93.184.216.34", 443, service: 'https-alt', ssl: true) } 86 | 87 | it 'must yield Values::Website' do 88 | yielded_value = nil 89 | 90 | subject.process(port) do |value| 91 | yielded_value = value 92 | end 93 | 94 | expect(yielded_value).to be_kind_of(Ronin::Recon::Values::Website) 95 | expect(yielded_value.scheme).to eq('https') 96 | expect(yielded_value.host).to eq(port.host) 97 | end 98 | end 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /lib/ronin/recon/cli/worker_command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative 'command' 22 | require_relative '../registry' 23 | 24 | module Ronin 25 | module Recon 26 | class CLI 27 | # 28 | # Base class for commands which load an individual worker. 29 | # 30 | class WorkerCommand < Command 31 | 32 | usage '[options] {--file FILE | NAME}' 33 | 34 | option :file, short: '-f', 35 | value: { 36 | type: String, 37 | usage: 'FILE' 38 | }, 39 | desc: 'The recon worker file to load' 40 | 41 | argument :name, required: false, 42 | desc: 'The recon worker to load' 43 | 44 | # The loaded worker class. 45 | # 46 | # @return [Class, nil] 47 | attr_reader :worker_class 48 | 49 | # 50 | # Loads the recon worker class. 51 | # 52 | # @param [String, nil] name 53 | # The optional recon worker name to load. 54 | # 55 | # @return [Class] 56 | # The loaded recon worker class. 57 | # 58 | def run(name=nil) 59 | if name then load_worker(name) 60 | elsif options[:file] then load_worker_from(options[:file]) 61 | else 62 | print_error("must specify --file or a NAME") 63 | exit(-1) 64 | end 65 | end 66 | 67 | # 68 | # Loads the recon worker class and sets {#worker_class}. 69 | # 70 | # @param [String] id 71 | # The recon worker name to load. 72 | # 73 | def load_worker(id) 74 | @worker_class = Recon.load_class(id) 75 | rescue Recon::ClassNotFound => error 76 | print_error(error.message) 77 | exit(1) 78 | rescue => error 79 | print_exception(error) 80 | print_error("an unhandled exception occurred while loading recon worker #{id}") 81 | exit(-1) 82 | end 83 | 84 | # 85 | # Loads the recon worker class from the given file and sets 86 | # {#worker_class}. 87 | # 88 | # @param [String] file 89 | # The file to load the recon worker class from. 90 | # 91 | def load_worker_from(file) 92 | @worker_class = Recon.load_class_from_file(file) 93 | rescue Recon::ClassNotFound => error 94 | print_error(error.message) 95 | exit(1) 96 | rescue => error 97 | print_exception(error) 98 | print_error("an unhandled exception occurred while loading recon worker from file #{file}") 99 | exit(-1) 100 | end 101 | 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /spec/output_formats_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/output_formats' 3 | 4 | describe Ronin::Recon::OutputFormats do 5 | describe "formats" do 6 | subject { described_class.formats } 7 | 8 | describe ":txt" do 9 | it "must equal Ronin::Core::OutputFormats::TXT" do 10 | expect(subject[:txt]).to be(Ronin::Core::OutputFormats::TXT) 11 | end 12 | end 13 | 14 | describe ":csv" do 15 | it "must equal Ronin::Core::OutputFormats::CSV" do 16 | expect(subject[:csv]).to be(Ronin::Core::OutputFormats::CSV) 17 | end 18 | end 19 | 20 | describe ":json" do 21 | it "must equal Ronin::Core::OutputFormats::JSON" do 22 | expect(subject[:json]).to be(Ronin::Core::OutputFormats::JSON) 23 | end 24 | end 25 | 26 | describe ":ndjson" do 27 | it "must equal Ronin::Core::OutputFormats::NDJSON" do 28 | expect(subject[:ndjson]).to be(Ronin::Core::OutputFormats::NDJSON) 29 | end 30 | end 31 | 32 | describe ":dir" do 33 | it "must equal Ronin::Recon::OutputFormats::Dir" do 34 | expect(subject[:dir]).to be(Ronin::Recon::OutputFormats::Dir) 35 | end 36 | end 37 | 38 | describe ":dot" do 39 | it "must equal Ronin::Recon::OutputFormats::Dot" do 40 | expect(subject[:dot]).to be(Ronin::Recon::OutputFormats::Dot) 41 | end 42 | end 43 | 44 | describe ":svg" do 45 | it "must equal Ronin::Recon::OutputFormats::SVG" do 46 | expect(subject[:svg]).to be(Ronin::Recon::OutputFormats::SVG) 47 | end 48 | end 49 | 50 | describe ":png" do 51 | it "must equal Ronin::Recon::OutputFormats::PNG" do 52 | expect(subject[:png]).to be(Ronin::Recon::OutputFormats::PNG) 53 | end 54 | end 55 | 56 | describe ":pdf" do 57 | it "must equal Ronin::Recon::OutputFormats::PDF" do 58 | expect(subject[:pdf]).to be(Ronin::Recon::OutputFormats::PDF) 59 | end 60 | end 61 | end 62 | 63 | describe "file_exts" do 64 | subject { described_class.file_exts } 65 | 66 | describe "'.txt'" do 67 | it "must equal Ronin::Core::OutputFormats::TXT" do 68 | expect(subject['.txt']).to be(Ronin::Core::OutputFormats::TXT) 69 | end 70 | end 71 | 72 | describe "'.csv'" do 73 | it "must equal Ronin::Core::OutputFormats::CSV" do 74 | expect(subject['.csv']).to be(Ronin::Core::OutputFormats::CSV) 75 | end 76 | end 77 | 78 | describe "'.json'" do 79 | it "must equal Ronin::Core::OutputFormats::JSON" do 80 | expect(subject['.json']).to be(Ronin::Core::OutputFormats::JSON) 81 | end 82 | end 83 | 84 | describe "'.ndjson'" do 85 | it "must equal Ronin::Core::OutputFormats::NDJSON" do 86 | expect(subject['.ndjson']).to be(Ronin::Core::OutputFormats::NDJSON) 87 | end 88 | end 89 | 90 | describe "''" do 91 | it "must equal Ronin::Recon::OutputFormats::Dir" do 92 | expect(subject['']).to be(Ronin::Recon::OutputFormats::Dir) 93 | end 94 | end 95 | 96 | describe "'.svg'" do 97 | it "must equal Ronin::Recon::OutputFormats::SVG" do 98 | expect(subject['.svg']).to be(Ronin::Recon::OutputFormats::SVG) 99 | end 100 | end 101 | 102 | describe "'.png'" do 103 | it "must equal Ronin::Recon::OutputFormats::PNG" do 104 | expect(subject['.png']).to be(Ronin::Recon::OutputFormats::PNG) 105 | end 106 | end 107 | 108 | describe "'.pdf'" do 109 | it "must equal Ronin::Recon::OutputFormats::PDF" do 110 | expect(subject['.pdf']).to be(Ronin::Recon::OutputFormats::PDF) 111 | end 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /lib/ronin/recon/builtin/dns/subdomain_enum.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative '../../dns_worker' 22 | require_relative '../../root' 23 | 24 | require 'wordlist' 25 | require 'async/queue' 26 | 27 | module Ronin 28 | module Recon 29 | module DNS 30 | # 31 | # Finds common subdomains of a domain using a wordlist of commong 32 | # subdomains. 33 | # 34 | class SubdomainEnum < DNSWorker 35 | 36 | # The path to the default common subdomains wordlist. 37 | DEFAULT_WORDLIST = File.join(WORDLISTS_DIR, 'subdomains-1000.txt.gz') 38 | 39 | register 'dns/subdomain_enum' 40 | 41 | summary 'Enumerates subdomains of a domain' 42 | description <<~DESC 43 | Attempts to find the subdomains of a given domain by looking up 44 | host names of the domain using a wordlist of common subdomains. 45 | DESC 46 | 47 | accepts Domain, Wildcard 48 | outputs Host 49 | 50 | param :concurrency, Integer, default: 10, 51 | desc: 'Sets the number of async tasks' 52 | 53 | param :wordlist, String, desc: 'Optional subdomain wordlist to use' 54 | 55 | # 56 | # Bruteforce resolves the subdomains of a given domain. 57 | # 58 | # @param [Values::Domain, Values::Wildcard] domain 59 | # The domain or wildcard host name to bruteforce. 60 | # 61 | # @yield [host] 62 | # Subdomains that have DNS records will be yielded. 63 | # 64 | # @yieldparam [Values::Host] host 65 | # A valid subdomain of the domain. 66 | # 67 | def process(domain) 68 | wordlist = Wordlist.open(params[:wordlist] || DEFAULT_WORDLIST) 69 | queue = Async::LimitedQueue.new(params[:concurrency]) 70 | 71 | Async do |task| 72 | task.async do 73 | case domain 74 | when Domain 75 | wordlist.each do |name| 76 | queue << "#{name}.#{domain.name}" 77 | end 78 | when Wildcard 79 | wordlist.each do |name| 80 | queue << domain.template.sub('*',name) 81 | end 82 | end 83 | 84 | # send stop messages for each sub-task 85 | params[:concurrency].times do 86 | queue << nil 87 | end 88 | end 89 | 90 | # spawn the sub-tasks 91 | params[:concurrency].times do 92 | task.async do 93 | while (subdomain = queue.dequeue) 94 | if dns_get_address(subdomain) 95 | yield Host.new(subdomain) 96 | end 97 | end 98 | end 99 | end 100 | end 101 | end 102 | 103 | end 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /lib/ronin/recon/builtin/api/crt_sh.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative '../../worker' 22 | 23 | require 'ronin/support/text/patterns/network' 24 | require 'async/http/internet/instance' 25 | require 'set' 26 | 27 | module Ronin 28 | module Recon 29 | module API 30 | # 31 | # A recon worker that queries https://crt.sh and returns host from each 32 | # domains certificate 33 | # 34 | class CrtSh < Worker 35 | 36 | register 'api/crt_sh' 37 | 38 | summary 'Queries https://crt.sh' 39 | 40 | description <<~DESC 41 | Queries https://crt.sh and returns the host names from each valid 42 | certificate for the domain. 43 | DESC 44 | 45 | accepts Domain 46 | outputs Host 47 | intensity :passive 48 | concurrency 1 49 | 50 | # The HTTP client for `https://crt.sh`. 51 | # 52 | # @return [Async::HTTP::Client] 53 | # 54 | # @api private 55 | attr_reader :client 56 | 57 | # 58 | # Initializes the `api/crt_sh` worker. 59 | # 60 | # @param [Hash{Symbol => Object}] kwargs 61 | # Additional keyword arguments. 62 | # 63 | # @api private 64 | # 65 | def initialize(**kwargs) 66 | super(**kwargs) 67 | 68 | @client = Async::HTTP::Client.new( 69 | Async::HTTP::Endpoint.for('https','crt.sh') 70 | ) 71 | end 72 | 73 | # Regular expression to verify valid host names. 74 | # 75 | # @api private 76 | HOST_NAME_REGEX = /\A#{Support::Text::Patterns::HOST_NAME}\z/ 77 | 78 | # 79 | # Returns host from each domains certificate. 80 | # 81 | # @param [Values::Domain] domain 82 | # The domain value to check. 83 | # 84 | # @yield [host] 85 | # If the domain has certificates, then a host value will be 86 | # yielded. 87 | # 88 | # @yieldparam [Values::Host] host 89 | # The host from certificate. 90 | # 91 | def process(domain) 92 | path = "/?dNSName=#{domain}&exclude=expired&output=json" 93 | response = @client.get(path) 94 | body = begin 95 | response.read 96 | ensure 97 | response.close 98 | end 99 | 100 | certs = JSON.parse(body, symbolize_names: true) 101 | hostnames = Set.new 102 | 103 | certs.each do |cert| 104 | common_name = cert[:common_name] 105 | 106 | if common_name && 107 | common_name =~ HOST_NAME_REGEX && 108 | hostnames.add?(common_name) 109 | yield Host.new(common_name) 110 | end 111 | end 112 | end 113 | 114 | end 115 | end 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /lib/ronin/recon/cli/commands/worker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require_relative '../worker_command' 22 | require_relative '../printing' 23 | 24 | require 'ronin/core/cli/printing/metadata' 25 | require 'ronin/core/cli/printing/params' 26 | 27 | require 'command_kit/printing/lists' 28 | 29 | module Ronin 30 | module Recon 31 | class CLI 32 | module Commands 33 | # 34 | # Prints information about a recon worker. 35 | # 36 | # ## Usage 37 | # 38 | # ronin-recon worker [options] {--file FILE | NAME} 39 | # 40 | # ## Options 41 | # 42 | # -f, --file FILE The recon worker file to load 43 | # -v, --verbose Enables verbose output 44 | # -h, --help Print help information 45 | # 46 | # ## Arguments 47 | # 48 | # [NAME] The recon worker to load 49 | # 50 | class Worker < WorkerCommand 51 | 52 | include Printing 53 | include Core::CLI::Printing::Metadata 54 | include Core::CLI::Printing::Params 55 | include CommandKit::Printing::Lists 56 | 57 | usage '[options] {--file FILE | NAME}' 58 | 59 | description 'Prints information about a recon worker' 60 | 61 | man_page 'ronin-recon-worker.1' 62 | 63 | # 64 | # Runs the `ronin-recon worker` command. 65 | # 66 | # @param [String, nil] name 67 | # The optional worker name to load and print metadata for. 68 | # 69 | def run(name=nil) 70 | super(name) 71 | 72 | print_worker(worker_class) 73 | end 74 | 75 | # 76 | # Prints the metadata for a recon worker class. 77 | # 78 | # @param [Class] worker 79 | # The worker class to print metadata for. 80 | # 81 | def print_worker(worker) 82 | puts "[ #{worker.id} ]" 83 | puts 84 | 85 | indent do 86 | puts "Summary: #{worker.summary}" if worker.summary 87 | 88 | print_authors(worker) 89 | print_description(worker) 90 | 91 | puts 'Accepts:' 92 | puts 93 | indent do 94 | print_list(worker.accepts.map(&method(:value_class_name))) 95 | end 96 | puts 97 | 98 | puts 'Outputs:' 99 | puts 100 | indent do 101 | print_list(worker.outputs.map(&method(:value_class_name))) 102 | end 103 | puts 104 | 105 | puts "Intensity: #{worker.intensity}" 106 | 107 | print_params(worker) 108 | end 109 | end 110 | 111 | end 112 | end 113 | end 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /spec/builtin/api/crt_sh_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ronin/recon/builtin/api/crt_sh' 3 | 4 | require 'webmock/rspec' 5 | 6 | describe Ronin::Recon::API::CrtSh do 7 | it "must set concurrency to 1" do 8 | expect(described_class.concurrency).to eq(1) 9 | end 10 | 11 | describe "#initialize" do 12 | it "must initialize #client for 'https://crt.sh'" do 13 | expect(subject.client).to be_kind_of(Async::HTTP::Client) 14 | # BUG: https://github.com/bblimke/webmock/issues/1060 15 | # expect(subject.client.endpoint).to be_kind_of(Async::HTTP::Endpoint) 16 | # expect(subject.client.endpoint.scheme).to eq('https') 17 | # expect(subject.client.endpoint.hostname).to eq('crt.sh') 18 | # expect(subject.client.endpoint.port).to eq(443) 19 | end 20 | end 21 | 22 | describe "#process" do 23 | let(:domain) { Ronin::Recon::Values::Domain.new("example.com") } 24 | let(:json) do 25 | '[{"issuer_ca_id":185752,"issuer_name":"C=US, O=DigiCert Inc, CN=DigiCert Global G2 TLS RSA SHA256 2020 CA1","common_name":"www.example.org","name_value":"example.com\nwww.example.com","id":12337892544,"entry_timestamp":"2024-03-10T20:13:50.549","not_before":"2024-01-30T00:00:00","not_after":"2025-03-01T23:59:59","serial_number":"075bcef30689c8addf13e51af4afe187","result_count":2},{"issuer_ca_id":185752,"issuer_name":"C=US, O=DigiCert Inc, CN=DigiCert Global G2 TLS RSA SHA256 2020 CA1","common_name":"www.example.org","name_value":"example.com\nwww.example.com","id":11920382870,"entry_timestamp":"2024-01-30T19:22:50.288","not_before":"2024-01-30T00:00:00","not_after":"2025-03-01T23:59:59","serial_number":"075bcef30689c8addf13e51af4afe187","result_count":2},{"issuer_ca_id":-1,"issuer_name":"Issuer Not Found","common_name":"example.com","name_value":"example.com","id":8506962125,"entry_timestamp":null,"not_before":"2023-01-27T01:21:18","not_after":"2033-01-24T01:21:18","serial_number":"1ac1e693c87d36563a92ca145c87bbc26fd49f4c","result_count":1}]' 26 | end 27 | 28 | before do 29 | stub_request(:get, "https://crt.sh/?dNSName=#{domain.name}&exclude=expired&output=json").to_return(status: 200, body: json) 30 | end 31 | 32 | it "must request query the crt.sh API and yield Host values for each unique host name" do 33 | yielded_values = [] 34 | 35 | Async do 36 | subject.process(domain) do |value| 37 | yielded_values << value 38 | end 39 | end 40 | 41 | expect(yielded_values).to eq([ 42 | Ronin::Recon::Values::Host.new('www.example.org'), 43 | Ronin::Recon::Values::Host.new('example.com') 44 | ]) 45 | end 46 | end 47 | 48 | describe "#process", :network do 49 | before(:all) { WebMock.allow_net_connect! } 50 | 51 | context "for domain with certificates" do 52 | let(:domain) { Ronin::Recon::Values::Domain.new("example.com") } 53 | let(:expected) do 54 | %w[ 55 | www.example.org 56 | example.com 57 | ] 58 | end 59 | 60 | it "must yield Values::Host for each certificate" do 61 | yielded_values = [] 62 | 63 | Async do 64 | subject.process(domain) do |host| 65 | yielded_values << host 66 | end 67 | end 68 | 69 | expect(yielded_values).to_not be_empty 70 | expect(yielded_values).to all(be_kind_of(Ronin::Recon::Values::Host)) 71 | expect(yielded_values.map(&:name)).to eq(expected) 72 | end 73 | end 74 | 75 | context "for domain with no certificates" do 76 | let(:domain) { Ronin::Recon::Values::Domain.new("invalid.com") } 77 | 78 | it "must not yield anything" do 79 | yielded_values = [] 80 | 81 | Async do 82 | subject.process(domain) do |host| 83 | yielded_values << host 84 | end 85 | end 86 | 87 | expect(yielded_values).to be_empty 88 | end 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lib/ronin/recon/graph.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # ronin-recon - A micro-framework and tool for performing reconnaissance. 4 | # 5 | # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com) 6 | # 7 | # ronin-recon 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-recon 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-recon. If not, see . 19 | # 20 | 21 | require 'set' 22 | 23 | module Ronin 24 | module Recon 25 | # 26 | # Represents a directed graph of discovered values and their parent values. 27 | # 28 | class Graph 29 | 30 | # The nodes in the graph. 31 | # 32 | # @return [Set] 33 | attr_reader :nodes 34 | 35 | # The edges between nodes in the graph. 36 | # 37 | # @return [Hash{Value => Set}] 38 | attr_reader :edges 39 | 40 | # 41 | # Initializes the graph. 42 | # 43 | # @api private 44 | # 45 | def initialize 46 | @nodes = Set.new 47 | @edges = {} 48 | end 49 | 50 | # 51 | # Adds a value to the graph, if it already hasn't been added. 52 | # 53 | # @param [Value] new_value 54 | # The new value node to add. 55 | # 56 | # @return [Boolean] 57 | # Indicates whether the value node was successfully added to the graph, 58 | # or if the value node was already added to the graph. 59 | # 60 | # @api private 61 | # 62 | def add_node(new_value) 63 | !@nodes.add?(new_value).nil? 64 | end 65 | 66 | # 67 | # Adds a value to the graph, if it already hasn't been added. 68 | # 69 | # @param [Value] new_value 70 | # The new value node to add. 71 | # 72 | # @param [Value, nil] parent_value 73 | # The parent value node of the new value node. 74 | # 75 | # @return [Boolean] 76 | # Indicates whether the value node was successfully added to the graph, 77 | # or if the value node was already added to the graph. 78 | # 79 | # @api private 80 | # 81 | def add_edge(new_value,parent_value) 82 | if parent_value 83 | node_parents = (@edges[new_value] ||= Set.new) 84 | 85 | return !node_parents.add?(parent_value).nil? 86 | end 87 | end 88 | 89 | # 90 | # Determines if the value is in the graph. 91 | # 92 | # @param [Value] value 93 | # The value node. 94 | # 95 | # @return [Boolean] 96 | # Indicates whether the value exists in the graph or not. 97 | # 98 | def include?(value) 99 | @nodes.include?(value) 100 | end 101 | 102 | # 103 | # Fetches the parent value nodes for the value. 104 | # 105 | # @param [Value] value 106 | # The value node to lookup. 107 | # 108 | # @return [Set, nil] 109 | # The set of parent value nodes or `nil` if the value does not exist in 110 | # the graph. 111 | # 112 | def [](value) 113 | @edges[value] 114 | end 115 | 116 | # 117 | # Determines if the graph is empty. 118 | # 119 | # @return [Boolean] 120 | # 121 | def empty? 122 | @nodes.empty? 123 | end 124 | 125 | end 126 | end 127 | end 128 | --------------------------------------------------------------------------------