Class: Dawn::KnowledgeBase

Inherits:
Object
  • Object
show all
Includes:
Singleton
Defined in:
lib/dawn/knowledge_base.rb

Overview

This is the YAML powered knowledge base

Dawnscanner KB will be a bunch of YAML file, stored in a hierachy of directories resembling security checks family. A digital signature will be also available to prevent KB tampering.

This class will be accountable for:

+ check for KB upgrade
+ fetching the KB file from the Internet
+ verifying the database signature
+ reading YAML file, creating the security check array

Another big change will be the MVC passed as constructor parameter, so only the checks regarding the particular app, will be loaded in the security check array. This should speed up BasicCheck internal routines.

Class usage will be very simple. After getting the singleton instance, you will load the KB content. The load method will be also responsible about all relevant checks.

Example

require “dawn/knowledge_base”

d = Dawn::KnowledgeBase.instance d.update if d.update? d.load

Last update: Mon Mar 22 05:08:55 PM CET 2021

Constant Summary collapse

GEM_CHECK =
:rubygem_check
DEPENDENCY_CHECK =
:dependency_check
UNSAFE_DEPENDENCY_CHECK =
:unsafe_dependency_check
PATTERN_MATCH_CHECK =
:pattern_match_check
RUBY_VERSION_CHECK =
:ruby_version_check
OS_CHECK =
:os_check
COMBO_CHECK =
:combo_check
CUSTOM_CHECK =
:custom_check
REMOTE_KB_URL_PREFIX =
"https://dawnscanner.org/data/"
FILES =
%w(kb.yaml bulletin.tar.gz generic_check.tar.gz owasp_ror_cheatsheet.tar.gz code_style.tar.gz code_quality.tar.gz owasp_top_10.tar.gz signatures.tar.gz)
VERSION =
"0.0.1"
@@enabled_checks =
[:generic_check, :code_quality, :bulletin, :code_style, :owasp_top_10]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ KnowledgeBase

Returns a new instance of KnowledgeBase.



86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/dawn/knowledge_base.rb', line 86

def initialize(options={})
  if $logger.nil?
    require 'dawn/logger'
    $logger = Logger.new(STDOUT)
    $logger.helo "knowledge-base-experimental", Dawn::VERSION
  end
  @path = default_path
  @path = options[:path] if options[:path]
  FileUtils.mkdir_p(@path)

  @enabled_checks = @@enabled_checks

  debug_me "KB root path is #{@path}"
end

Instance Attribute Details

#descriptorObject (readonly)

Returns the value of attribute descriptor.



82
83
84
# File 'lib/dawn/knowledge_base.rb', line 82

def descriptor
  @descriptor
end

#errorObject (readonly)

Returns the value of attribute error.



84
85
86
# File 'lib/dawn/knowledge_base.rb', line 84

def error
  @error
end

#pathObject (readonly)

Returns the value of attribute path.



83
84
85
# File 'lib/dawn/knowledge_base.rb', line 83

def path
  @path
end

#security_checksObject (readonly)

Returns the value of attribute security_checks.



81
82
83
# File 'lib/dawn/knowledge_base.rb', line 81

def security_checks
  @security_checks
end

Class Method Details

.enabled_checks=(checks) ⇒ Object



101
102
103
# File 'lib/dawn/knowledge_base.rb', line 101

def self.enabled_checks= checks
  @@enabled_checks=checks
end

.kb_descriptorObject



186
187
188
# File 'lib/dawn/knowledge_base.rb', line 186

def self.kb_descriptor
  {:kb=>{:version=>VERSION, :revision=>Time.now.strftime("%Y%m%d"), :api=>Dawn::VERSION}}.to_yaml
end

.path=(path_name) ⇒ Object



110
111
112
# File 'lib/dawn/knowledge_base.rb', line 110

def self.path= path_name
  @path=path_name
end

Instance Method Details

#allObject



211
212
213
# File 'lib/dawn/knowledge_base.rb', line 211

def all
  @security_checks
end

#default_pathObject



105
106
107
108
# File 'lib/dawn/knowledge_base.rb', line 105

def default_path
  @path = File.join(Dir.home, 'dawnscanner', 'kb')
  return @path
end

#dump(verbose = false) ⇒ Object



285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/dawn/knowledge_base.rb', line 285

def dump(verbose=false)
  puts "Security checks currently supported:"
  i=0
  KnowledgeBase.instance.all.each do |check|
    i+=1
    if verbose
      puts "Name: #{check.name}\tCVSS: #{check.cvss_score}\tReleased: #{check.release_date}"
      puts "Description\n#{check.message}"
      puts "Remediation\n#{check.remediation}\n\n"
    else
      puts "#{check.name}"
    end
  end
  puts "-----\nTotal: #{i}"

end

#find(name) ⇒ Object



123
124
125
126
# File 'lib/dawn/knowledge_base.rb', line 123

def find(name)
  debug_me "I'm asked to find #{name}"
  debug_me "Please implement find command"
end

#find_issues_by_gem(string = "") ⇒ Object

Find all security issues affecting the gem passed as argument. The gem parameter can contains also the version number, separated by a ‘:’

Parameters:

string

A string containing the gem name, and eventually the version, to search for vulnerabilities. e.g.

