lib/mihari/analyzers/shodan.rb in mihari-5.1.1 vs lib/mihari/analyzers/shodan.rb in mihari-5.1.2
- old
+ new
@@ -8,25 +8,27 @@
option :interval, default: proc { 0 }
# @return [String, nil]
attr_reader :api_key
+ # @return [Integer]
+ attr_reader :interval
+
+ # @return [String]
+ attr_reader :query
+
def initialize(*args, **kwargs)
super(*args, **kwargs)
@api_key = kwargs[:api_key] || Mihari.config.shodan_api_key
end
def artifacts
results = search
- return [] unless results || results.empty?
+ return [] if results.empty?
- results = results.map { |result| Structs::Shodan::Result.from_dynamic!(result) }
- matches = results.map { |result| result.matches || [] }.flatten
-
- uniq_matches = matches.uniq(&:ip_str)
- uniq_matches.map { |match| build_artifact(match, matches) }
+ results.map { |result| result.to_artifacts(source) }.flatten.uniq(&:data)
end
private
PAGE_SIZE = 100
@@ -40,33 +42,29 @@
end
#
# Search with pagination
#
- # @param [String] query
# @param [Integer] page
#
- # @return [Hash]
+ # @return [Structs::Shodan::Result]
#
- def search_with_page(query, page: 1)
+ def search_with_page(page: 1)
client.search(query, page: page)
end
#
# Search
#
- # @return [Array<Hash>]
+ # @return [Array<Structs::Shodan::Result>]
#
def search
responses = []
(1..Float::INFINITY).each do |page|
- res = search_with_page(query, page: page)
-
- break unless res
-
+ res = search_with_page(page: page)
responses << res
- break if res["total"].to_i <= page * PAGE_SIZE
+ break if res.total <= page * PAGE_SIZE
# sleep #{interval} seconds to avoid the rate limitation (if it is set)
sleep interval
rescue JSON::ParserError
# ignore JSON::ParserError
@@ -75,83 +73,17 @@
end
responses
end
#
- # Collect metadata from matches
- #
- # @param [Array<Structs::Shodan::Match>] matches
- # @param [String] ip
- #
- # @return [Array<Hash>]
- #
- def collect_metadata_by_ip(matches, ip)
- matches.select { |match| match.ip_str == ip }.map(&:metadata)
- end
-
- #
- # Collect ports from matches
- #
- # @param [Array<Structs::Shodan::Match>] matches
- # @param [String] ip
- #
- # @return [Array<String>]
- #
- def collect_ports_by_ip(matches, ip)
- matches.select { |match| match.ip_str == ip }.map(&:port)
- end
-
- #
- # Collect hostnames from matches
- #
- # @param [Array<Structs::Shodan::Match>] matches
- # @param [String] ip
- #
- # @return [Array<String>]
- #
- def collect_hostnames_by_ip(matches, ip)
- matches.select { |match| match.ip_str == ip }.map(&:hostnames).flatten.uniq
- end
-
- #
# Build an artifact from a Shodan search API response
#
# @param [Structs::Shodan::Match] match
# @param [Array<Structs::Shodan::Match>] matches
#
# @return [Artifact]
#
def build_artifact(match, matches)
- as = nil
- as = AutonomousSystem.new(asn: normalize_asn(match.asn)) unless match.asn.nil?
-
- geolocation = nil
- if !match.location.country_name.nil? && !match.location.country_code.nil?
- geolocation = Geolocation.new(
- country: match.location.country_name,
- country_code: match.location.country_code
- )
- end
-
- metadata = collect_metadata_by_ip(matches, match.ip_str)
-
- ports = collect_ports_by_ip(matches, match.ip_str).map do |port|
- Port.new(port: port)
- end
-
- reverse_dns_names = collect_hostnames_by_ip(matches, match.ip_str).map do |name|
- ReverseDnsName.new(name: name)
- end
-
- Artifact.new(
- data: match.ip_str,
- source: source,
- metadata: metadata,
- autonomous_system: as,
- geolocation: geolocation,
- ports: ports,
- reverse_dns_names: reverse_dns_names
- )
end
end
end
end