shodan 0.6.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +17 -8
- data/lib/shodan.rb +1 -0
- data/lib/shodan/shodan.rb +147 -0
- data/lib/shodan/version.rb +1 -1
- data/lib/test.rb +66 -0
- data/lib/test_exploits.rb +52 -0
- metadata +44 -57
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Visit the official Shodan API documentation at:
|
2
2
|
|
3
|
-
[
|
3
|
+
[https://developer.shodan.io](https://developer.shodan.io)
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -14,19 +14,32 @@ Before you can use the API, you need to have an API key.
|
|
14
14
|
|
15
15
|
[Get your API key here](http://www.shodanhq.com/api_doc)
|
16
16
|
|
17
|
-
Setup the
|
17
|
+
Setup the Shodan wrapper object:
|
18
18
|
|
19
19
|
require 'shodan'
|
20
20
|
|
21
|
-
api = Shodan::
|
21
|
+
api = Shodan::Shodan.new(MY_API_KEY)
|
22
22
|
|
23
23
|
Print a list of cisco-ios devices:
|
24
24
|
|
25
25
|
result = api.search("cisco-ios")
|
26
26
|
result['matches'].each{ |host|
|
27
|
-
puts host['
|
27
|
+
puts host['ip_str']
|
28
28
|
}
|
29
29
|
|
30
|
+
Print the 2nd page of results for the cisco-ios query:
|
31
|
+
|
32
|
+
result = api.search("cisco-ios", :page => 2)
|
33
|
+
result['matches'].each{ |host|
|
34
|
+
puts host['ip_str']
|
35
|
+
}
|
36
|
+
|
37
|
+
Find out how many results there are for "apache" and also return the top 5 organizations for the results:
|
38
|
+
|
39
|
+
result = api.count("apache", :facets => 'org:5')
|
40
|
+
puts "Total number of results: #{result['total']}"
|
41
|
+
puts result['facets']
|
42
|
+
|
30
43
|
Get all the information SHODAN has on the IP 217.140.75.46:
|
31
44
|
|
32
45
|
host = api.host('217.140.75.46')
|
@@ -41,7 +54,3 @@ To properly handle potential errors, you should wrap all requests in a try/excep
|
|
41
54
|
else
|
42
55
|
puts "Unknown error"
|
43
56
|
end
|
44
|
-
|
45
|
-
## Articles
|
46
|
-
|
47
|
-
* [Perl, Python and Ruby API libraries](http://www.surtri.com/2010/10/20/perl-python-ruby-api/)
|
data/lib/shodan.rb
CHANGED
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'cgi'
|
3
|
+
require 'json'
|
4
|
+
require 'openssl'
|
5
|
+
require 'net/http'
|
6
|
+
|
7
|
+
module Shodan
|
8
|
+
|
9
|
+
# The Shodan class interfaces with the official Shodan API.
|
10
|
+
# For more information on the API, please visit https://developer.shodan.io
|
11
|
+
#
|
12
|
+
# Author:: achillean (mailto:jmath@shodan.io)
|
13
|
+
#
|
14
|
+
# :title:Shodan::Shodan
|
15
|
+
class Shodan
|
16
|
+
attr_accessor :api_key
|
17
|
+
attr_accessor :base_url
|
18
|
+
attr_accessor :exploits
|
19
|
+
|
20
|
+
def initialize(api_key)
|
21
|
+
@api_key = api_key
|
22
|
+
@base_url = "https://api.shodan.io/"
|
23
|
+
@base_url_exploits = "https://exploits.shodan.io/api/"
|
24
|
+
|
25
|
+
@exploits = Exploits.new(self)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Internal method that sends out the HTTP request.
|
29
|
+
# Expects a webservice function (ex. 'search') name and a hash of arguments.
|
30
|
+
def request(type, func, args)
|
31
|
+
if type == "exploits"
|
32
|
+
base_url = @base_url_exploits
|
33
|
+
else
|
34
|
+
base_url = @base_url
|
35
|
+
end
|
36
|
+
|
37
|
+
# Convert the argument hash into a string
|
38
|
+
args_string = args.map{|k, v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"}.join("&")
|
39
|
+
|
40
|
+
# Craft the final request URL
|
41
|
+
url = "#{base_url}#{func}?key=#{@api_key}&#{args_string}"
|
42
|
+
|
43
|
+
# Send the request
|
44
|
+
puts url
|
45
|
+
uri = URI.parse(url)
|
46
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
47
|
+
http.use_ssl = true
|
48
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
49
|
+
response = http.get(uri.request_uri)
|
50
|
+
|
51
|
+
# Convert the JSON data into a native Ruby hash
|
52
|
+
data = JSON.parse(response.body)
|
53
|
+
|
54
|
+
# Raise an error if something went wrong
|
55
|
+
if data.has_key? 'error'
|
56
|
+
raise data['error']
|
57
|
+
end
|
58
|
+
|
59
|
+
return data
|
60
|
+
end
|
61
|
+
|
62
|
+
# Get all available information on an IP.
|
63
|
+
#
|
64
|
+
# Arguments:
|
65
|
+
# ip - host IP (string)
|
66
|
+
#
|
67
|
+
# Returns a hash containing the host information
|
68
|
+
def host(ip)
|
69
|
+
return request('shodan', "shodan/host/#{ip}", {})
|
70
|
+
end
|
71
|
+
|
72
|
+
# Perform a search on Shodan.
|
73
|
+
#
|
74
|
+
# Arguments:
|
75
|
+
# query - search query; same format as the website (string)
|
76
|
+
#
|
77
|
+
# Returns a hash containing the search results
|
78
|
+
def search(query, params={})
|
79
|
+
params[:query] = query
|
80
|
+
return request('shodan', 'shodan/host/search', params)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Find how many results there are for a search term.
|
84
|
+
#
|
85
|
+
# Arguments:
|
86
|
+
# query - search query; same format as the website (string)
|
87
|
+
#
|
88
|
+
# Returns a hash containing the total number of search results
|
89
|
+
def count(query, params={})
|
90
|
+
params[:query] = query
|
91
|
+
return request('shodan', 'shodan/host/count', params)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns information about the current API key.
|
95
|
+
def info()
|
96
|
+
return request('shodan', 'api-info', {})
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# The Exploits class shouldn't be used independently,
|
101
|
+
# as it depends on the Shodan class.
|
102
|
+
#
|
103
|
+
# Author:: achillean (mailto:jmath@shodan.io)
|
104
|
+
#
|
105
|
+
# :title:Shodan::Exploits
|
106
|
+
class Exploits
|
107
|
+
attr_accessor :api
|
108
|
+
|
109
|
+
def initialize(api)
|
110
|
+
@api = api
|
111
|
+
end
|
112
|
+
|
113
|
+
# Search the Shodan Exploits archive for exploits.
|
114
|
+
#
|
115
|
+
# Arguments:
|
116
|
+
# query -- Search terms
|
117
|
+
#
|
118
|
+
# Optional arguments:
|
119
|
+
# facets -- A comma-separated list of properties to get summary information on.
|
120
|
+
# page -- The page number to page through results 100 exploits at a time.
|
121
|
+
#
|
122
|
+
# Returns:
|
123
|
+
# A dictionary with 2 main items: matches (list) and total (int).
|
124
|
+
# Please visit https://developer.shodan.io/api/exploit-specification for up-to-date information on what an Exploit result contains.
|
125
|
+
def search(query, params={})
|
126
|
+
params[:query] = query
|
127
|
+
return @api.request('exploits', 'search', params)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Search the Shodan Exploits archive for exploits but don't return results, only the number of matches.
|
131
|
+
#
|
132
|
+
# Arguments:
|
133
|
+
# query -- Search terms
|
134
|
+
#
|
135
|
+
# Optional arguments:
|
136
|
+
# facets -- A comma-separated list of properties to get summary information on.
|
137
|
+
#
|
138
|
+
# Returns:
|
139
|
+
# A dictionary with 1 main item: total (int).
|
140
|
+
def count(query, params={})
|
141
|
+
params[:query] = query
|
142
|
+
return @api.request('exploits', 'count', params)
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
data/lib/shodan/version.rb
CHANGED
data/lib/test.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# shodan_ips.py
|
4
|
+
# Search SHODAN and print a list of IPs matching the query
|
5
|
+
#
|
6
|
+
# Author: achillean
|
7
|
+
$:.unshift File.dirname(__FILE__)
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'shodan'
|
11
|
+
|
12
|
+
# Configuration
|
13
|
+
API_KEY = "eMLZhC7qt42If0U8ndfZTlCWSSxFYass"
|
14
|
+
|
15
|
+
# Input validation
|
16
|
+
if ARGV.length == 0
|
17
|
+
puts "Usage: ./shodan_ips.rb <search query>"
|
18
|
+
exit 1
|
19
|
+
end
|
20
|
+
|
21
|
+
begin
|
22
|
+
# Setup the API
|
23
|
+
api = Shodan::Shodan.new(API_KEY)
|
24
|
+
|
25
|
+
# Perform the search
|
26
|
+
query = ARGV.join(" ")
|
27
|
+
|
28
|
+
puts "\n--- shodan.info ---"
|
29
|
+
result = api.info()
|
30
|
+
puts result
|
31
|
+
|
32
|
+
puts '--- shodan.search ---'
|
33
|
+
result = api.search(query, :page => 2, :facets => 'port')
|
34
|
+
|
35
|
+
# Loop through the matches and print the IPs
|
36
|
+
puts "Total: #{result['total']}"
|
37
|
+
result['matches'][0..10].each{ |host|
|
38
|
+
puts host['ip_str']
|
39
|
+
}
|
40
|
+
|
41
|
+
puts "\nTop 10 Ports"
|
42
|
+
result['facets']['port'].each{ |facet|
|
43
|
+
puts "#{facet['value']}: #{facet['count']}"
|
44
|
+
}
|
45
|
+
|
46
|
+
puts "\n--- shodan.count ---"
|
47
|
+
result = api.count(query, :facets => 'port')
|
48
|
+
|
49
|
+
# Loop through the matches and print the IPs
|
50
|
+
puts "Total: #{result['total']}"
|
51
|
+
result['matches'][0..10].each{ |host|
|
52
|
+
puts host['ip_str']
|
53
|
+
}
|
54
|
+
|
55
|
+
puts "\nTop 10 Ports"
|
56
|
+
result['facets']['port'].each{ |facet|
|
57
|
+
puts "#{facet['value']}: #{facet['count']}"
|
58
|
+
}
|
59
|
+
|
60
|
+
puts "\n--- shodan.host ---"
|
61
|
+
result = api.host('217.140.75.46')
|
62
|
+
puts result
|
63
|
+
rescue Exception => e
|
64
|
+
puts "Error: #{e.to_s}"
|
65
|
+
exit 1
|
66
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# shodan_ips.py
|
4
|
+
# Search SHODAN and print a list of IPs matching the query
|
5
|
+
#
|
6
|
+
# Author: achillean
|
7
|
+
$:.unshift File.dirname(__FILE__)
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'shodan'
|
11
|
+
|
12
|
+
# Configuration
|
13
|
+
API_KEY = "eMLZhC7qt42If0U8ndfZTlCWSSxFYass"
|
14
|
+
|
15
|
+
# Input validation
|
16
|
+
if ARGV.length == 0
|
17
|
+
puts "Usage: ./shodan_ips.rb <search query>"
|
18
|
+
exit 1
|
19
|
+
end
|
20
|
+
|
21
|
+
begin
|
22
|
+
# Setup the API
|
23
|
+
api = Shodan::Shodan.new(API_KEY)
|
24
|
+
|
25
|
+
# Perform the search
|
26
|
+
query = ARGV.join(" ")
|
27
|
+
|
28
|
+
puts "\n--- shodan.exploits.search ---"
|
29
|
+
result = api.exploits.search(query, :page => 2, :facets => 'author')
|
30
|
+
|
31
|
+
# Loop through the matches and print the IPs
|
32
|
+
puts "Total: #{result['total']}"
|
33
|
+
result['matches'][0..10].each{ |exploit|
|
34
|
+
puts exploit
|
35
|
+
}
|
36
|
+
|
37
|
+
puts result['facets']
|
38
|
+
|
39
|
+
puts "\n--- shodan.exploits.search ---"
|
40
|
+
result = api.exploits.count(query, :facets => 'author')
|
41
|
+
|
42
|
+
# Loop through the matches and print the IPs
|
43
|
+
puts "Total: #{result['total']}"
|
44
|
+
result['matches'][0..10].each{ |exploit|
|
45
|
+
puts exploit
|
46
|
+
}
|
47
|
+
|
48
|
+
puts result['facets']
|
49
|
+
rescue Exception => e
|
50
|
+
puts "Error: #{e.to_s}"
|
51
|
+
exit 1
|
52
|
+
end
|
metadata
CHANGED
@@ -1,88 +1,75 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: shodan
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 6
|
9
|
-
- 1
|
10
|
-
version: 0.6.1
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- John Matherly
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
dependencies:
|
21
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2014-02-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
22
15
|
name: json
|
23
|
-
|
24
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
25
17
|
none: false
|
26
|
-
requirements:
|
27
|
-
- -
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
hash: 11
|
30
|
-
segments:
|
31
|
-
- 1
|
32
|
-
- 4
|
33
|
-
- 6
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
34
21
|
version: 1.4.6
|
35
22
|
type: :runtime
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.4.6
|
30
|
+
description: ! ' A Ruby library to interact with the Shodan API.
|
40
31
|
|
32
|
+
'
|
33
|
+
email: jmath@shodan.io
|
34
|
+
executables: []
|
41
35
|
extensions: []
|
42
|
-
|
43
|
-
extra_rdoc_files:
|
36
|
+
extra_rdoc_files:
|
44
37
|
- LICENSE
|
45
38
|
- README.md
|
46
|
-
files:
|
39
|
+
files:
|
47
40
|
- README.md
|
48
41
|
- LICENSE
|
49
42
|
- HISTORY.md
|
43
|
+
- lib/test_exploits.rb
|
44
|
+
- lib/test.rb
|
45
|
+
- lib/shodan.rb
|
50
46
|
- lib/shodan/version.rb
|
47
|
+
- lib/shodan/shodan.rb
|
51
48
|
- lib/shodan/api.rb
|
52
|
-
- lib/shodan.rb
|
53
|
-
has_rdoc: true
|
54
49
|
homepage: http://github.com/achillean/shodan-ruby
|
55
50
|
licenses: []
|
56
|
-
|
57
51
|
post_install_message:
|
58
|
-
rdoc_options:
|
52
|
+
rdoc_options:
|
59
53
|
- --charset=UTF-8
|
60
|
-
require_paths:
|
54
|
+
require_paths:
|
61
55
|
- lib
|
62
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
57
|
none: false
|
64
|
-
requirements:
|
65
|
-
- -
|
66
|
-
- !ruby/object:Gem::Version
|
67
|
-
|
68
|
-
|
69
|
-
- 0
|
70
|
-
version: "0"
|
71
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
63
|
none: false
|
73
|
-
requirements:
|
74
|
-
- -
|
75
|
-
- !ruby/object:Gem::Version
|
76
|
-
|
77
|
-
segments:
|
78
|
-
- 0
|
79
|
-
version: "0"
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
80
68
|
requirements: []
|
81
|
-
|
82
69
|
rubyforge_project:
|
83
|
-
rubygems_version: 1.
|
70
|
+
rubygems_version: 1.8.23
|
84
71
|
signing_key:
|
85
72
|
specification_version: 3
|
86
|
-
summary: A Ruby library to interact with the
|
73
|
+
summary: A Ruby library to interact with the Shodan API.
|
87
74
|
test_files: []
|
88
|
-
|
75
|
+
has_rdoc:
|