Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .idea/play-with-rust.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions projects/lazyrecon/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "lazyrecon"
version = "0.1.0"
authors = ["Sylvain Kerkour <sylvain@kerkour.fr>", "Thomas Bui <thomasbui.dev@gmail.com>"]
edition = "2024"

[dependencies]
serde_json = "1"
clap = { version = "4.5", features = ["derive"] }
thiserror = "2.0.12"
anyhow = "1.0"
rayon = "1.5"
trust-dns-resolver = "0.23.2"
reqwest = { version = "0.12", default-features = false, features = ["json", "blocking", "rustls-tls"] }
serde = { version = "1", features = ["derive"] }
151 changes: 151 additions & 0 deletions projects/lazyrecon/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# LazyRecon Overview

This Rust project is a multi-threaded scanner designed for efficient attack surface discovery. It automates the process of finding subdomains and scanning their common TCP ports, helping security professionals quickly map out potential entry points.

This project draws inspiration from the book [Black Hat Rust](https://kerkour.com/black-hat-rust) by [Sylvain Kerkour](https://kerkour.com/), which explores offensive security techniques and tooling using the Rust programming language.

## Key Components

- **Subdomain Enumerator:** Discovers subdomains for a given target domain using a wordlist and DNS resolution.
- **Port Scanner:** For each discovered subdomain, scans a set of common TCP ports to identify open services.
- **Multithreading:** Both subdomain enumeration and port scanning leverage Rust's threading capabilities for high performance and speed.

## Architecture

The scanner is organized into the following modules:

- `src/main.rs`: Entry point. Handles CLI argument parsing, configuration, and coordinates the overall workflow.
- `src/subdomains.rs`: Implements subdomain enumeration logic, including DNS resolution and crt.sh integration.
- `src/ports.rs`: Contains TCP port scanning logic for discovered subdomains.
- `src/common_ports.rs`: Defines the set of common ports to scan.
- `src/model.rs`: Data models for subdomains, ports, and crt.sh entries.
- `src/error.rs`: Custom error types and error handling utilities.

The workflow is as follows:

1. **Input:** The user provides a target domain.
2. **Subdomain Enumeration:** The tool spawns threads to resolve potential subdomains in parallel.
3. **Port Scanning:** For each valid subdomain, threads are spawned to scan the specified ports.
4. **Results:** Open ports and discovered subdomains are reported in real-time or return to the screen.

## How to Use

### Prerequisites

- [Rust toolchain](https://www.rust-lang.org/tools/install) installed

### Run

```sh
# RUN cargo run -- --help for these details

Usage: lazyrecon [OPTIONS] --target <TARGET>

Options:
-t, --target <TARGET> Target domain to scan
-o, --output <FILE> Save results to a file instead of printing to console
-f, --format <FORMAT> Output format: plain or json (default: plain)
-h, --help Print help
-V, --version Print version
```

```sh
# Print result in the console
cargo run -- -t github.com
# OR
cargo run -- --target github.com

# Scan and save result in a specified file
cargo run -- -t github.com -o result.txt

# Scan and save result in a specified JSON file
cargo run -- -t github.com -o result.json -f json
```

# What is Reconnaissance?

Reconnaissance, often referred to as "recon" is the initial phase of a security assessment or penetration test where information about a target system, network, or organization is collected.

The objective is to gather as much relevant data as possible—such as domain names, IP addresses, technologies in use, and potential vulnerabilities—without alerting the target. This intelligence forms the foundation for identifying possible attack vectors and planning further actions.

There are 2 ways to perform reconnaissance: **Passive** and **Active**

![Passive vs Active Reconnaissance](./images/passive-vs-active-recons.png)

## Passive Reconnaissance

This is the process of gathering information about a target without interacting with it directly by using publicly available sources, also known as **OSINT** (Open Source INTelligence).

For example:

- Searching for information in public databases (WHOIS, DNS records)
- Reviewing social media profiles and posts
- Examining company websites and press releases
- Looking up leaked credentials or data breaches
- Gathering information from search engines (Google dorking)

### What Data is Collected During Passive Reconnaissance?

During passive reconnaissance, attackers or security professionals may collect:

- Domain names and subdomains
- IP addresses and network ranges
- WHOIS registration details
- DNS records (A, MX, TXT, etc.)
- Employee names, email addresses, and phone numbers
- Technology stacks and software versions
- Publicly available documents (PDFs, Word files, etc.)
- Social media activity and organizational structure
- Leaked credentials or sensitive data from breaches

Tools like [Shodan](https://www.shodan.io/) can be used to discover exposed servers, webcams, routers, and other devices, as well as to gather intelligence about a target's online infrastructure.

This information helps build a profile of the target without alerting them to the investigation.

## Active Reconnaissance

This is the process of gathering information about a target directly by interacting with it.

Active reconnaissance can be detected by firewalls, intrusion detection systems, or honeypots (a honeypot is an external endpoint that shall never be used by regular users, so the only user is hitting this endpoint are attackers, it can be a mail server, an HTTP server, or even a document with remote content embedded). To reduce the risk of detection, consider the following techniques:

- Limit the frequency and volume of requests to avoid triggering rate limits or alarms.
- Randomize request patterns and timing to mimic normal user behavior.
- Use proxy servers or VPNs to mask your source IP address.
- Rotate user-agent strings and other request headers.
- Avoid scanning sensitive or high-profile endpoints.
- Monitor for signs of detection, such as connection resets or blocks, and adjust your approach accordingly.

Careful planning and stealthy techniques can help minimize the chances of being detected during active reconnaissance.

# Reconnaissance Process

The reconnaissance of a target can be split into 2 steps:

1. Assets discovery
2. Vulnerability identification

## Assets Discovery

Traditionally, assets can be defined as technical elements such as: IP addresses, servers, domain names, networks, etc. Nowadays, the scope is broader and encompasses social network accounts, public source code repositories, IoT objects, etc. Basically, everything is on or connected to the internet.

The goal of assets discovery is to identify all resources and components associated with a target that could potentially be exploited.

By mapping out the full attack surface—including domains, subdomains, IP addresses, servers, APIs, cloud resources, and third-party services—security professionals or attackers can determine where vulnerabilities may exist and prioritize further investigation or protection efforts. Comprehensive asset discovery ensures that no critical entry points are overlooked during the reconnaissance phase.

### Subdomain Enumeration

The most common method is subdomains enumeration. It's the process of identifying and mapping all valid (resolvable) subdomains associated with a given domain name.

Think of a domain like a main building (e.g., `example.com`). Subdomains are like different departments or specialized areas within that building (e.g., `blog.example.com`, `shop.example.com`, `dev.example.com`, `api.example.com`). Each subdomain can host a separate website, application, or service.

#### Why is Subdomain Enumeration Important?

- **Expanded Attack Surface**: Every subdomain represents a potential entry point for attackers. A forgotten, misconfigured, or vulnerable subdomain can be a weak link in an organization's security, even if the main domain is well-secured.

- **Discovery of Hidden Assets**: Organizations often have numerous subdomains, some of which might be for internal use, testing, or outdated applications. These "hidden" subdomains are less likely to be regularly patched or monitored, making them prime targets for exploitation.

- **Information Gathering**: Discovering subdomains can reveal valuable information about an organization's infrastructure, technologies used, development environments, and even internal naming conventions. This intelligence can be used to craft more targeted attacks.

- **Vulnerability Detection**: By identifying all subdomains, security professionals can then scan each one for vulnerabilities, misconfigurations, or exposed sensitive information.

- **Preventing Subdomain Takeover**: Unused or improperly configured subdomains pointing to external services can be "taken over" by attackers, allowing them to host malicious content under the organization's legitimate domain. Enumeration helps identify and mitigate such risks.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions projects/lazyrecon/src/common_ports.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pub const MOST_COMMON_PORTS_100: &[u16] = &[
80, 23, 443, 21, 22, 25, 3389, 110, 445, 139, 143, 53, 135, 3306, 8080, 1723, 111, 995, 993,
5900, 1025, 587, 8888, 199, 1720, 465, 548, 113, 81, 6001, 10000, 514, 5060, 179, 1026, 2000,
8443, 8000, 32768, 554, 26, 1433, 49152, 2001, 515, 8008, 49154, 1027, 5666, 646, 5000, 5631,
631, 49153, 8081, 2049, 88, 79, 5800, 106, 2121, 1110, 49155, 6000, 513, 990, 5357, 427, 49156,
543, 544, 5101, 144, 7, 389, 8009, 3128, 444, 9999, 5009, 7070, 5190, 3000, 5432, 1900, 3986,
13, 1029, 9, 5051, 6646, 49157, 1028, 873, 1755, 2717, 4899, 9100, 119, 37,
];
15 changes: 15 additions & 0 deletions projects/lazyrecon/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use thiserror::Error;

#[derive(Error, Debug, Clone)]
pub enum Error {
#[error("Usage: cargo run -- <example.com>")]
CliUsage,
#[error("Reqwest: {0}")]
Reqwest(String),
}

impl std::convert::From<reqwest::Error> for Error {
fn from(error: reqwest::Error) -> Self {
Error::Reqwest(error.to_string())
}
}
143 changes: 143 additions & 0 deletions projects/lazyrecon/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use std::{
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
thread,
time::Duration,
};

use clap::{Arg, Command};

use anyhow::Ok;
use rayon::prelude::*;
use reqwest::{blocking::Client, redirect};

use crate::model::Subdomain;
pub use error::Error;
mod common_ports;
mod error;
mod model;
mod ports;
mod subdomains;

fn main() -> Result<(), anyhow::Error> {
let matches = Command::new("lazyrecon")
.version("1.0")
.author("Your Name <your@email.com>")
.about("Multi-threaded subdomain and port scanner")
.arg(
Arg::new("target")
.short('t')
.long("target")
.value_name("TARGET")
.help("Target domain to scan")
.required(true),
)
.arg(
Arg::new("output")
.short('o')
.long("output")
.value_name("FILE")
.help("Save results to a file instead of printing to console")
.required(false),
)
.arg(
Arg::new("format")
.short('f')
.long("format")
.value_name("FORMAT")
.help("Output format: plain or json (default: plain)")
.required(false),
)
.get_matches();

let target = matches.get_one::<String>("target").unwrap();
let output_path = matches.get_one::<String>("output");
let format = matches
.get_one::<String>("format")
.map(|s| s.as_str())
.unwrap_or("plain");

println!("Scanning target: {}", target);

// Set the HTTP request timeout duration to 5 seconds
let http_timeout = Duration::from_secs(5);

// Build a blocking HTTP client with a redirect policy (max 4 redirects) and the specified timeout
let http_client = Client::builder()
.redirect(redirect::Policy::limited(4))
.timeout(http_timeout)
.build()?;

// Spinner setup
let spinning = Arc::new(AtomicBool::new(true));
let spinning_clone = Arc::clone(&spinning);
let spinner_handle = thread::spawn(move || {
let spinner_chars = ['|', '/', '-', '\\'];
let mut i = 0;
while spinning_clone.load(Ordering::Relaxed) {
print!("\rLoading... {}", spinner_chars[i % spinner_chars.len()]);
std::io::Write::flush(&mut std::io::stdout()).unwrap();
thread::sleep(std::time::Duration::from_millis(100));
i += 1;
}
print!("\r \r"); // Clear the line after done
});

// Use a custom thread pool to improve speed
let pool = rayon::ThreadPoolBuilder::new()
.num_threads(256)
.build()
.unwrap();

let results = pool.install(|| {
let scan_result: Vec<Subdomain> = subdomains::enumerate(&http_client, target)
.unwrap()
.into_par_iter()
.map(ports::scan_ports)
.collect();
scan_result
});

let output = if format == "json" {
// Serialize results as JSON
serde_json::to_string_pretty(&results).unwrap()
} else {
// Format results as plain text
let mut out = String::new();
for subdomain in &results {
out.push_str("\n==============================\n");
out.push_str(&format!("Subdomain: {}\n", &subdomain.domain));
out.push_str("------------------------------\n");
if subdomain.open_ports.is_empty() {
out.push_str(" No open ports found.\n");
} else {
out.push_str(" Open Ports:\n");
for port in &subdomain.open_ports {
out.push_str(&format!(" - {}\n", port.port));
}
}
}
out.push_str("\n==============================\n");
out
};

if let Some(path) = output_path {
use std::fs::File;
use std::io::Write;
let mut file = File::create(path)?;
file.write_all(output.as_bytes())?;
println!("Results saved to {}", path);
} else {
print!("{}", output);
}

// Stop spinner and wait for thread to finish
spinning.store(false, Ordering::Relaxed);
spinner_handle.join().unwrap();

println!("Scan complete!");

Ok(())
}
18 changes: 18 additions & 0 deletions projects/lazyrecon/src/model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize)]
pub struct Subdomain {
pub domain: String,
pub open_ports: Vec<Port>,
}

#[derive(Debug, Clone, Serialize)]
pub struct Port {
pub port: u16,
pub is_open: bool,
}

#[derive(Debug, Deserialize, Clone)]
pub struct CrtShEntry {
pub name_value: String,
}
Loading