Class: Yawast::Scanner::Plugins::SSL::Sweet32

Inherits:
Object
  • Object
show all
Defined in:
lib/scanner/plugins/ssl/sweet32.rb

Class Method Summary collapse

Class Method Details

.check_tdesObject



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/scanner/plugins/ssl/sweet32.rb', line 149

def self.check_tdes
  ret = false
  puts 'Confirming your OpenSSL supports 3DES cipher suites...'

  # find all versions that don't include '_server' or '_client'
  versions = OpenSSL::SSL::SSLContext::METHODS.find_all { |v| !v.to_s.include?('_client') && !v.to_s.include?('_server')}

  versions.each do |version|
    # ignore SSLv23, as it's an auto-negotiate, which just adds noise
    next unless version.to_s != 'SSLv23' && version.to_s != 'SSLv2'
    # try to get the list of ciphers supported for each version
    Yawast::Shared::Output.log_append_value 'openssl', 'tls_versions', version.to_s

    ciphers = nil

    get_ciphers_failed = false
    begin
      ciphers = OpenSSL::SSL::SSLContext.new(version).ciphers
    rescue StandardError => e
      Yawast::Utilities.puts_error "\tError getting cipher suites for #{version}, skipping. (#{e.message})"
      get_ciphers_failed = true
    end

    if !ciphers.nil?
      ciphers.each do |cipher|
        ret = true if cipher[0].include?('3DES') || cipher[0].include?('CBC3')

        Yawast::Shared::Output.log_append_value 'openssl', 'tls_ciphers', version.to_s, cipher[0]
      end
    elsif !get_ciphers_failed
      Yawast::Utilities.puts_info "\t#{version}: No cipher suites available."
    end
  end

  puts ''
  ret
end

.get_tdes_session_msg_count(uri, limit = 10_000) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/scanner/plugins/ssl/sweet32.rb', line 8

def self.get_tdes_session_msg_count(uri, limit = 10_000)
  Yawast::Shared::Output.log_value 'ssl', 'sweet32', 'limit', limit

  # this method will send a number of HEAD requests to see
  #  if the connection is eventually killed.
  unless check_tdes
    # if the OpenSSL install doesn't support 3DES, bailout
    Yawast::Utilities.puts_error "Your copy of OpenSSL doesn't support 3DES cipher suites - SWEET32 test aborted."
    puts '  See here for more information: https://github.com/adamcaudill/yawast/wiki/OpenSSL-&-3DES-Compatibility'

    Yawast::Shared::Output.log_value 'ssl', 'sweet32', '3des_supported', false
    return
  end

  Yawast::Shared::Output.log_value 'ssl', 'sweet32', '3des_supported', true

  puts 'TLS Session Request Limit: Checking number of requests accepted using 3DES suites...'

  count = 0
  begin
    req = Yawast::Shared::Http.get_http(uri)
    req.use_ssl = uri.scheme == 'https'
    req.keep_alive_timeout = 600
    headers = Yawast::Shared::Http.get_headers

    # we will use HEAD by default, but allow GET if we have issues with HEAD
    use_head = true

    # force 3DES - this is to ensure that 3DES specific limits are caught
    req.ciphers = ['3DES']
    cipher = nil

    # attempt to find a version that supports 3DES
    versions = OpenSSL::SSL::SSLContext::METHODS.find_all { |v| !v.to_s.include?('_client') && !v.to_s.include?('_server')}
    versions.each do |version|
      next unless version.to_s != 'SSLv23'
      req.ssl_version = version

      begin
        req.start do |http|

          head = nil
          begin
            head = if use_head
                     http.head(uri.path, headers)
                   else
                     http.request_get(uri.path, headers)
                   end

            cipher = http.instance_variable_get(:@socket).io.cipher[0]
          rescue StandardError
            # check if we are using HEAD or GET. If we've already switched to GET, no need to do this again.
            if use_head
              head = http.request_get(uri.path, headers)

              # if we are here, that means that HEAD failed, but GET didn't, so we'll use GET from now on.
              use_head = false
              Yawast::Utilities.puts_error 'Error: HEAD request failed; using GET requests for SWEET32 check...'
            end
          end

          # check to see if this is on Cloudflare - they break Keep-Alive limits, creating a false positive
          unless head.nil?
            head.each do |k, v|
              next unless k.casecmp('server').zero?
              if v == 'cloudflare'
                puts 'Cloudflare server found: SWEET32 mitigated: https://support.cloudflare.com/hc/en-us/articles/231510928'
              end
            end
          end
        end

        print "Using #{version} (#{cipher})"

        Yawast::Shared::Output.log_value 'ssl', 'sweet32', 'tls_version', version
        Yawast::Shared::Output.log_value 'ssl', 'sweet32', 'tls_cipher', cipher
        Yawast::Shared::Output.log_value 'ssl', 'sweet32', 'use_head_req', use_head

        break
      rescue StandardError # rubocop:disable Lint/HandleExceptions
        # we don't care
      end
    end

    # reset the req object
    req = Yawast::Shared::Http.get_http(uri)
    req.use_ssl = uri.scheme == 'https'
    req.keep_alive_timeout = 600

    req.ciphers = [*cipher]

    req.start do |http|
      limit.times do |i|
        if use_head
          http.head(uri.path, headers)
        else
          http.request_get(uri.path, headers)
        end

        # HACK: to detect transparent disconnects
        if http.instance_variable_get(:@ssl_context).session_cache_stats[:cache_hits] != 0
          raise 'TLS Reconnected'
        end

        count += 1

        print '.' if (i % 20).zero?
      end
    end
  rescue StandardError => e
    puts

    if e.message.include?('alert handshake failure') || e.message.include?('no cipher match')
      Yawast::Utilities.puts_info 'TLS Session Request Limit: Server does not support 3DES cipher suites'
    else
      Yawast::Utilities.puts_info "TLS Session Request Limit: Connection terminated after #{count} requests (#{e.message})"
    end

    Yawast::Shared::Output.log_value 'ssl', 'sweet32', 'vulnerable', false
    Yawast::Shared::Output.log_value 'ssl', 'sweet32', 'requests', count
    Yawast::Shared::Output.log_value 'ssl', 'sweet32', 'exception', e.message

    Yawast::Shared::Output.log_hash 'vulnerabilities',
                                    'tls_sweet32',
                                    {vulnerable: false}

    return
  end

  puts
  limit_formatted = limit.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
  Yawast::Utilities.puts_vuln "TLS Session Request Limit: Connection not terminated after #{limit_formatted} requests; possibly vulnerable to SWEET32"

  Yawast::Shared::Output.log_value 'ssl', 'sweet32', 'vulnerable', true
  Yawast::Shared::Output.log_value 'ssl', 'sweet32', 'requests', count

  Yawast::Shared::Output.log_hash 'vulnerabilities',
                                  'tls_sweet32',
                                  {vulnerable: true}
end