├── .rspec ├── lib ├── searchpass │ ├── version.rb │ └── cli.rb └── searchpass.rb ├── exe └── searchpass ├── spec ├── spec_helper.rb └── searchpass_spec.rb ├── Gemfile ├── .travis.yml ├── .gitignore ├── bin ├── setup └── console ├── README.md ├── LICENSE.txt ├── searchpass.gemspec └── Rakefile /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /lib/searchpass/version.rb: -------------------------------------------------------------------------------- 1 | module Searchpass 2 | VERSION = "1.1.0" 3 | end 4 | -------------------------------------------------------------------------------- /exe/searchpass: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "searchpass" 4 | 5 | Searchpass::CLI.run!(ARGV) 6 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | require 'searchpass' 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in searchpass.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | rvm: 4 | - 2.2.3 5 | before_install: gem install bundler -v 1.12.5 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /lib/searchpass.rb: -------------------------------------------------------------------------------- 1 | require "optparse" 2 | require "ostruct" 3 | require "json" 4 | require "searchpass/version" 5 | require "searchpass/cli" 6 | 7 | module Searchpass 8 | # Your code goes here... 9 | end 10 | -------------------------------------------------------------------------------- /spec/searchpass_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Searchpass do 4 | it 'has a version number' do 5 | expect(Searchpass::VERSION).not_to be nil 6 | end 7 | 8 | it 'does something useful' do 9 | expect(false).to eq(true) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "searchpass" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Searchpass 2 | 3 | Searchpass is a simple tool for offline searching of default credentials for network devices, web applications and more. 4 | 5 | ## Usage 6 | 7 | ``` 8 | Usage: searchpass [options] term [term2] ... [termN] 9 | 10 | Specific options: 11 | -c, --case Perform case sensitive matching 12 | -e, --exact Perform exact matching 13 | -h, --help Show this message 14 | --version Show version 15 | ``` 16 | 17 | ## Installation 18 | 19 | gem install searchpass 20 | 21 | ## Credit 22 | 23 | The list of default passwords has been obtained from [cirt.net](https://cirt.net/passwords). Thanks to cirt.net and everyone who has contributed! 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Michael Henriksen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /searchpass.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'searchpass/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "searchpass" 8 | spec.version = Searchpass::VERSION 9 | spec.authors = ["Michael Henriksen"] 10 | spec.email = ["michenriksen@neomailbox.ch"] 11 | 12 | spec.summary = %q{Find default passwords for devices and systems.} 13 | spec.description = %q{A simple tool for offline searching of default credentials for network devices, web applications and more.} 14 | spec.homepage = "https://github.com/michenriksen/searchpass" 15 | spec.license = "MIT" 16 | 17 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 18 | spec.bindir = "exe" 19 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 20 | spec.require_paths = ["lib"] 21 | 22 | spec.add_development_dependency "bundler", "~> 1.12" 23 | spec.add_development_dependency "rake", "~> 10.0" 24 | spec.add_development_dependency "rspec", "~> 3.0" 25 | spec.add_development_dependency "httparty", "~> 0.14.0" 26 | spec.add_development_dependency "nokogiri", "~> 1.6", ">= 1.6.8" 27 | end 28 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | require "httparty" 4 | require "nokogiri" 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | task :default => :spec 9 | 10 | task :scrape do 11 | DEFAULT_PASSWORDS_BASE_URI = "https://cirt.net/passwords" 12 | print "[*] Requesting #{DEFAULT_PASSWORDS_BASE_URI}... " 13 | response = HTTParty.get(DEFAULT_PASSWORDS_BASE_URI) 14 | if response.code != 200 15 | puts "Failed" 16 | puts "[-] #{DEFAULT_PASSWORDS_BASE_URI} returned: #{response.code} #{response.message}" 17 | exit(1) 18 | end 19 | puts "Done" 20 | 21 | doc = Nokogiri::HTML(response.body) 22 | vendors = doc.css("table td a") 23 | credentials = [] 24 | puts "[*] Found #{vendors.count} vendors on page." 25 | 26 | vendors.each do |vendor| 27 | url, name = "#{DEFAULT_PASSWORDS_BASE_URI}#{vendor.attr("href")}", vendor.text 28 | print "[*] Requesting #{url}... " 29 | response = HTTParty.get(url) 30 | if response.code != 200 31 | puts "Failed" 32 | puts "[-] #{url} returned: #{response.code} #{response.message}" 33 | exit(1) 34 | end 35 | puts "Done" 36 | 37 | doc = Nokogiri::HTML(response.body) 38 | count = doc.css("table").count 39 | if count.zero? 40 | "[-] No credentials for #{name}" 41 | next 42 | end 43 | print "[*] Collecting #{count} #{count == 1 ? 'credential' : 'credentials'} for #{name}... " 44 | doc.css("table").each do |cred_table| 45 | cred_name = cred_table.css("h3").text.split(" - ", 2).last 46 | credential = {"Vendor" => name, "Name" => cred_name} 47 | cred_table.css("tr").each do |tr| 48 | tds = tr.css("td") 49 | next unless tds.count == 2 50 | attribute = tds.first.text 51 | value = tds.last.text 52 | credential[attribute] = value 53 | end 54 | credentials << credential 55 | end 56 | puts "Done" 57 | sleep 0.5 58 | end 59 | 60 | print "[*] Writing credentials to credentials.json... " 61 | File.open("credentials.json", "w") do |f| 62 | f.write(JSON.pretty_generate(credentials)) 63 | end 64 | puts "Done" 65 | puts "[*] Scraping complete!" 66 | end 67 | -------------------------------------------------------------------------------- /lib/searchpass/cli.rb: -------------------------------------------------------------------------------- 1 | module Searchpass 2 | class CLI 3 | PROGRAM_NAME = "searchpass".freeze 4 | CREDENTIALS_FILE = File.join(File.dirname(__FILE__), "..", "..", "credentials.json") 5 | CREDENTIALS_SEPARATOR = "========================================================" 6 | 7 | class Error < StandardError; end 8 | class CredentialsFileError < Searchpass::CLI::Error; end 9 | class CredentialsFileNotReadable < Searchpass::CLI::CredentialsFileError; end 10 | class CredentialsFileCorrupt < Searchpass::CLI::CredentialsFileError; end 11 | 12 | def self.run!(args) 13 | @options = OpenStruct.new 14 | @options.case_sensitive = false 15 | @options.exact = false 16 | @opt_parser = OptionParser.new do |opts| 17 | opts.banner = "Usage: #{PROGRAM_NAME} [options] term [term2] ... [termN]" 18 | opts.program_name = PROGRAM_NAME 19 | opts.separator "" 20 | opts.separator "Specific options:" 21 | 22 | opts.on("-c", "--case", "Perform case sensitive matching") do |v| 23 | @options.case_sensitive = v 24 | end 25 | 26 | opts.on("-e", "--exact", "Perform exact matching") do |v| 27 | @options.exact = v 28 | end 29 | 30 | opts.on_tail("-h", "--help", "Show this message") do 31 | puts opts 32 | exit 33 | end 34 | 35 | opts.on_tail("--version", "Show version") do 36 | puts Searchpass::VERSION 37 | exit 38 | end 39 | end 40 | @opt_parser.parse!(args) 41 | if args.empty? 42 | puts "No search terms given" 43 | puts "See -h or --help for usage" 44 | exit(1) 45 | end 46 | perform_search(args) 47 | rescue OptionParser::InvalidOption => e 48 | puts e.message 49 | puts "See -h or --help for usage" 50 | exit(1) 51 | rescue => e 52 | puts "ERROR: #{e.message}" 53 | exit(1) 54 | end 55 | 56 | protected 57 | 58 | def self.perform_search(args) 59 | matches = [] 60 | credentials.each do |credential| 61 | haystack = "#{credential['Vendor']} #{credential['Name']} #{credential['Version']}" 62 | match = true 63 | if !@options.case_sensitive 64 | haystack.downcase! 65 | args.map! { |a| a.downcase } 66 | end 67 | if @options.exact 68 | args = [args.join(" ")] 69 | end 70 | args.each do |arg| 71 | if @options.exact 72 | if haystack != arg 73 | match = false 74 | break 75 | end 76 | else 77 | if !haystack.include?(arg) 78 | match = false 79 | break 80 | end 81 | end 82 | end 83 | matches << credential if match 84 | end 85 | if matches.empty? 86 | puts "No matches for #{args.join(' ')}" 87 | else 88 | puts "Found #{matches.count} #{matches.count == 1 ? 'match' : 'matches'}:\n" 89 | matches.each do |match| 90 | output_credential(match) 91 | end 92 | end 93 | end 94 | 95 | def self.output_credential(credential) 96 | puts "\nVendor: #{credential['Vendor']}" 97 | puts "Name: #{credential['Name']}" 98 | if credential.key?("Version") && !credential["Version"].empty? 99 | puts "Version: #{credential['Version']}" 100 | end 101 | if credential.key?("Method") && !credential["Method"].empty? 102 | puts "Method: #{credential['Method']}" 103 | end 104 | if credential.key?("User ID") && !credential["User ID"].empty? 105 | puts "User ID: #{credential['User ID']}" 106 | end 107 | if credential.key?("Password") && !credential["Password"].empty? 108 | puts "Password: #{credential['Password']}" 109 | end 110 | if credential.key?("Level") && !credential["Level"].empty? 111 | puts "Level: #{credential['Level']}" 112 | end 113 | if credential.key?("Doc") && !credential["Doc"].empty? 114 | puts "Doc: #{credential['Doc']}" 115 | end 116 | if credential.key?("Notes") && !credential["Notes"].empty? 117 | puts "Notes: #{credential['Notes']}" 118 | end 119 | puts "\n#{CREDENTIALS_SEPARATOR}\n" 120 | end 121 | 122 | def self.credentials 123 | @credentials ||= read_credentials_file 124 | end 125 | 126 | def self.read_credentials_file 127 | if !File.exists?(CREDENTIALS_FILE) 128 | fail CredentialsFileNotReadable, "Credentials file does not exist" 129 | end 130 | 131 | if !File.readable?(CREDENTIALS_FILE) 132 | fail CredentialsFileNotReadable, "Credentials file is not readable" 133 | end 134 | credentials = JSON.parse(File.read(CREDENTIALS_FILE)).tap do |c| 135 | if !c.is_a?(Array) 136 | fail CredentialsFileCorrupt, "Credentials file is invalid" 137 | end 138 | end 139 | rescue JSON::ParserError 140 | raise CredentialsFileCorrupt, "Credentials file contains invalid JSON" 141 | end 142 | end 143 | end 144 | --------------------------------------------------------------------------------