ScanSSL 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/scanssl +93 -0
- data/lib/scanssl.rb +58 -0
- data/lib/scanssl/certInfo.rb +65 -0
- data/lib/scanssl/fileExport.rb +18 -0
- data/lib/scanssl/scanHost.rb +128 -0
- data/lib/scanssl/settings.rb +28 -0
- data/lib/scanssl/version.rb +3 -0
- metadata +56 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1ade7f0a27b608559183de6a444401019558d66c
|
4
|
+
data.tar.gz: 15e1e854ba06cdc47589865f88c0b4ce7afef132
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 07dec5c21cf71cee698a7fe3831d7c57ee4f1e232b220c1fc57df55b9ca5e39204068569cbb52ac77285e6ef7adb9c91343e55d6cd29c5b5d4cd5fc9c9769c82
|
7
|
+
data.tar.gz: 445d6b2ad93032f71c1e1a15789e46ae93c7747547dd9435ec9416dd228fe728decf76c7b6d43ff3590fff98438e13149f6da6841d15863fc3a6cc401f0214cf
|
data/bin/scanssl
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# ScanSSL gem.
|
4
|
+
# Some info here.
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'optparse'
|
8
|
+
|
9
|
+
unless File.respond_to? :realpath
|
10
|
+
class File #:nodoc:
|
11
|
+
def self.realpath path
|
12
|
+
return realpath(File.readlink(path)) if symlink?(path)
|
13
|
+
path
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
$: << File.expand_path(File.dirname(File.realpath(__FILE__)) + '/../lib')
|
18
|
+
|
19
|
+
require 'scanssl'
|
20
|
+
require 'rubygems'
|
21
|
+
|
22
|
+
options = {}
|
23
|
+
|
24
|
+
begin
|
25
|
+
OptionParser.new do |opts|
|
26
|
+
opts.banner = "Usage: #{File.basename($0)} [options]"
|
27
|
+
|
28
|
+
opts.on('-s', '--server server', 'Server to scan') do |server|
|
29
|
+
options[:server] = server
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on('-p', '--port port', 'Port to scan') do |port|
|
33
|
+
options[:port] = port
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on('-d', '--debug', 'Debug mode') do
|
37
|
+
options[:debug] = true
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on('-c', '--certificate', 'Displays certificate information') do
|
41
|
+
options[:check_cert] = true
|
42
|
+
ScanSSL
|
43
|
+
end
|
44
|
+
|
45
|
+
opts.on('-o', '--output filename', 'File to save results in') do |filename|
|
46
|
+
options[:output] = filename
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on('-t', '--type filetype', 'Type file: txt, pdf, html') do |filetype|
|
50
|
+
options[:file_type] = filetype
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on('-v', '--version', 'Version number') do
|
54
|
+
puts "ScanSSL version: #{ScanSSL::VERSION}"
|
55
|
+
exit
|
56
|
+
end
|
57
|
+
|
58
|
+
opts.on('-h', '--help', 'Displays Help') do
|
59
|
+
puts opts
|
60
|
+
exit
|
61
|
+
end
|
62
|
+
end.parse!
|
63
|
+
|
64
|
+
rescue OptionParser::MissingArgument
|
65
|
+
puts "Wrong argument. Check: -h for more help."
|
66
|
+
|
67
|
+
rescue OptionParser::InvalidOption
|
68
|
+
puts "Invalid argument. Check: -h for more help."
|
69
|
+
end
|
70
|
+
|
71
|
+
unless options[:server]
|
72
|
+
puts "Missing -s/--server argument!"
|
73
|
+
puts "Try -h"
|
74
|
+
exit
|
75
|
+
end
|
76
|
+
|
77
|
+
unless options[:port]
|
78
|
+
puts "Missing -p/--port argument!"
|
79
|
+
puts "Try -h"
|
80
|
+
exit
|
81
|
+
end
|
82
|
+
|
83
|
+
pid = fork do
|
84
|
+
ScanSSL::Command.call(options)
|
85
|
+
end
|
86
|
+
|
87
|
+
Signal.trap("INT") do
|
88
|
+
puts "Terminating Scan..."
|
89
|
+
Process.kill("TERM", pid)
|
90
|
+
exit 0
|
91
|
+
end
|
92
|
+
|
93
|
+
Process.waitpid(pid, 0)
|
data/lib/scanssl.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
require 'getoptlong'
|
3
|
+
require 'openssl'
|
4
|
+
require 'socket'
|
5
|
+
require 'webrick'
|
6
|
+
require 'prawn'
|
7
|
+
|
8
|
+
require 'scanssl/version'
|
9
|
+
require 'scanssl/settings'
|
10
|
+
require 'scanssl/certInfo'
|
11
|
+
require 'scanssl/fileExport'
|
12
|
+
require 'scanssl/scanHost'
|
13
|
+
|
14
|
+
module ScanSSL
|
15
|
+
class Command
|
16
|
+
def self.call(options = {})
|
17
|
+
@server = options[:server]
|
18
|
+
@port = options[:port]
|
19
|
+
|
20
|
+
# Get Certiticate Information
|
21
|
+
if options[:check_cert] == true
|
22
|
+
a = ScanSSL::CertInfo.new(@server, @port)
|
23
|
+
# Not sure yet if we need to have an access to one of those
|
24
|
+
# datas so right now I'm returning each of them.
|
25
|
+
# We can convert it to hash or array.
|
26
|
+
colorOutputCert(a.valid?,
|
27
|
+
a.valid_from,
|
28
|
+
a.valid_until,
|
29
|
+
a.issuer,
|
30
|
+
a.subject,
|
31
|
+
a.algorithm,
|
32
|
+
a.key_size,
|
33
|
+
a.public_key)
|
34
|
+
end
|
35
|
+
|
36
|
+
if options[:check_cert] == nil
|
37
|
+
run = ScanSSL::ScanHost.new
|
38
|
+
puts run.scan(@server, @port)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.colorOutputCert(cValid, cFrom, cUntil, cIssuer, cSubject, cAlgorithm, cKey, cPublic)
|
43
|
+
puts "== Certificate Information ==".bold
|
44
|
+
puts "domain: #{@server}"
|
45
|
+
puts "port: #{@port}"
|
46
|
+
puts "----------------"
|
47
|
+
puts "valid: #{cValid}"
|
48
|
+
puts "valid from:#{cFrom}"
|
49
|
+
puts "valid until: #{cUntil}"
|
50
|
+
puts "issuer: #{cIssuer}"
|
51
|
+
puts "subject: #{cSubject}"
|
52
|
+
puts "algorithm: #{cAlgorithm}"
|
53
|
+
puts "key size: #{cKey}"
|
54
|
+
puts "public key:"
|
55
|
+
puts "#{cPublic}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module ScanSSL
|
2
|
+
class CertInfo < Certificate
|
3
|
+
def initialize(server, port)
|
4
|
+
@ssl_context = OpenSSL::SSL::SSLContext.new
|
5
|
+
@cert_store = OpenSSL::X509::Store.new
|
6
|
+
@cert_store.set_default_paths
|
7
|
+
@ssl_context.cert_store = @cert_store
|
8
|
+
@tcp_socket = TCPSocket.new(server, port)
|
9
|
+
@socket_destination = OpenSSL::SSL::SSLSocket.new @tcp_socket, @ssl_context
|
10
|
+
@socket_destination.connect
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid?
|
14
|
+
return TRUTH_TABLE[(@socket_destination.verify_result == 0)]
|
15
|
+
end
|
16
|
+
|
17
|
+
def valid_from
|
18
|
+
return cert.not_before
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid_until
|
22
|
+
return cert.not_after
|
23
|
+
end
|
24
|
+
|
25
|
+
def issuer
|
26
|
+
return certprops.select { |name, data, type| name == "O" }.first[1]
|
27
|
+
end
|
28
|
+
|
29
|
+
def subject
|
30
|
+
return cert.subject
|
31
|
+
end
|
32
|
+
|
33
|
+
def algorithm
|
34
|
+
return cert.signature_algorithm
|
35
|
+
end
|
36
|
+
|
37
|
+
def key_size
|
38
|
+
begin
|
39
|
+
key_size = OpenSSL::PKey::RSA.new(cert.public_key).to_text.match(/Public-Key: \((.*) bit/).to_a[1].strip.to_i
|
40
|
+
if key_size.between?(1000, 2000)
|
41
|
+
key_size = $1
|
42
|
+
elsif key_size > 2000
|
43
|
+
key_size = $1
|
44
|
+
else
|
45
|
+
key_size = $1
|
46
|
+
end
|
47
|
+
return key_size
|
48
|
+
end
|
49
|
+
rescue
|
50
|
+
return "Problem with key_size"
|
51
|
+
end
|
52
|
+
|
53
|
+
def public_key
|
54
|
+
return cert.public_key
|
55
|
+
end
|
56
|
+
|
57
|
+
def cert
|
58
|
+
return OpenSSL::X509::Certificate.new(@socket_destination.peer_cert)
|
59
|
+
end
|
60
|
+
|
61
|
+
def certprops
|
62
|
+
return OpenSSL::X509::Name.new(cert.issuer).to_a
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ScanSSL
|
2
|
+
class Export
|
3
|
+
def self.pdf(file, data)
|
4
|
+
Prawn::Document.generate(file) do
|
5
|
+
text "Hello :-) This is my #{data}"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.txt(file, data)
|
10
|
+
ftxt = File.open("#{path}/#{file}", "a")
|
11
|
+
ftxt.write(data)
|
12
|
+
ftxt.close
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.csv(file, data)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module ScanSSL
|
2
|
+
# I think the best will be to put the result to the hash or array
|
3
|
+
# and return it to ScanSLL::Command so we can send it to Colorize method
|
4
|
+
# and sort.
|
5
|
+
#
|
6
|
+
class ScanHost < Certificate
|
7
|
+
def scan(server, port)
|
8
|
+
ssl2_array = []
|
9
|
+
ssl3_array = []
|
10
|
+
tls1_array = []
|
11
|
+
tls1_1_array = []
|
12
|
+
tls1_2_array = []
|
13
|
+
threads = []
|
14
|
+
|
15
|
+
c = []
|
16
|
+
PROTOCOLS.each do |protocol|
|
17
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
18
|
+
ssl_context.ciphers = CIPHERS
|
19
|
+
ssl_context.options = protocol
|
20
|
+
threads << Thread.new do
|
21
|
+
ssl_context.ciphers.each do |cipher|
|
22
|
+
begin
|
23
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
24
|
+
ssl_context.options = protocol
|
25
|
+
ssl_context.ciphers = cipher[0].to_s
|
26
|
+
begin
|
27
|
+
tcp_socket = WEBrick::Utils.timeout(5){
|
28
|
+
TCPSocket.new(server, port)
|
29
|
+
}
|
30
|
+
rescue => e
|
31
|
+
puts e.message
|
32
|
+
exit 1
|
33
|
+
end
|
34
|
+
socket_destination = OpenSSL::SSL::SSLSocket.new tcp_socket, ssl_context
|
35
|
+
WEBrick::Utils.timeout(5) {
|
36
|
+
socket_destination.connect
|
37
|
+
}
|
38
|
+
if protocol == SSLV3
|
39
|
+
ssl_version, cipher, bits, vulnerability = result_parse(cipher[0], cipher[3], protocol)
|
40
|
+
result = "Server supports: %-22s %-42s %-10s %s\n"%[ssl_version, cipher, bits, vulnerability]
|
41
|
+
ssl3_array << result
|
42
|
+
elsif protocol == TLSV1
|
43
|
+
ssl_version, cipher, bits, vulnerability = result_parse(cipher[0], cipher[2], protocol)
|
44
|
+
result = "Server supports: %-22s %-42s %-10s %s\n"%[ssl_version, cipher, bits, vulnerability]
|
45
|
+
tls1_array << result
|
46
|
+
elsif protocol == TLSV1_1
|
47
|
+
ssl_version, cipher, bits, vulnerability = result_parse(cipher[0], cipher[2], protocol)
|
48
|
+
result = "Server supports: %-22s %-42s %-10s %s\n"%[ssl_version, cipher, bits, vulnerability]
|
49
|
+
tls1_1_array << result
|
50
|
+
elsif protocol == TLSV1_2
|
51
|
+
ssl_version, cipher, bits, vulnerability = result_parse(cipher[0], cipher[2], protocol)
|
52
|
+
result = "Server supports: %-22s %-42s %-10s %s\n"%[ssl_version, cipher, bits, vulnerability]
|
53
|
+
tls1_2_array << result
|
54
|
+
elsif protocol == SSLV2
|
55
|
+
ssl_version, cipher, bits, vulnerability = result_parse(cipher[0], cipher[2], protocol)
|
56
|
+
result = "Server supports: %-22s %-42s %-10s %s\n"%[ssl_version, cipher, bits, vulnerability]
|
57
|
+
ssl2_array << result
|
58
|
+
end
|
59
|
+
|
60
|
+
rescue Exception => e
|
61
|
+
if @debug
|
62
|
+
puts e.message
|
63
|
+
puts e.backtrace.join "\n"
|
64
|
+
if protocol == SSLV2
|
65
|
+
puts "Server Don't Supports: SSLv2 #{c[0]} #{c[2]} bits"
|
66
|
+
elsif protocol == SSLV3
|
67
|
+
puts "Server Don't Supports: SSLv3 #{c[0]} #{c[3]} bits"
|
68
|
+
elsif protocol == TLSV1
|
69
|
+
puts "Server Don't Supports: TLSv1 #{c[0]} #{c[2]} bits"
|
70
|
+
elsif protocol == TLSV1_1
|
71
|
+
puts "Server Don't Supports: TLSv1.1 #{c[0]} #{c[2]} bits"
|
72
|
+
elsif protocol == TLSV1_2
|
73
|
+
puts "Server Don't Supports: TLSv1.2 #{c[0]} #{c[2]} bits"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
ensure
|
77
|
+
socket_destination.close if socket_destination rescue nil
|
78
|
+
tcp_socket.close if tcp_socket rescue nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
begin
|
84
|
+
threads.map(&:join)
|
85
|
+
rescue Interrupt
|
86
|
+
end
|
87
|
+
return ssl3_array, ssl2_array, tls1_array, tls1_1_array, tls1_2_array
|
88
|
+
end
|
89
|
+
|
90
|
+
def result_parse(cipher_name, cipher_bits, protocol)
|
91
|
+
ssl_version = PROTOCOL_COLOR_NAME[protocol]
|
92
|
+
cipher = case cipher_name
|
93
|
+
when /^(RC4|MD5)/
|
94
|
+
cipher_name.colorize(:yellow)
|
95
|
+
when /^RC2/
|
96
|
+
cipher_name.colorize(:red)
|
97
|
+
when /^EXP/
|
98
|
+
cipher_name.colorize(:red)
|
99
|
+
else
|
100
|
+
cipher_name.colorize(:gree)
|
101
|
+
end
|
102
|
+
|
103
|
+
bits = case cipher_bits
|
104
|
+
when 48, 56, 40
|
105
|
+
cipher_bits.to_s.colorize(:red)
|
106
|
+
when 112
|
107
|
+
cipher_bits.to_s.colorize(:yellow)
|
108
|
+
else
|
109
|
+
cipher_bits.to_s.colorize(:green)
|
110
|
+
end
|
111
|
+
detect_vulnerabilites(ssl_version, cipher, bits)
|
112
|
+
end
|
113
|
+
|
114
|
+
def detect_vulnerabilites(ssl_version, cipher, bits)
|
115
|
+
if ssl_version.match(/SSLv3/).to_s != "" && cipher.match(/RC/i).to_s == ""
|
116
|
+
return ssl_version, cipher, bits, " POODLE (CVE-2014-3566)".colorize(:red)
|
117
|
+
elsif cipher.match(/RC2/i)
|
118
|
+
return ssl_version, cipher, bits, " Chosen-Plaintext Attack".colorize(:red)
|
119
|
+
elsif cipher.match(/EXP/i)
|
120
|
+
return ssl_version, cipher, bits, " FREAK (CVE-2015-0204)".colorize(:red)
|
121
|
+
elsif cipher.match(/RC4/i)
|
122
|
+
return ssl_version, cipher, bits, " Bar-Mitzvah Attack".colorize(:yellow)
|
123
|
+
else
|
124
|
+
return ssl_version, cipher, bits, ''
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ScanSSL
|
2
|
+
class Certificate
|
3
|
+
NO_SSLV2 = 16777216
|
4
|
+
NO_SSLV3 = 33554432
|
5
|
+
NO_TLSV1 = 67108864
|
6
|
+
NO_TLSV1_1 = 268435456
|
7
|
+
NO_TLSV1_2 = 134217728
|
8
|
+
|
9
|
+
SSLV2 = NO_SSLV3 + NO_TLSV1 + NO_TLSV1_1 + NO_TLSV1_2
|
10
|
+
SSLV3 = NO_SSLV2 + NO_TLSV1 + NO_TLSV1_1 + NO_TLSV1_2
|
11
|
+
TLSV1 = NO_SSLV2 + NO_SSLV3 + NO_TLSV1_1 + NO_TLSV1_2
|
12
|
+
TLSV1_1 = NO_SSLV2 + NO_SSLV3 + NO_TLSV1 + NO_TLSV1_2
|
13
|
+
TLSV1_2 = NO_SSLV2 + NO_SSLV3 + NO_TLSV1 + NO_TLSV1_1
|
14
|
+
|
15
|
+
PROTOCOLS = [SSLV2, SSLV3, TLSV1, TLSV1_1, TLSV1_2]
|
16
|
+
CIPHERS = 'ALL::COMPLEMENTOFDEFAULT::COMPLEMENTOFALL'
|
17
|
+
|
18
|
+
PROTOCOL_COLOR_NAME = {
|
19
|
+
SSLV2 => 'SSLv2',
|
20
|
+
SSLV3 => 'SSLv3',
|
21
|
+
TLSV1 => 'TLSv1',
|
22
|
+
TLSV1_1 => 'TLSv1.1',
|
23
|
+
TLSV1_2 => 'TLSv1.2'
|
24
|
+
}
|
25
|
+
|
26
|
+
TRUTH_TABLE = { true => 'true', false => 'false' }
|
27
|
+
end
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ScanSSL
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- bararchy
|
8
|
+
- ik5
|
9
|
+
- elichai
|
10
|
+
- Dor Lerner
|
11
|
+
- wolfedale
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
date: 2015-11-03 00:00:00.000000000 Z
|
16
|
+
dependencies: []
|
17
|
+
description: A simple and easy to use SSL Cipher scanner
|
18
|
+
email: bar.hofesh@gmail.com
|
19
|
+
executables:
|
20
|
+
- scanssl
|
21
|
+
extensions: []
|
22
|
+
extra_rdoc_files: []
|
23
|
+
files:
|
24
|
+
- bin/scanssl
|
25
|
+
- lib/scanssl.rb
|
26
|
+
- lib/scanssl/certInfo.rb
|
27
|
+
- lib/scanssl/fileExport.rb
|
28
|
+
- lib/scanssl/scanHost.rb
|
29
|
+
- lib/scanssl/settings.rb
|
30
|
+
- lib/scanssl/version.rb
|
31
|
+
homepage: https://github.com/bararchy/ruby-SSLscanner
|
32
|
+
licenses:
|
33
|
+
- GPL3
|
34
|
+
metadata: {}
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubyforge_project:
|
51
|
+
rubygems_version: 2.5.1
|
52
|
+
signing_key:
|
53
|
+
specification_version: 4
|
54
|
+
summary: ScanSSL
|
55
|
+
test_files: []
|
56
|
+
has_rdoc:
|