Compare commits

..
Author SHA1 Message Date
bee 84232b66a3 updated readme 2024-11-12 16:44:55 +00:00
23 changed files with 3504 additions and 1506 deletions

No files matched your search

+28 -173
View File
@@ -1,179 +1,34 @@
name: CD Pipeline
on: [push]
name: Build
on:
pull_request:
branches: [master]
push:
branches:
- master
jobs:
build-nix:
env:
IN_PIPELINE: true
runs-on: ${{ matrix.os }}
if: github.ref == 'refs/heads/master'
test:
name: Test Suite
strategy:
matrix:
type: [ubuntu-x64, ubuntu-x86, armv7, aarch64]
include:
- type: ubuntu-x64
os: ubuntu-latest
target: x86_64-unknown-linux-musl
name: x86_64-linux-rustscan
path: target/x86_64-unknown-linux-musl/release/rustscan
pkg_config_path: /usr/lib/x86_64-linux-gnu/pkgconfig
- type: ubuntu-x86
os: ubuntu-latest
target: i686-unknown-linux-musl
name: x86-linux-rustscan
path: target/i686-unknown-linux-musl/release/rustscan
pkg_config_path: /usr/lib/i686-linux-gnu/pkgconfig
- type: armv7
os: ubuntu-latest
target: armv7-unknown-linux-gnueabihf
name: armv7-linux-rustscan
path: target/armv7-unknown-linux-gnueabihf/release/rustscan
pkg_config_path: /usr/lib/x86_64-linux-gnu/pkgconfig
- type: aarch64
os: ubuntu-latest
target: aarch64-unknown-linux-gnu
name: aarch64-linux-rustscan
path: target/aarch64-unknown-linux-gnu/release/rustscan
pkg_config_path: /usr/lib/x86_64-linux-gnu/pkgconfig
steps:
- uses: actions/checkout@v4
- name: Cache cargo & target directories
uses: Swatinem/rust-cache@v2
- name: Build binary
uses: houseabsolute/actions-rust-cross@v0
with:
command: build
target: ${{ matrix.target }}
args: "--locked --release"
strip: true
toolchain: stable
- name: Build tar.gz for homebrew installs
if: matrix.type == 'ubuntu-x64'
run: |
tar czf ${{ matrix.name }}.tar.gz -C target/x86_64-unknown-linux-musl/release rustscan
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.name }}
path: ${{ matrix.path }}
- uses: actions/upload-artifact@v4
if: matrix.type == 'ubuntu-x64'
with:
name: ${{ matrix.name }}.tar.gz
path: ${{ matrix.name }}.tar.gz
build-deb:
needs: [build-nix]
runs-on: ubuntu-latest
env:
IN_PIPELINE: true
steps:
- uses: actions/checkout@v4
- name: Install cargo-deb
run: cargo install -f cargo-deb
- uses: awalsh128/cache-apt-pkgs-action@v1
with:
packages: musl-tools # provides musl-gcc
version: 1.0
- name: Install musl toolchain
run: rustup target add x86_64-unknown-linux-musl
- name: Deb Build
run: cargo deb --target=x86_64-unknown-linux-musl
- name: Upload Deb Artifact
uses: actions/upload-artifact@v4
with:
name: rustscan.deb
path: ./target/x86_64-unknown-linux-musl/debian/*
build-macos:
env:
IN_PIPELINE: true
runs-on: macos-latest
if: github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v4
- name: Cache cargo & target directories
uses: Swatinem/rust-cache@v2
- name: Build binary
uses: houseabsolute/actions-rust-cross@v0
with:
command: build
target: x86_64-apple-darwin
args: "--locked --release"
strip: true
toolchain: stable
- name: Build tar.gz for homebrew installs
run: |
tar czf x86_64-macos-rustscan.tar.gz -C target/x86_64-apple-darwin/release rustscan
- uses: actions/upload-artifact@v4
with:
name: x86_64-macos-rustscan
path: target/x86_64-apple-darwin/release/rustscan
- uses: actions/upload-artifact@v4
with:
name: x86_64-macos-rustscan.tar.gz
path: x86_64-macos-rustscan.tar.gz
build-macos-aarch64:
env:
IN_PIPELINE: true
runs-on: macos-latest
if: github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v4
- name: Cache cargo & target directories
uses: Swatinem/rust-cache@v2
- name: Build binary
uses: houseabsolute/actions-rust-cross@v0
with:
command: build
target: aarch64-apple-darwin
args: "--locked --release"
strip: true
toolchain: stable
- name: Build tar.gz for homebrew installs
run: |
tar czf aarch64-macos-rustscan.tar.gz -C target/aarch64-apple-darwin/release rustscan
- uses: actions/upload-artifact@v4
with:
name: aarch64-macos-rustscan
path: target/aarch64-apple-darwin/release/rustscan
- uses: actions/upload-artifact@v4
with:
name: aarch64-macos-rustscan.tar.gz
path: aarch64-macos-rustscan.tar.gz
build-windows:
env:
IN_PIPELINE: true
os: [ubuntu-latest, macos-latest, windows-latest]
rust: [stable]
runs-on: ${{ matrix.os }}
if: github.ref == 'refs/heads/master'
strategy:
matrix:
type: [windows-x64, windows-x86]
include:
- type: windows-x64
os: windows-latest
target: x86_64-pc-windows-msvc
name: x86_64-windows-rustscan.exe
path: target\x86_64-pc-windows-msvc\release\rustscan.exe
- type: windows-x86
os: windows-latest
target: i686-pc-windows-msvc
name: x86-windows-rustscan.exe
path: target\i686-pc-windows-msvc\release\rustscan.exe
steps:
- uses: actions/checkout@v4
- name: Cache cargo & target directories
uses: Swatinem/rust-cache@v2
- name: Build binary
uses: houseabsolute/actions-rust-cross@v0
- name: Checkout sources
uses: actions/checkout@v4
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
command: build
target: ${{ matrix.target }}
args: "--locked --release"
strip: true
toolchain: stable
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.name }}
path: ${{ matrix.path }}
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
- uses: taiki-e/install-action@nextest
- uses: Swatinem/rust-cache@v2
- name: Install Just
uses: extractions/setup-just@v2
- name: Run just
run: just test
-19
View File
@@ -1,19 +0,0 @@
name: Dependabot Auto-Merge
on: pull_request
permissions:
contents: write
pull-requests: write
jobs:
dependabot:
runs-on: ubuntu-latest
if: ${{ github.actor == 'dependabot[bot]' }}
steps:
- name: Enable auto-merge for Dependabot PRs
run: |
gh pr review --approve "$PR_URL"
gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-34
View File
@@ -1,34 +0,0 @@
name: Test
on:
pull_request:
branches: [master]
push:
branches:
- master
jobs:
test:
name: Test Suite
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
rust: [stable]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
- uses: taiki-e/install-action@nextest
- uses: Swatinem/rust-cache@v2
- name: Install Just
uses: extractions/setup-just@v2
- name: Run just
run: just test
-2
View File
@@ -1,8 +1,6 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# generated file by build.rs for mapping nmap-payloads to rust
src/generated.rs
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Generated
+326 -874
View File
File diff suppressed because it is too large. Load diff
+12 -16
View File
@@ -1,6 +1,6 @@
[package]
name = "rustscan"
version = "2.4.1"
version = "2.3.0"
authors = ["Autumn <[email protected]>"]
edition = "2018"
description = "Faster Nmap Scanning with Rust"
@@ -17,33 +17,33 @@ exclude = [
]
[dependencies]
clap = { version = "4.5.31", features = ["derive", "wrap_help"] }
colored = "3.0.0"
clap = { version = "4.5.13", features = ["derive", "wrap_help"] }
colored = "2.1.0"
async-std = "1.13.0"
futures = "0.3"
rlimit = "0.10.2"
log = "0.4.25"
env_logger = "0.11.6"
log = "0.4.22"
env_logger = "0.11.5"
anstream = "=0.6.18"
dirs = "6.0.0"
dirs = "5.0.1"
gcd = "2.0.1"
rand = "0.9.0"
rand = "0.8.5"
colorful = "0.3.2"
ansi_term = "0.12.1"
toml = "0.8.20"
toml = "0.8.19"
serde = "1.0.124"
serde_derive = "1.0.116"
cidr-utils = "0.6.1"
itertools = "0.14.0"
hickory-resolver = { version = "0.24.3", features = ["dns-over-rustls"] }
itertools = "0.13.0"
hickory-resolver = { version = "0.24.0", features = ["dns-over-rustls"] }
anyhow = "1.0.40"
subprocess = "0.2.6"
text_placeholder = { version = "0.5", features = ["struct_context"] }
once_cell = "1.20.3"
once_cell = "1.20.2"
[dev-dependencies]
parameterized = "2.0.0"
wait-timeout = "0.2"
criterion = { version = "0.5", features = ["html_reports"] }
[package.metadata.deb]
depends = "$auto, nmap"
@@ -61,7 +61,3 @@ path = "src/main.rs"
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ["cfg(tarpaulin_include)"] }
[[bench]]
name = "benchmark_portscan"
harness = false
+25
View File
@@ -0,0 +1,25 @@
# Build Stage
FROM rust:alpine as builder
LABEL maintainer="RustScan <https://github.com/RustScan>"
RUN apk add --no-cache build-base
# Encourage some layer caching here rather then copying entire directory that includes docs to builder container ~CMN
WORKDIR /app/rustscan
COPY Cargo.toml Cargo.lock ./
COPY src/ src/
RUN cargo build --release
# Release Stage
FROM alpine:3.20.3 as release
LABEL author="Hydragyrum <https://github.com/Hydragyrum>"
LABEL author="LeoFVO <https://github.com/LeoFVO>"
RUN addgroup -S rustscan && \
adduser -S -G rustscan rustscan && \
ulimit -n 100000 && \
apk add --no-cache nmap nmap-scripts wget ca-certificates bind-tools
USER rustscan
COPY --from=builder /app/rustscan/target/release/rustscan /usr/local/bin/rustscan
ENTRYPOINT [ "/usr/local/bin/rustscan" ]
+2 -6
View File
@@ -19,10 +19,6 @@ The Modern Port Scanner. **Find ports quickly (3 seconds at its fastest)**. Run
# 🛠️ Installation
You can install RustScan's binary from our [releases page](https://github.com/RustScan/RustScan/releases).
We would prefer you to install with a package manager so it is tested and works for your system.
RustScan is in many repositories already. Install it with whatever tools you wish:
[![Packaging status](https://repology.org/badge/vertical-allrepos/rustscan.svg)](https://repology.org/project/rustscan/versions)
@@ -145,7 +141,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="https://skerritt.blog"><img src="https://avatars3.githubusercontent.com/u/10378052?v=4" width="100px;" alt=""/><br /><sub><b>Bee</b></sub></a><br /><a href="#infra-beeskerritt" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/RustScan/RustScan/commits?author=beeskerritt" title="Tests">⚠️</a> <a href="https://github.com/RustScan/RustScan/commits?author=beesan" title="Code">💻</a> <a href="#design-beeskerritt" title="Design">🎨</a></td>
<td align="center"><a href="https://skerritt.blog"><img src="https://avatars3.githubusercontent.com/u/10378052?v=4" width="100px;" alt=""/><br /><sub><b>Brandon</b></sub></a><br /><a href="#infra-brandonskerritt" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/RustScan/RustScan/commits?author=brandonskerritt" title="Tests">⚠️</a> <a href="https://github.com/RustScan/RustScan/commits?author=brandonskerritt" title="Code">💻</a> <a href="#design-brandonskerritt" title="Design">🎨</a></td>
<td align="center"><a href="https://sakiir.ovh"><img src="https://avatars1.githubusercontent.com/u/9950578?v=4" width="100px;" alt=""/><br /><sub><b>SakiiR</b></sub></a><br /><a href="https://github.com/RustScan/RustScan/commits?author=SakiiR" title="Code">💻</a> <a href="https://github.com/RustScan/RustScan/issues?q=author%3ASakiiR" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/smackhack"><img src="https://avatars2.githubusercontent.com/u/48143394?v=4" width="100px;" alt=""/><br /><sub><b>smackhack</b></sub></a><br /><a href="#ideas-smackhack" title="Ideas, Planning, & Feedback">🤔</a> <a href="#example-smackhack" title="Examples">💡</a></td>
<td align="center"><a href="http://bernardoamc.github.io/"><img src="https://avatars0.githubusercontent.com/u/428984?v=4" width="100px;" alt=""/><br /><sub><b>Bernardo Araujo</b></sub></a><br /><a href="https://github.com/RustScan/RustScan/commits?author=bernardoamc" title="Code">💻</a> <a href="https://github.com/RustScan/RustScan/issues?q=author%3Abernardoamc" title="Bug reports">🐛</a> <a href="#design-bernardoamc" title="Design">🎨</a></td>
@@ -232,4 +228,4 @@ This project follows the [all-contributors](https://github.com/all-contributors/
[badge-3]: https://img.shields.io/github/downloads/rustscan/rustscan/total?label=GitHub%20Downloads
[badge-4]: https://img.shields.io/crates/d/rustscan?label=Cargo%20Downloads
[badge-5]: https://img.shields.io/discord/754001738184392704
[badge-6]: https://github.com/RustScan/RustScan/actions/workflows/build.yml/badge.svg?branch=master
[badge-6]: https://github.com/RustScan/RustScan/workflows/Build/badge.svg?branch=master
-80
View File
@@ -1,80 +0,0 @@
use async_std::task::block_on;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use rustscan::input::{PortRange, ScanOrder};
use rustscan::port_strategy::PortStrategy;
use rustscan::scanner::Scanner;
use std::net::IpAddr;
use std::time::Duration;
fn portscan_tcp(scanner: &Scanner) {
let _scan_result = block_on(scanner.run());
}
fn portscan_udp(scanner: &Scanner) {
let _scan_result = block_on(scanner.run());
}
fn bench_address() {
let _addrs = ["127.0.0.1".parse::<IpAddr>().unwrap()];
}
fn bench_port_strategy() {
let range = PortRange {
start: 1,
end: 1_000,
};
let _strategy = PortStrategy::pick(&Some(range.clone()), None, ScanOrder::Serial);
}
fn criterion_benchmark(c: &mut Criterion) {
let addrs = vec!["127.0.0.1".parse::<IpAddr>().unwrap()];
let range = PortRange {
start: 1,
end: 1_000,
};
let strategy_tcp = PortStrategy::pick(&Some(range.clone()), None, ScanOrder::Serial);
let strategy_udp = PortStrategy::pick(&Some(range.clone()), None, ScanOrder::Serial);
let scanner_tcp = Scanner::new(
&addrs,
10,
Duration::from_millis(10),
1,
false,
strategy_tcp,
true,
vec![],
false,
);
c.bench_function("portscan tcp", |b| {
b.iter(|| portscan_tcp(black_box(&scanner_tcp)))
});
let scanner_udp = Scanner::new(
&addrs,
10,
Duration::from_millis(10),
1,
false,
strategy_udp,
true,
vec![],
true,
);
let mut udp_group = c.benchmark_group("portscan udp");
udp_group.measurement_time(Duration::from_secs(20));
udp_group.bench_function("portscan udp", |b| {
b.iter(|| portscan_udp(black_box(&scanner_udp)))
});
udp_group.finish();
// Benching helper functions
c.bench_function("parse address", |b| b.iter(bench_address));
c.bench_function("port strategy", |b| b.iter(bench_port_strategy));
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
+1 -1
View File
@@ -194,7 +194,7 @@ fn parser(payload: &str) -> Vec<u8> {
for (idx, char) in payload.chars().enumerate() {
if char == '\\' && payload.chars().nth(idx + 1) == Some('x') {
continue;
} else if char.is_ascii_hexdigit() {
} else if char.is_ascii_digit() {
tmp_str.push(char);
if tmp_str.len() == 2 {
bytes.push(u8::from_str_radix(&tmp_str, 16).unwrap());
-4
View File
@@ -1,4 +0,0 @@
tags = ["core_approved", "example"]
ports = ["80", "443", "8080"]
developer = ["example", "https://example.org"]
directory = "fixtures/.rustscan_scripts"
+1 -2
View File
@@ -1,7 +1,6 @@
test:
cargo nextest run
cargo clippy -- --deny warnings
cargo clippy --tests -- --deny warnings
cargo clippy
cargo fmt --check
cargo doc --workspace --all-features --no-deps --document-private-items
+23 -119
View File
@@ -69,19 +69,10 @@ pub fn parse_addresses(input: &Opts) -> Vec<IpAddr> {
}
}
// Finally, craft a list of addresses to be excluded from the scan.
let mut excluded_ips: BTreeSet<IpAddr> = BTreeSet::new();
if let Some(exclude_addresses) = &input.exclude_addresses {
for addr in exclude_addresses {
excluded_ips.extend(parse_address(addr, &backup_resolver));
}
}
// Remove duplicated/excluded IPs.
let mut seen = BTreeSet::new();
ips.retain(|ip| seen.insert(*ip) && !excluded_ips.contains(ip));
ips
ips.into_iter()
.collect::<BTreeSet<_>>()
.into_iter()
.collect()
}
/// Given a string, parse it as a host, IP address, or CIDR.
@@ -102,7 +93,7 @@ pub fn parse_address(address: &str, resolver: &Resolver) -> Vec<IpAddr> {
.map(|cidr| cidr.iter().map(|c| c.address()).collect())
.ok()
.or_else(|| {
format!("{}:80", &address)
format!("{}:{}", &address, 80)
.to_socket_addrs()
.ok()
.map(|mut iter| vec![iter.next().unwrap().ip()])
@@ -202,11 +193,8 @@ mod tests {
#[test]
fn parse_correct_addresses() {
let opts = Opts {
addresses: vec!["127.0.0.1".to_owned(), "192.168.0.0/30".to_owned()],
..Default::default()
};
let mut opts = Opts::default();
opts.addresses = vec!["127.0.0.1".to_owned(), "192.168.0.0/30".to_owned()];
let ips = parse_addresses(&opts);
assert_eq!(
@@ -221,71 +209,10 @@ mod tests {
);
}
#[test]
fn parse_addresses_with_address_exclusions() {
let opts = Opts {
addresses: vec!["192.168.0.0/30".to_owned()],
exclude_addresses: Some(vec!["192.168.0.1".to_owned()]),
..Default::default()
};
let ips = parse_addresses(&opts);
assert_eq!(
ips,
[
Ipv4Addr::new(192, 168, 0, 0),
Ipv4Addr::new(192, 168, 0, 2),
Ipv4Addr::new(192, 168, 0, 3)
]
);
}
#[test]
fn parse_addresses_with_cidr_exclusions() {
let opts = Opts {
addresses: vec!["192.168.0.0/29".to_owned()],
exclude_addresses: Some(vec!["192.168.0.0/30".to_owned()]),
..Default::default()
};
let ips = parse_addresses(&opts);
assert_eq!(
ips,
[
Ipv4Addr::new(192, 168, 0, 4),
Ipv4Addr::new(192, 168, 0, 5),
Ipv4Addr::new(192, 168, 0, 6),
Ipv4Addr::new(192, 168, 0, 7),
]
);
}
#[test]
fn parse_addresses_with_incorrect_address_exclusions() {
let opts = Opts {
addresses: vec!["192.168.0.0/30".to_owned()],
exclude_addresses: Some(vec!["192.168.0.1".to_owned()]),
..Default::default()
};
let ips = parse_addresses(&opts);
assert_eq!(
ips,
[
Ipv4Addr::new(192, 168, 0, 0),
Ipv4Addr::new(192, 168, 0, 2),
Ipv4Addr::new(192, 168, 0, 3)
]
);
}
#[test]
fn parse_correct_host_addresses() {
let opts = Opts {
addresses: vec!["google.com".to_owned()],
..Default::default()
};
let mut opts = Opts::default();
opts.addresses = vec!["google.com".to_owned()];
let ips = parse_addresses(&opts);
assert_eq!(ips.len(), 1);
@@ -293,11 +220,8 @@ mod tests {
#[test]
fn parse_correct_and_incorrect_addresses() {
let opts = Opts {
addresses: vec!["127.0.0.1".to_owned(), "im_wrong".to_owned()],
..Default::default()
};
let mut opts = Opts::default();
opts.addresses = vec!["127.0.0.1".to_owned(), "im_wrong".to_owned()];
let ips = parse_addresses(&opts);
assert_eq!(ips, [Ipv4Addr::new(127, 0, 0, 1),]);
@@ -305,61 +229,43 @@ mod tests {
#[test]
fn parse_incorrect_addresses() {
let opts = Opts {
addresses: vec!["im_wrong".to_owned(), "300.10.1.1".to_owned()],
..Default::default()
};
let mut opts = Opts::default();
opts.addresses = vec!["im_wrong".to_owned(), "300.10.1.1".to_owned()];
let ips = parse_addresses(&opts);
assert!(ips.is_empty());
}
#[test]
fn parse_hosts_file_and_incorrect_hosts() {
// Host file contains IP, Hosts, incorrect IPs, incorrect hosts
let opts = Opts {
addresses: vec!["fixtures/hosts.txt".to_owned()],
..Default::default()
};
let mut opts = Opts::default();
opts.addresses = vec!["fixtures/hosts.txt".to_owned()];
let ips = parse_addresses(&opts);
assert_eq!(ips.len(), 3);
}
#[test]
fn parse_empty_hosts_file() {
// Host file contains IP, Hosts, incorrect IPs, incorrect hosts
let opts = Opts {
addresses: vec!["fixtures/empty_hosts.txt".to_owned()],
..Default::default()
};
let mut opts = Opts::default();
opts.addresses = vec!["fixtures/empty_hosts.txt".to_owned()];
let ips = parse_addresses(&opts);
assert_eq!(ips.len(), 0);
}
#[test]
fn parse_naughty_host_file() {
// Host file contains IP, Hosts, incorrect IPs, incorrect hosts
let opts = Opts {
addresses: vec!["fixtures/naughty_string.txt".to_owned()],
..Default::default()
};
let mut opts = Opts::default();
opts.addresses = vec!["fixtures/naughty_string.txt".to_owned()];
let ips = parse_addresses(&opts);
assert_eq!(ips.len(), 0);
}
#[test]
fn parse_duplicate_cidrs() {
let opts = Opts {
addresses: vec!["79.98.104.0/21".to_owned(), "79.98.104.0/24".to_owned()],
..Default::default()
};
let mut opts = Opts::default();
opts.addresses = vec!["79.98.104.0/21".to_owned(), "79.98.104.0/24".to_owned()];
let ips = parse_addresses(&opts);
@@ -379,11 +285,9 @@ mod tests {
#[test]
fn resolver_args_google_dns() {
let mut opts = Opts::default();
// https://developers.google.com/speed/public-dns
let opts = Opts {
resolver: Some("8.8.8.8,8.8.4.4".to_owned()),
..Default::default()
};
opts.resolver = Some("8.8.8.8,8.8.4.4".to_owned());
let resolver = get_resolver(&opts.resolver);
let lookup = resolver.lookup_ip("www.example.com.").unwrap();
+2996
View File
File diff suppressed because it is too large. Load diff
+2 -14
View File
@@ -89,10 +89,6 @@ pub struct Opts {
#[arg(short, long)]
pub no_config: bool,
/// Hide the banner
#[arg(long)]
pub no_banner: bool,
/// Custom path to config file
#[arg(short, long, value_parser)]
pub config_path: Option<PathBuf>,
@@ -155,10 +151,6 @@ pub struct Opts {
#[arg(short, long, value_delimiter = ',')]
pub exclude_ports: Option<Vec<u16>>,
/// A list of comma separated CIDRs, IPs, or hosts to be excluded from scanning.
#[arg(short = 'x', long = "exclude-addresses", value_delimiter = ',')]
pub exclude_addresses: Option<Vec<String>>,
/// UDP scanning mode, finds UDP ports that send back responses
#[arg(long)]
pub udp: bool,
@@ -225,7 +217,7 @@ impl Opts {
self.ports = Some(ports);
}
merge_optional!(range, resolver, ulimit, exclude_ports, exclude_addresses);
merge_optional!(range, resolver, ulimit, exclude_ports);
}
}
@@ -245,12 +237,10 @@ impl Default for Opts {
resolver: None,
scan_order: ScanOrder::Serial,
no_config: true,
no_banner: false,
top: false,
scripts: ScriptsRequired::Default,
config_path: None,
exclude_ports: None,
exclude_addresses: None,
udp: false,
}
}
@@ -276,7 +266,6 @@ pub struct Config {
command: Option<Vec<String>>,
scripts: Option<ScriptsRequired>,
exclude_ports: Option<Vec<u16>>,
exclude_addresses: Option<Vec<String>>,
udp: Option<bool>,
}
@@ -292,7 +281,7 @@ impl Config {
/// addresses = ["127.0.0.1", "127.0.0.1"]
/// ports = [80, 443, 8080]
/// greppable = true
/// scan_order = "Serial"
/// scan_order: "Serial"
/// exclude_ports = [8080, 9090, 80]
/// udp = false
///
@@ -351,7 +340,6 @@ mod tests {
scan_order: Some(ScanOrder::Random),
scripts: None,
exclude_ports: None,
exclude_addresses: None,
udp: Some(false),
}
}
+10 -10
View File
@@ -21,17 +21,17 @@
//! start: 1,
//! end: 1_000,
//! };
//! let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random); // can be serial, random or manual https://github.com/RustScan/RustScan/blob/master/src/port_strategy/mod.rs
//! let strategy = PortStrategy::pick(&Some(range), None, ScanOrder::Random);
//! let scanner = Scanner::new(
//! &addrs, // the addresses to scan
//! 10, // batch_size is how many ports at a time should be scanned
//! Duration::from_millis(100), //T imeout is the time RustScan should wait before declaring a port closed. As datatype Duration.
//! 1, // Tries, how many retries should RustScan do?
//! true, // greppable is whether or not RustScan should print things, or wait until the end to print only the ip
//! strategy, // the port strategy used
//! true, // accessible, should the output be A11Y compliant?
//! vec![9000], // What ports should RustScan exclude?
//! false, // is this a UDP scan?
//! &addrs,
//! 10,
//! Duration::from_millis(100),
//! 1,
//! true,
//! strategy,
//! true,
//! vec![9000],
//! false,
//! );
//!
//! let scan_result = block_on(scanner.run());
+35 -37
View File
@@ -62,7 +62,7 @@ fn main() {
debug!("Scripts initialized {:?}", &scripts_to_run);
if !opts.greppable && !opts.accessible && !opts.no_banner {
if !opts.greppable && !opts.accessible {
print_opening(&opts);
}
@@ -83,6 +83,11 @@ fn main() {
#[cfg(not(unix))]
let batch_size: u16 = AVERAGE_BATCH_SIZE;
// Added by wasuaje - 01/26/2024:
// exclude_ports is an exclusion port list
//
// Added by brendanglancy - 5/19/2024:
// udp is an option to do a udp scan
let scanner = Scanner::new(
&ips,
batch_size,
@@ -195,17 +200,22 @@ fn main() {
#[allow(clippy::items_after_statements, clippy::needless_raw_string_hashes)]
fn print_opening(opts: &Opts) {
debug!("Printing opening");
let s = r#".----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
The Modern Day Port Scanner."#;
let s = format!(
"{}\n{}\n{}\n{}\n{}",
r#".----. .-. .-. .----..---. .----. .---. .--. .-. .-."#,
r#"| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |"#,
r#"| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |"#,
r#"`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'"#,
r#"The Modern Day Port Scanner."#
);
println!("{}", s.gradient(Color::Green).bold());
let info = r#"________________________________________
: http://discord.skerritt.blog :
: https://github.com/RustScan/RustScan :
--------------------------------------"#;
let info = format!(
"{}\n{}\n{}\n{}",
r#"________________________________________"#,
r#": http://discord.skerritt.blog :"#,
r#": https://github.com/RustScan/RustScan :"#,
r#" --------------------------------------"#
);
println!("{}", info.gradient(Color::Yellow).bold());
funny_opening!();
@@ -295,10 +305,8 @@ mod tests {
#[test]
#[cfg(unix)]
fn batch_size_lowered() {
let opts = Opts {
batch_size: 50_000,
..Default::default()
};
let mut opts = Opts::default();
opts.batch_size = 50_000;
let batch_size = infer_batch_size(&opts, 120);
assert!(batch_size < opts.batch_size);
@@ -307,10 +315,8 @@ mod tests {
#[test]
#[cfg(unix)]
fn batch_size_lowered_average_size() {
let opts = Opts {
batch_size: 50_000,
..Default::default()
};
let mut opts = Opts::default();
opts.batch_size = 50_000;
let batch_size = infer_batch_size(&opts, 9_000);
assert!(batch_size == 3_000);
@@ -320,10 +326,8 @@ mod tests {
fn batch_size_equals_ulimit_lowered() {
// because ulimit and batch size are same size, batch size is lowered
// to ULIMIT - 100
let opts = Opts {
batch_size: 50_000,
..Default::default()
};
let mut opts = Opts::default();
opts.batch_size = 50_000;
let batch_size = infer_batch_size(&opts, 5_000);
assert!(batch_size == 4_900);
@@ -332,11 +336,9 @@ mod tests {
#[cfg(unix)]
fn batch_size_adjusted_2000() {
// ulimit == batch_size
let opts = Opts {
batch_size: 50_000,
ulimit: Some(2_000),
..Default::default()
};
let mut opts = Opts::default();
opts.batch_size = 50_000;
opts.ulimit = Some(2_000);
let batch_size = adjust_ulimit_size(&opts);
assert!(batch_size == 2_000);
@@ -345,11 +347,9 @@ mod tests {
#[test]
#[cfg(unix)]
fn test_high_ulimit_no_greppable_mode() {
let opts = Opts {
batch_size: 10,
greppable: false,
..Default::default()
};
let mut opts = Opts::default();
opts.batch_size = 10;
opts.greppable = false;
let batch_size = infer_batch_size(&opts, 1_000_000);
@@ -358,10 +358,8 @@ mod tests {
#[test]
fn test_print_opening_no_panic() {
let opts = Opts {
ulimit: Some(2_000),
..Default::default()
};
let mut opts = Opts::default();
opts.ulimit = Some(2_000);
// print opening should not panic
print_opening(&opts);
}
+2 -2
View File
@@ -1,8 +1,8 @@
//! Provides a means to hold configuration options specifically for port scanning.
mod range_iterator;
use crate::input::{PortRange, ScanOrder};
use rand::rng;
use rand::seq::SliceRandom;
use rand::thread_rng;
use range_iterator::RangeIterator;
/// Represents options of port scanning.
@@ -35,7 +35,7 @@ impl PortStrategy {
}
ScanOrder::Serial => PortStrategy::Manual(ports.unwrap()),
ScanOrder::Random => {
let mut rng = rng();
let mut rng = thread_rng();
let mut ports = ports.unwrap();
ports.shuffle(&mut rng);
PortStrategy::Manual(ports)
+5 -5
View File
@@ -27,8 +27,8 @@ impl RangeIterator {
// Randomly choose a number within the range to be the first
// and assign it as a pick.
let mut rng = rand::rng();
let normalized_first_pick = rng.random_range(0..normalized_end);
let mut rng = rand::thread_rng();
let normalized_first_pick = rng.gen_range(0..normalized_end);
Self {
active: true,
@@ -83,14 +83,14 @@ fn pick_random_coprime(end: u32) -> u32 {
let range_boundary = end / 4;
let lower_range = range_boundary;
let upper_range = end - range_boundary;
let mut rng = rand::rng();
let mut candidate = rng.random_range(lower_range..upper_range);
let mut rng = rand::thread_rng();
let mut candidate = rng.gen_range(lower_range..upper_range);
for _ in 0..10 {
if end.gcd(candidate) == 1 {
return candidate;
}
candidate = rng.random_range(lower_range..upper_range);
candidate = rng.gen_range(lower_range..upper_range);
}
end - 1
+6 -5
View File
@@ -25,6 +25,8 @@ use std::{
/// batch_size is how many ports at a time should be scanned
/// Timeout is the time RustScan should wait before declaring a port closed. As datatype Duration.
/// greppable is whether or not RustScan should print things, or wait until the end to print only the ip and open ports.
/// Added by wasuaje - 01/26/2024:
/// exclude_ports is an exclusion port list
#[cfg(not(tarpaulin_include))]
#[derive(Debug)]
pub struct Scanner {
@@ -69,6 +71,8 @@ impl Scanner {
/// Runs scan_range with chunk sizes
/// If you want to run RustScan normally, this is the entry point used
/// Returns all open ports as `Vec<u16>`
/// Added by wasuaje - 01/26/2024:
/// Filtering port against exclude port list
pub async fn run(&self) -> Vec<SocketAddr> {
let ports: Vec<u16> = self
.port_strategy
@@ -193,10 +197,7 @@ impl Scanner {
}
}
Err(io::Error::new(
io::ErrorKind::Other,
format!("UDP scan timed-out for all tries on socket {}", socket),
))
Ok(socket)
}
/// Performs the connection to the socket with timeout
@@ -222,7 +223,7 @@ impl Scanner {
Ok(stream)
}
/// Binds to a UDP socket so we can send and receive packets
/// Binds to a UDP socket so we can send and recieve packets
/// # Example
///
/// ```compile_fail
+1 -1
View File
@@ -30,7 +30,7 @@ impl<'s> SocketIterator<'s> {
}
#[allow(clippy::doc_link_with_quotes)]
impl Iterator for SocketIterator<'_> {
impl<'s> Iterator for SocketIterator<'s> {
type Item = SocketAddr;
/// Returns a socket based on the combination of one of the provided
+27 -100
View File
@@ -79,17 +79,15 @@ use crate::input::ScriptsRequired;
use anyhow::{anyhow, Result};
use log::debug;
use serde_derive::{Deserialize, Serialize};
use std::convert::TryInto;
use std::fs::{self, File};
use std::io::{self, prelude::*};
use std::net::IpAddr;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::string::ToString;
use subprocess::{Exec, ExitStatus};
use text_placeholder::Template;
#[cfg(unix)]
use std::os::unix::process::ExitStatusExt;
static DEFAULT: &str = r#"tags = ["core_approved", "RustScan", "default"]
developer = [ "RustScan", "https://github.com/RustScan" ]
ports_separator = ","
@@ -108,21 +106,17 @@ pub fn init_scripts(scripts: &ScriptsRequired) -> Result<Vec<ScriptFile>> {
scripts_to_run.push(default_script);
}
ScriptsRequired::Custom => {
let script_config = ScriptConfig::read_config()?;
debug!("Script config \n{:?}", script_config);
let script_dir_base = if let Some(config_directory) = &script_config.directory {
PathBuf::from(config_directory)
} else {
dirs::home_dir().ok_or_else(|| anyhow!("Could not infer scripts path."))?
};
let script_paths = find_scripts(script_dir_base)?;
let scripts_dir_base =
dirs::home_dir().ok_or_else(|| anyhow!("Could not infer scripts path."))?;
let script_paths = find_scripts(scripts_dir_base)?;
debug!("Scripts paths \n{:?}", script_paths);
let parsed_scripts = parse_scripts(script_paths);
debug!("Scripts parsed \n{:?}", parsed_scripts);
let script_config = ScriptConfig::read_config()?;
debug!("Script config \n{:?}", script_config);
// Only Scripts that contain all the tags found in ScriptConfig will be selected.
if let Some(config_hashset) = script_config.tags {
for script in parsed_scripts {
@@ -190,14 +184,14 @@ struct ExecPartsScript {
script: String,
ip: String,
port: String,
ipversion: String,
ipversion: String
}
#[derive(Serialize)]
struct ExecParts {
ip: String,
port: String,
ipversion: String,
ipversion: String
}
impl Script {
@@ -254,8 +248,8 @@ impl Script {
port: ports_str,
ipversion: match &self.ip {
IpAddr::V4(_) => String::from("4"),
IpAddr::V6(_) => String::from("6"),
},
IpAddr::V6(_) => String::from("6")
}
};
to_run = default_template.fill_with_struct(&exec_parts_script)?;
} else {
@@ -264,8 +258,8 @@ impl Script {
port: ports_str,
ipversion: match &self.ip {
IpAddr::V4(_) => String::from("4"),
IpAddr::V6(_) => String::from("6"),
},
IpAddr::V6(_) => String::from("6")
}
};
to_run = default_template.fill_with_struct(&exec_parts)?;
}
@@ -277,41 +271,19 @@ impl Script {
#[cfg(not(tarpaulin_include))]
fn execute_script(script: &str) -> Result<String> {
debug!("\nScript arguments {}", script);
let (cmd, arg) = if cfg!(unix) {
("sh", "-c")
} else {
("cmd.exe", "/c")
};
match Command::new(cmd)
.args([arg, script])
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.output()
{
Ok(output) => {
let status = output.status;
let es = match status.code() {
Some(code) => code,
_ => {
#[cfg(unix)]
{
status.signal().unwrap()
}
#[cfg(windows)]
{
return Err(anyhow!("Unknown exit status"));
}
}
let process = Exec::shell(script);
match process.capture() {
Ok(c) => {
let es = match c.exit_status {
ExitStatus::Exited(c) => c.try_into().unwrap(),
ExitStatus::Signaled(c) => c.into(),
ExitStatus::Other(c) => c,
ExitStatus::Undetermined => -1,
};
if es != 0 {
return Err(anyhow!("Exit code = {}", es));
}
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
Ok(c.stdout_str())
}
Err(error) => {
debug!("Command error {}", error.to_string());
@@ -320,7 +292,8 @@ fn execute_script(script: &str) -> Result<String> {
}
}
pub fn find_scripts(path: PathBuf) -> Result<Vec<PathBuf>> {
pub fn find_scripts(mut path: PathBuf) -> Result<Vec<PathBuf>> {
path.push(".rustscan_scripts");
if path.is_dir() {
debug!("Scripts folder found {}", &path.display());
let mut files_vec: Vec<PathBuf> = Vec::new();
@@ -385,7 +358,6 @@ pub struct ScriptConfig {
pub tags: Option<Vec<String>>,
pub ports: Option<Vec<String>>,
pub developer: Option<Vec<String>>,
pub directory: Option<String>,
}
#[cfg(not(tarpaulin_include))]
@@ -404,7 +376,7 @@ impl ScriptConfig {
#[cfg(test)]
mod tests {
use super::*;
use super::{find_scripts, parse_scripts, Script, ScriptFile};
// Function for testing only, it inserts static values into ip and open_ports
// Doesn't use impl in case it's implemented in the super module at some point
@@ -422,7 +394,7 @@ mod tests {
#[test]
fn find_and_parse_scripts() {
let scripts = find_scripts("fixtures/.rustscan_scripts".into()).unwrap();
let scripts = find_scripts("fixtures/".into()).unwrap();
let scripts = parse_scripts(scripts);
assert_eq!(scripts.len(), 4);
}
@@ -519,49 +491,4 @@ mod tests {
// output has a newline at the end by default, .trim() trims it
assert_eq!(output.trim(), "Total args passed to fixtures/.rustscan_scripts/test_script.pl : 2\nArg # 1 : 127.0.0.1\nArg # 2 : 80,8080");
}
#[test]
fn test_custom_directory_config() {
// Create test config
let config_str = r#"
tags = ["core_approved", "example"]
directory = "fixtures/.rustscan_scripts"
"#;
let config: ScriptConfig = toml::from_str(config_str).unwrap();
assert_eq!(
config.directory,
Some("fixtures/.rustscan_scripts".to_string())
);
// Test that the directory is actually used
let script_dir_base = PathBuf::from(config.directory.unwrap());
let scripts = find_scripts(script_dir_base).unwrap();
// Verify we found the test script
assert!(scripts.iter().any(|p| p
.file_name()
.and_then(|f| f.to_str())
.map(|s| s == "test_script.txt")
.unwrap_or(false)));
}
#[test]
fn test_default_directory_fallback() {
let config_str = r#"
tags = ["core_approved", "example"]
"#;
let config: ScriptConfig = toml::from_str(config_str).unwrap();
assert_eq!(config.directory, None);
// Test fallback to home directory
let script_dir_base = if let Some(config_directory) = &config.directory {
PathBuf::from(config_directory)
} else {
dirs::home_dir().unwrap()
};
assert_eq!(script_dir_base, dirs::home_dir().unwrap());
}
}
+2 -2
View File
@@ -68,7 +68,7 @@ macro_rules! output {
macro_rules! funny_opening {
// prints a funny quote / opening
() => {
use rand::seq::IndexedRandom;
use rand::seq::SliceRandom;
let quotes = vec![
"Nmap? More like slowmap.🐢",
"🌍HACK THE PLANET🌍",
@@ -98,7 +98,7 @@ macro_rules! funny_opening {
"TreadStone was here 🚀",
"With RustScan, I scan ports so fast, even my firewall gets whiplash 💨",
];
let random_quote = quotes.choose(&mut rand::rng()).unwrap();
let random_quote = quotes.choose(&mut rand::thread_rng()).unwrap();
println!("{}\n", random_quote);
};