mihari 3.3.0 → 3.6.0
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 +4 -4
- data/README.md +4 -0
- data/config.ru +1 -0
- data/lib/mihari/analyzers/base.rb +34 -6
- data/lib/mihari/analyzers/censys.rb +37 -9
- data/lib/mihari/analyzers/onyphe.rb +34 -9
- data/lib/mihari/analyzers/shodan.rb +26 -5
- data/lib/mihari/cli/analyzer.rb +4 -0
- data/lib/mihari/cli/base.rb +0 -5
- data/lib/mihari/commands/init.rb +4 -4
- data/lib/mihari/commands/search.rb +17 -8
- data/lib/mihari/commands/web.rb +1 -0
- data/lib/mihari/{constraints.rb → constants.rb} +0 -0
- data/lib/mihari/database.rb +42 -3
- data/lib/mihari/mixins/rule.rb +5 -1
- data/lib/mihari/models/alert.rb +28 -10
- data/lib/mihari/models/artifact.rb +55 -0
- data/lib/mihari/models/autonomous_system.rb +9 -0
- data/lib/mihari/models/dns.rb +53 -0
- data/lib/mihari/models/geolocation.rb +9 -0
- data/lib/mihari/models/reverse_dns.rb +24 -0
- data/lib/mihari/models/whois.rb +119 -0
- data/lib/mihari/schemas/configuration.rb +1 -0
- data/lib/mihari/schemas/rule.rb +5 -15
- data/lib/mihari/serializers/alert.rb +6 -4
- data/lib/mihari/serializers/artifact.rb +11 -2
- data/lib/mihari/serializers/autonomous_system.rb +9 -0
- data/lib/mihari/serializers/dns.rb +11 -0
- data/lib/mihari/serializers/geolocation.rb +11 -0
- data/lib/mihari/serializers/reverse_dns.rb +11 -0
- data/lib/mihari/serializers/tag.rb +4 -2
- data/lib/mihari/serializers/whois.rb +11 -0
- data/lib/mihari/structs/censys.rb +92 -0
- data/lib/mihari/structs/onyphe.rb +47 -0
- data/lib/mihari/structs/shodan.rb +53 -0
- data/lib/mihari/templates/rule.yml.erb +3 -0
- data/lib/mihari/types.rb +21 -0
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/app.rb +2 -0
- data/lib/mihari/web/controllers/alerts_controller.rb +3 -4
- data/lib/mihari/web/controllers/artifacts_controller.rb +46 -2
- data/lib/mihari/web/controllers/ip_address_controller.rb +36 -0
- data/lib/mihari/web/controllers/sources_controller.rb +2 -2
- data/lib/mihari/web/controllers/tags_controller.rb +3 -1
- data/lib/mihari/web/public/index.html +1 -1
- data/lib/mihari/web/public/redoc-static.html +12 -10
- data/lib/mihari/web/public/static/fonts/fa-brands-400.1a575a41.woff +0 -0
- data/lib/mihari/web/public/static/fonts/fa-brands-400.513aa607.ttf +0 -0
- data/lib/mihari/web/public/static/fonts/fa-brands-400.592643a8.eot +0 -0
- data/lib/mihari/web/public/static/fonts/fa-brands-400.ed311c7a.woff2 +0 -0
- data/lib/mihari/web/public/static/fonts/fa-regular-400.766913e6.ttf +0 -0
- data/lib/mihari/web/public/static/fonts/fa-regular-400.b0e2db3b.eot +0 -0
- data/lib/mihari/web/public/static/fonts/fa-regular-400.b91d376b.woff2 +0 -0
- data/lib/mihari/web/public/static/fonts/fa-regular-400.d1d7e3b4.woff +0 -0
- data/lib/mihari/web/public/static/fonts/fa-solid-900.0c6bfc66.eot +0 -0
- data/lib/mihari/web/public/static/fonts/fa-solid-900.b9625119.ttf +0 -0
- data/lib/mihari/web/public/static/fonts/fa-solid-900.d745348d.woff +0 -0
- data/lib/mihari/web/public/static/fonts/fa-solid-900.d824df7e.woff2 +0 -0
- data/lib/mihari/web/public/static/img/fa-brands-400.1d5619cd.svg +3717 -0
- data/lib/mihari/web/public/static/img/fa-regular-400.c5d109be.svg +801 -0
- data/lib/mihari/web/public/static/img/fa-solid-900.37bc7099.svg +5034 -0
- data/lib/mihari/web/public/static/js/app.8e3e5150.js +36 -0
- data/lib/mihari/web/public/static/js/app.8e3e5150.js.map +1 -0
- data/lib/mihari/web/public/static/js/app.b5914c39.js +36 -0
- data/lib/mihari/web/public/static/js/app.b5914c39.js.map +1 -0
- data/lib/mihari.rb +25 -4
- data/mihari.gemspec +9 -2
- metadata +140 -8
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: c9c1cbdf0570c25e2d89d7f6fd402b64991dfaebc75cf3cf5422a56504287ae9
         | 
