class Dawn::KnowledgeBase
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
Constants
- COMBO_CHECK
- CUSTOM_CHECK
- DEPENDENCY_CHECK
- FILES
- GEM_CHECK
- OS_CHECK
- PATTERN_MATCH_CHECK
- REMOTE_KB_URL_PREFIX
- RUBY_VERSION_CHECK
- UNSAFE_DEPENDENCY_CHECK
- VERSION
Attributes
Public Class Methods
# File lib/dawn/knowledge_base.rb, line 101 def self.enabled_checks= checks @@enabled_checks=checks end
# 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
# 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
# File lib/dawn/knowledge_base.rb, line 110 def self.path= path_name @path=path_name end
Public Instance Methods
# File lib/dawn/knowledge_base.rb, line 211 def all @security_checks end
# File lib/dawn/knowledge_base.rb, line 105 def default_path @path = File.join(Dir.home, 'dawnscanner', 'kb') return @path end
# 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
# 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 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).
# 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
# File lib/dawn/knowledge_base.rb, line 114 def is_packed? return __packed? end
# File lib/dawn/knowledge_base.rb, line 118 def is_valid? return __valid? end
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.
# 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
# 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
# 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
Private Instance Methods
# File lib/dawn/knowledge_base.rb, line 351 def __load? api = @descriptor[:kb][:api] v = Dawn::VERSION require "dawn/kb/version_check" vc = Dawn::Kb::VersionCheck.new return true if vc.is_higher?(v, api) # => true if v > api return false end
Check if the local KB is packet or not.
Returns true if at least one KB tarball file it has been found in the local DB path
# File lib/dawn/knowledge_base.rb, line 344 def __packed? FILES.each do |fn| return true if fn.end_with? 'tar.gz' and File.exist?(File.join(@path, fn)) end return false end
# File lib/dawn/knowledge_base.rb, line 310 def __valid? lines = "" unless File.exist?(File.join(@path, "kb.yaml")) $logger.error "Missing kb.yaml in #{path}. Giving up" return false end unless File.exist?(File.join(@path, "kb.yaml.sig")) $logger.error "Missing kb.yaml signature in #{path}. Giving up" return false end lines = File.read(File.join(@path, "kb.yaml")) hash_file = Digest::SHA256.hexdigest lines hash_orig = File.read(File.join(@path, "kb.yaml.sig")) v = __verify_hash(hash_orig, hash_file) if v debug_me("good kb.yaml file found. Reading knowledge base descriptor") @descriptor = YAML.load(lines) else $logger.error("kb.yaml signature mismatch. Found #{hash_file} while expecting #{hash_orig}. Giving up") return false end return true end
# File lib/dawn/knowledge_base.rb, line 304 def __verify_hash(original, computed) t=original.split(' ') return false if t.length != 2 return (t[0] == computed) end