$ dawn kb list sinatra        =>  returns all bulletins affecting sinatra gem
$ dawn kb list sinatra 2.0.0  =>  return all bulletins affecting
                                  sinatra gem version 2.0.0

Returns:

An array with all the vulnerabilities affecting the gem (or the particular gem version if provided).



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/dawn/knowledge_base.rb', line 144

def find_issues_by_gem(string = "")
  issues = []
  @security_checks.each do |check|
    if check.kind == Dawn::KnowledgeBase::DEPENDENCY_CHECK or check.kind == Dawn::KnowledgeBase::UNSAFE_DEPENDENCY_CHECK
         debug_me "applying check #{check.name}"
         name = string.split(':')[0]
         version = string.split(':')[1]
         check.please_ignore_dep_version = true if version.nil?
         check.dependencies  = [{:name=>name, :version=>version}]
         issues << check if check.vuln?
    end
  end
  debug_me "#{issues}"
  return issues
end

#is_packed?Boolean

Returns:

  • (Boolean)


114
115
116
# File 'lib/dawn/knowledge_base.rb', line 114

def is_packed?
  return __packed?
end

#is_valid?Boolean

Returns:

  • (Boolean)


118
119
120
# File 'lib/dawn/knowledge_base.rb', line 118

def is_valid?
  return __valid?
end

#load(lint = false) ⇒ Object

Load security checks from db/ folder.

Returns an array of security checks, matching the mvc to be reviewed and the enabled check list or an empty array if an error occured.



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/dawn/knowledge_base.rb', line 219

def load(lint=false)
  good    =0
  invalid =0

  unless @security_checks.nil?
    debug_me("KB was previously loaded")
    return @security_checks
  end
  @security_checks = []
  # $path = File.join(Dir.pwd, "db")

  unless __valid?
    @error = "An invalid library it has been found. Please use --recovery flag to force fresh install from dawnscanner.org"
    return []
  end

  unless __load?
    @error = "The library must be consumed with dawnscanner up to v#{@descriptor[:kb][:api]}. You are using dawnscanner v#{Dawn::VERSION}"
    return []
  end

  @enabled_checks.each do |d|

    dir = File.join(@path, d.to_s)

    # Please note that if we enter in this branch, it means someone
    # tampered the KB between the previous __valid? check and this point.
    # Of course this is a very rare situation, but we must handle it.
    unless Dir.exist?(dir)
      $logger.warn "Missing check directory #{dir}"
    else
      Dir.glob(dir+"/**/*.yml").each do |f|
        begin
          data = YAML.load_file(f, permitted_classes: [Dawn::Kb::UnsafeDependencyCheck,
                                                       Dawn::Kb::BasicCheck,
                                                       Dawn::Kb::ComboCheck,
                                                       Dawn::Kb::DependencyCheck,
                                                       Dawn::Kb::DeprecationCheck,
                                                       Dawn::Kb::OperatingSystemCheck,
                                                       Dawn::Kb::PatternMatchCheck,
                                                       Dawn::Kb::RubygemCheck,
                                                       Dawn::Kb::RubyVersionCheck,
                                                       Dawn::Kb::VersionCheck,
                                                       Date,
                                                       Symbol])
          @security_checks << data
          good+=1
          $logger.info("#{File.basename(f)} loaded") if lint
        rescue Exception => e
          $logger.error(e.message)
          invalid+=1
        end
      end
    end

    if lint
      $logger.info("#{invalid} invalid checks out of #{good+invalid}")
    end


  end

  debug_me "#{@security_checks.count}"
  return @security_checks
end

#unpackObject



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
# File 'lib/dawn/knowledge_base.rb', line 160

def unpack
  # https://weblog.jamisbuck.org/2015/7/23/tar-gz-in-ruby.html
  FILES.each do |f|
    full_name = File.join(path,f)
    if File.file?(full_name) and File.extname(full_name).eql?('.gz')
      File.open(full_name, "rb") do |file|
        Zlib::GzipReader.wrap(file) do |gz|
          Gem::Package::TarReader.new(gz) do |tar|
            tar.each do |entry|
              if entry.file?
                FileUtils.mkdir_p(File.dirname(File.join(path, entry.full_name)))
                File.open(File.join(path, entry.full_name), "wb") do |f|
                  f.write(entry.read)
                end
                File.chmod(entry.header.mode, File.join(path,entry.full_name))
              end
            end
          end
        end
      end
    else
      $logger.warn("can't open " + f)
    end
  end
end

#update?Boolean

Returns:

  • (Boolean)


190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/dawn/knowledge_base.rb', line 190

def update?
  FileUtils.mkdir_p("tmp")
  begin
    response = Net::HTTP.get URI(REMOTE_KB_URL_PREFIX + "kb.yaml")
    open("tmp/kb.yaml", "w") do |f|
      f.puts(response)
    end
    response = Net::HTTP.get URI(REMOTE_KB_URL_PREFIX + "kb.yaml.sig")
    open("tmp/kb.yaml.sig", "w") do |f|
      f.puts(response)
    end
  rescue Exception => e
    $logger.error e.to_s
    return false
  end

  # Verify kb.yaml signature

  YAML.load(response)
end