├── image1.png ├── Gemfile ├── .gitignore ├── Dockerfile ├── CHANGELOG.md ├── README.md └── catphish.rb /image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ring0lab/catphish/HEAD/image1.png -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'simpleidn' 3 | gem 'whois-parser' 4 | gem 'trollop' 5 | gem 'trollop-subcommands' 6 | gem 'rest-client' 7 | gem 'nokogiri' 8 | gem 'json' 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS generated files # 2 | ###################### 3 | .DS_Store 4 | .DS_Store? 5 | ._* 6 | .Spotlight-V100 7 | .Trashes 8 | ehthumbs.db 9 | Thumbs.db 10 | 11 | # Bundle generated files 12 | ######################## 13 | Gemfile.lock 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # USAGE: 2 | # TAG=$(git rev-parse --short HEAD) 3 | # docker build --tag "catphish:${TAG}" . 4 | # docker run --rm=true "catphish:${TAG}" --Domain ring0labs.com --All 5 | FROM ruby:2.3.4-alpine 6 | 7 | RUN apk add --update \ 8 | build-base \ 9 | && rm -rf /var/cache/apk/* 10 | 11 | # Install it into the /opt/ dir 12 | WORKDIR /opt/catphish 13 | COPY * /opt/catphish/ 14 | RUN bundle install 15 | 16 | # Use the script as the entrypoint so we can supply args directly to the docker daemon 17 | # See https://serverfault.com/a/647790 18 | ENTRYPOINT ["/opt/catphish/catphish.rb"] 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | 8 | ## [UNRELEASED] 9 | 10 | ### Added 11 | 12 | - Dockerfile 13 | 14 | ## 1.0.0 - 2017-06-26 15 | 16 | ### Added 17 | 18 | - Improved coding structure 19 | - Improved parsing options 20 | - Improved user interface 21 | - New option allows a user to choose a specific set of top-level domains 22 | 23 | ## 0.0.4 - 2017-04-18 24 | 25 | ### Added 26 | 27 | - Punycode algorithm for vietnamese and cyrillic characters map. 28 | 29 | ## 0.0.3 - 2016-10-26 30 | 31 | ### Added 32 | 33 | - Analyze target domain to generate similar-looking domains for phishing attacks. 34 | 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CATPHISH 2 | Generate similar-looking domains for phishing attacks. Check expired domains and their categorized domain status to evade proxy categorization. Whitelisted domains are perfect for your C2 servers. Perfect for Red Team engagements. 3 | 4 | # Installation 5 | ``` 6 | bundle install 7 | ``` 8 | 9 | # Current Algorithms 10 | * SingularOrPluralise 11 | * prependOrAppend 12 | * doubleExtensions 13 | * mirrorization 14 | * homoglyphs 15 | * dashOmission 16 | * Punycode 17 | 18 | Version 1.1.0: 19 | ``` 20 | The parser for command line options is modified to compensate with the new expired feature. This new option structure gives the tool a new look and more organized. 21 | ``` 22 | 23 | # Usage 24 | Running the tool: 25 | ``` 26 | catphish.rb [global options] COMMAND [command options] 27 | ``` 28 | Options: 29 | ``` 30 | COMMANDS 31 | generate Generate domains 32 | expired Find available expired domains 33 | (experimental) 34 | 35 | Additional help 36 | catphish.rb COMMAND -h 37 | 38 | Global Options 39 | -l, --logo, --no-logo ASCII art banner 40 | (default: true) 41 | -c, --column-header, --no-column-header Header for each column 42 | of the output (default: 43 | true) 44 | -D, --Domain= Target domain to analyze 45 | -V, --Verbose Show all domains, 46 | including non-available 47 | ones 48 | -h, --help Show this message 49 | ``` 50 | Generate all type: 51 | ``` 52 | catphish.rb -D DOMAIN generate -A 53 | ``` 54 | Check available expired domains: 55 | ``` 56 | catphish.rb -D DOMAIN expired 57 | ``` 58 | Check against a specific domain for categorization status: 59 | ``` 60 | catphish.rb -D DOMAIN expired -c 61 | ``` 62 | Check all available expired domains against a specific vendor 63 | ``` 64 | catphish.rb -D DOMAIN expired -p PROXY_TYPE 65 | ``` 66 | 67 | ## Docker 68 | 69 | You can also run the tool with Docker! This lets you try it out without any of the required dependencies (ruby), except 70 | Docker itself. This presumes that you have the docker daemon installed. If not, see 71 | [Docker's documentation](https://docs.docker.com/engine/installation/). 72 | 73 | First, build the container 74 | 75 | ``` 76 | $ cd path/to/repository 77 | 78 | # Generate a tag so we know how to find the container later to run it. You can use anything (latest is common); 79 | # here the git hash is used. 80 | $ TAG=$(git rev-parse --short HEAD) 81 | 82 | # Run the build 83 | $ docker build --tag "catphish:${TAG}" . 84 | 85 | # Eventually docker will print something like: 86 | # 87 | # Successfully built 8f0b8bfe0c41 88 | # Successfully tagged catphish:f947517 89 | 90 | ``` 91 | 92 | Perfect! Now, you can execute catphish via Docker: 93 | 94 | ``` 95 | $ docker run \ 96 | --rm=true \ 97 | "catphish:${TAG}" \ 98 | --Domain ring0labs.com \ 99 | --All 100 | ``` 101 | 102 | # In Action 103 | 104 | ![alt tag](https://github.com/ring0lab/catphish/blob/master/image1.png) 105 | 106 | # COPYRIGHT 107 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 108 | 109 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 110 | 111 | You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. 112 | -------------------------------------------------------------------------------- /catphish.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # catphish - Domain Suggester 4 | # version: 1.1.0 5 | # author: Viet Luu 6 | # author: Kent 'picat' Gruber 7 | # web: www.ring0lab.com 8 | 9 | require 'set' 10 | require 'trollop/subcommands' 11 | require 'resolv' 12 | require 'simpleidn' 13 | require 'whois-parser' 14 | require 'rest-client' 15 | require 'csv' 16 | require 'nokogiri' 17 | require 'json' 18 | 19 | module Catphish 20 | 21 | VERSION = '1.1.0' 22 | 23 | # Some popular domains to use. 24 | POPULAR_TOP_DOMAINS = ['.com', '.co', '.net', '.org', '.info'] 25 | 26 | # Some country domains to use. 27 | COUNTRY_TOP_DOMAINS = [ 28 | ".ac", ".ad", ".ae", ".af", ".ag", ".ai", ".al", ".am", ".an", ".ao", ".aq", ".ar", ".as", 29 | ".at", ".au", ".aw", ".ax", ".az", ".ba", ".bb", ".bd", ".be", ".bf", ".bg", ".bh", ".bi", 30 | ".bj", ".bm", ".bn", ".bo", ".bq", ".br", ".bs", ".bt", ".bv", ".bw", ".by", ".bz", ".ca", 31 | ".cc", ".cd", ".cf", ".cg", ".ch", ".ci", ".ck", ".cl", ".cm", ".cn", ".co", ".cr", ".cu", 32 | ".cv", ".cw", ".cx", ".cy", ".cz", ".de", ".dj", ".dk", ".dm", ".do", ".dz", ".ec", ".ee", 33 | ".eg", ".eh", ".er", ".es", ".et", ".eu", ".fi", ".fj", ".fk", ".fm", ".fo", ".fr", ".ga", 34 | ".gb", ".gd", ".ge", ".gf", ".gg", ".gh", ".gi", ".gl", ".gm", ".gn", ".gp", ".gq", ".gr", 35 | ".gs", ".gt", ".gu", ".gw", ".gy", ".hk", ".hm", ".hn", ".hr", ".ht", ".hu", ".id", ".ie", 36 | ".il", ".im", ".in", ".io", ".iq", ".ir", ".is", ".it", ".je", ".jm", ".jo", ".jp", ".ke", 37 | ".kg", ".kh", ".ki", ".km", ".kn", ".kp", ".kr", ".kw", ".ky", ".kz", ".la", ".lb", ".lc", 38 | ".li", ".lk", ".lr", ".ls", ".lt", ".lu", ".lv", ".ly", ".ma", ".mc", ".md", ".me", ".mg", 39 | ".mh", ".mk", ".ml", ".mm", ".mn", ".mo", ".mp", ".mq", ".mr", ".ms", ".mt", ".mu", ".mv", 40 | ".mw", ".mx", ".my", ".mz", ".na", ".nc", ".ne", ".nf", ".ng", ".ni", ".nl", ".no", ".np", 41 | ".nr", ".nu", ".nz", ".om", ".pa", ".pe", ".pf", ".pg", ".ph", ".pk", ".pl", ".pm", ".pn", 42 | ".pr", ".ps", ".pt", ".pw", ".py", ".qa", ".re", ".ro", ".rs", ".ru", ".rw", ".sa", ".sb", 43 | ".sc", ".sd", ".se", ".sg", ".sh", ".si", ".sj", ".sk", ".sl", ".sm", ".sn", ".so", ".sr", 44 | ".ss", ".st", ".su", ".sv", ".sx", ".sy", ".sz", ".tc", ".td", ".tf", ".tg", ".th", ".tj", 45 | ".tk", ".tl", ".tm", ".tn", ".to", ".tp", ".tr", ".tt", ".tv", ".tw", ".tz", ".ua", ".ug", 46 | ".uk", ".us", ".uy", ".uz", ".va", ".vc", ".ve", ".vg", ".vi", ".vn", ".vu", ".wf", ".ws", 47 | ".ye", ".yt", ".za", ".zm", ".zw" ] 48 | 49 | # Some generic domains. 50 | GENERIC_DOMAINS = [ 51 | ".academy", ".accountant", ".accountants", ".active", ".actor", ".adult", ".aero", 52 | ".agency", ".airforce", ".apartments", ".app", ".archi", ".army", ".associates", 53 | ".attorney", ".auction", ".audio", ".autos", ".band", ".bar", ".bargains", ".beer", 54 | ".best", ".bid", ".bike", ".bingo", ".bio", ".biz", ".black", ".blackfriday", ".blog", 55 | ".blue", ".boo", ".boutique", ".build", ".builders", ".business", ".buzz", ".cab", ".cam", 56 | ".camera", ".camp", ".cancerresearch", ".capital", ".cards", ".care", ".career", ".careers", 57 | ".cars", ".cash", ".casino", ".catering", ".center", ".ceo", ".channel", ".chat", ".cheap", 58 | ".christmas", ".church", ".city", ".claims", ".cleaning", ".click", ".clinic", ".clothing", 59 | ".cloud", ".club", ".coach", ".codes", ".coffee", ".college", ".community", ".company", 60 | ".computer", ".condos", ".construction", ".consulting", ".contractors", ".cooking", ".cool", 61 | ".coop", ".country", ".coupons", ".credit", ".creditcard", ".cricket", ".cruises", ".dad", 62 | ".dance", ".date", ".dating", ".day", ".deals", ".degree", ".delivery", ".democrat", ".dental", 63 | ".dentist", ".design", ".diamonds", ".diet", ".digital", ".direct", ".directory", ".discount", 64 | ".dog", ".domains", ".download", ".eat", ".education", ".email", ".energy", ".engineer", 65 | ".engineering", ".equipment", ".esq", ".estate", ".events", ".exchange", ".expert", ".exposed", 66 | ".express", ".fail", ".faith", ".family", ".fans", ".farm", ".fashion", ".feedback", ".finance", 67 | ".financial", ".fish", ".fishing", ".fit", ".fitness", ".flights", ".florist", ".flowers", ".fly", 68 | ".foo", ".football", ".forsale", ".foundation", ".fund", ".furniture", ".fyi", ".gallery", ".garden", 69 | ".gift", ".gifts", ".gives", ".glass", ".global", ".gold", ".golf", ".gop", ".graphics", ".green", 70 | ".gripe", ".guide", ".guitars", ".guru", ".healthcare", ".help", ".here", ".hiphop", ".hiv", ".hockey", 71 | ".holdings", ".holiday", ".homes", ".horse", ".host", ".hosting", ".house", ".how", ".info", ".ing", 72 | ".ink", ".institute", ".insure", ".international", ".investments", ".jewelry", ".jobs", ".kim", ".kitchen", 73 | ".land", ".lawyer", ".lease", ".legal", ".lgbt", ".life", ".lighting", ".limited", ".limo", ".link", 74 | ".loan", ".loans", ".lol", ".lotto", ".love", ".luxe", ".luxury", ".management", ".market", ".marketing", 75 | ".markets", ".mba", ".media", ".meet", ".meme", ".memorial", ".men", ".menu", ".mobi", ".moe", ".money", 76 | ".mortgage", ".motorcycles", ".mov", ".movie", ".museum", ".name", ".navy", ".network", ".new", ".news", 77 | ".ngo", ".ninja", ".one", ".ong", ".onl", ".online", ".ooo", ".organic", ".partners", ".parts", ".party", 78 | ".pharmacy", ".photo", ".photography", ".photos", ".physio", ".pics", ".pictures", ".pid", ".pink", ".pizza", 79 | ".place", ".plumbing", ".plus", ".poker", ".porn", ".post", ".press", ".pro", ".productions", ".prof", 80 | ".properties", ".property", ".qpon", ".racing", ".recipes", ".red", ".rehab", ".ren", ".rent", ".rentals", 81 | ".repair", ".report", ".republican", ".rest", ".review", ".reviews", ".rich", ".rip", ".rocks", ".rodeo", 82 | ".rsvp", ".run", ".sale", ".school", ".science", ".services", ".sex", ".sexy", ".shoes", ".show", ".singles", 83 | ".site", ".soccer", ".social", ".software", ".solar", ".solutions", ".space", ".studio", ".style", ".sucks", 84 | ".supplies", ".supply", ".support", ".surf", ".surgery", ".systems", ".tattoo", ".tax", ".taxi", ".team", 85 | ".store", ".tech", ".technology", ".tel", ".tennis", ".theater", ".tips", ".tires", ".today", ".tools", ".top", 86 | ".tours", ".town", ".toys", ".trade", ".training", ".travel", ".university", ".vacations", ".vet", ".video", 87 | ".villas", ".vision", ".vodka", ".vote", ".voting", ".voyage", ".wang", ".watch", ".webcam", ".website", ".wed", 88 | ".wedding", ".whoswho", ".wiki", ".win", ".wine", ".work", ".works", ".world", ".wtf", ".xxx", ".xyz", ".yoga", ".zone"] 89 | 90 | # It's like a treasure map, but for chars. 91 | CHARS_MAP = { 92 | "a" => ["\u1EA1", "\u0101", "\u0203", "\u00E0", "\u00E1"], 93 | "e" => ["\u1EB9", "\u0113", "\u0207", "\u00E8", "\u00E9"], 94 | "c" => ["\u0107"], 95 | "d" => ["\u0111", "\u010F"], 96 | "i" => ["\u1EC9", "\u1ECB", "\u012B", "\u00EC", "\u020B"], 97 | "o" => ["\u1ECD", "\u014D", "\u020F", "\u00F2", "\u00F3"], 98 | "u" => ["\u1EE5", "\u016B", "\u0217", "\u00F9", "\u00FA"], 99 | "r" => ["\u0155", "\u0213"], 100 | "t" => ["\u0165"], 101 | "y" => ["\u1EF7", "\u00FD"], 102 | "z" => ["\u017E"] 103 | } 104 | 105 | # Current langs: Vietnamese, Croation and Czech 106 | CYRILLIC_CHARS_MAP = { 107 | "a" => "\u0430", "b" => "\u0432", "c" => "\u0441", "e" => "\u0435", 108 | "f" => "\u0493", "h" => "\u04BB", "i" => "\u0456", "k" => "\u043A", 109 | "l" => "\u04CF", "m" => "\u043C", "n" => "\u04E5", "o" => "\u043E", 110 | "p" => "\u0440", "r" => "\u0433", "s" => "\u0455", "t" => "\u0442", 111 | "u" => "\u0446", "w" => "\u0428", "x" => "\u0445", "y" => "\u0423" 112 | } 113 | 114 | # Homoglyp substitute character mappings. 115 | HOMOGLYPH_SUBSTITUTE_CHARACTERS = { 116 | "0" => "o", "1" => "l", "o" => "0", "m" => "rm", "d" => "cl", 117 | "g" => "q", "i" => "l", "l" => "i", "p" => "q", "cl" => "d", 118 | "q" => "g", "u" => "v", "v" => "u", "w" => "vv", "y" => "v" 119 | } 120 | 121 | # Create a new container, optionally in a block syntax. 122 | def self.new_container 123 | return Set.new unless block_given? 124 | yield Set.new 125 | end 126 | 127 | # Mirrorization method. 128 | def self.mirrorization(domain) 129 | domain = domain.split('.')[0] 130 | new_container do |container| 131 | (0...domain.size).each do |i| 132 | d = domain.clone 133 | if (i == domain.size - 2 || d[i+1] == '-') 134 | d[i+1] = d[i] + d[i+1] 135 | elsif (d[i] == '-') 136 | d[i] = d[i] 137 | elsif (d[i] == d[i+1] || d[i] == d[i-1]) 138 | # do nothing 139 | else 140 | d[i+1] = d[i] 141 | end 142 | # names like google.com seem to trick this up 143 | container << ['Mirrorization',d] unless d == domain 144 | end 145 | container 146 | end 147 | end 148 | 149 | # Singular or plural method. 150 | def self.singular_or_pluralise(domain) 151 | domain = domain.split('.')[0] 152 | new_container do |container| 153 | if (domain[domain.size - 1] == 's') 154 | container << ['SingularOrPluralise', domain.chomp(domain[domain.size - 1])] 155 | else 156 | container << ['SingularOrPluralise', domain + "s"] 157 | end 158 | end 159 | end 160 | 161 | # Prepend or append method. 162 | def self.prepend_or_append(domain) 163 | domain = domain.split('.')[0] 164 | words = ['www-', '-www', 'http-', '-https'] 165 | new_container do |container| 166 | words.each do |w| 167 | d = domain.clone 168 | if (w[0] == '-') 169 | d = d + w 170 | else 171 | d = w + d 172 | end 173 | container << ['PrependOrAppend',d] 174 | end 175 | container 176 | end 177 | end 178 | 179 | # Homoglyphs method. 180 | def self.homoglyphs(domain) 181 | domain = domain.split('.')[0] 182 | new_container do |container| 183 | HOMOGLYPH_SUBSTITUTE_CHARACTERS.each do |k, v| 184 | next unless domain.include?(k) 185 | container << ['Homoglyphs', domain.sub(k, v)] 186 | container << ['Homoglyphs', domain.gsub(k, v)] 187 | end 188 | container << ['Homoglyphs',domain.sub('cl', 'd')] 189 | container << ['Homoglyphs',domain.gsub('cl', 'd')] 190 | end 191 | end 192 | 193 | # Double extensions method. 194 | def self.double_extensions(domain) 195 | new_container do |container| 196 | container << ['DoubleExtensions', domain.split('.')[0] + '-' + domain.split('.')[1]] 197 | end 198 | end 199 | 200 | # Dash omission method. 201 | def self.dash_omission(domain) 202 | domain = domain.split('.')[0] 203 | new_container do |container| 204 | if (domain.include?('-')) 205 | container << ['DashOmission', domain.gsub('-', '')] 206 | end 207 | container 208 | end 209 | end 210 | 211 | # Punycode method. 212 | def self.punycode(domain) 213 | domain = domain.split('.')[0] 214 | new_container do |container| 215 | @D2 = domain.clone 216 | CHARS_MAP.each do |k, v| 217 | d = domain.clone 218 | (0...domain.size).each do |i| 219 | if (d[i] == k) 220 | (0...v.size).each do |i2| 221 | d[i] = v[i2] 222 | @D2[i] = v[i2] 223 | container << ['Punycode',d, SimpleIDN.to_ascii(d)] 224 | d = domain.clone 225 | end 226 | end 227 | end 228 | 229 | cont = container.dup 230 | cont.each do |domain| 231 | temp_domain = [] 232 | (0...v.size).each do |i3| 233 | temp_domain << (domain[1].gsub!(k, v[i3])) 234 | if !temp_domain[0].nil? 235 | container << ['Punycode',temp_domain[0], SimpleIDN.to_ascii(temp_domain[0])] 236 | end 237 | end 238 | end 239 | end 240 | 241 | container << ['Punycode',@D2, SimpleIDN.to_ascii(@D2)] 242 | 243 | d = domain.clone 244 | punyValid = true 245 | if domain =~ /d|g|q|v|z/ 246 | punyValid = false 247 | end 248 | CYRILLIC_CHARS_MAP.each do |k, v| 249 | (0...domain.size).each do |i| 250 | if (d[i] == k) 251 | d[i] = v 252 | end 253 | end 254 | end 255 | if punyValid 256 | container << ['Punycode',d, SimpleIDN.to_ascii(d)] 257 | end 258 | container 259 | end 260 | end 261 | 262 | # Whois information for a given domain. 263 | def self.whois_information(domain, extension = nil, retries = 1) 264 | domain = domain + extension if extension 265 | begin 266 | Whois.whois(domain + extension).parser.available? 267 | rescue 268 | retry if (retries -= 1) >= 0 269 | end 270 | end 271 | 272 | # IP Address information for a given domain. 273 | def self.resolv_information(domain, extension = nil, retries = 1) 274 | domain = domain + extension if extension 275 | begin 276 | Resolv.getaddress domain 277 | rescue 278 | retry if (retries -= 1) >= 0 279 | end 280 | end 281 | 282 | # Checking for Expired Domains Network 283 | # Catphish is currently supporting Fortiguard, Juniper, Trustwave, BlueCoat, and IBM for URL filtering check. 284 | # Future support: PALTO ALTO, Mcafee and SonicWall. 285 | def self.check_expired_domains(domain, check, proxy) 286 | container = [] 287 | t_domain = [] 288 | t_year = [] 289 | total = 0 290 | 291 | payload = 292 | { 293 | fdomainstart: '', 294 | fdomain: '', 295 | fdomainend: '', 296 | fsimilarweb: '1', 297 | fwhois: '22', 298 | ftrmaxhost: '0', 299 | ftrminhost: '0', 300 | ftrbl: '0', 301 | ftrdomainpop: '0', 302 | ftrabirth_year: '0', 303 | q: domain, 304 | button_submit: 'Apply Filter' 305 | } 306 | 307 | if !check 308 | RestClient.post("https://www.expireddomains.net/domain-name-search/?q=#{domain}", payload) do |res| 309 | if total == 0 310 | total = res.body.to_s.split(/About \(.*?)\s\<\/strong\> Domains/)[1].to_i 311 | end 312 | Nokogiri::HTML(res.body).css('td.field_abirth').each do |year| 313 | t_year << year.to_s.split(/title="First seen\s(.*?)\,/)[1] 314 | end 315 | Nokogiri::HTML(res.body).css('td.field_domain').each do |domain| 316 | t_domain << domain.to_s.split(/title\=\"(.*?)\"/)[1] 317 | end 318 | end 319 | 320 | (0...t_domain.size).each do |i| 321 | container << [t_domain[i], t_year[i]] 322 | end 323 | check_url_filter(container, proxy, total) 324 | else 325 | container << [domain] 326 | check_url_filter(container, proxy, 1) 327 | end 328 | end 329 | 330 | def self.check_url_filter(container, proxy, total) 331 | puts "Found #{total} expired domains\n" 332 | printf "%-30s %-30s %s\n\n", "Domain", "Age", "Categorize" 333 | 334 | proxies = 335 | { 336 | Fortiguard: 'check_fortiguard', 337 | Juniper: 'check_juniper', 338 | Trustwave: 'check_trustwave', 339 | Bluecoat: 'check_bluecoat', 340 | Ibm: 'check_ibm' 341 | } 342 | 343 | # Improve Dynamic Calling for proxy selection. 344 | begin 345 | container.each do |domain| 346 | if proxy.downcase == 'all' 347 | proxies.each do |k, v| 348 | printf "%-30s %-30s %s\n\n", domain[0], domain[1], "#{k.upcase}: " + Catphish.public_send("#{v}", domain[0]) 349 | end 350 | else 351 | printf "%-30s %-30s %s\n\n", domain[0], domain[1], "#{proxy.upcase}: " + Catphish.public_send(proxies[:"#{proxy.capitalize}"], domain[0]) 352 | end 353 | printf "%-30s", "-" * 100 + "\n" 354 | end 355 | rescue 356 | puts "Error: option '-P' needs a valid proxy type." 357 | end 358 | end 359 | 360 | # Check Fortiguard URL Filter 361 | def self.check_fortiguard(domain) 362 | RestClient.get("https://fortiguard.com/webfilter?q=#{domain}") do |res| 363 | return Nokogiri::HTML(res.body).at('meta[name="og:description"]')['content'].split(':')[1][1..-1] 364 | end 365 | end 366 | 367 | # Check Juniper URL Filter 368 | def self.check_juniper(domain) 369 | RestClient.post("http://mtas.surfcontrol.com/mtas/Juniper-Results.php", {url: domain, submit1: 'Test Site'}) do |res| 370 | if res.body.split(/categorized as \(.*?)\/)[1] == ' ' 371 | return 'Unknown' 372 | else 373 | return res.body.split(/categorized as \(.*?)\/)[1] 374 | end 375 | end 376 | end 377 | 378 | #Check Trustwave URL Filter 379 | def self.check_trustwave(domain) 380 | RestClient.post("https://www3.trustwave.com/support/m86filtercheck.asp", {checkurl: domain, Submit: 'Check Database'}) do |res| 381 | if res.body.split(/The URL \.*\<\/b\>(.*?)\<\/p\>/)[1] == ' is not found in the database.' 382 | return 'Unknown' 383 | elsif res.body.include?("daily quota") 384 | return 'Daily query limit exceeded' 385 | else 386 | return Nokogiri::HTML(res.body).css('li')[146].text 387 | end 388 | end 389 | end 390 | 391 | # Check Bluecoat URL Filter 392 | def self.check_bluecoat(domain) 393 | RestClient.post("https://sitereview.bluecoat.com/rest/categorization", {url: domain}) do |res| 394 | return JSON.parse(res.body)["categorization"].to_s.split(/\>(.*?)\<\/a\>/)[1] 395 | end 396 | end 397 | 398 | # Check IBM URL Filter 399 | def self.check_ibm(domain) 400 | header = 401 | { 402 | 'User-Agent': 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)', 403 | 'Accept': 'application/json, text/plain, */*', 404 | 'x-ui': 'XFE', 405 | 'Origin': "https://exchange.xforce.ibmcloud.com/url/#{domain}", 406 | 'Referer': "https://exchange.xforce.ibmcloud.com/url/#{domain}", 407 | 'Content-Type': 'application/json;charset=utf-8' 408 | } 409 | 410 | RestClient.get("https://api.xforce.ibmcloud.com/url/#{domain}", header) do |res| 411 | if JSON.parse(res.body)["error"].to_s == "Not found." 412 | return "Unknown" 413 | else 414 | return JSON.parse(res.body)["result"]["cats"].to_s.split(/\"(.*?)\"/)[1] 415 | end 416 | end 417 | end 418 | 419 | # The "main" method of sorts of the application. 420 | def self.start(domain_container, domain_types: POPULAR_TOP_DOMAINS, all: false, punycode: false, header: false) 421 | if punycode 422 | printf "%-30s %-30s %-30s %s\n\n", "Type", "Domain", "Punycode", "Status" if header 423 | else 424 | printf "%-30s %-30s %s\n\n", "Type", "Domain", "Status" if header 425 | end 426 | threads = [] 427 | domain_container.each do |d| 428 | domain_types.each do |extension| 429 | threads << Thread.new do 430 | if punycode 431 | if whois_information(d[2] || d[1], extension) 432 | if d[2].nil? 433 | printf "%-30s %-30s %-30s %s\n", d[0], d[1] + extension, "none", "\e[32mAvailable\e[0m" 434 | else 435 | printf "%-30s %-30s %-30s %s\n", d[0], d[1] + extension, d[2] + extension, "\e[32mAvailable\e[0m" 436 | end 437 | elsif all 438 | printf "%-30s %-30s %-30s %s\n", d[0], d[1] + extension, d[2], "Not Available" 439 | end 440 | else 441 | begin 442 | if Resolv.getaddress("#{d[1] + extension}") and all 443 | printf "%-30s %-30s %s\n", d[0], d[1] + extension, "Not Available" 444 | end 445 | rescue Exception 446 | if punycode 447 | printf "%-30s %-30s %-30s %s\n", d[0], d[1] + extension, "none", "\e[32mAvailable\e[0m" 448 | else 449 | printf "%-30s %-30s %s\n", d[0], d[1] + extension, "\e[32mAvailable\e[0m" 450 | end 451 | end 452 | end 453 | end 454 | end 455 | end 456 | threads.each(&:join) 457 | end 458 | 459 | # Logos are pretty cool. 460 | def self.logo 461 | " 462 | ██████╗ █████╗ ████████╗██████╗ ██╗ ██╗██╗███████╗██╗ ██╗ 463 | ██╔════╝██╔══██╗╚══██╔══╝██╔══██╗██║ ██║██║██╔════╝██║ ██║ 464 | ██║ ███████║ ██║ ██████╔╝███████║██║███████╗███████║ 465 | ██║ ██╔══██║ ██║ ██╔═══╝ ██╔══██║██║╚════██║██╔══██║ 466 | ╚██████╗██║ ██║ ██║ ██║ ██║ ██║██║███████║██║ ██║ 467 | ╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═╝ 468 | [v]#{VERSION} 469 | Author: Mr. V & Picat 470 | Web: ring0lab.com 471 | " 472 | end 473 | 474 | end 475 | 476 | # Default to a help menu if nothing has been given. 477 | ARGV[0] = '-h' if ARGV.empty? 478 | 479 | # Clean shutdown. 480 | trap("INT") { puts "\nCatphish is shutting down..."; exit} 481 | 482 | Trollop::Subcommands::register_global do 483 | banner <<-END 484 | #{Catphish.logo} 485 | Usage 486 | #{File.basename($0)} [global options] COMMAND [command options] 487 | 488 | COMMANDS 489 | generate Generate domains 490 | expired Find available expired domains (experimental) 491 | 492 | Additional help 493 | #{File.basename($0)} COMMAND -h 494 | 495 | Global Options 496 | END 497 | opt :logo, "ASCII art banner", type: :bool, default: true 498 | opt :column_header, "Header for each column of the output", type: :bool, default: true 499 | opt :Domain, "Target domain to analyze", type: :string, required: (ARGV[0] == '-h' ? false : true) 500 | opt :Verbose, "Show all domains, including non-available ones", type: :bool, default: false 501 | end 502 | 503 | Trollop::Subcommands::register_subcommand('generate') do 504 | banner <<-END 505 | #{Catphish.logo} 506 | Usage 507 | #{File.basename($0)} -D [domain] generate [options] 508 | Options 509 | END 510 | opt :type, "Type of level domains: (popular, country, generic)", type: :string, default: 'popular' 511 | opt :All, "Use all of the possible methods", type: :bool, default: false 512 | opt :Mirrorization, "Use the mirrorization method", type: :bool, default: false 513 | opt :singular_or_pluralise, "Use the singular or pluralise method", type: :bool, default: false 514 | opt :prepend_or_append, "Use the prepend or append method", type: :bool, default: false 515 | opt :Top_level_domains, "Use a specific ( set of ) top-level domain(s)", type: :strings, required: false 516 | opt :Homoglyphs, "Use the homoglyphs method", type: :bool, default: false 517 | opt :double_extensions, "Use the double extensions method", type: :bool, default: false 518 | opt :Dash_omission, "Use the dash omission method", type: :bool, default: false 519 | opt :Punycode, "Use the punycode method", type: :bool, default: false 520 | end 521 | 522 | Trollop::Subcommands::register_subcommand('expired') do 523 | banner <<-END 524 | #{Catphish.logo} 525 | Usage 526 | #{File.basename($0)} -D [domain] expired [options] 527 | Options 528 | END 529 | opt :check, "Check category of the provided domain", type: :bool, default: false 530 | opt :proxy, "Proxy type: Fortiguard, Juniper, Trustwave, BlueCoat, IBM", type: :string, default: 'all' 531 | end 532 | 533 | opts = Trollop::Subcommands::parse! 534 | 535 | # If given top level domains, use those. Otherwise, use whatever was 536 | # given for the type or default to popular domains. 537 | 538 | case opts.subcommand 539 | when "generate" 540 | if opts.subcommand_options[:Top_level_domains] 541 | type = Catphish.new_container do |container| 542 | opts.subcommand_options[:Top_level_domains].each do |domain| 543 | domain = "." + domain unless domain[0] == "." 544 | container << domain 545 | end 546 | container 547 | end 548 | else 549 | case opts.subcommand_options[:type].downcase.to_sym 550 | when :country 551 | type = Catphish::COUNTRY_TOP_DOMAINS 552 | when :generic 553 | type = Catphish::GENERIC_DOMAINS 554 | else 555 | type = Catphish::POPULAR_TOP_DOMAINS 556 | end 557 | end 558 | 559 | # Get all of the domains we're interested in processing. 560 | domains = Catphish.new_container do |container| 561 | if opts.subcommand_options[:All] 562 | [:Mirrorization, :singular_or_pluralise, :prepend_or_append, :Homoglyphs, :double_extensions, :Dash_omission, :Punycode].each do |opt| 563 | Catphish.send(opt.to_s.downcase.to_sym, opts.global_options[:Domain]).each do |domain| 564 | container << domain 565 | end 566 | end 567 | else 568 | [:Mirrorization, :singular_or_pluralise, :prepend_or_append, :Homoglyphs, :double_extensions, :Dash_omission, :Punycode].each do |opt| 569 | next unless opts.subcommand_options[opt] 570 | Catphish.send(opt.to_s.downcase.to_sym, opts.global_options[:Domain]).each do |domain| 571 | container << domain 572 | end 573 | end 574 | end 575 | container 576 | end 577 | 578 | # If there are no domains to process, then fail. 579 | if domains.empty? 580 | puts "Nothing to process ( try other options )!" 581 | exit 1 582 | end 583 | 584 | # Check if punycode is going to show up to the party. 585 | if opts.subcommand_options[:Punycode] || opts.subcommand_options[:All] 586 | puny = true 587 | else 588 | puny = false 589 | end 590 | 591 | # Print the logo to the screen, or maybe not. 592 | puts Catphish.logo if opts.global_options[:logo] 593 | 594 | # Start the heavy logic. 595 | Catphish.start(domains, domain_types: type, all: opts.global_options[:Verbose], punycode: puny, header: opts.global_options[:column_header]) 596 | when "expired" 597 | # Print the logo to the screen, or maybe not. 598 | puts Catphish.logo if opts.global_options[:logo] 599 | # Start the heavy logic. 600 | Catphish.check_expired_domains(opts.global_options[:Domain], opts.subcommand_options[:check], opts.subcommand_options[:proxy]) 601 | end --------------------------------------------------------------------------------