| 4 | 
            +
              data.tar.gz: d5b7a0db7b49f245e3c949135011fb674e28a9d3c251e165f827e8f3d90673b1
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: e76a216dedbc1aec17748c37a1b874c2c825fed6f7716ef356a48ddf2861584da299c384737e588a48b67165874f495192bb42a4c20c2f29f4620f8b559d1a83
         | 
| 7 | 
            +
              data.tar.gz: 2f3e380b252ba238594ccacd2df8e362a62b9685aafeff34d6506e7301184cf5b38c4244db9498842f4aee9df109d7c43a9ad99cecc3b63616d333a7f5093333
         | 
    
        data/README.md
    CHANGED
    
    | @@ -53,6 +53,10 @@ Mihari supports the following services by default. | |
| 53 53 |  | 
| 54 54 | 
             
            - [Mihari Knowledge Base](https://www.notion.so/Mihari-Knowledge-Base-266994ff61204428ba6cfcebe40b0bd1)
         | 
| 55 55 |  | 
| 56 | 
            +
            ## Presentations
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            - [Adversary Infrastructure Tracking with Mihari](https://ninoseki.github.io/presentations/Adversary%20Infrastructure%20Tracking%20with%20Mihari.pdf)
         | 
| 59 | 
            +
             | 
| 56 60 | 
             
            ## License
         | 
| 57 61 |  | 
| 58 62 | 
             
            The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
         | 
    
        data/config.ru
    CHANGED
    
    
| @@ -51,7 +51,7 @@ module Mihari | |
| 51 51 | 
             
                  # @return [nil]
         | 
| 52 52 | 
             
                  #
         | 
| 53 53 | 
             
                  def run
         | 
| 54 | 
            -
                     | 
| 54 | 
            +
                    set_enriched_artifacts
         | 
| 55 55 |  | 
| 56 56 | 
             
                    Parallel.each(valid_emitters) do |emitter|
         | 
| 57 57 | 
             
                      run_emitter emitter
         | 
| @@ -66,7 +66,7 @@ module Mihari | |
| 66 66 | 
             
                  # @return [nil]
         | 
| 67 67 | 
             
                  #
         | 
| 68 68 | 
             
                  def run_emitter(emitter)
         | 
| 69 | 
            -
                    emitter.run(title: title, description: description, artifacts:  | 
| 69 | 
            +
                    emitter.run(title: title, description: description, artifacts: enriched_artifacts, source: source, tags: tags)
         | 
| 70 70 | 
             
                  rescue StandardError => e
         | 
| 71 71 | 
             
                    puts "Emission by #{emitter.class} is failed: #{e}"
         | 
| 72 72 | 
             
                  end
         | 
| @@ -88,7 +88,7 @@ module Mihari | |
| 88 88 | 
             
                      # No need to set data_type manually
         | 
| 89 89 | 
             
                      # It is set automatically in #initialize
         | 
| 90 90 | 
             
                      artifact.is_a?(Artifact) ? artifact : Artifact.new(data: artifact, source: source)
         | 
| 91 | 
            -
                    end.select(&:valid?)
         | 
| 91 | 
            +
                    end.select(&:valid?).uniq(&:data)
         | 
| 92 92 | 
             
                  end
         | 
| 93 93 |  | 
| 94 94 | 
             
                  private
         | 
| @@ -105,12 +105,26 @@ module Mihari | |
| 105 105 | 
             
                  end
         | 
| 106 106 |  | 
| 107 107 | 
             
                  #
         | 
| 108 | 
            -
                  #  | 
| 108 | 
            +
                  # Enriched artifacts
         | 
| 109 | 
            +
                  #
         | 
| 110 | 
            +
                  # @return [Array<Mihari::Artifact>]
         | 
| 111 | 
            +
                  #
         | 
| 112 | 
            +
                  def enriched_artifacts
         | 
| 113 | 
            +
                    @enriched_artifacts ||= unique_artifacts.map do |artifact|
         | 
| 114 | 
            +
                      artifact.enrich_whois
         | 
| 115 | 
            +
                      artifact.enrich_dns
         | 
| 116 | 
            +
                      artifact.enrich_reverse_dns
         | 
| 117 | 
            +
                      artifact
         | 
| 118 | 
            +
                    end
         | 
| 119 | 
            +
                  end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                  #
         | 
| 122 | 
            +
                  # Set enriched artifacts
         | 
| 109 123 | 
             
                  #
         | 
| 110 124 | 
             
                  # @return [nil]
         | 
| 111 125 | 
             
                  #
         | 
| 112 | 
            -
                  def  | 
| 113 | 
            -
                    retry_on_error {  | 
| 126 | 
            +
                  def set_enriched_artifacts
         | 
| 127 | 
            +
                    retry_on_error { enriched_artifacts }
         | 
| 114 128 | 
             
                  rescue ArgumentError => _e
         | 
| 115 129 | 
             
                    klass = self.class.to_s.split("::").last.to_s
         | 
| 116 130 | 
             
                    raise Error, "Please configure #{klass} API settings properly"
         | 
| @@ -127,6 +141,20 @@ module Mihari | |
| 127 141 | 
             
                      emitter.valid? ? emitter : nil
         | 
| 128 142 | 
             
                    end
         | 
| 129 143 | 
             
                  end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                  #
         | 
| 146 | 
            +
                  # Normalize ASN value
         | 
| 147 | 
            +
                  #
         | 
| 148 | 
            +
                  # @param [String, Integer] asn
         | 
| 149 | 
            +
                  #
         | 
| 150 | 
            +
                  # @return [Integer]
         | 
| 151 | 
            +
                  #
         | 
| 152 | 
            +
                  def normalize_asn(asn)
         | 
| 153 | 
            +
                    return asn if asn.is_a?(Integer)
         | 
| 154 | 
            +
                    return asn.to_i unless asn.start_with?("AS")
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                    asn.delete_prefix("AS").to_i
         | 
| 157 | 
            +
                  end
         | 
| 130 158 | 
             
                end
         | 
| 131 159 | 
             
              end
         | 
| 132 160 | 
             
            end
         | 
| @@ -17,31 +17,59 @@ module Mihari | |
| 17 17 | 
             
                  private
         | 
| 18 18 |  | 
| 19 19 | 
             
                  def search
         | 
| 20 | 
            -
                     | 
| 20 | 
            +
                    artifacts = []
         | 
| 21 21 |  | 
| 22 22 | 
             
                    cursor = nil
         | 
| 23 23 | 
             
                    loop do
         | 
| 24 24 | 
             
                      response = api.search(query, cursor: cursor)
         | 
| 25 | 
            -
                       | 
| 25 | 
            +
                      response = Structs::Censys::Response.from_dynamic!(response)
         | 
| 26 26 |  | 
| 27 | 
            -
                       | 
| 28 | 
            -
             | 
| 27 | 
            +
                      artifacts << response_to_artifacts(response)
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                      cursor = response.result.links.next
         | 
| 29 30 | 
             
                      break if cursor == ""
         | 
| 30 31 | 
             
                    end
         | 
| 31 32 |  | 
| 32 | 
            -
                     | 
| 33 | 
            +
                    artifacts.flatten.uniq(&:data)
         | 
| 33 34 | 
             
                  end
         | 
| 34 35 |  | 
| 35 36 | 
             
                  #
         | 
| 36 37 | 
             
                  # Extract IPv4s from Censys search API response
         | 
| 37 38 | 
             
                  #
         | 
| 38 | 
            -
                  # @param [ | 
| 39 | 
            +
                  # @param [Structs::Censys::Response] response
         | 
| 39 40 | 
             
                  #
         | 
| 40 41 | 
             
                  # @return [Array<String>]
         | 
| 41 42 | 
             
                  #
         | 
| 42 | 
            -
                  def  | 
| 43 | 
            -
                    hits  | 
| 44 | 
            -
             | 
| 43 | 
            +
                  def response_to_artifacts(response)
         | 
| 44 | 
            +
                    response.result.hits.map { |hit| build_artifact(hit) }
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  #
         | 
| 48 | 
            +
                  # Build an artifact from a Shodan search API response
         | 
| 49 | 
            +
                  #
         | 
| 50 | 
            +
                  # @param [Structs::Censys::Hit] hit
         | 
| 51 | 
            +
                  #
         | 
| 52 | 
            +
                  # @return [Artifact]
         | 
| 53 | 
            +
                  #
         | 
| 54 | 
            +
                  def build_artifact(hit)
         | 
| 55 | 
            +
                    as = AutonomousSystem.new(asn: normalize_asn(hit.autonomous_system.asn))
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                    # sometimes Censys overlooks country
         | 
| 58 | 
            +
                    # then set geolocation as nil
         | 
| 59 | 
            +
                    geolocation = nil
         | 
| 60 | 
            +
                    unless hit.location.country.nil?
         | 
| 61 | 
            +
                      geolocation = Geolocation.new(
         | 
| 62 | 
            +
                        country: hit.location.country,
         | 
| 63 | 
            +
                        country_code: hit.location.country_code
         | 
| 64 | 
            +
                      )
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    Artifact.new(
         | 
| 68 | 
            +
                      data: hit.ip,
         | 
| 69 | 
            +
                      source: source,
         | 
| 70 | 
            +
                      autonomous_system: as,
         | 
| 71 | 
            +
                      geolocation: geolocation
         | 
| 72 | 
            +
                    )
         | 
| 45 73 | 
             
                  end
         | 
| 46 74 |  | 
| 47 75 | 
             
                  def configuration_keys
         | 
| @@ -1,6 +1,7 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require "onyphe"
         | 
| 4 | 
            +
            require "normalize_country"
         | 
| 4 5 |  | 
| 5 6 | 
             
            module Mihari
         | 
| 6 7 | 
             
              module Analyzers
         | 
| @@ -11,14 +12,13 @@ module Mihari | |
| 11 12 | 
             
                  option :tags, default: proc { [] }
         | 
| 12 13 |  | 
| 13 14 | 
             
                  def artifacts
         | 
| 14 | 
            -
                     | 
| 15 | 
            -
                    return [] unless  | 
| 15 | 
            +
                    responses = search
         | 
| 16 | 
            +
                    return [] unless responses
         | 
| 16 17 |  | 
| 17 | 
            -
                     | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
                    flat_results.filter_map { |result| result["ip"] }.uniq
         | 
| 18 | 
            +
                    results = responses.map(&:results).flatten
         | 
| 19 | 
            +
                    results.map do |result|
         | 
| 20 | 
            +
                      build_artifact result
         | 
| 21 | 
            +
                    end
         | 
| 22 22 | 
             
                  end
         | 
| 23 23 |  | 
| 24 24 | 
             
                  private
         | 
| @@ -34,7 +34,8 @@ module Mihari | |
| 34 34 | 
             
                  end
         | 
| 35 35 |  | 
| 36 36 | 
             
                  def search_with_page(query, page: 1)
         | 
| 37 | 
            -
                    api.simple.datascan(query, page: page)
         | 
| 37 | 
            +
                    res = api.simple.datascan(query, page: page)
         | 
| 38 | 
            +
                    Structs::Onyphe::Response.from_dynamic!(res)
         | 
| 38 39 | 
             
                  end
         | 
| 39 40 |  | 
| 40 41 | 
             
                  def search
         | 
| @@ -42,11 +43,35 @@ module Mihari | |
| 42 43 | 
             
                    (1..Float::INFINITY).each do |page|
         | 
| 43 44 | 
             
                      res = search_with_page(query, page: page)
         | 
| 44 45 | 
             
                      responses << res
         | 
| 45 | 
            -
             | 
| 46 | 
            +
             | 
| 47 | 
            +
                      total = res.total
         | 
| 46 48 | 
             
                      break if total <= page * PAGE_SIZE
         | 
| 47 49 | 
             
                    end
         | 
| 48 50 | 
             
                    responses
         | 
| 49 51 | 
             
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  #
         | 
| 54 | 
            +
                  # Build an artifact from an Onyphe search API result
         | 
| 55 | 
            +
                  #
         | 
| 56 | 
            +
                  # @param [Structs::Onyphe::Result] result
         | 
| 57 | 
            +
                  #
         | 
| 58 | 
            +
                  # @return [Artifact]
         | 
| 59 | 
            +
                  #
         | 
| 60 | 
            +
                  def build_artifact(result)
         | 
| 61 | 
            +
                    as = AutonomousSystem.new(asn: normalize_asn(result.asn))
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    geolocation = Geolocation.new(
         | 
| 64 | 
            +
                      country: NormalizeCountry(result.country_code, to: :short),
         | 
| 65 | 
            +
                      country_code: result.country_code
         | 
| 66 | 
            +
                    )
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                    Artifact.new(
         | 
| 69 | 
            +
                      data: result.ip,
         | 
| 70 | 
            +
                      source: source,
         | 
| 71 | 
            +
                      autonomous_system: as,
         | 
| 72 | 
            +
                      geolocation: geolocation
         | 
| 73 | 
            +
                    )
         | 
| 74 | 
            +
                  end
         | 
| 50 75 | 
             
                end
         | 
| 51 76 | 
             
              end
         | 
| 52 77 | 
             
            end
         | 
| @@ -14,12 +14,11 @@ module Mihari | |
| 14 14 | 
             
                    results = search
         | 
| 15 15 | 
             
                    return [] unless results || results.empty?
         | 
| 16 16 |  | 
| 17 | 
            +
                    results = results.map { |result| Structs::Shodan::Result.from_dynamic!(result) }
         | 
| 17 18 | 
             
                    results.map do |result|
         | 
| 18 | 
            -
                      matches = result | 
| 19 | 
            -
                      matches. | 
| 20 | 
            -
             | 
| 21 | 
            -
                      end
         | 
| 22 | 
            -
                    end.flatten.compact.uniq
         | 
| 19 | 
            +
                      matches = result.matches || []
         | 
| 20 | 
            +
                      matches.map { |match| build_artifact match }
         | 
| 21 | 
            +
                    end.flatten.compact.uniq(&:data)
         | 
| 23 22 | 
             
                  end
         | 
| 24 23 |  | 
| 25 24 | 
             
                  private
         | 
| @@ -57,6 +56,28 @@ module Mihari | |
| 57 56 | 
             
                    end
         | 
| 58 57 | 
             
                    responses
         | 
| 59 58 | 
             
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  #
         | 
| 61 | 
            +
                  # Build an artifact from a Shodan search API response
         | 
| 62 | 
            +
                  #
         | 
| 63 | 
            +
                  # @param [Structs::Shodan::Match] match
         | 
| 64 | 
            +
                  #
         | 
| 65 | 
            +
                  # @return [Artifact]
         | 
| 66 | 
            +
                  #
         | 
| 67 | 
            +
                  def build_artifact(match)
         | 
| 68 | 
            +
                    as = AutonomousSystem.new(asn: normalize_asn(match.asn))
         | 
| 69 | 
            +
                    geolocation = Geolocation.new(
         | 
| 70 | 
            +
                      country: match.location.country_name,
         | 
| 71 | 
            +
                      country_code: match.location.country_code
         | 
| 72 | 
            +
                    )
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                    Artifact.new(
         | 
| 75 | 
            +
                      data: match.ip_str,
         | 
| 76 | 
            +
                      source: source,
         | 
| 77 | 
            +
                      autonomous_system: as,
         | 
| 78 | 
            +
                      geolocation: geolocation
         | 
| 79 | 
            +
                    )
         | 
| 80 | 
            +
                  end
         | 
| 60 81 | 
             
                end
         | 
| 61 82 | 
             
              end
         | 
| 62 83 | 
             
            end
         | 
    
        data/lib/mihari/cli/analyzer.rb
    CHANGED
    
    | @@ -22,6 +22,10 @@ require "mihari/commands/json" | |
| 22 22 | 
             
            module Mihari
         | 
| 23 23 | 
             
              module CLI
         | 
| 24 24 | 
             
                class Analyzer < Base
         | 
| 25 | 
            +
                  class_option :ignore_old_artifacts, type: :boolean, default: false, desc: "Whether to ignore old artifacts from checking or not."
         | 
| 26 | 
            +
                  class_option :ignore_threshold, type: :numeric, default: 0, desc: "Number of days to define whether an artifact is old or not."
         | 
| 27 | 
            +
                  class_option :config, type: :string, desc: "Path to the config file"
         | 
| 28 | 
            +
             | 
| 25 29 | 
             
                  include Mihari::Commands::BinaryEdge
         | 
| 26 30 | 
             
                  include Mihari::Commands::Censys
         | 
| 27 31 | 
             
                  include Mihari::Commands::CIRCL
         | 
    
        data/lib/mihari/cli/base.rb
    CHANGED
    
    | @@ -12,11 +12,6 @@ module Mihari | |
| 12 12 | 
             
                  include Mihari::Mixins::Hash
         | 
| 13 13 | 
             
                  include Mixins::Utils
         | 
| 14 14 |  | 
| 15 | 
            -
                  class_option :config, type: :string, desc: "Path to the config file"
         | 
| 16 | 
            -
             | 
| 17 | 
            -
                  class_option :ignore_old_artifacts, type: :boolean, default: false, desc: "Whether to ignore old artifacts from checking or not. Only affects with analyze commands."
         | 
| 18 | 
            -
                  class_option :ignore_threshold, type: :numeric, default: 0, desc: "Number of days to define whether an artifact is old or not. Only affects with analyze commands."
         | 
| 19 | 
            -
             | 
| 20 15 | 
             
                  class << self
         | 
| 21 16 | 
             
                    def exit_on_failure?
         | 
| 22 17 | 
             
                      true
         | 
    
        data/lib/mihari/commands/init.rb
    CHANGED
    
    | @@ -5,10 +5,10 @@ require "colorize" | |
| 5 5 | 
             
            module Mihari
         | 
| 6 6 | 
             
              module Commands
         | 
| 7 7 | 
             
                module Initialization
         | 
| 8 | 
            -
                   | 
| 9 | 
            -
             | 
| 10 | 
            -
                    include Mixins::Rule
         | 
| 8 | 
            +
                  include Mixins::Configuration
         | 
| 9 | 
            +
                  include Mixins::Rule
         | 
| 11 10 |  | 
| 11 | 
            +
                  def self.included(thor)
         | 
| 12 12 | 
             
                    thor.class_eval do
         | 
| 13 13 | 
             
                      desc "config", "Create a config file"
         | 
| 14 14 | 
             
                      method_option :filename, type: :string, default: "mihari.yml"
         | 
| @@ -37,7 +37,7 @@ module Mihari | |
| 37 37 |  | 
| 38 38 | 
             
                        initialize_rule_yaml filename
         | 
| 39 39 |  | 
| 40 | 
            -
                        puts "The rule file is  | 
| 40 | 
            +
                        puts "The rule file is initialized as #{filename}.".colorize(:blue)
         | 
| 41 41 | 
             
                      end
         | 
| 42 42 | 
             
                    end
         | 
| 43 43 | 
             
                  end
         | 
| @@ -8,17 +8,27 @@ module Mihari | |
| 8 8 | 
             
                  def self.included(thor)
         | 
| 9 9 | 
             
                    thor.class_eval do
         | 
| 10 10 | 
             
                      desc "search [RULE]", "Search by a rule"
         | 
| 11 | 
            +
                      method_option :config, type: :string, desc: "Path to the config file"
         | 
| 11 12 | 
             
                      def search_by_rule(rule)
         | 
| 12 13 | 
             
                        # convert str(YAML) to hash or str(path/YAML file) to hash
         | 
| 13 14 | 
             
                        rule = load_rule(rule)
         | 
| 14 15 |  | 
| 15 16 | 
             
                        # validate rule schema
         | 
| 16 | 
            -
                        validate_rule | 
| 17 | 
            +
                        rule = validate_rule(rule)
         | 
| 17 18 |  | 
| 18 | 
            -
                        analyzer = build_rule_analyzer( | 
| 19 | 
            +
                        analyzer = build_rule_analyzer(
         | 
| 20 | 
            +
                          title: rule[:title],
         | 
| 21 | 
            +
                          description: rule[:description],
         | 
| 22 | 
            +
                          queries: rule[:queries],
         | 
| 23 | 
            +
                          tags: rule[:tags],
         | 
| 24 | 
            +
                          allowed_data_types: rule[:allowed_data_types],
         | 
| 25 | 
            +
                          disallowed_data_values: rule[:disallowed_data_values],
         | 
| 26 | 
            +
                          source: rule[:source],
         | 
| 27 | 
            +
                          id: rule[:id]
         | 
| 28 | 
            +
                        )
         | 
| 19 29 |  | 
| 20 | 
            -
                        ignore_old_artifacts =  | 
| 21 | 
            -
                        ignore_threshold =  | 
| 30 | 
            +
                        ignore_old_artifacts = rule[:ignore_old_artifacts]
         | 
| 31 | 
            +
                        ignore_threshold = rule[:ignore_threshold]
         | 
| 22 32 |  | 
| 23 33 | 
             
                        with_error_handling do
         | 
| 24 34 | 
             
                          run_rule_analyzer analyzer, ignore_old_artifacts: ignore_old_artifacts, ignore_threshold: ignore_threshold
         | 
| @@ -42,7 +52,7 @@ module Mihari | |
| 42 52 | 
             
                  #
         | 
| 43 53 | 
             
                  # @return [Mihari::Analyzers::Rule]
         | 
| 44 54 | 
             
                  #
         | 
| 45 | 
            -
                  def build_rule_analyzer(title:, description:, queries:, tags: nil, allowed_data_types: nil, disallowed_data_values: nil, source: nil)
         | 
| 55 | 
            +
                  def build_rule_analyzer(title:, description:, queries:, tags: nil, allowed_data_types: nil, disallowed_data_values: nil, source: nil, id: nil)
         | 
| 46 56 | 
             
                    tags = [] if tags.nil?
         | 
| 47 57 | 
             
                    allowed_data_types = ALLOWED_DATA_TYPES if allowed_data_types.nil?
         | 
| 48 58 | 
             
                    disallowed_data_values = [] if disallowed_data_values.nil?
         | 
| @@ -54,7 +64,8 @@ module Mihari | |
| 54 64 | 
             
                      queries: queries,
         | 
| 55 65 | 
             
                      allowed_data_types: allowed_data_types,
         | 
| 56 66 | 
             
                      disallowed_data_values: disallowed_data_values,
         | 
| 57 | 
            -
                      source: source
         | 
| 67 | 
            +
                      source: source,
         | 
| 68 | 
            +
                      id: id
         | 
| 58 69 | 
             
                    )
         | 
| 59 70 | 
             
                  end
         | 
| 60 71 |  | 
| @@ -62,8 +73,6 @@ module Mihari | |
| 62 73 | 
             
                  # Run rule analyzer
         | 
| 63 74 | 
             
                  #
         | 
| 64 75 | 
             
                  # @param [Mihari::Analyzer::Rule] analyzer
         | 
| 65 | 
            -
                  # @param [Boolean] ignore_old_artifacts
         | 
| 66 | 
            -
                  # @param [Integer] ignore_threshold
         | 
| 67 76 | 
             
                  #
         | 
| 68 77 | 
             
                  # @return [nil]
         | 
| 69 78 | 
             
                  #
         | 
    
        data/lib/mihari/commands/web.rb
    CHANGED
    
    | @@ -8,6 +8,7 @@ module Mihari | |
| 8 8 | 
             
                      desc "web", "Launch the web app"
         | 
| 9 9 | 
             
                      method_option :port, type: :numeric, default: 9292
         | 
| 10 10 | 
             
                      method_option :host, type: :string, default: "localhost"
         | 
| 11 | 
            +
                      method_option :config, type: :string, desc: "Path to the config file"
         | 
| 11 12 | 
             
                      def web
         | 
| 12 13 | 
             
                        port = options["port"].to_i || 9292
         | 
| 13 14 | 
             
                        host = options["host"] || "localhost"
         | 
| 
            File without changes
         | 
    
        data/lib/mihari/database.rb
    CHANGED
    
    | @@ -32,12 +32,48 @@ class InitialSchema < ActiveRecord::Migration[6.1] | |
| 32 32 | 
             
              end
         | 
| 33 33 | 
             
            end
         | 
| 34 34 |  | 
| 35 | 
            -
            class  | 
| 35 | 
            +
            class AddeSourceToArtifactSchema < ActiveRecord::Migration[6.1]
         | 
| 36 36 | 
             
              def change
         | 
| 37 37 | 
             
                add_column :artifacts, :source, :string, if_not_exists: true
         | 
| 38 38 | 
             
              end
         | 
| 39 39 | 
             
            end
         | 
| 40 40 |  | 
| 41 | 
            +
            class EnrichmentsSchema < ActiveRecord::Migration[6.1]
         | 
| 42 | 
            +
              def change
         | 
| 43 | 
            +
                create_table :autonomous_systems, if_not_exists: true do |t|
         | 
| 44 | 
            +
                  t.integer :asn, null: false
         | 
| 45 | 
            +
                  t.belongs_to :artifact, foreign_key: true
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                create_table :geolocations, if_not_exists: true do |t|
         | 
| 49 | 
            +
                  t.string :country, null: false
         | 
| 50 | 
            +
                  t.string :country_code, null: false
         | 
| 51 | 
            +
                  t.belongs_to :artifact, foreign_key: true
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                create_table :whois_records, if_not_exists: true do |t|
         | 
| 55 | 
            +
                  t.string :domain, null: false
         | 
| 56 | 
            +
                  t.date :created_on
         | 
| 57 | 
            +
                  t.date :updated_on
         | 
| 58 | 
            +
                  t.date :expires_on
         | 
| 59 | 
            +
                  t.json :registrar
         | 
| 60 | 
            +
                  t.json :contacts
         | 
| 61 | 
            +
                  t.belongs_to :artifact, foreign_key: true
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                create_table :dns_records, if_not_exists: true do |t|
         | 
| 65 | 
            +
                  t.string :resource, null: false
         | 
| 66 | 
            +
                  t.string :value, null: false
         | 
| 67 | 
            +
                  t.belongs_to :artifact, foreign_key: true
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                create_table :reverse_dns_names, if_not_exists: true do |t|
         | 
| 71 | 
            +
                  t.string :name, null: false
         | 
| 72 | 
            +
                  t.belongs_to :artifact, foreign_key: true
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
            end
         | 
| 76 | 
            +
             | 
| 41 77 | 
             
            def adapter
         | 
| 42 78 | 
             
              return "postgresql" if Mihari.config.database.start_with?("postgresql://", "postgres://")
         | 
| 43 79 | 
             
              return "mysql2" if Mihari.config.database.start_with?("mysql2://")
         | 
| @@ -59,10 +95,12 @@ module Mihari | |
| 59 95 | 
             
                      )
         | 
| 60 96 | 
             
                    end
         | 
| 61 97 |  | 
| 98 | 
            +
                    # ActiveRecord::Base.logger = Logger.new STDOUT
         | 
| 62 99 | 
             
                    ActiveRecord::Migration.verbose = false
         | 
| 63 100 |  | 
| 64 101 | 
             
                    InitialSchema.migrate(:up)
         | 
| 65 | 
            -
                     | 
| 102 | 
            +
                    AddeSourceToArtifactSchema.migrate(:up)
         | 
| 103 | 
            +
                    EnrichmentsSchema.migrate(:up)
         | 
| 66 104 | 
             
                  rescue StandardError
         | 
| 67 105 | 
             
                    # Do nothing
         | 
| 68 106 | 
             
                  end
         | 
| @@ -76,7 +114,8 @@ module Mihari | |
| 76 114 | 
             
                    return unless ActiveRecord::Base.connected?
         | 
| 77 115 |  | 
| 78 116 | 
             
                    InitialSchema.migrate(:down)
         | 
| 79 | 
            -
                     | 
| 117 | 
            +
                    AddeSourceToArtifactSchema.migrate(:down)
         | 
| 118 | 
            +
                    EnrichmentsSchema.migrate(:down)
         | 
| 80 119 | 
             
                  end
         | 
| 81 120 | 
             
                end
         | 
| 82 121 | 
             
              end
         |