subscan/
lib.rs

1
2
3
4
5
6
7
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
148
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
#![forbid(unsafe_code)]

//! <!-- markdownlint-disable MD033 MD041 -->
//! <div align="center">
//!   <picture>
//!     <source media="(prefers-color-scheme: dark)" srcset="https://github.com/eredotpkfr/subscan/blob/main/assets/logo-light.png?raw=true">
//!     <img alt="Subscan Logo" height="105px" src="https://github.com/eredotpkfr/subscan/blob/main/assets/logo-dark.png?raw=true">
//!   </picture>
//! </div>
//! <br>
//! <p align="center">
//!   <a href="https://github.com/eredotpkfr/subscan/?tab=readme-ov-file#install">Install</a> •
//!   <a href="https://github.com/eredotpkfr/subscan/?tab=readme-ov-file#usage">Usage</a> •
//!   <a href="https://docs.rs/subscan/latest/subscan/">Doc</a> •
//!   <a href="https://www.erdoganyoksul.com/subscan/">Book</a> •
//!   <a href="https://github.com/eredotpkfr/subscan/?tab=readme-ov-file#docker">Docker</a> •
//!   <a href="https://github.com/eredotpkfr/subscan/?tab=readme-ov-file#development">Development</a>
//! </p>
//! <!-- markdownlint-enable MD033 MD041 -->
//!
//! Subscan is a powerful subdomain enumeration tool built with
//! [Rust](https://www.rust-lang.org/), specifically designed for penetration testing purposes.
//! It combines various discovery techniques into a single, lightweight binary, making
//! subdomain hunting easier and faster for security researchers

/// In-memory cache to store all modules
pub mod cache;
/// Includes CLI components
pub mod cli;
/// Project constants
pub mod constants;
/// Enumerations and project type definitions
pub mod enums;
/// Subscan error type
pub mod error;
/// Data extractors like
/// [`extractors::regex`], [`extractors::html`], etc.
pub mod extractors;
/// Trait implementations
pub mod interfaces;
/// Logger utilities
pub mod logger;
/// All modules listed under this module, core components for subscan
pub mod modules;
/// `Subscan` worker pool definitions, allows to run modules as asynchronously
pub mod pools;
/// HTTP requesters listed under this module
/// like [`requesters::chrome`], [`requesters::client`], etc.
pub mod requesters;
/// IP address resolver component
pub mod resolver;
/// Project core type definitions
pub mod types;
/// Utilities for the handle different stuff things
pub mod utilities;

use constants::LOG_TIME_FORMAT;
use resolver::Resolver;
use tokio::sync::OnceCell;

use crate::{
    cache::CacheManager,
    cli::Cli,
    interfaces::module::SubscanModuleInterface,
    pools::{brute::SubscanBrutePool, module::SubscanModulePool},
    types::{config::subscan::SubscanConfig, core::SubscanModule, result::subscan::SubscanResult},
};

static INIT: OnceCell<()> = OnceCell::const_new();

/// Main [`Subscan`] object definition
#[derive(Default)]
pub struct Subscan {
    /// Subscan configurations
    pub config: SubscanConfig,
    /// Cache manager instance to manage modules cache
    pub manager: CacheManager,
}

impl From<Cli> for Subscan {
    fn from(cli: Cli) -> Self {
        Self {
            config: cli.into(),
            manager: CacheManager::default(),
        }
    }
}

impl From<SubscanConfig> for Subscan {
    fn from(config: SubscanConfig) -> Self {
        Self {
            config,
            manager: CacheManager::default(),
        }
    }
}

impl Subscan {
    pub fn new(config: SubscanConfig) -> Self {
        Self {
            config,
            manager: CacheManager::default(),
        }
    }

    async fn init(&self) {
        let rconfig = self.config.clone().into();
        let inner = || async { self.manager.configure(rconfig).await };

        INIT.get_or_init(inner).await;
    }

    pub async fn module(&self, name: &str) -> &SubscanModule {
        self.manager.module(name).await.expect("Module not found!")
    }

    pub async fn modules(&self) -> &Vec<SubscanModule> {
        self.manager.modules().await
    }

    pub async fn scan(&self, domain: &str) -> SubscanResult {
        self.init().await;

        let mut result = SubscanResult::from(domain);

        let time = result.metadata.started_at.format(LOG_TIME_FORMAT);
        let pool = SubscanModulePool::from(domain, self.config.clone());

        log::info!("Started scan on {} ({})", domain, time);

        pool.clone().start(self.modules().await).await;

        result.update_with_pool_result(pool.result().await).await;
        result.with_finished().await
    }

    pub async fn run(&self, name: &str, domain: &str) -> SubscanResult {
        let mut result = SubscanResult::from(domain);

        let time = result.metadata.started_at.format(LOG_TIME_FORMAT);
        let pool = SubscanModulePool::from(domain, self.config.clone());
        let module = self.module(name).await;
        let rconfig = self.config.clone().into();

        module.lock().await.configure(rconfig).await;

        log::info!(
            "Running {} module on {} ({})",
            module.lock().await.name().await,
            domain,
            time
        );

        pool.clone().start(&vec![module.clone()]).await;

        result.update_with_pool_result(pool.result().await).await;
        result.with_finished().await
    }

    pub async fn brute(&self, domain: &str) -> SubscanResult {
        let mut result = SubscanResult::from(domain);

        let time = result.metadata.started_at.format(LOG_TIME_FORMAT);
        let concurrency = self.config.resolver.concurrency;

        let resolver = Resolver::boxed_from(self.config.resolver.clone());
        let pool = SubscanBrutePool::new(domain.into(), concurrency, resolver);
        let wordlist = self
            .config
            .wordlist
            .clone()
            .expect("Wordlist must be specified!");

        log::info!("Started brute force attack on {} ({})", domain, time);

        pool.clone().start(wordlist).await;

        result.update_with_pool_result(pool.result().await).await;
        result.with_finished().await
    }
}