dronebl.rb 0.0.1
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.
- checksums.yaml +7 -0
- data/bin/dronebl-add +106 -0
- data/bin/dronebl-query +126 -0
- data/lib/dronebl-client.rb +65 -0
- metadata +50 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 90452b68385bacb07b71362b95b43f9529c52a00
|
4
|
+
data.tar.gz: c6c7ac736b865e46054effce79866393a7174280
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cac7d9db9af50b35875a0a596d33e53c4b6c66d82ed3dd09a3cc0b08c02eb3486b7551cc27422e5d081734f92575b79725fa7d793a73a66ed800ff3951d1ad88
|
7
|
+
data.tar.gz: afc184cfb563ca52072ae0f87285abd8d1b4344206940966acb47acf13025ec08843594dcba74c813e027608f7930df8b30371cb105fb1a0632dc700558f3f35
|
data/bin/dronebl-add
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# copyright Rylee Fowler 2014
|
3
|
+
# see LICENSE for more details
|
4
|
+
require 'dronebl-client'
|
5
|
+
require 'resolv'
|
6
|
+
require 'optparse'
|
7
|
+
class Options
|
8
|
+
attr_reader :read_from_stdin, :key_str, :key_file, :type, :comment, :dry_run,
|
9
|
+
:use_key_file, :no_check
|
10
|
+
attr_accessor :ips
|
11
|
+
def initialize
|
12
|
+
@key_file = File.expand_path "~/.droneblkey"
|
13
|
+
@ips = []
|
14
|
+
@opt_parser = OptionParser.new do |opts|
|
15
|
+
opts.banner = "Usage: #{$0} [options]"
|
16
|
+
opts.separator ""
|
17
|
+
opts.separator "Options available:"
|
18
|
+
opts.on('-I', '--ips [ip1,ip2,ip3,...]', Array, 'Read in IPs.') do |list|
|
19
|
+
@ips += list
|
20
|
+
end
|
21
|
+
opts.on('-s', '--stdin', 'Read a newline-delimited list of IPs from STDIN') do
|
22
|
+
@read_from_stdin = true
|
23
|
+
end
|
24
|
+
opts.on('-k', '--key KEY', String, 'Use KEY as your DroneBL RPC2 key') do |key|
|
25
|
+
@key_str = key
|
26
|
+
end
|
27
|
+
opts.on('-f', '--keyfile [FILE_PATH]', 'Read the file at FILE_PATH and use it as the RPC key') do |path|
|
28
|
+
@use_key_file = true
|
29
|
+
@key_file = path
|
30
|
+
end
|
31
|
+
opts.on('-t', '--type TYPE', String, 'Mark submissions as TYPE. Valid types can be seen with the "-T" option.') do |type|
|
32
|
+
@type = type
|
33
|
+
end
|
34
|
+
opts.on('-T', '--show-types', 'Show all valid types for IP submission') do
|
35
|
+
puts DroneBL::TYPES.map { |k, v| "#{k} : #{v}"}.join("\n")
|
36
|
+
exit
|
37
|
+
end
|
38
|
+
opts.on('-c', '--comment [COMMENT]', String, 'Attach the given comment to the listings.') do |comment|
|
39
|
+
@comment = comment
|
40
|
+
opts.on('-u', '--use-key-file', 'Use the default key file located at ~/.droneblkey') do
|
41
|
+
@use_key_file = true
|
42
|
+
end
|
43
|
+
opts.on('-d', '--dry-run', 'Prints the query to be run to STDOUT instead of sending it as a query to the DroneBL RPC service.') do
|
44
|
+
@dry_run = true
|
45
|
+
end
|
46
|
+
#opts.on('-n', '--no-pre-check', 'Do not check DroneBL for the IPs you are about to submit with a lookup before doing it.') do
|
47
|
+
# @no_check = true
|
48
|
+
end
|
49
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
50
|
+
puts opts
|
51
|
+
exit
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
def parse! args
|
57
|
+
@opt_parser.parse! args
|
58
|
+
|
59
|
+
if (@use_key_file && !(File.exists? @key_file)) && @key_str.nil?
|
60
|
+
abort "No key string provided and #{@key_file} does not exist -- unable to authenticate. See http://dronebl.org/rpckey_signup if you need a key."
|
61
|
+
end
|
62
|
+
if @use_key_file && !@key_str.nil?
|
63
|
+
abort "Trying to use both --use-key-file and --key -- make up your mind!"
|
64
|
+
end
|
65
|
+
if (@use_key_file)
|
66
|
+
DroneBL::key = File.read(File.expand_path(@key_file)).chomp
|
67
|
+
else
|
68
|
+
DroneBL::key = @key_str
|
69
|
+
end
|
70
|
+
if @ips.nil? || (@ips.empty? && !@read_from_stdin)
|
71
|
+
abort 'No IPs given and you\'re not reading from stdin -- this is a noop!'
|
72
|
+
end
|
73
|
+
if @type.nil?
|
74
|
+
abort "No type given! Please read #{$0} --help again."
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# end option parsing logic, begin program logic
|
81
|
+
opts = Options.new
|
82
|
+
opts.parse! ARGV
|
83
|
+
|
84
|
+
if opts.read_from_stdin
|
85
|
+
while line = STDIN.gets
|
86
|
+
opts.ips << line.chomp
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
opts.ips.uniq!
|
91
|
+
|
92
|
+
prevalid, ipv6 = opts.ips.partition { |ip| (ip.match(Resolv::IPv6::Regex).nil?) }
|
93
|
+
valid, invalid = prevalid.partition { |ip| !(ip.match(Resolv::IPv4::Regex).nil?) }
|
94
|
+
invalid += ipv6
|
95
|
+
if @dry_run
|
96
|
+
puts DroneBL::gen_add_query valid
|
97
|
+
puts "#{valid.count} IPs will be added as type #{opts.type}#{" with comment 'opts.comment if opts.comment}'"}."
|
98
|
+
else
|
99
|
+
response = DroneBL::add(valid, type, comment)
|
100
|
+
print_table response, opts.long_types
|
101
|
+
puts "#{valid.count} IPs looked up. #{response.map { |r| r['ip'] }.uniq.length} unique IPs found in response."
|
102
|
+
end
|
103
|
+
unless invalid.empty?
|
104
|
+
puts "IPs not valid for lookup: "
|
105
|
+
invalid.each { |ip| puts ip }
|
106
|
+
end
|
data/bin/dronebl-query
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# copyright Rylee Fowler 2014
|
3
|
+
# see LICENSE for more details
|
4
|
+
require 'dronebl-client'
|
5
|
+
require 'resolv'
|
6
|
+
require 'optparse'
|
7
|
+
class Options
|
8
|
+
attr_reader :read_from_stdin, :key_str, :key_file, :get_archived, :long_types,
|
9
|
+
:dry_run, :time_format, :use_key_file
|
10
|
+
attr_accessor :ips
|
11
|
+
def initialize
|
12
|
+
@key_file = File.expand_path "~/.droneblkey"
|
13
|
+
@ips = []
|
14
|
+
@opt_parser = OptionParser.new do |opts|
|
15
|
+
opts.banner = "Usage: #{$0} [options]"
|
16
|
+
opts.separator ""
|
17
|
+
opts.separator "Options available:"
|
18
|
+
opts.on('-I', '--ips [ip1,ip2,ip3,...]', Array, 'Read in IPs.') do |list|
|
19
|
+
@ips += list
|
20
|
+
end
|
21
|
+
opts.on('-s', '--stdin', 'Read a newline-delimited list of IPs from STDIN') do
|
22
|
+
@read_from_stdin = true
|
23
|
+
end
|
24
|
+
opts.on('-k', '--key KEY', String, 'Use KEY as your DroneBL RPC2 key') do |key|
|
25
|
+
@key_str = key
|
26
|
+
end
|
27
|
+
opts.on('-f', '--keyfile [FILE_PATH]', 'Read the file at FILE_PATH and use it as the RPC key') do |path|
|
28
|
+
@use_key_file = true
|
29
|
+
@key_file = path
|
30
|
+
end
|
31
|
+
opts.on('-a', '--get-archived', 'Get *all* DroneBL listings of the given IP, not just active ones.') do
|
32
|
+
@get_archived = true
|
33
|
+
end
|
34
|
+
opts.on('-T', '--time-format [FORMAT]', String, 'Interpolate the time format string with FORMAT instead of the default. See `man 3 strftime` for format specifiers.') do |fmt|
|
35
|
+
@time_format = fmt
|
36
|
+
end
|
37
|
+
opts.on('-u', '--use-key-file', 'Use the default key file located at ~/.droneblkey') do
|
38
|
+
@use_key_file = true
|
39
|
+
end
|
40
|
+
opts.on('-L', '--long-types', 'Print type definitions instead of numeric types for record matches.') do
|
41
|
+
@long_types = true
|
42
|
+
end
|
43
|
+
opts.on('-d', '--dry-run', 'Prints the query to be run to STDOUT instead of sending it as a query to the DroneBL RPC service.') do
|
44
|
+
@dry_run = true
|
45
|
+
end
|
46
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
47
|
+
puts opts
|
48
|
+
exit
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
def parse! args
|
54
|
+
@opt_parser.parse! args
|
55
|
+
|
56
|
+
if (@use_key_file && !(File.exists? @key_file)) && @key_str.nil?
|
57
|
+
abort "No key string provided and #{@key_file} does not exist -- unable to authenticate. See http://dronebl.org/rpckey_signup if you need a key."
|
58
|
+
end
|
59
|
+
if @use_key_file && !@key_str.nil?
|
60
|
+
abort "Trying to use both --use-key-file and --key -- make up your mind!"
|
61
|
+
end
|
62
|
+
if (@use_key_file)
|
63
|
+
DroneBL::key = File.read(File.expand_path(@key_file)).chomp
|
64
|
+
else
|
65
|
+
DroneBL::key = @key_str
|
66
|
+
end
|
67
|
+
if @ips.nil? || (@ips.empty? && !@read_from_stdin)
|
68
|
+
puts @ips
|
69
|
+
abort 'No IPs given and you\'re not reading from stdin -- this is a noop!'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# end option parsing logic, begin program logic
|
75
|
+
def print_table data, long_types=false
|
76
|
+
typelen = 5
|
77
|
+
if long_types
|
78
|
+
typelen = 35
|
79
|
+
end
|
80
|
+
puts [
|
81
|
+
'IP'.ljust(15),
|
82
|
+
'Currently listed?'.rjust(17),
|
83
|
+
'Type'.rjust(typelen),
|
84
|
+
'Comment'.ljust(25),
|
85
|
+
'ID'.ljust(9),
|
86
|
+
'Time'.ljust(25)
|
87
|
+
].join '|'
|
88
|
+
puts '-' * 135
|
89
|
+
data.each do |data|
|
90
|
+
puts [
|
91
|
+
data['ip'].ljust(15),
|
92
|
+
(data['listed'] == '1' ? 'YES' : 'NO').rjust(17),
|
93
|
+
(long_types ? DroneBL::TYPES[data['type']] : data['type']).rjust(typelen),
|
94
|
+
data['comment'].ljust(25),
|
95
|
+
data['id'].ljust(9),
|
96
|
+
Time.at(data['timestamp'].to_i).to_s.ljust(25)
|
97
|
+
].join '|'
|
98
|
+
end
|
99
|
+
puts '=' * 135
|
100
|
+
end
|
101
|
+
opts = Options.new
|
102
|
+
opts.parse! ARGV
|
103
|
+
|
104
|
+
if opts.read_from_stdin
|
105
|
+
while line = STDIN.gets
|
106
|
+
opts.ips << line.chomp
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
opts.ips.uniq!
|
111
|
+
|
112
|
+
prevalid, ipv6 = opts.ips.partition { |ip| (ip.match(Resolv::IPv6::Regex).nil?) }
|
113
|
+
valid, invalid = prevalid.partition { |ip| !(ip.match(Resolv::IPv4::Regex).nil?) }
|
114
|
+
invalid += ipv6
|
115
|
+
if @dry_run
|
116
|
+
puts DroneBL::gen_lookup_query valid
|
117
|
+
puts "#{valid.count} IPs will be looked up."
|
118
|
+
else
|
119
|
+
response = DroneBL::lookup(valid)
|
120
|
+
print_table response, opts.long_types
|
121
|
+
puts "#{valid.count} IPs looked up. #{response.map { |r| r['ip'] }.uniq.length} unique IPs found in response."
|
122
|
+
end
|
123
|
+
unless invalid.empty?
|
124
|
+
puts "IPs not valid for lookup: "
|
125
|
+
invalid.each { |ip| puts ip }
|
126
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# copyright Rylee Fowler 2014
|
3
|
+
# see LICENSE for more details
|
4
|
+
|
5
|
+
require 'nokogiri'
|
6
|
+
require 'httparty'
|
7
|
+
module DroneBL
|
8
|
+
include HTTParty
|
9
|
+
format :xml
|
10
|
+
base_uri 'http://dronebl.org'
|
11
|
+
TYPES = {"1"=>"Testing class.",
|
12
|
+
"2"=>"Sample data",
|
13
|
+
"3"=>"IRC spam drone",
|
14
|
+
"5"=>"Bottler (experimental)",
|
15
|
+
"6"=>"Unknown worm or spambot",
|
16
|
+
"7"=>"DDoS drone",
|
17
|
+
"8"=>"Open SOCKS proxy",
|
18
|
+
"9"=>"Open HTTP proxy",
|
19
|
+
"10"=>"Proxychain",
|
20
|
+
"11"=>"Web Page Proxy",
|
21
|
+
"13"=>"Automated dictionary attacks",
|
22
|
+
"14"=>"Open WINGATE proxy",
|
23
|
+
"15"=>"Compromised router / gateway",
|
24
|
+
"16"=>"Autorooting worms",
|
25
|
+
"17"=>"Automatically determined botnet IP",
|
26
|
+
"255"=>"Uncategorized threat class"}
|
27
|
+
class << self
|
28
|
+
attr_accessor :key
|
29
|
+
def parse_response xml
|
30
|
+
# This giant mess of hax is needed because the DroneBL response to queries
|
31
|
+
# is encased in CDATA for whatever reason.
|
32
|
+
resp = Nokogiri::XML(xml).at("response")
|
33
|
+
if resp['type'].downcase == 'error'
|
34
|
+
abort "call failed: '#{resp.css('message').text}' data: '#{resp.css('data').text}'"
|
35
|
+
end
|
36
|
+
Nokogiri::XML("<?xml version='1.0'>\n<results>#{resp.text}</results>").css("result").map(&:to_h) # thanks to jhass in #ruby on freenode
|
37
|
+
end
|
38
|
+
|
39
|
+
def gen_lookup_query ips, archived=false
|
40
|
+
<<EOF
|
41
|
+
<?xml version='1.0'?>
|
42
|
+
<request key='#{key}'>
|
43
|
+
#{ips.map { |ip| "<lookup ip='#{ip}' listed='#{archived ? 2 : 1}'>"}.join("\n")}
|
44
|
+
</request>
|
45
|
+
EOF
|
46
|
+
end
|
47
|
+
def gen_add_query ips, type, comment=''
|
48
|
+
"<?xml version='1.0'?>
|
49
|
+
<request key='#{key}'>
|
50
|
+
#{ips.map { |ip| "<add ip='#{ip}' type='#{type}'#{ " comment='#{comment}'" unless comment.empty?}>"}.join("\n")}
|
51
|
+
</request>"
|
52
|
+
end
|
53
|
+
|
54
|
+
def lookup ips, archived=false
|
55
|
+
query = gen_lookup_query ips, archived
|
56
|
+
parse_response post('/RPC2', {:body => query }).body
|
57
|
+
end
|
58
|
+
|
59
|
+
def add ips, type, comment=''
|
60
|
+
query = gen_add_query ips, type, comment
|
61
|
+
parse_response post('/RPC2', {:body => query }).body
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dronebl.rb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Rylee Fowler
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-04 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: 'NOTE: You should create a ~/.droneblkey file with your key inside of
|
14
|
+
it for the best experience with this.'
|
15
|
+
email: rylee@rylee.me
|
16
|
+
executables:
|
17
|
+
- dronebl-query
|
18
|
+
- dronebl-add
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- bin/dronebl-add
|
23
|
+
- bin/dronebl-query
|
24
|
+
- lib/dronebl-client.rb
|
25
|
+
homepage: http://github.com/rylai-/dronebl.rb
|
26
|
+
licenses:
|
27
|
+
- MIT
|
28
|
+
metadata: {}
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
require_paths:
|
32
|
+
- lib
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
requirements: []
|
44
|
+
rubyforge_project:
|
45
|
+
rubygems_version: 2.2.2
|
46
|
+
signing_key:
|
47
|
+
specification_version: 4
|
48
|
+
summary: Interface to the DroneBL RPC2 service
|
49
|
+
test_files: []
|
50
|
+
has_rdoc:
|