shodan 0.6.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|