shodanz 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +280 -0
- data/Rakefile +6 -0
- data/examples/debug.rb +9 -0
- data/examples/top_10_countries_running.rb +66 -0
- data/lib/shodanz/api.rb +32 -0
- data/lib/shodanz/apis/exploits.rb +85 -0
- data/lib/shodanz/apis/rest.rb +214 -0
- data/lib/shodanz/apis/streaming.rb +187 -0
- data/lib/shodanz/client.rb +22 -0
- data/lib/shodanz/version.rb +3 -0
- data/lib/shodanz.rb +14 -0
- data/shodanz.gemspec +28 -0
- metadata +133 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7a5b350b66032bb9e169a2adf65271c7841d8e0f
|
4
|
+
data.tar.gz: 2b57aabeeec1acf297a96e171710f15a0758dbd1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d580381f2f606eb7c59ba1ee9019086c526d0f4a750de8317ecf8e2b3d1098201c52fffc30e6543adec32c4dc9d96b880d0b0af7315ddb47cf5b54f791fae68d
|
7
|
+
data.tar.gz: 25d2ffa6642f68d92a394be99fa16f63668777b13e9eb8198f21190443237791251eee5e0b4f3cd7bd6d8df1b8cc99bc54c76b42b30bbdaebf0579586d28f170
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at kgruber1@emich.edu. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Kent Gruber
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,280 @@
|
|
1
|
+
# Shodanz
|
2
|
+
|
3
|
+
A modern Ruby [gem](https://rubygems.org/) for [Shodan](https://www.shodan.io/), the world's first search engine for Internet-connected devices.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
$ gem install shodanz
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
require "shodanz"
|
13
|
+
|
14
|
+
client = Shodanz.client.new
|
15
|
+
```
|
16
|
+
|
17
|
+
## REST API
|
18
|
+
|
19
|
+
The REST API provides methods to search Shodan, look up hosts, get summary information on queries and a variety of utility methods to make developing easier. Refer to the [REST API](https://developer.shodan.io/api) documentation for more ideas on how to use it.
|
20
|
+
|
21
|
+
### Shodan Search Methods
|
22
|
+
|
23
|
+
Search'n for stuff, are 'ya?
|
24
|
+
|
25
|
+
#### Host Information
|
26
|
+
|
27
|
+
Returns all services that have been found on the given host IP.
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
client.rest_api.host("8.8.8.8") # Default
|
31
|
+
client.rest_api.host("8.8.8.8", history: true) # All historical banners should be returned.
|
32
|
+
client.rest_api.host("8.8.8.8", minify: true) # Only return the list of ports and the general host information, no banners.
|
33
|
+
```
|
34
|
+
|
35
|
+
#### Host Search
|
36
|
+
|
37
|
+
Search Shodan using the same query syntax as the website and use facets to get summary information for different properties.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
client.rest_api.host_search("mongodb")
|
41
|
+
client.rest_api.host_search("nginx")
|
42
|
+
client.rest_api.host_search("apache", after: "1/12/16")
|
43
|
+
client.rest_api.host_search("ssh", port: 22, page: 1)
|
44
|
+
client.rest_api.host_search("ssh", port: 22, page: 2)
|
45
|
+
client.rest_api.host_search("ftp", port: 21, facets: { link: "Ethernet or modem" })
|
46
|
+
```
|
47
|
+
|
48
|
+
#### Search Shodan without Results
|
49
|
+
|
50
|
+
This method behaves identical to `host_search` with the only difference that this method does not return any host results, it only returns the total number of results that matched the query and any facet information that was requested. As a result this method does not consume query credits.
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
client.rest_api.host_count("apache")
|
54
|
+
client.rest_api.host_count("apache", country: "US")
|
55
|
+
client.rest_api.host_count("apache", country: "US", state: "MI")
|
56
|
+
client.rest_api.host_count("apache", country: "US", state: "MI", city: "Detroit")
|
57
|
+
client.rest_api.host_count("apache", country: "US", state: "MI", city: "Detroit")
|
58
|
+
client.rest_api.host_count("nginx". facets: { country: 5 })
|
59
|
+
client.rest_api.host_count("apache". facets: { country: 5 })
|
60
|
+
```
|
61
|
+
|
62
|
+
#### Scan Targets
|
63
|
+
|
64
|
+
Use this method to request Shodan to crawl an IP or netblock.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
client.rest_api.scan("8.8.8.8")
|
68
|
+
```
|
69
|
+
|
70
|
+
#### Crawl Internet for Port
|
71
|
+
|
72
|
+
Use this method to request Shodan to crawl the Internet for a specific port.
|
73
|
+
|
74
|
+
This method is restricted to security researchers and companies with a Shodan Data license. To apply for access to this method as a researcher, please email `jmath@shodan.io` with information about your project. Access is restricted to prevent abuse.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
client.rest_api.crawl_for(port: 80, protocol: "http")
|
78
|
+
```
|
79
|
+
|
80
|
+
#### List Community Queries
|
81
|
+
|
82
|
+
Use this method to obtain a list of search queries that users have saved in Shodan.
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
client.rest_api.community_queries
|
86
|
+
client.rest_api.community_queries(page: 2)
|
87
|
+
client.rest_api.community_queries(sort: "votes")
|
88
|
+
client.rest_api.community_queries(sort: "votes", page: 2)
|
89
|
+
client.rest_api.community_queries(order: "asc")
|
90
|
+
client.rest_api.community_queries(order: "desc")
|
91
|
+
```
|
92
|
+
|
93
|
+
#### Search Community Queries
|
94
|
+
|
95
|
+
Use this method to search the directory of search queries that users have saved in Shodan.
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
client.rest_api.search_for_community_query("the best")
|
99
|
+
client.rest_api.search_for_community_query("the best", page: 2)
|
100
|
+
```
|
101
|
+
|
102
|
+
#### Popular Community Query Tags
|
103
|
+
|
104
|
+
Use this method to obtain a list of popular tags for the saved search queries in Shodan.
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
client.rest_api.popular_query_tags
|
108
|
+
client.rest_api.popular_query_tags(20)
|
109
|
+
```
|
110
|
+
|
111
|
+
#### Protocols
|
112
|
+
|
113
|
+
This method returns an object containing all the protocols that can be used when launching an Internet scan.
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
client.rest_api.protocols
|
117
|
+
```
|
118
|
+
|
119
|
+
#### Ports
|
120
|
+
|
121
|
+
This method returns a list of port numbers that the Shodan crawlers are looking for.
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
client.rest_api.ports
|
125
|
+
```
|
126
|
+
|
127
|
+
#### Account Profile
|
128
|
+
|
129
|
+
Returns information about the Shodan account linked to this API key.
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
client.rest_api.profile
|
133
|
+
```
|
134
|
+
|
135
|
+
#### DNS Lookup
|
136
|
+
|
137
|
+
Look up the IP address for the provided list of hostnames.
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
client.rest_api.resolve("google.com")
|
141
|
+
client.rest_api.resolve("google.com", "bing.com")
|
142
|
+
```
|
143
|
+
|
144
|
+
#### Reverse DNS Lookup
|
145
|
+
|
146
|
+
Look up the hostnames that have been defined for the given list of IP addresses.
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
client.rest_api.reverse_lookup("74.125.227.230")
|
150
|
+
client.rest_api.reverse_lookup("74.125.227.230", "204.79.197.200")
|
151
|
+
```
|
152
|
+
|
153
|
+
#### HTTP Headers
|
154
|
+
|
155
|
+
Shows the HTTP headers that your client sends when connecting to a webserver.
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
client.rest_api.http_headers
|
159
|
+
```
|
160
|
+
|
161
|
+
#### Your IP Address
|
162
|
+
|
163
|
+
Get your current IP address as seen from the Internet.
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
client.rest_api.my_ip
|
167
|
+
```
|
168
|
+
|
169
|
+
#### Honeypot Score
|
170
|
+
|
171
|
+
Calculates a honeypot probability score ranging from 0 (not a honeypot) to 1.0 (is a honeypot).
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
client.rest_api.honeypot_score('8.8.8.8')
|
175
|
+
```
|
176
|
+
|
177
|
+
#### API Plan Information
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
client.rest_api.info
|
181
|
+
```
|
182
|
+
|
183
|
+
### Streaming API
|
184
|
+
|
185
|
+
The Streaming API is an HTTP-based service that returns a real-time stream of data collected by Shodan. Refer to the [Streaming API](https://developer.shodan.io/api/stream) documentation for more ideas on how to use it.
|
186
|
+
#### Banners
|
187
|
+
|
188
|
+
This stream provides ALL of the data that Shodan collects. Use this stream if you need access to everything and/ or want to store your own Shodan database locally. If you only care about specific ports, please use the Ports stream.
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
client.streaming_api.banners do |data|
|
192
|
+
# do something with banner data
|
193
|
+
puts data
|
194
|
+
end
|
195
|
+
```
|
196
|
+
|
197
|
+
#### Banners Filtered by ASN
|
198
|
+
|
199
|
+
This stream provides a filtered, bandwidth-saving view of the Banners stream in case you are only interested in devices located in certain ASNs.
|
200
|
+
|
201
|
+
```ruby
|
202
|
+
client.streaming_api.banners_within_asns(3303, 32475) do |data|
|
203
|
+
# do something with banner data
|
204
|
+
puts data
|
205
|
+
end
|
206
|
+
```
|
207
|
+
|
208
|
+
#### Banners Filtered by Country
|
209
|
+
|
210
|
+
This stream provides a filtered, bandwidth-saving view of the Banners stream in case you are only interested in devices located in certain countries.
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
client.streaming_api.banners_within_countries("DE", "US", "JP") do |data|
|
214
|
+
# do something with banner data
|
215
|
+
puts data
|
216
|
+
end
|
217
|
+
```
|
218
|
+
|
219
|
+
#### Banners Filtered by Ports
|
220
|
+
|
221
|
+
Only returns banner data for the list of specified ports. This stream provides a filtered, bandwidth-saving view of the Banners stream in case you are only interested in a specific list of ports.
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
client.streaming_api.banners_on_ports(21, 22, 80) do |data|
|
225
|
+
# do something with banner data
|
226
|
+
puts data
|
227
|
+
end
|
228
|
+
```
|
229
|
+
|
230
|
+
#### Banners by Network Alerts
|
231
|
+
|
232
|
+
Subscribe to banners discovered on all IP ranges described in the network alerts.
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
client.streaming_api.alerts do |data|
|
236
|
+
# do something with banner data
|
237
|
+
puts data
|
238
|
+
end
|
239
|
+
```
|
240
|
+
|
241
|
+
#### Banner Filtered by Alert ID
|
242
|
+
|
243
|
+
Subscribe to banners discovered on the IP range defined in a specific network alert.
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
client.streaming_api.alert("HKVGAIRWD79Z7W2T") do |data|
|
247
|
+
# do something with banner data
|
248
|
+
puts data
|
249
|
+
end
|
250
|
+
```
|
251
|
+
|
252
|
+
### Exploits API
|
253
|
+
|
254
|
+
The Exploits API provides access to several exploit/ vulnerability data sources. Refer to the [Exploits API](https://developer.shodan.io/api/exploits/rest) documentation for more ideas on how to use it.
|
255
|
+
|
256
|
+
#### Search
|
257
|
+
|
258
|
+
Search across a variety of data sources for exploits and use facets to get summary information.
|
259
|
+
|
260
|
+
```ruby
|
261
|
+
client.exploits_api.search("python") # Search for Snek vulns.
|
262
|
+
client.exploits_api.search(post: 22) # Port number for the affected service if the exploit is remote.
|
263
|
+
client.exploits_api.search(type: "shellcode") # A category of exploit to search for.
|
264
|
+
client.exploits_api.search(osvdb: "100007") # Open Source Vulnerability Database ID for the exploit.
|
265
|
+
```
|
266
|
+
|
267
|
+
#### Count
|
268
|
+
|
269
|
+
This method behaves identical to the Exploits API `search` method with the difference that it doesn't return any results.
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
client.exploits_api.count("python") # Count Snek vulns.
|
273
|
+
client.exploits_api.count(port: 22) # Port number for the affected service if the exploit is remote.
|
274
|
+
client.exploits_api.search(type: "shellcode") # A category of exploit to search for.
|
275
|
+
client.exploits_api.count(osvdb: "100007") # Open Source Vulnerability Database ID for the exploit.
|
276
|
+
```
|
277
|
+
|
278
|
+
## License
|
279
|
+
|
280
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/examples/debug.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
require 'shodanz'
|
3
|
+
require 'command_lion'
|
4
|
+
require 'yaml'
|
5
|
+
require 'pry'
|
6
|
+
|
7
|
+
module Top10
|
8
|
+
|
9
|
+
@rest_api = Shodanz.api.rest.new
|
10
|
+
|
11
|
+
def self.check(product)
|
12
|
+
begin
|
13
|
+
@rest_api.host_count(product: product, facets: { country: 10 })["facets"]["country"].collect { |x| x.values }.to_h.invert
|
14
|
+
rescue
|
15
|
+
puts "Unable to succesffully check the Shodan API."
|
16
|
+
exit 1
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
CommandLion::App.run do
|
23
|
+
name "Top 10 Countires Running a Product Using Shodan"
|
24
|
+
|
25
|
+
command :product do
|
26
|
+
description "Search for this given product."
|
27
|
+
type :string
|
28
|
+
flag "--product"
|
29
|
+
|
30
|
+
# Check is Shodan Enviroemnt Variable Set
|
31
|
+
before do
|
32
|
+
unless ENV['SHODAN_API_KEY']
|
33
|
+
puts "Need to set the 'SHODAN_API_KEY' enviroment variable before using this app!"
|
34
|
+
exit 1 # [ ╯´・ω・]╯︵┸━┸)
|
35
|
+
end
|
36
|
+
if argument.empty?
|
37
|
+
puts "What kind of nonsense is this?! You need to provide some argument..."
|
38
|
+
exit 1 # [ ╯ ゚▽゚]╯︵┻━┻)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Do stuff.
|
43
|
+
action do
|
44
|
+
result = Top10.check(argument)
|
45
|
+
if options[:json].given?
|
46
|
+
puts JSON.pretty_generate(result)
|
47
|
+
elsif options[:yaml].given?
|
48
|
+
puts result.to_yaml
|
49
|
+
else
|
50
|
+
result.each do |country, count|
|
51
|
+
puts "#{country}\t#{count}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
option :json do
|
57
|
+
description "Use JSON as the format to output to STDOUT."
|
58
|
+
flag "--json"
|
59
|
+
end
|
60
|
+
|
61
|
+
option :yaml do
|
62
|
+
description "Use YAML as the format to output to STDOUT."
|
63
|
+
flag "--yaml"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/shodanz/api.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative "apis/rest.rb"
|
2
|
+
require_relative "apis/streaming.rb"
|
3
|
+
require_relative "apis/exploits.rb"
|
4
|
+
|
5
|
+
module Shodanz
|
6
|
+
# There are 2 APIs for accessing Shodan: the REST API
|
7
|
+
# and the Streaming API. The REST API provides methods
|
8
|
+
# to search Shodan, look up hosts, get summary information
|
9
|
+
# on queries and a variety of utility methods to make
|
10
|
+
# developing easier. The Streaming API provides a raw,
|
11
|
+
# real-time feed of the data that Shodan is currently
|
12
|
+
# collecting. There are several feeds that can be subscribed
|
13
|
+
# to, but the data can't be searched or otherwise interacted
|
14
|
+
# with; it's a live feed of data meant for large-scale
|
15
|
+
# consumption of Shodan's information.
|
16
|
+
module API
|
17
|
+
# REST API class.
|
18
|
+
def self.rest
|
19
|
+
REST
|
20
|
+
end
|
21
|
+
|
22
|
+
# Streaming API class.
|
23
|
+
def self.streaming
|
24
|
+
Streaming
|
25
|
+
end
|
26
|
+
|
27
|
+
# Exploits API class.
|
28
|
+
def self.exploits
|
29
|
+
Exploits
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Shodanz
|
2
|
+
|
3
|
+
module API
|
4
|
+
# The Exploits API provides access to several exploit
|
5
|
+
# and vulnerability data sources. At the moment, it
|
6
|
+
# searches across the following:
|
7
|
+
# - Exploit DB
|
8
|
+
# - Metasploit
|
9
|
+
# - Common Vulnerabilities and Exposures (CVE)
|
10
|
+
#
|
11
|
+
# @author Kent 'picat' Gruber
|
12
|
+
class Exploits
|
13
|
+
attr_accessor :key
|
14
|
+
|
15
|
+
# The path to the REST API endpoint.
|
16
|
+
URL = "https://exploits.shodan.io/api/"
|
17
|
+
|
18
|
+
# @param key [String] SHODAN API key, defaulted to the *SHODAN_API_KEY* enviroment variable.
|
19
|
+
def initialize(key: ENV['SHODAN_API_KEY'])
|
20
|
+
self.key = key
|
21
|
+
warn "No key has been found or provided!" unless self.key?
|
22
|
+
end
|
23
|
+
|
24
|
+
# Check if there's an API key.
|
25
|
+
def key?
|
26
|
+
return true if @key; false
|
27
|
+
end
|
28
|
+
|
29
|
+
# Search across a variety of data sources for exploits and
|
30
|
+
# use facets to get summary information.
|
31
|
+
# == Example
|
32
|
+
# api.search("SQL", port: 443)
|
33
|
+
# api.search(port: 22)
|
34
|
+
# api.search(type: "dos")
|
35
|
+
def search(query = "", facets: {}, page: 1, **params)
|
36
|
+
params[:query] = query
|
37
|
+
params = turn_into_query(params)
|
38
|
+
facets = turn_into_facets(facets)
|
39
|
+
params[:page] = page
|
40
|
+
get("search", params.merge(facets))
|
41
|
+
end
|
42
|
+
|
43
|
+
# This method behaves identical to the "/search" method with
|
44
|
+
# the difference that it doesn't return any results.
|
45
|
+
# == Example
|
46
|
+
# api.count(type: "dos")
|
47
|
+
def count(query = "", facets: {}, page: 1, **params)
|
48
|
+
params[:query] = query
|
49
|
+
params = turn_into_query(params)
|
50
|
+
facets = turn_into_facets(params)
|
51
|
+
params[:page] = page
|
52
|
+
get("count", params.merge(facets))
|
53
|
+
end
|
54
|
+
|
55
|
+
# Perform a direct GET HTTP request to the REST API.
|
56
|
+
def get(path, **params)
|
57
|
+
resp = Unirest.get "#{URL}#{path}?key=#{@key}", parameters: params
|
58
|
+
raise resp.body["error"] if resp.code != 200 and resp.body.has_key?("error")
|
59
|
+
resp.body
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def turn_into_query(params)
|
65
|
+
filters = params.reject { |key, value| key == :query }
|
66
|
+
filters.each do |key, value|
|
67
|
+
params[:query] << " #{key}:#{value}"
|
68
|
+
end
|
69
|
+
params.select { |key, value| key == :query }
|
70
|
+
end
|
71
|
+
|
72
|
+
def turn_into_facets(facets)
|
73
|
+
filters = facets.reject { |key, value| key == :facets }
|
74
|
+
facets[:facets] = []
|
75
|
+
filters.each do |key, value|
|
76
|
+
facets[:facets] << "#{key}:#{value}"
|
77
|
+
end
|
78
|
+
facets[:facets] = facets[:facets].join(",")
|
79
|
+
facets.select { |key, value| key == :facets }
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
module Shodanz
|
2
|
+
|
3
|
+
module API
|
4
|
+
# The REST API provides methods to search Shodan, look up
|
5
|
+
# hosts, get summary information on queries and a variety
|
6
|
+
# of other utilities. This requires you to have an API key
|
7
|
+
# which you can get from Shodan.
|
8
|
+
# @author Kent 'picat' Gruber
|
9
|
+
class REST
|
10
|
+
attr_accessor :key
|
11
|
+
|
12
|
+
# The path to the REST API endpoint.
|
13
|
+
URL = "https://api.shodan.io/"
|
14
|
+
|
15
|
+
# @param key [String] SHODAN API key, defaulted to the *SHODAN_API_KEY* enviroment variable.
|
16
|
+
def initialize(key: ENV['SHODAN_API_KEY'])
|
17
|
+
self.key = key
|
18
|
+
warn "No key has been found or provided!" unless self.key?
|
19
|
+
end
|
20
|
+
|
21
|
+
# Check if there's an API key.
|
22
|
+
def key?
|
23
|
+
return true if @key; false
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns all services that have been found on the given host IP.
|
27
|
+
# @param ip [String]
|
28
|
+
# @option params [Hash]
|
29
|
+
# @return [Hash]
|
30
|
+
# == Examples
|
31
|
+
# # Typical usage.
|
32
|
+
# rest_api.host("8.8.8.8")
|
33
|
+
#
|
34
|
+
# # All historical banners should be returned.
|
35
|
+
# rest_api.host("8.8.8.8", history: true)
|
36
|
+
#
|
37
|
+
# # Only return the list of ports and the general host information, no banners.
|
38
|
+
# rest_api.host("8.8.8.8", minify: true)
|
39
|
+
def host(ip, **params)
|
40
|
+
get("shodan/host/#{ip}", params)
|
41
|
+
end
|
42
|
+
|
43
|
+
# This method behaves identical to "/shodan/host/search" with the only
|
44
|
+
# difference that this method does not return any host results, it only
|
45
|
+
# returns the total number of results that matched the query and any
|
46
|
+
# facet information that was requested. As a result this method does
|
47
|
+
# not consume query credits.
|
48
|
+
# == Examples
|
49
|
+
# rest_api.host_count("apache")
|
50
|
+
# rest_api.host_count("apache", country: "US")
|
51
|
+
# rest_api.host_count("apache", country: "US", state: "MI")
|
52
|
+
# rest_api.host_count("apache", country: "US", state: "MI", city: "Detroit")
|
53
|
+
def host_count(query = "", facets: {}, **params)
|
54
|
+
params[:query] = query
|
55
|
+
params = turn_into_query(params)
|
56
|
+
facets = turn_into_facets(facets)
|
57
|
+
get("shodan/host/count", params.merge(facets))
|
58
|
+
end
|
59
|
+
|
60
|
+
# Search Shodan using the same query syntax as the website and use facets
|
61
|
+
# to get summary information for different properties.
|
62
|
+
# == Example
|
63
|
+
# rest_api.host_search("apache", country: "US", facets: { city: "Detroit" }, page: 1, minify: false)
|
64
|
+
def host_search(query = "", facets: {}, page: 1, minify: true, **params)
|
65
|
+
params[:query] = query
|
66
|
+
params = turn_into_query(params)
|
67
|
+
facets = turn_into_facets(facets)
|
68
|
+
params[:page] = page
|
69
|
+
params[:minify] = minify
|
70
|
+
get("shodan/host/search", params.merge(facets))
|
71
|
+
end
|
72
|
+
|
73
|
+
# This method lets you determine which filters are being used by
|
74
|
+
# the query string and what parameters were provided to the filters.
|
75
|
+
def host_search_tokens(query = "", **params)
|
76
|
+
params[:query] = query
|
77
|
+
params = turn_into_query(params)
|
78
|
+
get("shodan/host/search/tokens", params)
|
79
|
+
end
|
80
|
+
|
81
|
+
# This method returns a list of port numbers that the crawlers are looking for.
|
82
|
+
def ports
|
83
|
+
get("shodan/ports")
|
84
|
+
end
|
85
|
+
|
86
|
+
# List all protocols that can be used when performing on-demand Internet scans via Shodan.
|
87
|
+
def protocols
|
88
|
+
get("shodan/protocols")
|
89
|
+
end
|
90
|
+
|
91
|
+
# Use this method to request Shodan to crawl a network.
|
92
|
+
#
|
93
|
+
# This method uses API scan credits: 1 IP consumes 1 scan credit. You
|
94
|
+
# must have a paid API plan (either one-time payment or subscription)
|
95
|
+
# in order to use this method.
|
96
|
+
#
|
97
|
+
# IP, IPs or netblocks (in CIDR notation) that should get crawled.
|
98
|
+
def scan(*ips)
|
99
|
+
raise "Not enough scan credits!" unless self.info["scan_credits"] >= 1
|
100
|
+
post("shodan/scan", ips: ips.join(","))
|
101
|
+
end
|
102
|
+
|
103
|
+
# Use this method to request Shodan to crawl the Internet for a specific port.
|
104
|
+
#
|
105
|
+
# This method is restricted to security researchers and companies with
|
106
|
+
# a Shodan Data license. To apply for access to this method as a researcher,
|
107
|
+
# please email jmath@shodan.io with information about your project.
|
108
|
+
# Access is restricted to prevent abuse.
|
109
|
+
#
|
110
|
+
# == Example
|
111
|
+
# rest_api.crawl_for(port: 80, protocol: "http")
|
112
|
+
def crawl_for(**params)
|
113
|
+
params[:query] = ""
|
114
|
+
params = turn_into_query(params)
|
115
|
+
post("shodan/scan/internet", params)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Check the progress of a previously submitted scan request.
|
119
|
+
def scan_status(id)
|
120
|
+
get("shodan/scan/#{id}")
|
121
|
+
end
|
122
|
+
|
123
|
+
# Use this method to obtain a list of search queries that users have saved in Shodan.
|
124
|
+
def community_queries(**params)
|
125
|
+
get("shodan/query", params)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Use this method to search the directory of search queries that users have saved in Shodan.
|
129
|
+
def search_for_community_query(query, **params)
|
130
|
+
params[:query] = query
|
131
|
+
params = turn_into_query(params)
|
132
|
+
get("shodan/query/search", params)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Use this method to obtain a list of popular tags for the saved search queries in Shodan.
|
136
|
+
def popular_query_tags(size = 10)
|
137
|
+
params = {}
|
138
|
+
params[:size] = size
|
139
|
+
get("shodan/query/tags", params)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns information about the Shodan account linked to this API key.
|
143
|
+
def profile
|
144
|
+
get("account/profile")
|
145
|
+
end
|
146
|
+
|
147
|
+
# Look up the IP address for the provided list of hostnames.
|
148
|
+
def resolve(*hostnames)
|
149
|
+
get("dns/resolve", hostnames: hostnames.join(","))
|
150
|
+
end
|
151
|
+
|
152
|
+
# Look up the hostnames that have been defined for the given list of IP addresses.
|
153
|
+
def reverse_lookup(*ips)
|
154
|
+
get("dns/reverse", ips: ips.join(","))
|
155
|
+
end
|
156
|
+
|
157
|
+
# Shows the HTTP headers that your client sends when connecting to a webserver.
|
158
|
+
def http_headers
|
159
|
+
get("tools/httpheaders")
|
160
|
+
end
|
161
|
+
|
162
|
+
# Get your current IP address as seen from the Internet.
|
163
|
+
def my_ip
|
164
|
+
get("tools/my_ip")
|
165
|
+
end
|
166
|
+
|
167
|
+
# Calculates a honeypot probability score ranging from 0 (not a honeypot) to 1.0 (is a honeypot).
|
168
|
+
def honeypot_score(ip)
|
169
|
+
get("labs/honeyscore/#{ip}")
|
170
|
+
end
|
171
|
+
|
172
|
+
# Returns information about the API plan belonging to the given API key.
|
173
|
+
def info
|
174
|
+
get('api-info')
|
175
|
+
end
|
176
|
+
|
177
|
+
# Perform a direct GET HTTP request to the REST API.
|
178
|
+
def get(path, **params)
|
179
|
+
resp = Unirest.get "#{URL}#{path}?key=#{@key}", parameters: params
|
180
|
+
raise resp if resp.code != 200 #and resp.body.has_key?("error")
|
181
|
+
resp.body
|
182
|
+
end
|
183
|
+
|
184
|
+
# Perform a direct POST HTTP request to the REST API.
|
185
|
+
def post(path, **params)
|
186
|
+
resp = Unirest.post "#{URL}#{path}?key=#{@key}", parameters: params
|
187
|
+
raise resp.body["error"] if resp.code != 200 and resp.body.has_key?("error")
|
188
|
+
resp.body
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
def turn_into_query(params)
|
194
|
+
filters = params.reject { |key, value| key == :query }
|
195
|
+
filters.each do |key, value|
|
196
|
+
params[:query] << " #{key}:#{value}"
|
197
|
+
end
|
198
|
+
params.select { |key, value| key == :query }
|
199
|
+
end
|
200
|
+
|
201
|
+
def turn_into_facets(facets)
|
202
|
+
filters = facets.reject { |key, value| key == :facets }
|
203
|
+
facets[:facets] = []
|
204
|
+
filters.each do |key, value|
|
205
|
+
facets[:facets] << "#{key}:#{value}"
|
206
|
+
end
|
207
|
+
facets[:facets] = facets[:facets].join(",")
|
208
|
+
facets.select { |key, value| key == :facets }
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
module Shodanz
|
2
|
+
|
3
|
+
module API
|
4
|
+
# The REST API provides methods to search Shodan, look up
|
5
|
+
# hosts, get summary information on queries and a variety
|
6
|
+
# of other utilities. This requires you to have an API key
|
7
|
+
# which you can get from Shodan.
|
8
|
+
#
|
9
|
+
# Note: Only 1-5% of the data is currently provided to
|
10
|
+
# subscription-based API plans. If your company is interested
|
11
|
+
# in large-scale, real-time access to all of the Shodan data
|
12
|
+
# please contact us for pricing information (sales@shodan.io).
|
13
|
+
#
|
14
|
+
# @author Kent 'picat' Gruber
|
15
|
+
class Streaming
|
16
|
+
attr_accessor :key
|
17
|
+
|
18
|
+
# The Streaming API is an HTTP-based service that returns
|
19
|
+
# a real-time stream of data collected by Shodan.
|
20
|
+
URL = "https://stream.shodan.io/"
|
21
|
+
|
22
|
+
# @param key [String] SHODAN API key, defaulted to the *SHODAN_API_KEY* enviroment variable.
|
23
|
+
def initialize(key: ENV['SHODAN_API_KEY'])
|
24
|
+
self.key = key
|
25
|
+
warn "No key has been found or provided!" unless self.key?
|
26
|
+
end
|
27
|
+
|
28
|
+
# Check if there's an API key.
|
29
|
+
def key?
|
30
|
+
return true if @key; false
|
31
|
+
end
|
32
|
+
|
33
|
+
# This stream provides ALL of the data that Shodan collects.
|
34
|
+
# Use this stream if you need access to everything and/ or want to
|
35
|
+
# store your own Shodan database locally. If you only care about specific
|
36
|
+
# ports, please use the Ports stream.
|
37
|
+
#
|
38
|
+
# Sometimes data may be piped down stream that is weird to parse. You can choose
|
39
|
+
# to keep this data optionally; and it will not be parsed for you.
|
40
|
+
#
|
41
|
+
# == Example
|
42
|
+
# api.banners do |banner|
|
43
|
+
# # do something with banner as hash
|
44
|
+
# puts data
|
45
|
+
# end
|
46
|
+
def banners(**params)
|
47
|
+
slurp_stream("shodan/banners", params) do |data|
|
48
|
+
yield data
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# This stream provides a filtered, bandwidth-saving view of the Banners
|
53
|
+
# stream in case you are only interested in devices located in certain ASNs.
|
54
|
+
# == Example
|
55
|
+
# api.banners_within_asns(3303, 32475) do |data|
|
56
|
+
# # do something with the banner hash
|
57
|
+
# puts data
|
58
|
+
# end
|
59
|
+
def banners_within_asns(*asns, **params)
|
60
|
+
slurp_stream("shodan/asn/#{asns.join(",")}", params) do |data|
|
61
|
+
yield data
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# This stream provides a filtered, bandwidth-saving view of the Banners
|
66
|
+
# stream in case you are only interested in devices located in a certain ASN.
|
67
|
+
# == Example
|
68
|
+
# api.banners_within_asn(3303) do |data|
|
69
|
+
# # do something with the banner hash
|
70
|
+
# puts data
|
71
|
+
# end
|
72
|
+
def banners_within_asn(param)
|
73
|
+
banners_within_asns(param) do |data|
|
74
|
+
yield data
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Only returns banner data for the list of specified ports. This
|
79
|
+
# stream provides a filtered, bandwidth-saving view of the Banners
|
80
|
+
# stream in case you are only interested in a specific list of ports.
|
81
|
+
# == Example
|
82
|
+
# api.banners_within_countries("US","DE","JP") do |data|
|
83
|
+
# # do something with the banner hash
|
84
|
+
# puts data
|
85
|
+
# end
|
86
|
+
def banners_within_countries(*params)
|
87
|
+
slurp_stream("shodan/countries/#{params.join(",")}") do |data|
|
88
|
+
yield data
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Only returns banner data for the list of specified ports. This
|
93
|
+
# stream provides a filtered, bandwidth-saving view of the
|
94
|
+
# Banners stream in case you are only interested in a
|
95
|
+
# specific list of ports.
|
96
|
+
# == Example
|
97
|
+
# api.banners_on_port(80, 443) do |data|
|
98
|
+
# # do something with the banner hash
|
99
|
+
# puts data
|
100
|
+
# end
|
101
|
+
def banners_on_ports(*params)
|
102
|
+
slurp_stream("shodan/ports/#{params.join(",")}") do |data|
|
103
|
+
yield data
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Only returns banner data for a specific port. This
|
108
|
+
# stream provides a filtered, bandwidth-saving view of the
|
109
|
+
# Banners stream in case you are only interested in a
|
110
|
+
# specific list of ports.
|
111
|
+
# == Example
|
112
|
+
# api.banners_on_port(80) do |banner|
|
113
|
+
# # do something with the banner hash
|
114
|
+
# puts data
|
115
|
+
# end
|
116
|
+
def banners_on_port(param)
|
117
|
+
banners_on_ports(param) do |data|
|
118
|
+
yield data
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Subscribe to banners discovered on all IP ranges described in the network alerts.
|
123
|
+
# Use the REST API methods to create/ delete/ manage your network alerts and
|
124
|
+
# use the Streaming API to subscribe to them.
|
125
|
+
def alerts
|
126
|
+
slurp_stream("alert") do |data|
|
127
|
+
yield data
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Subscribe to banners discovered on the IP range defined in a specific network alert.
|
132
|
+
def alert(id)
|
133
|
+
slurp_stream("alert/#{id}") do |data|
|
134
|
+
yield data
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
# Perform the main function of consuming the streaming API.
|
141
|
+
def slurp_stream(path, **params)
|
142
|
+
uri = URI("#{URL}#{path}?key=#{@key}")
|
143
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
|
144
|
+
request = Net::HTTP::Get.new uri
|
145
|
+
begin
|
146
|
+
http.request request do |resp|
|
147
|
+
raise "Unable to connect to Streaming API" if resp.code != "200"
|
148
|
+
# Buffer for Shodan's bullshit.
|
149
|
+
raw_body = ""
|
150
|
+
resp.read_body do |chunk|
|
151
|
+
if /^\{"product":.*\}\}\n/.match(chunk)
|
152
|
+
begin
|
153
|
+
yield Oj.load(chunk)
|
154
|
+
rescue
|
155
|
+
# yolo
|
156
|
+
end
|
157
|
+
elsif /.*\}\}\n$/.match(chunk)
|
158
|
+
next if raw_body.empty?
|
159
|
+
raw_body << chunk
|
160
|
+
raw_body
|
161
|
+
elsif /^\{.*\b/.match(chunk)
|
162
|
+
raw_body << chunk
|
163
|
+
end
|
164
|
+
if m = /^\{"product":.*\}\}\n/.match(raw_body)
|
165
|
+
index = 0
|
166
|
+
while matched = m[index]
|
167
|
+
index += 1
|
168
|
+
raw_body = raw_body.gsub(/^\{"product":.*\}\}\n/, "")
|
169
|
+
begin
|
170
|
+
yield Oj.load(matched)
|
171
|
+
rescue
|
172
|
+
# yolo
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
ensure
|
179
|
+
http.finish
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Shodanz
|
2
|
+
class Client
|
3
|
+
# Create a new client to connect to any of the APIs.
|
4
|
+
def initialize
|
5
|
+
@rest_api = Shodanz.api.rest.new
|
6
|
+
@streaming_api = Shodanz.api.streaming.new
|
7
|
+
@exploits_api = Shodanz.api.exploits.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def rest_api
|
11
|
+
@rest_api
|
12
|
+
end
|
13
|
+
|
14
|
+
def streaming_api
|
15
|
+
@streaming_api
|
16
|
+
end
|
17
|
+
|
18
|
+
def exploits_api
|
19
|
+
@exploits_api
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/shodanz.rb
ADDED
data/shodanz.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "shodanz/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "shodanz"
|
8
|
+
spec.version = Shodanz::VERSION
|
9
|
+
spec.authors = ["Kent 'picatz' Gruber"]
|
10
|
+
spec.email = ["kgruber1@emich.edu"]
|
11
|
+
|
12
|
+
spec.summary = %q{A modern Ruby gem for Shodan, the world's first search engine for Internet-connected devices.}
|
13
|
+
spec.description = %q{Featuring full support for the REST, Streaming and Exploits API}
|
14
|
+
spec.homepage = "https://github.com/picatz/shodanz"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency "unirest"
|
23
|
+
spec.add_dependency "oj"
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.15"
|
26
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
27
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shodanz
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kent 'picatz' Gruber
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-10-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: unirest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: oj
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.15'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.15'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
description: Featuring full support for the REST, Streaming and Exploits API
|
84
|
+
email:
|
85
|
+
- kgruber1@emich.edu
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".rspec"
|
92
|
+
- ".travis.yml"
|
93
|
+
- CODE_OF_CONDUCT.md
|
94
|
+
- Gemfile
|
95
|
+
- LICENSE.txt
|
96
|
+
- README.md
|
97
|
+
- Rakefile
|
98
|
+
- examples/debug.rb
|
99
|
+
- examples/top_10_countries_running.rb
|
100
|
+
- lib/shodanz.rb
|
101
|
+
- lib/shodanz/api.rb
|
102
|
+
- lib/shodanz/apis/exploits.rb
|
103
|
+
- lib/shodanz/apis/rest.rb
|
104
|
+
- lib/shodanz/apis/streaming.rb
|
105
|
+
- lib/shodanz/client.rb
|
106
|
+
- lib/shodanz/version.rb
|
107
|
+
- shodanz.gemspec
|
108
|
+
homepage: https://github.com/picatz/shodanz
|
109
|
+
licenses:
|
110
|
+
- MIT
|
111
|
+
metadata: {}
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 2.6.12
|
129
|
+
signing_key:
|
130
|
+
specification_version: 4
|
131
|
+
summary: A modern Ruby gem for Shodan, the world's first search engine for Internet-connected
|
132
|
+
devices.
|
133
|
+
test_files: []
|