Posted by
2153216126
Shodan Search Tool w/My Ruby API Class
Today I just wanted to share a little something I made for Shodan. If you don't know whatShodan is, then I highly recommend you check them out and do some quick googling to see what others have done with its help. I initially tried using their published ruby gem and published API documentation but it failed miserably (likely could just be me, but seems their code is outdated with how their site provides output now, idk). I really like Shodan though so I decided to create my own version of their API so I could get started on making a cool search assistant I can run from the command line with some basic logging for analysis after. Once I finished redoing the API class, I made a little CLI based search tool to make quick Shodan research a snap and am now sharing with the rest of the world, hope its helpful for others.
Prerequisites:
sudo gem install colorize curb json nokogiri
NOTE: curb uses libcurl under the hood so you might need to install this if not already included on your OS
Basic Help Menu:
You can run a basic Shodan search and display the results, which are also logged to the results folder.
The logged results are overwritten on each search so you need to rename it or move it if you want to use it later and plan to run multiple searches.
I also made option for quick search which runs a Shodan search and returns the list of IP addresses from results, skipping all the details. I typically run a normal search, then a follow up quick search on same keywords to pass of lists to other tools in a speedy fashion while manual review is more involved with the full search results...
Shodan also offers up a nice search feature to search for exploits which leverages multiple exploit databases. I currently have the Exploit-db and Metasploit search engines available and fully working. This means you can easily search for known exploits with variety of keywords and get matching results displayed and logged for you.
You can even download the exploit/poc code from search results by referencing the ID number from results.
ToDo List:
Include options to search tool for premium search options (somewhat built into my API Class already but not in tool). Include a Gemfile for easy installs for bundler lovers. Also I have not uploaded things to Github yet as I fried my old box and lost a lot of stuff, working on recovery still but should have it updated soon. Until then you can find things on Pastebin, available for a long while...
My Shodan API Standalone Class:
Direct link: http://pastebin.com/q6LZJqcD
My Shodan API Search Tool, Source Code:
Direct Link: http://pastebin.com/B0SdmmrX
Helpful for me, hope it is for you too!
Until next time, Enjoy!
Prerequisites:
sudo gem install colorize curb json nokogiri
NOTE: curb uses libcurl under the hood so you might need to install this if not already included on your OS
Basic Help Menu:
You can run a basic Shodan search and display the results, which are also logged to the results folder.
The logged results are overwritten on each search so you need to rename it or move it if you want to use it later and plan to run multiple searches.
I also made option for quick search which runs a Shodan search and returns the list of IP addresses from results, skipping all the details. I typically run a normal search, then a follow up quick search on same keywords to pass of lists to other tools in a speedy fashion while manual review is more involved with the full search results...
Shodan also offers up a nice search feature to search for exploits which leverages multiple exploit databases. I currently have the Exploit-db and Metasploit search engines available and fully working. This means you can easily search for known exploits with variety of keywords and get matching results displayed and logged for you.
You can even download the exploit/poc code from search results by referencing the ID number from results.
ToDo List:
Include options to search tool for premium search options (somewhat built into my API Class already but not in tool). Include a Gemfile for easy installs for bundler lovers. Also I have not uploaded things to Github yet as I fried my old box and lost a lot of stuff, working on recovery still but should have it updated soon. Until then you can find things on Pastebin, available for a long while...
My Shodan API Standalone Class:
Direct link: http://pastebin.com/q6LZJqcD
My Shodan API Search Tool, Source Code:
- #!/usr/bin/env ruby
- #
- # Shodan API Search Assistant
- # By: Hood3dRob1n
- #
- ########### ENTER API KEY HERE ###########
- APIKEY='YOURAPIKEYNEEDS2GORIGHTINHEREYO!' #
- ###########################################
- ##### STD GEMS #######
- require 'fileutils' #
- require 'optparse' #
- require 'resolv' #
- #### NON-STD GEMS ####
- require 'rubygems' #
- require 'colorize' #
- require 'curb' #
- require 'json' #
- require 'nokogiri' #
- ######################
- HOME=File.expand_path(File.dirname(__FILE__))
- RESULTS = HOME + '/results/'
- # Banner
- def banner
- puts
- puts "Shodan API Search Assistant".light_green
- puts "By".light_green + ": Hood3dRob1n".white
- end
- # Clear Terminal
- def cls
- if RUBY_PLATFORM =~ /win32|win64|\.NET|windows|cygwin|mingw32/i
- system('cls')
- else
- system('clear')
- end
- end
- # Custom ShodanAPI Class :)
- # The pre-built option is broken and doesn't work in several places....
- # So we re-wrote it!
- class ShodanAPI
- # Initialize ShodanAPI via passed API Key
- def initialize(apikey)
- @url="http://www.shodanhq.com/api/"
- if shodan_connect(apikey)
- @key=apikey
- end
- end
- # Check API Key against API Info Query
- # Return True on success, False on Error or Failure
- def shodan_connect(apikey)
- url = @url + "info?key=#{apikey}"
- begin
- c = Curl::Easy.perform(url)
- if c.body_str =~ /"unlocked_left": \d+, "telnet": .+, "plan":".+", "https": .+, "unlocked": .+/i
- results = JSON.parse(c.body_str)
- @plan = results['plan']
- @unlocked = results['unlocked']
- @unlocks = results['unlocked_left']
- @https = results['https']
- @telnet = results['telnet']
- return true
- elsif c.body_str =~ /"error": "API access denied"/i
- puts "Access Denied using API Key '#{apikey}'".light_red +"!".white
- puts "Check Key & Try Again".light_red + "....".white
- return false
- else
- puts "Unknown Problem with Connection to Shodan API".light_green + "!".white
- return false
- end
- rescue => e
- puts "Problem with Connection to Shodan API".light_red +"!".white
- puts "\t=> #{e}"
- return false
- end
- end
- # Just checks our key is working (re-using shodan_connect so updates @unlocks)
- # Returns True or False
- def connected?
- if shodan_connect(@key)
- return true
- else
- return false
- end
- end
- # Return the number of unlocks remaining
- def unlocks
- if shodan_connect(@key)
- return @unlocks.to_i
- else
- return nil
- end
- end
- # Check if HTTPS is Enabled
- def https?
- if shodan_connect(@key)
- if @https
- return true
- else
- return false
- end
- else
- return false
- end
- end
- # Check if Telnet is Enabled
- def telnet?
- if shodan_connect(@key)
- if @telnet
- return true
- else
- return false
- end
- else
- return false
- end
- end
- # Actually display Basic Info for current API Key
- def info
- url = @url + 'info?key=' + @key
- begin
- c = Curl::Easy.perform(url)
- results = JSON.parse(c.body_str)
- puts
- puts "Shodan API Key Confirmed".light_green + "!".white
- puts "API Key".light_green + ": #{@key}".white
- puts "Plan Type".light_green + ": #{results['plan']}".white
- puts "Unlocked".light_green + ": #{results['unlocked']}".white
- puts "Unlocks Remaining".light_green + ": #{results['unlocked_left']}".white
- puts "HTTPS Enabled".light_green + ": #{results['https']}".white
- puts "Telnet Enabled".light_green + ": #{results['telnet']}".white
- return true
- rescue => e
- puts "Problem with Connection to Shodan API".light_red +"!".white
- puts "\t=> #{e}".white
- return false
- end
- end
- # Lookup all available information for a specific IP address
- # Returns results hash or nil
- def host(ip)
- url = @url + 'host?ip=' + ip + '&key=' + @key
- begin
- c = Curl::Easy.perform(url)
- results = JSON.parse(c.body_str)
- return results
- rescue => e
- puts "Problem running Host Search".light_red + "!".white
- puts "\t=> #{e}".white
- return nil
- end
- end
- # Returns the number of devices that a search query found
- # Unrestricted usage of all advanced filters
- # Return results count or nil on failure
- def count(string)
- url = @url + 'count?q=' + string + '&key=' + @key
- begin
- c = Curl::Easy.perform(url)
- results = JSON.parse(c.body_str)
- return results['total']
- rescue => e
- puts "Problem grabbing results count".light_red + "!".white
- puts "\t=> #{e}".white
- return nil
- end
- end
- # Search Shodan for devices using a search query
- # Returns results hash or nil
- def search(string, filters={})
- prem_filters = [ 'city', 'country', 'geo', 'net', 'before','after', 'org', 'isp', 'title', 'html' ]
- cheap_filters = [ 'hostname', 'os', 'port' ]
- url = @url + 'search?q=' + string
- if not filters.empty?
- filters.each do |k, v|
- if cheap_filters.include?(k)
- url += ' ' + k + ":\"#{v}\""
- end
- if prem_filters.include?(k)
- if @unlocks.to_i > 1
- url += ' ' + k + ":\"#{v}\""
- @unlocks = @unlocks.to_i - 1 # Remove an unlock for use of filter
- else
- puts "Not Enough Unlocks Left to run Premium Filter Search".light_red + "!".white
- puts "Try removing '#{k}' filter and trying again".light_red + "....".white
- return nil
- end
- end
- end
- end
- url += '&key=' + @key
- begin
- c = Curl::Easy.perform(url)
- results = JSON.parse(c.body_str)
- return results
- rescue => e
- puts "Problem running Shodan Search".light_red + "!".white
- puts "\t=> #{e}".white
- return nil
- end
- end
- # Quick Search Shodan for devices using a search query
- # Results are limited to only the IP addresses
- # Returns results array or nil
- def quick_search(string, filters={})
- prem_filters = [ 'city', 'country', 'geo', 'net', 'before','after', 'org', 'isp', 'title', 'html' ]
- cheap_filters = [ 'hostname', 'os', 'port' ]
- url = @url + 'search?q=' + string
- if not filters.empty?
- filters.each do |k, v|
- if cheap_filters.include?(k)
- url += ' ' + k + ":\"#{v}\""
- end
- if prem_filters.include?(k)
- if @unlocks.to_i > 1
- url += ' ' + k + ":\"#{v}\""
- @unlocks = @unlocks.to_i - 1
- else
- puts "Not Enough Unlocks Left to run Premium Filter Search".light_red + "!".white
- puts "Try removing '#{k}' filter and trying again".light_red + "....".white
- return nil
- end
- end
- end
- end
- url += '&key=' + @key
- begin
- ips=[]
- c = Curl::Easy.perform(url)
- results = JSON.parse(c.body_str)
- results['matches'].each do |host|
- ips << host['ip']
- end
- return ips
- rescue => e
- puts "Problem running Shodan Quick Search".light_red +"!".white
- puts "\t=> #{e}".white
- return nil
- end
- end
- # Perform Shodan Exploit Search as done on Web
- # Provide Search String and source
- # Source can be: metasploit, exploitdb, or cve
- # Returns results hash array on success: { downloadID => { link => description } }
- # Returns nil on failure
- def sploit_search(string, source)
- sources = [ "metasploit", "exploitdb", "cve" ]
- if sources.include?(source.downcase)
- sploits = 'https://exploits.shodan.io/?q=' + string + ' source:"' + source.downcase + '"'
- begin
- results={}
- c = Curl::Easy.perform(sploits)
- page = Nokogiri::HTML(c.body_str) # Parsable doc object now
- # Enumerate target section, parse out link & description
- page.css('div[class="search-result well"]').each do|linematch|
- if linematch.to_s =~ /<div class="search-result well">\s+<a href="(.+)"\s/
- link=$1
- end
- if linematch.to_s =~ /class="title">(.+)\s+<\/a>/
- desc=$1.gsub('<em>', '').gsub('</em>', '')
- end
- case source.downcase
- when 'cve'
- dl_id = 'N/A for CVE Search'
- when 'exploitdb'
- dl_id = link.split('/')[-1] unless link.nil?
- when 'metasploit'
- dl_id = link.sub('http://www.metasploit.com/','').sub(/\/$/, '') unless link.nil?
- end
- results.store(dl_id, { link => desc}) unless (link.nil? orlink == '') or (desc.nil? or desc == '') or (dl_id.nil? or dl_id =='N/A for CVE Search')
- end
- return results
- rescue Curl::Err::ConnectionFailedError => e
- puts "Shitty connection yo".light_red + ".....".white
- return nil
- rescue => e
- puts "Unknown connection problem".light_red + ".....".white
- puts "\t=> #{e}".white
- return nil
- end
- else
- puts "Invalid Search Source Requested".light_red + "!".white
- return nil
- end
- end
- # Download Exploit Code from Exploit-DB or MSF Github Page
- # By passing in the Download ID (which can be seen in sploit_search() results)
- # Return { 'Download' => dl_link, 'Viewing' => v_link, 'Exploit' => c.body_str }
- # or nil on failure
- def sploit_download(id, source)
- sources = [ "metasploit", "exploitdb" ]
- if sources.include?(source.downcase)
- case source.downcase
- when 'exploitdb'
- dl_link = "http://www.exploit-db.com/download/#{id}/"
- v_link = "http://www.exploit-db.com/exploits/#{id}/"
- when 'metasploit'
- dl_link = "https://raw.github.com/rapid7/metasploit-framework/master/#{id.sub('/exploit/', '/exploits/')}.rb"
- v_link = "http://www.rapid7.com/db/#{id}/"
- end
- begin
- c = Curl::Easy.perform(dl_link)
- page = Nokogiri::HTML(c.body_str) # Parsable doc object now
- results = { 'Download' => dl_link, 'Viewing' => v_link,'Exploit' => c.body_str }
- return results
- rescue Curl::Err::ConnectionFailedError => e
- puts "Shitty connection yo".light_red + ".....".white
- return false
- rescue => e
- puts "Unknown connection problem".light_red + ".....".white
- puts "#{e}".light_red
- return false
- end
- else
- puts "Invalid Download Source Requested".light_red + "!".white
- return false
- end
- end
- end
- ### MAIN ###
- options = {}
- optparse = OptionParser.new do |opts|
- opts.banner = "Usage:".light_green + "#{$0} ".white + "[".light_green + "OPTIONS".white + "]".light_green
- opts.separator ""
- opts.separator "EX:".light_green + " #{$0} -s cisco-ios".white
- opts.separator "EX:".light_green + " #{$0} -h 217.140.75.46".white
- opts.separator "EX:".light_green + " #{$0} --quick-search IIS/5.1".white
- opts.separator "EX:".light_green + " #{$0} -S exploitdb -x udev".white
- opts.separator "EX:".light_green + " #{$0} -d 8678 -S exploitdb".white
- opts.separator "EX:".light_green + " #{$0} --source metasploit --exploit-search udev".white
- opts.separator "EX:".light_green + " #{$0} -S metasploit -d modules/exploit/linux/local/udev_netlink".white
- opts.separator ""
- opts.separator "Options: ".light_green
- opts.on('-q', '--quick-search STRING', "\n\tShodan Quick Search".white) do |search_str|
- options[:method] = 3 # 1=> Normal, 2=> IP, 3=> Quick, 4=>Exploit Search, 5=>Exploit Download
- options[:search] = search_str.chomp
- end
- opts.on('-s', '--shodan-search STRING', "\n\tShodan Search".white)do |search_str|
- options[:method] = 1 # 1=> Normal, 2=> IP, 3=> Quick, 4=>Exploit Search, 5=>Exploit Download
- options[:search] = search_str.chomp
- end
- opts.on('-h', '--host-search HOST', "\n\tShodan Host Search against IP".white) do |search_str|
- options[:method] = 2 # 1=> Normal, 2=> IP, 3=> Quick, 4=>Exploit Search, 5=>Exploit Download
- if search_str.chomp =~ /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/
- options[:search] = search_str.chomp
- else
- begin
- ip = Resolv.getaddress(search_str.chomp) # Resolve Host Domain to IP
- options[:search] = ip
- rescue Resolv::ResolvError => e
- cls
- banner
- puts
- puts "Unable to Resolve Host to IP".light_red + "!".white
- puts
- puts opts
- puts
- exit 69;
- end
- end
- end
- opts.on('-S', '--source SOURCE', "\n\tSet Exploit Source: exploitdb or metasploit".white) do |source|
- sources=["metasploit", "exploitdb"]
- if sources.include?(source.downcase.chomp)
- options[:source] = source.downcase.chomp
- else
- cls
- banner
- puts
- puts "Invalid Search Source Requested".light_red + "!".white
- puts "\t=> #{source}".light_red
- puts
- puts opts
- puts
- exit 69;
- end
- end
- opts.on('-x', '--exploit-search STRING', "\n\tShodan Exploit Search for String (requires -S)".white) do |search_str|
- options[:method] = 4 # 1=> Normal, 2=> IP, 3=> Quick, 4=>Exploit Search, 5=>Exploit Download
- options[:search] = search_str.chomp
- end
- opts.on('-d', '--download-id ID', "\n\tDownload Exploit by Exploit ID (requires -S)".white) do |search_str|
- options[:method] = 5 # 1=> Normal, 2=> IP, 3=> Quick, 4=>Exploit-DB Search, 5=>Exploit-DB Download
- options[:search] = search_str.chomp
- end
- opts.on('-H', '--help', "\n\tHelp Menu".white) do
- cls
- banner
- puts
- puts opts
- puts
- exit 69;
- end
- end
- begin
- foo = ARGV[0] || ARGV[0] = "-H"
- optparse.parse!
- mandatory = [:method,:search]
- missing = mandatory.select{ |param| options[param].nil? }
- if not missing.empty?
- cls
- banner
- puts
- puts "Missing options: ".red + " #{missing.join(', ')}".white
- puts optparse
- exit 666;
- end
- rescue OptionParser::InvalidOption, OptionParser::MissingArgument,OptionParser::AmbiguousOption
- cls
- banner
- puts
- puts $!.to_s.red
- puts
- puts optparse
- puts
- exit 666;
- end
- banner
- shodan = ShodanAPI.new(APIKEY)
- if shodan.connected?
- # Display Basic API Key Info
- shodan.info
- puts
- # Create Results Dir if it doesnt exist
- Dir.mkdir(RESULTS) unless File.exists?(RESULTS) andFile.directory?(RESULTS)
- # Now run as requested....
- case options[:method].to_i
- when 1
- results = shodan.search(options[:search].to_s)
- if not results.nil?
- puts "Shodan Search".light_green + ": #{options[:search].to_s}".white
- f=File.open(RESULTS + "shodan_search_results.txt", 'w+')
- f.puts "Shodan Search: #{options[:search].to_s}"
- puts "Total Results Found".light_green + ": #{results['total']}".white
- f.puts "Total Results Found: #{results['total']}"
- results['countries'].each do |country|
- puts " #{country['name']}".light_green + ": #{country['count']}".white
- f.puts " #{country['name']}: #{country['count']}"
- end
- puts
- f.puts
- results['matches'].each do |host|
- puts "Host IP".light_green + ": #{host['ip']}".white
- f.puts "Host IP: #{host['ip']}"
- puts "#{host['data']}".white
- f.puts host['data']
- end
- f.puts
- f.close
- else
- puts "No Results Found for ".light_red + "#{string}".white + " via Shodan Search".light_red + "!".white
- end
- puts
- when 2
- # Check Host Results
- results = shodan.host(options[:search].to_s)
- if not results.nil?
- f=File.open(RESULTS + "shodan_host_search_results.txt", 'w+')
- puts "Host IP".light_green + ": #{results['ip']}".white unlessresults['ip'].nil?
- f.puts "Host IP: #{results['ip']}" unless results['ip'].nil?
- puts "ISP".light_green + ": #{results['data'][0]['isp']}".white unless results['data'][0]['isp'].nil?
- f.puts "ISP: #{results['data'][0]['isp']}" unlessresults['data'][0]['isp'].nil?
- puts "Hostname(s)".light_green + ": #{results['hostnames'].join(',')}".white unlessresults['hostnames'].empty?
- f.puts "Hostname(s): #{results['hostnames'].join(',')}" unlessresults['hostnames'].empty?
- puts "Host OS".light_green + ": #{results['os']}".white unlessresults['os'].nil?
- f.puts "Host OS: #{results['os']}" unless results['os'].nil?
- puts "Country".light_green + ": #{results['country_name']}".white unless results['country_name'].nil?
- f.puts "Country: #{results['country_name']}" unlessresults['country_name'].nil?
- puts "City".light_green + ": #{results['city']}".white unlessresults['city'].nil?
- f.puts "City: #{results['city']}" unless results['city'].nil?
- puts "Longitude".light_green + ": #{results['longitude']}".white unless results['longitude'].nil? orresults['longitude'].nil?
- f.puts "Longitude: #{results['longitude']}" unlessresults['longitude'].nil? or results['longitude'].nil?
- puts "Latitude".light_green + ": #{results['latitude']}".whiteunless results['longitude'].nil? or results['longitude'].nil?
- f.puts "Latitude: #{results['latitude']}" unlessresults['longitude'].nil? or results['longitude'].nil?
- f.puts
- puts
- # We need to split and re-pair up the ports & banners as ports comes after banners in results iteration
- ban=nil
- port_banners={}
- results['data'][0].each do |k, v|
- if k == 'port'
- port=v
- if not ban.nil?
- port_banners.store(port, ban) # store them in hash so we pair them up properly
- ban=nil
- end
- elsif k == 'banner'
- ban=v
- end
- end
- # Now we can display them in proper pairs
- port_banners.each do |port, ban|
- puts "Port".light_green + ": #{port}".white
- f.puts "Port: #{port}"
- puts "Banner".light_green + ": \n#{ban}".white
- f.puts "Banner: \n#{ban}"
- end
- f.puts
- f.close
- else
- puts "No results found for host".light_red + "!".white
- end
- puts
- when 3
- # Perform Quick Shodan Search
- string = options[:search].to_s
- ips = shodan.quick_search(string)
- if not ips.nil?
- puts "Shodan Search".light_green + ": #{string}".white
- puts "Total Results".light_green + ": #{ips.size}".white
- puts "IP Addresses Returned".light_green + ": ".white
- f=File.open(RESULTS + 'quick_search-ips.lst', 'w+')
- ips.each {|x| puts " #{x}".white; f.puts x }
- f.close
- else
- puts "No Results Found for ".light_red + "#{string}".white + " via Shodan Quick Search".light_red + "!".white
- end
- puts
- when 4
- # Search for Exploits
- string = options[:search].to_s
- source = options[:source].to_s
- results = shodan.sploit_search(string, source)
- if not results.nil?
- f=File.open(RESULTS + "shodan_#{source}_search_results.txt",'w+')
- puts "Shodan Exploit Search".light_green + ": #{string}".white
- f.puts "Shodan Exploit Search: #{string}"
- results.each do |id, stuff|
- puts "ID".light_green + ": #{id}".white unless id.nil?
- f.puts "ID: #{id}" unless id.nil?
- stuff.each do |link, desc|
- puts "View".light_green + ": #{link.sub('http://www.metasploit.com/', 'http://www.rapid7.com/db/')}".white unless link.nil?
- f.puts "View: #{link.sub('http://www.metasploit.com/', 'http://www.rapid7.com/db/')}" unless link.nil?
- if not link.nil? and source.downcase == 'metasploit'
- puts "Github Link".light_green + ": https://raw.github.com/rapid7/metasploit-framework/master/#{link.sub('http://www.metasploit.com/', '').sub('/exploit/', '/exploits/').sub(/\/$/, '')}.rb".white
- f.puts "Github Link: https://raw.github.com/rapid7/metasploit-framework/master/#{link.sub('http://www.metasploit.com/', '').sub('/exploit/', '/exploits/').sub(/\/$/, '')}.rb"
- end
- puts "Exploit Description".light_green + ": \n#{desc}".white unless desc.nil?
- f.puts "Exploit Description: \n#{desc}" unless desc.nil?
- f.puts
- puts
- end
- end
- f.close
- else
- puts "No Results Found for ".light_red + "#{string}".white + " via Shodan Exploit Search".light_red + "!".white
- end
- puts
- when 5
- # Now download one of the exploits you found....
- id=options[:search].to_s
- source = options[:source].to_s
- results = shodan.sploit_download(id, source)
- if not results.nil?
- downloads = RESULTS + 'downloads/'
- Dir.mkdir(downloads) unless File.exists?(downloads) andFile.directory?(downloads)
- f=File.open(downloads + "#{source}-#{id}.code", 'w+')
- results.each do |k, v|
- if k == 'Exploit'
- puts "Saved to".light_green + ": #{downloads}#{source}-#{id}.code".white
- puts "#{k}".light_green + ": \n#{v}".white
- f.puts v
- else
- puts "#{k}".light_green + ": #{v}".white
- end
- end
- f.close
- else
- puts "No Download Results Found for ID".light_red + "#: #{id}".white
- end
- end
- else
- exit 666;
- end
- #EOF
Helpful for me, hope it is for you too!
Until next time, Enjoy!
Ahrefs
ReplyDeleteWhile we’re on the topic of SEO, I wanted to mention Ahrefs. Ahrefs is a tool that allows you to do keyword research to ensure you’re targeting the best keywords with the highest traffic and lowest difficulty to rank for.
While this tool isn’t free or cheap, they do offer a free two-week trial. Alternatively, you can use their competitors like Moz or SEMrush (who also have free trials, hint hint). Whichever one you choose, if you’re serious about ranking on Google, I highly recommend a keyword research tool. Without them, you only have access to Google Keyword Planner, which doesn’t really help you find the right keywords.