Skip to content

Commit 94d5038

Browse files
committed
updates
1 parent 7a8869f commit 94d5038

38 files changed

Lines changed: 3143 additions & 0 deletions

lib/asrfacet_rb.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,33 @@
6060
require_relative "asrfacet_rb/intelligence/open_asset_model"
6161
require_relative "asrfacet_rb/intelligence/asset_graph"
6262
require_relative "asrfacet_rb/intelligence/session_manager"
63+
require_relative "asrfacet_rb/intelligence/dns/dns_resolver"
64+
require_relative "asrfacet_rb/intelligence/dns/dns_wildcard"
65+
require_relative "asrfacet_rb/intelligence/dns/dns_permutator"
66+
require_relative "asrfacet_rb/intelligence/dns/dns_brute_forcer"
67+
require_relative "asrfacet_rb/intelligence/dns/zone_transfer"
68+
require_relative "asrfacet_rb/intelligence/sources/base_source"
69+
require_relative "asrfacet_rb/intelligence/sources/crtsh_source"
70+
require_relative "asrfacet_rb/intelligence/sources/hackertarget_source"
71+
require_relative "asrfacet_rb/intelligence/sources/wayback_source"
72+
require_relative "asrfacet_rb/intelligence/sources/rapiddns_source"
73+
require_relative "asrfacet_rb/intelligence/sources/alienvault_source"
74+
require_relative "asrfacet_rb/intelligence/sources/bufferover_source"
75+
require_relative "asrfacet_rb/intelligence/sources/urlscan_source"
76+
require_relative "asrfacet_rb/intelligence/sources/commoncrawl_source"
77+
require_relative "asrfacet_rb/intelligence/sources/virustotal_source"
78+
require_relative "asrfacet_rb/intelligence/sources/securitytrails_source"
79+
require_relative "asrfacet_rb/intelligence/sources/shodan_source"
80+
require_relative "asrfacet_rb/intelligence/sources/censys_source"
81+
require_relative "asrfacet_rb/intelligence/sources/github_source"
82+
require_relative "asrfacet_rb/intelligence/sources/dnsdumpster_source"
83+
require_relative "asrfacet_rb/intelligence/sources/threatcrowd_source"
84+
require_relative "asrfacet_rb/intelligence/sources/whoisxmlapi_source"
85+
require_relative "asrfacet_rb/intelligence/enrichment/whois_enricher"
86+
require_relative "asrfacet_rb/intelligence/enrichment/asn_enricher"
87+
require_relative "asrfacet_rb/intelligence/enrichment/certificate_enricher"
88+
require_relative "asrfacet_rb/intelligence/enrichment/ip_enricher"
89+
require_relative "asrfacet_rb/intelligence/enrichment/http_enricher"
6390
require_relative "asrfacet_rb/intelligence/analysis/relationship_mapper"
6491
require_relative "asrfacet_rb/intelligence/analysis/attack_surface"
6592
require_relative "asrfacet_rb/intelligence/analysis/asset_differ"
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# frozen_string_literal: true
2+
# SPDX-License-Identifier: Proprietary
3+
#
4+
# ASRFacet-Rb: Attack Surface Reconnaissance Framework
5+
# Copyright (c) 2026 voltsparx
6+
#
7+
# Author: voltsparx
8+
# Repository: https://github.com/voltsparx/ASRFacet-Rb
9+
# Contact: voltsparx@gmail.com
10+
# License: See LICENSE file in the project root
11+
#
12+
# This file is part of ASRFacet-Rb and is subject to the terms
13+
# and conditions defined in the LICENSE file.
14+
15+
require "set"
16+
require_relative "dns_permutator"
17+
require_relative "dns_wildcard"
18+
19+
module ASRFacet
20+
module Intelligence
21+
module Dns
22+
class DnsBruteForcer
23+
DEFAULT_MAX_PARALLELISM = 50
24+
DEFAULT_WORDLIST = File.expand_path(File.join(__dir__, "..", "..", "..", "..", "wordlists", "subdomains_small.txt")).freeze
25+
26+
def initialize(resolver:, wildcard_detector: nil, permutator: nil, event_bus: nil, logger: nil, max_parallelism: DEFAULT_MAX_PARALLELISM, wordlist_path: nil)
27+
@resolver = resolver
28+
@wildcard_detector = wildcard_detector || DnsWildcard.new(resolver: resolver, logger: logger)
29+
@permutator = permutator || DnsPermutator.new
30+
@event_bus = event_bus
31+
@logger = logger
32+
@max_parallelism = max_parallelism.to_i.positive? ? max_parallelism.to_i : DEFAULT_MAX_PARALLELISM
33+
@wordlist_path = wordlist_path || DEFAULT_WORDLIST
34+
end
35+
36+
def run(domain, discovered_subdomains = [])
37+
target = domain.to_s.downcase
38+
@wildcard_detector.detect(target)
39+
candidates = candidate_list(target, discovered_subdomains)
40+
results = Set.new
41+
mutex = Mutex.new
42+
pool = ASRFacet::ThreadPool.new(@max_parallelism)
43+
44+
candidates.each do |fqdn|
45+
pool.enqueue(label: fqdn) do
46+
next unless discovered?(target, fqdn)
47+
48+
mutex.synchronize { results << fqdn }
49+
emit_subdomain(target, fqdn)
50+
end
51+
end
52+
53+
pool.wait
54+
results.to_a.sort
55+
end
56+
57+
private
58+
59+
def candidate_list(domain, discovered_subdomains)
60+
built_in = load_wordlist.map { |label| "#{label}.#{domain}" }
61+
permutations = @permutator.generate(discovered_subdomains, domain)
62+
(built_in + permutations).map(&:downcase).uniq.sort
63+
end
64+
65+
def load_wordlist
66+
return [] unless File.file?(@wordlist_path)
67+
68+
File.readlines(@wordlist_path, chomp: true)
69+
.map(&:strip)
70+
.reject(&:empty?)
71+
.uniq
72+
rescue Errno::EACCES, Errno::ENOENT, IOError
73+
[]
74+
end
75+
76+
def discovered?(domain, fqdn)
77+
responses = @resolver.resolve_types(fqdn, %i[a aaaa cname])
78+
answers = responses.values.flat_map { |response| Array(response[:answers]) }
79+
return false if answers.empty?
80+
81+
@wildcard_detector.filter(domain, fqdn)
82+
rescue StandardError
83+
false
84+
end
85+
86+
def emit_subdomain(domain, fqdn)
87+
return if @event_bus.nil?
88+
89+
@event_bus.emit(
90+
:subdomain,
91+
{
92+
host: fqdn,
93+
parent: domain,
94+
data: { source: "dns_brute_forcer" }
95+
},
96+
dispatch_now: true
97+
)
98+
rescue StandardError
99+
nil
100+
end
101+
end
102+
end
103+
end
104+
end

0 commit comments

Comments
 (0)