Compare commits

...
Author SHA1 Message Date
Bee 5c613082fb Bee ci changes (#250)
* 1.10.0 release

* building CI

* building CI
2020-09-29 15:33:09 +01:00
Bee 2868e13db4 Merge branch 'master' into bee-CI-changes 2020-09-29 15:28:25 +01:00
Brandon 31ed2e8914 building CI 2020-09-29 15:24:36 +01:00
Brandon 0426aa83e3 building CI 2020-09-29 15:19:42 +01:00
Brandon 4deb5eff53 1.10.0 release 2020-09-29 12:28:12 +01:00
Brandon 81ad00dc29 1.10.0 release 2020-09-29 00:29:58 +01:00
Bee 64cac6e0fa Added DNS lookup using Google DNS, refactored IP code (#246)
* Added DNS lookup using Google DNS, refactored IP code

* fixed comments & cargo fmt

* Fixed code review comments, added tests, removed unwrap() panics

* Added assertion to test
2020-09-28 21:19:55 +01:00
dependabot-preview[bot] 739e9716fc Bump structopt from 0.3.17 to 0.3.18 (#243)
Bumps [structopt](https://github.com/TeXitoi/structopt) from 0.3.17 to 0.3.18.
- [Release notes](https://github.com/TeXitoi/structopt/releases)
- [Changelog](https://github.com/TeXitoi/structopt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TeXitoi/structopt/compare/v0.3.17...v0.3.18)

Signed-off-by: dependabot-preview[bot] <[email protected]>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-09-24 13:38:47 +01:00
Bee 6bf70f0f9a Fixed output error, Warning! ran when file opened (#237)
Take file of addresses as input
2020-09-23 12:42:13 +01:00
Ben (CMNatic) 57dfb15852 Update Dockerfile to encourage layer caching (#241)
At the moment the Dockerfile brings the entire current directory in as a context for the building container. We can encourage some caching here by only bringing forward the essentials:
`cargo.tml`, `cargo.lock` and the `src`

+ Added a maintainer label
2020-09-23 12:40:51 +01:00
allcontributors[bot]andBee 282065752c docs: add dmitris as a contributor (#232)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
Co-authored-by: Bee <[email protected]>
2020-09-22 12:55:29 +01:00
Brandon 982ae0d926 README updates 2020-09-21 23:19:29 +01:00
Brandon 53f8a15e16 Merge branch 'master' of github.com:RustScan/RustScan into master 2020-09-21 23:19:19 +01:00
Bee fc214a78b4 Increase code coverage (#236)
* Updated code coverage
2020-09-21 17:36:40 +01:00
Brandon 0ae289c25f Merge branch 'master' of github.com:RustScan/RustScan into master 2020-09-21 14:17:52 +01:00
bergabman 9326fc4a0f Refactored test, intorduced default() in test context for structs for easier test writing, Scanner struct quiet changed to greppable. (#235) 2020-09-21 00:00:11 +01:00
Brandon c05ebaf857 Merge branch 'master' of github.com:RustScan/RustScan into master 2020-09-20 15:58:39 +01:00
Ben (CMNatic) 6f8df7e77e Merge pull request #234 from CMNatic/master
Fix version tag (v1.9.0) typo in docker usage
2020-09-20 13:11:35 +01:00
Ben (CMNatic) 488853f40e Merge branch 'master' into master 2020-09-20 13:09:52 +01:00
Ben (CMNatic) 57c1e33650 typo in docker instructions 2020-09-20 13:02:17 +01:00
Ben (CMNatic) 699b578ade Update README for v1.9.0 release (#233)
DockerHub tag for v1.9.0 release has been pushed too
2020-09-20 13:00:30 +01:00
Ben (CMNatic) ebe5a326a8 Update README for v1.9.0 release
DockerHub tag for v1.9.0 release has been pushed too
2020-09-20 12:55:47 +01:00
Brandon abb7a3872c releasing 2020-09-20 12:27:51 +01:00
Dmitry SavintsevandBee f74cb456b1 socket_iterator: use itertools.iproducts for ip x ports set (#231)
Replace the current manual iterator-like implementation
with the call to the itertools.iproduct macro
to generate the cartesian product of the IPs and ports.

stdlib reference:
https://docs.rs/itertools/0.9.0/itertools/macro.iproduct.html

Co-authored-by: Bee <[email protected]>
2020-09-20 12:15:46 +01:00
Brandon 492d2fb3b3 fixed accessible issues 2020-09-20 01:25:41 +01:00
bergabmanandBee af53591ccb Added top ports, fixed accessibility. (#230)
* Refactored scanner struct for accessibility, fixed macros

Co-authored-by: Bee <[email protected]>
2020-09-19 18:24:08 +01:00
Bee 6405b43f5a Merge pull request #229 from dmitris/patch-1
fix typo
2020-09-19 00:54:24 +01:00
Dmitry Savintsev 9a71253697 fix typo 2020-09-18 22:13:33 +02:00
17 changed files with 1056 additions and 462 deletions

No files matched your search

+9
View File
@@ -193,6 +193,15 @@
"bug",
"design"
]
},
{
"login": "dmitris",
"name": "Dmitry Savintsev",
"avatar_url": "https://avatars0.githubusercontent.com/u/31205?v=4",
"profile": "https://github.com/dmitris",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,
-3
View File
@@ -1,4 +1 @@
github: bee-san
patreon: brandonskerritt
ko_fi: hacker
custom: ["https://monzo.me/bee", "https://paypal.me/brandonskerritt"]
+19
View File
@@ -0,0 +1,19 @@
name: Bump
on:
push:
tags:
- '*'
jobs:
bump:
runs-on: macos-latest
steps:
- name: Checkout repo
uses: actions/checkout@v2
- name: Bump formulae
uses: dawidd6/action-homebrew-bump-formula@v3
continue-on-error: true
with:
token: ${{secrets.brew}}
formula: rustscan
+77
View File
@@ -43,3 +43,80 @@ pkg/
# End of https://www.toptal.com/developers/gitignore/api/rust,archlinuxpackages
*.deb
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
.idea/*
.idea
.idea*
Generated
+614 -92
View File
File diff suppressed because it is too large. Load diff
+4 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "rustscan"
version = "1.8.0"
version = "1.10.0"
authors = ["Bee <[email protected]>"]
edition = "2018"
description = "Faster Nmap Scanning with Rust"
@@ -18,7 +18,7 @@ exclude = [
[dependencies]
colored = "2.0.0"
structopt = "0.3.17"
structopt = "0.3.18"
async-std = "1.6.4"
futures = "0.3"
rlimit = "0.4.0"
@@ -34,6 +34,8 @@ toml = "0.5.6"
serde = "1.0.116"
serde_derive = "1.0.116"
cidr-utils = "0.5.0"
itertools = "0.9.0"
trust-dns-resolver = { version = "0.19.5", features = ["dns-over-rustls"] }
[package.metadata.deb]
depends = "$auto, nmap"
+5 -2
View File
@@ -1,8 +1,11 @@
FROM rust:alpine as builder
LABEL maintainer="CMNatic <https://github.com/CMNatic">
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 /usr/src/rustscan
COPY . .
COPY Cargo.toml Cargo.lock ./
COPY src src
RUN cargo install --path .
FROM alpine:3.12
@@ -13,4 +16,4 @@ RUN addgroup -S rustscan && \
apk add --no-cache nmap nmap-scripts wget
COPY --from=builder /usr/local/cargo/bin/rustscan /usr/local/bin/rustscan
USER rustscan
ENTRYPOINT [ "/usr/local/bin/rustscan" ]
ENTRYPOINT [ "/usr/local/bin/rustscan" ]
+8 -15
View File
@@ -24,7 +24,7 @@
| <p align="center"><a href="https://hub.docker.com/r/cmnatic/rustscan">🐋 Docker (Recommended) </a></p> | <p align="center"><a href="https://github.com/RustScan/RustScan/releases">👩‍💻 Kali / Debian </p> | <p align="center"><a href="https://aur.archlinux.org/packages/rustscan/">🏗️ Arch </a></p> | <p align="center"><a href="https://crates.io/crates/rustscan">🔧 Cargo (Universal) </a></p> |
| ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| <p align="center"><img src="https://github.com/RustScan/RustScan/blob/master/pictures/docker.png?raw=true" /></p> | <p align="center"><img src="https://github.com/RustScan/RustScan/blob/master/pictures/kali.png?raw=true" /></p> | <p align="center"><img src="https://github.com/RustScan/RustScan/blob/master/pictures/arch.png?raw=true" /></p> | <p align="center"><img src="https://github.com/RustScan/RustScan/blob/master/pictures/rust.png?raw=true" /></p> |
| `docker pull rustscan/rustscan:v1.8.0` <p>[Usage](https://github.com/RustScan/RustScan#docker-whale) | [Read the install guide](https://github.com/Rustscan/RustScan/blob/master/README.md#%EF%B8%8F-debian--kali) | `yay -S rustscan` | `cargo install rustscan` |
| `docker pull rustscan/rustscan:v1.9.0` <p>[Usage](https://github.com/RustScan/RustScan#docker-whale) | [Read the install guide](https://github.com/Rustscan/RustScan/blob/master/README.md#%EF%B8%8F-debian--kali) | `yay -S rustscan` | `cargo install rustscan` |
<hr>
@@ -130,22 +130,22 @@ To install Docker, [follow their guide](https://docs.docker.com/engine/install/)
**Once Docker is installed, you can either build your own image using the `Dockerfile` (alpine) provided in the repo, or alternatively, use the published Docker image like below (most convenient)**
Please see our [DockerHub](https://hub.docker.com/repository/docker/rustscan/rustscan) for further our published versions. However, we recommend using our latest release [v1.8.0](https://github.com/RustScan/RustScan/releases/tag/1.8.0)
Please see our [DockerHub](https://hub.docker.com/repository/docker/rustscan/rustscan) for further our published versions. However, we recommend using our latest release [v1.9.0](https://github.com/RustScan/RustScan/releases/tag/1.9.0)
```
Stable and supported: rustscan/rustscan:v1.8.0
Stable and supported: rustscan/rustscan:v1.9.0
Bleeding edge (run at your own risk!): rustscan/rustscan:latest
```
We strongly recommend using the `v1.8.0` tag, as this is the latest major - stable - release of RustScan. This README uses the `v1.8.0` image by default, however, note that the `latest` image is considered experimental.
We strongly recommend using the `v1.9.0` tag, as this is the latest major - stable - release of RustScan. This README uses the `v1.9.0` image by default, however, note that the `latest` image is considered experimental.
#### To get started:
Simply run this command against the IP you want to target:
```bash
docker run -it --rm --name rustscan rustscan/rustscan:v1.8.0 <rustscan arguments here> <ip address to scan>
docker run -it --rm --name rustscan rustscan/rustscan:v1.9.0 <rustscan arguments here> <ip address to scan>
```
Note: this will scan the Docker's localhost, not your own.
@@ -155,7 +155,7 @@ Once done, you will no longer need to re-download the image (except when RustSca
You will have to run this command every time, so we suggest aliasing it to something memorable.
```bash
alias rustscan='docker run -it --rm --name rustscan rustscan/rustscan:v1.8.0'
alias rustscan='docker run -it --rm --name rustscan rustscan/rustscan:v1.9.0'
```
Then we can scan:
@@ -188,15 +188,7 @@ docker build -t <yourimagename> .
**Note for Mac users** Mac OS has a very, very small ulimit size. This will negatively impact RustScan by a significant amount. Please use the Docker container, or tell RustScan to up the ulimit size on every run.
Tap the brew:
```bash
brew tap brandonskerritt/rustscan
```
Install it:
```bash
brew install rustscan
```
@@ -435,7 +427,7 @@ Please read the [contributing.md file](contributing.md)
## Contributors ✨
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-19-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-20-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
@@ -468,6 +460,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center"><a href="https://liberapay.com/Artem4/"><img src="https://avatars0.githubusercontent.com/u/5614476?v=4" width="100px;" alt=""/><br /><sub><b>Artem Polishchuk</b></sub></a><br /><a href="#platform-tim77" title="Packaging/porting to new platform">📦</a></td>
<td align="center"><a href="https://github.com/buermarc"><img src="https://avatars2.githubusercontent.com/u/44375277?v=4" width="100px;" alt=""/><br /><sub><b>buermarc</b></sub></a><br /><a href="https://github.com/RustScan/RustScan/commits?author=buermarc" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/bergabman"><img src="https://avatars1.githubusercontent.com/u/44554109?v=4" width="100px;" alt=""/><br /><sub><b>bergabman</b></sub></a><br /><a href="https://github.com/RustScan/RustScan/commits?author=bergabman" title="Code">💻</a> <a href="https://github.com/RustScan/RustScan/issues?q=author%3Abergabman" title="Bug reports">🐛</a> <a href="#design-bergabman" title="Design">🎨</a></td>
<td align="center"><a href="https://github.com/dmitris"><img src="https://avatars0.githubusercontent.com/u/31205?v=4" width="100px;" alt=""/><br /><sub><b>Dmitry Savintsev</b></sub></a><br /><a href="https://github.com/RustScan/RustScan/commits?author=dmitris" title="Code">💻</a></td>
</tr>
</table>
+1 -1
View File
@@ -11,7 +11,7 @@ RustScan has 2 major labels for GitHub issues you should look at:
These are issues for newcomers to open source!
[https://github.com/RustScan/RustScan/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22](https://github.com/RustScan/RustScan/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
* Help wanted
These are issues that aren't really for newcomers, but we could still do wiht help!
These are issues that aren't really for newcomers, but we could still do with help!
[https://github.com/RustScan/RustScan/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22+label%3A%22help+wanted%22](https://github.com/RustScan/RustScan/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22+label%3A%22help+wanted%22)
If you want to, solve the issue or comment on the issue for help.
+6
View File
@@ -0,0 +1,6 @@
127.0.0.1
google.com
example.com
66666666666666.666666666666666666.66666666666666666.6666666666
....
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.radiatorFixtures
+86 -125
View File
@@ -1,4 +1,5 @@
use serde_derive::Deserialize;
use std::collections::HashMap;
use std::fs;
use structopt::{clap::arg_enum, StructOpt};
@@ -23,6 +24,7 @@ pub struct PortRange {
pub end: u16,
}
#[cfg(not(tarpaulin_include))]
fn parse_range(input: &str) -> Result<PortRange, String> {
let range = input
.split("-")
@@ -70,9 +72,9 @@ pub struct Opts {
#[structopt(short, long)]
pub no_config: bool,
/// Quiet mode. Only output the ports. No Nmap. Useful for grep or outputting to a file.
/// Greppable mode. Only output the ports. No Nmap. Useful for grep or outputting to a file.
#[structopt(short, long)]
pub quiet: bool,
pub greppable: bool,
/// Accessible mode. Turns off features which negatively affect screen readers.
#[structopt(long)]
@@ -110,8 +112,13 @@ pub struct Opts {
/// For things like --script '(safe and vuln)' enclose it in quotations marks \"'(safe and vuln)'\"")
#[structopt(last = true)]
pub command: Vec<String>,
/// Use the top 1000 ports.
#[structopt(long)]
pub top: bool,
}
#[cfg(not(tarpaulin_include))]
impl Opts {
pub fn read() -> Self {
let mut opts = Opts::from_args();
@@ -146,7 +153,7 @@ impl Opts {
}
}
merge_required!(addresses, quiet, accessible, batch_size, timeout, scan_order, command);
merge_required!(addresses, greppable, accessible, batch_size, timeout, scan_order, command);
}
fn merge_optional(&mut self, config: &Config) {
@@ -160,19 +167,31 @@ impl Opts {
}
}
merge_optional!(ports, range, ulimit);
// Only use top ports when the user asks for them
if self.top {
if config.ports.is_some() {
let mut ports: Vec<u16> = Vec::with_capacity(config.ports.clone().unwrap().len());
for entry in config.ports.clone().unwrap().keys() {
ports.push(entry.parse().unwrap())
}
self.ports = Some(ports);
}
}
merge_optional!(range, ulimit);
}
}
/// Struct used to deserialize the options specified within our config file.
/// These will be further merged with our command line arguments in order to
/// generate the final Opts struct.
#[cfg(not(tarpaulin_include))]
#[derive(Debug, Deserialize)]
pub struct Config {
addresses: Option<Vec<String>>,
ports: Option<Vec<u16>>,
ports: Option<HashMap<String, u16>>,
range: Option<PortRange>,
quiet: Option<bool>,
greppable: Option<bool>,
accessible: Option<bool>,
batch_size: Option<u16>,
timeout: Option<u32>,
@@ -182,6 +201,7 @@ pub struct Config {
command: Option<Vec<String>>,
}
#[cfg(not(tarpaulin_include))]
impl Config {
/// Reads the configuration file with TOML format and parses it into a
/// Config struct.
@@ -194,40 +214,21 @@ impl Config {
/// scan_order: "Serial"
///
pub fn read() -> Self {
let mut paths = Vec::new();
paths.push(match dirs::home_dir() {
Some(mut path) => {
path.push(".rustscan.toml");
path
}
let mut home_dir = match dirs::home_dir() {
Some(dir) => dir,
None => panic!("Could not infer config file path."),
});
};
home_dir.push(".rustscan.toml");
paths.push(match dirs::config_dir() {
Some(mut path) => {
path.push("rustscan");
path.push("config.toml");
path
let mut content = String::new();
if home_dir.exists() {
content = match fs::read_to_string(home_dir) {
Ok(content) => content,
Err(_) => String::new(),
}
None => panic!("Could not infer config file path."),
});
let mut contents: Option<String> = None;
for path in paths.iter().rev() {
contents = match fs::read_to_string(path) {
Ok(content) => {
contents = Some(content);
break;
}
Err(_) => None,
};
}
if contents.is_none() {
contents = Some(String::new());
}
let config: Config = match toml::from_str(&contents.unwrap()) {
let config: Config = match toml::from_str(&content) {
Ok(config) => config,
Err(e) => {
println!("Found {} in configuration file.\nAborting scan.\n", e);
@@ -242,42 +243,53 @@ impl Config {
#[cfg(test)]
mod tests {
use super::{Config, Opts, PortRange, ScanOrder};
impl Config {
fn default() -> Self {
Self {
addresses: Some(vec!["127.0.0.1".to_owned()]),
ports: None,
range: None,
greppable: Some(true),
batch_size: Some(25_000),
timeout: Some(1_000),
ulimit: None,
no_nmap: Some(false),
command: Some(vec!["-A".to_owned()]),
accessible: Some(true),
scan_order: Some(ScanOrder::Random),
}
}
}
impl Opts {
pub fn default() -> Self {
Self {
addresses: vec![],
ports: None,
range: None,
greppable: true,
batch_size: 0,
timeout: 0,
ulimit: None,
command: vec![],
accessible: false,
no_nmap: false,
scan_order: ScanOrder::Serial,
no_config: true,
top: false,
}
}
}
#[test]
fn opts_no_merge_when_config_is_ignored() {
let mut opts = Opts {
addresses: vec![],
ports: None,
range: None,
quiet: false,
batch_size: 0,
timeout: 0,
ulimit: None,
command: vec![],
accessible: false,
no_nmap: false,
scan_order: ScanOrder::Serial,
no_config: true,
};
let config = Config {
addresses: Some(vec!["127.0.0.1".to_owned()]),
ports: None,
range: None,
quiet: Some(true),
batch_size: Some(25_000),
timeout: Some(1_000),
ulimit: None,
no_nmap: Some(false),
command: Some(vec!["-A".to_owned()]),
accessible: Some(true),
scan_order: Some(ScanOrder::Random),
};
let mut opts = Opts::default();
let config = Config::default();
opts.merge(&config);
assert_eq!(opts.addresses, vec![] as Vec<String>);
assert_eq!(opts.quiet, false);
assert_eq!(opts.greppable, true);
assert_eq!(opts.accessible, false);
assert_eq!(opts.timeout, 0);
assert_eq!(opts.command, vec![] as Vec<String>);
@@ -286,39 +298,13 @@ mod tests {
#[test]
fn opts_merge_required_arguments() {
let mut opts = Opts {
addresses: vec![],
ports: None,
range: None,
quiet: false,
batch_size: 0,
timeout: 0,
ulimit: None,
command: vec![],
accessible: false,
scan_order: ScanOrder::Serial,
no_nmap: false,
no_config: false,
};
let config = Config {
addresses: Some(vec!["127.0.0.1".to_owned()]),
ports: None,
no_nmap: Some(false),
range: None,
quiet: Some(true),
batch_size: Some(25_000),
timeout: Some(1_000),
ulimit: None,
command: Some(vec!["-A".to_owned()]),
accessible: Some(true),
scan_order: Some(ScanOrder::Random),
};
let mut opts = Opts::default();
let config = Config::default();
opts.merge_required(&config);
assert_eq!(opts.addresses, config.addresses.unwrap());
assert_eq!(opts.quiet, config.quiet.unwrap());
assert_eq!(opts.greppable, config.greppable.unwrap());
assert_eq!(opts.timeout, config.timeout.unwrap());
assert_eq!(opts.command, config.command.unwrap());
assert_eq!(opts.accessible, config.accessible.unwrap());
@@ -327,41 +313,16 @@ mod tests {
#[test]
fn opts_merge_optional_arguments() {
let mut opts = Opts {
addresses: vec![],
ports: None,
range: None,
quiet: false,
batch_size: 0,
timeout: 0,
ulimit: None,
command: vec![],
accessible: false,
scan_order: ScanOrder::Serial,
no_nmap: false,
no_config: false,
};
let config = Config {
addresses: None,
ports: Some(vec![80, 403]),
range: Some(PortRange {
start: 1,
end: 1_000,
}),
quiet: None,
batch_size: None,
timeout: None,
no_nmap: Some(false),
ulimit: Some(1_000),
command: None,
accessible: None,
scan_order: None,
};
let mut opts = Opts::default();
let mut config = Config::default();
config.range = Some(PortRange {
start: 1,
end: 1_000,
});
config.ulimit = Some(1_000);
opts.merge_optional(&config);
assert_eq!(opts.ports, config.ports);
assert_eq!(opts.range, config.range);
assert_eq!(opts.ulimit, config.ulimit);
}
+163 -173
View File
@@ -21,9 +21,14 @@ use futures::executor::block_on;
use rlimit::Resource;
use rlimit::{getrlimit, setrlimit};
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::io::BufReader;
use std::net::ToSocketAddrs;
use std::process::Command;
use std::{net::IpAddr, time::Duration};
use trust_dns_resolver::config::*;
use trust_dns_resolver::Resolver;
extern crate colorful;
extern crate dirs;
@@ -50,14 +55,18 @@ fn main() {
debug!("Main() `opts` arguments are {:?}", opts);
if !opts.quiet && !opts.accessible {
print_opening();
if !opts.greppable && !opts.accessible {
print_opening(&opts);
}
let ips: Vec<IpAddr> = parse_addresses(&opts);
if ips.is_empty() {
warning!("No IPs could be resolved, aborting scan.", false);
warning!(
"No IPs could be resolved, aborting scan.",
opts.greppable,
opts.accessible
);
std::process::exit(1);
}
@@ -68,8 +77,9 @@ fn main() {
&ips,
batch_size,
Duration::from_millis(opts.timeout.into()),
opts.quiet,
opts.greppable,
PortStrategy::pick(opts.range, opts.ports, opts.scan_order),
opts.accessible,
);
debug!("Scanner finished building: {:?}", scanner);
@@ -101,33 +111,36 @@ fn main() {
ip,
opts.batch_size,
"'rustscan -b <batch_size> <ip address>'");
warning!(x, opts.quiet);
warning!(x, opts.greppable, opts.accessible);
}
let mut nmap_bench = NamedTimer::start("Nmap");
for (ip, ports) in ports_per_ip.iter_mut() {
let nmap_str_ports: Vec<String> = ports.into_iter().map(|port| port.to_string()).collect();
detail!("Starting Nmap", opts.quiet || opts.no_nmap);
// nmap port style is 80,443. Comma separated with no spaces.
let ports_str = nmap_str_ports.join(",");
// if quiet mode is on nmap should not be spawned
if opts.quiet || opts.no_nmap {
if opts.greppable || opts.no_nmap {
println!("{} -> [{}]", &ip, ports_str);
continue;
}
detail!("Starting Nmap", opts.greppable, opts.accessible);
let addr = ip.to_string();
let user_nmap_args =
shell_words::split(&opts.command.join(" ")).expect("failed to parse nmap arguments");
let nmap_args = build_nmap_arguments(&addr, &ports_str, &user_nmap_args, ip.is_ipv6());
output!(format!(
"The Nmap command to be run is nmap {}\n",
&nmap_args.join(" ")
));
output!(
format!(
"The Nmap command to be run is nmap {}\n",
&nmap_args.join(" ")
),
opts.greppable.clone(),
opts.accessible.clone()
);
// Runs the nmap command and spawns it as a process.
let mut child = Command::new("nmap")
@@ -148,7 +161,7 @@ fn main() {
}
/// Prints the opening title of RustScan
fn print_opening() {
fn print_opening(opts: &Opts) {
debug!("Printing opening");
let s = r#".----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
@@ -163,19 +176,17 @@ Faster Nmap scanning with Rust."#;
println!("{}", info.gradient(Color::Yellow).bold());
funny_opening!();
let config_path = match dirs::config_dir() {
Some(mut path) => {
path.push("rustscan");
path.push("config.toml");
path
}
None => panic!("Couldn't find config dir."),
let mut home_dir = match dirs::home_dir() {
Some(dir) => dir,
None => panic!("Could not infer config file path."),
};
home_dir.push(".rustscan.toml");
detail!(format!(
"{} {:?}",
"The config file is expected to be at", config_path
));
detail!(
format!("The config file is expected to be at {:?}", home_dir),
opts.greppable,
opts.accessible
);
}
#[cfg(not(tarpaulin_include))]
fn build_nmap_arguments<'a>(
@@ -198,25 +209,97 @@ fn build_nmap_arguments<'a>(
arguments
}
/// Goes through all possible IP inputs (files or via argparsing)
/// Parses the string(s) into IPs
fn parse_addresses(opts: &Opts) -> Vec<IpAddr> {
let mut ips: Vec<IpAddr> = Vec::new();
let resolver =
&Resolver::new(ResolverConfig::cloudflare_tls(), ResolverOpts::default()).unwrap();
for ip_or_host in &opts.addresses {
match IpCidr::from_str(ip_or_host) {
Ok(cidr) => cidr.iter().for_each(|ip| ips.push(ip)),
_ => match format!("{}:{}", &ip_or_host, 80).to_socket_addrs() {
Ok(mut iter) => ips.push(iter.nth(0).unwrap().ip()),
match read_ips_from_file(ip_or_host.to_owned(), &resolver) {
Ok(x) => ips.extend(x),
_ => match parse_to_ip(ip_or_host.to_owned(), &resolver) {
Ok(x) => ips.extend(x),
_ => {
let failed_to_resolve = format!("Host {:?} could not be resolved.", ip_or_host);
warning!(failed_to_resolve, opts.quiet);
warning!(
format!("Host {:?} could not be resolved.", ip_or_host),
opts.greppable,
opts.accessible
);
}
},
}
}
ips
}
/// Uses DNS to get the IPS assiocated with host
fn resolve_ips_from_host(
source: &String,
resolver: &Resolver,
) -> Result<Vec<IpAddr>, std::io::Error> {
let mut ips: Vec<std::net::IpAddr> = Vec::new();
match resolver.lookup_ip(&source) {
Ok(x) => {
for ip in x.iter() {
ips.push(ip);
}
}
_ => (),
};
return Ok(ips);
}
#[cfg(not(tarpaulin_include))]
/// Parses an input file of IPs and uses those
fn read_ips_from_file(
ips: String,
resolver: &Resolver,
) -> Result<Vec<std::net::IpAddr>, std::io::Error> {
// if we cannot open it as a file, it is not a file so move on
let file = File::open(ips)?;
let reader = BufReader::new(file);
let mut ips: Vec<std::net::IpAddr> = Vec::new();
for str_ip in reader.lines() {
match str_ip {
Ok(x) => match parse_to_ip(x, resolver) {
Ok(result) => ips.extend(result),
Err(e) => {
debug!("{} is not a valid IP or host", e);
}
},
Err(_) => {
debug!("Line in file is not valid");
}
}
}
Ok(ips)
}
/// Given a string, parse it as an host, IP address, or CIDR.
/// This allows us to pass files as hosts or cidr or IPs easily
/// Call this everytime you have a possible IP_or_host
fn parse_to_ip(address: String, resolver: &Resolver) -> Result<Vec<IpAddr>, std::io::Error> {
let mut ips: Vec<IpAddr> = Vec::new();
match IpCidr::from_str(&address) {
Ok(cidr) => cidr.iter().for_each(|ip| ips.push(ip)),
_ => match format!("{}:{}", &address, 80).to_socket_addrs() {
Ok(mut iter) => ips.push(iter.nth(0).unwrap().ip()),
_ => match resolve_ips_from_host(&address, resolver) {
Ok(hosts) => ips.extend(hosts),
_ => (),
},
},
};
Ok(ips)
}
fn adjust_ulimit_size(opts: &Opts) -> rlimit::rlim {
if opts.ulimit.is_some() {
let limit: rlimit::rlim = opts.ulimit.unwrap();
@@ -225,10 +308,17 @@ fn adjust_ulimit_size(opts: &Opts) -> rlimit::rlim {
Ok(_) => {
detail!(
format!("Automatically increasing ulimit value to {}.", limit),
opts.quiet
opts.greppable,
opts.accessible
);
}
Err(_) => {
warning!(
"ERROR. Failed to set ulimit value.",
opts.greppable,
opts.accessible
);
}
Err(_) => println!("{}", "ERROR. Failed to set ulimit value."),
}
}
@@ -243,7 +333,7 @@ fn infer_batch_size(opts: &Opts, ulimit: rlimit::rlim) -> u16 {
// Adjust the batch size when the ulimit value is lower than the desired batch size
if ulimit < batch_size {
warning!("File limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive servers",
opts.quiet
opts.greppable, opts.accessible
);
// When the OS supports high file limits like 8000, but the user
@@ -253,7 +343,7 @@ fn infer_batch_size(opts: &Opts, ulimit: rlimit::rlim) -> u16 {
// ulimit is smaller than aveage batch size
// user must have very small ulimit
// decrease batch size to half of ulimit
warning!("Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with '--ulimit 5000'. ");
warning!("Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with '--ulimit 5000'. ", opts.greppable, opts.accessible);
info!("Halving batch_size because ulimit is smaller than average batch size");
batch_size = ulimit / 2
} else if ulimit > DEFAULT_FILE_DESCRIPTORS_LIMIT {
@@ -266,10 +356,8 @@ fn infer_batch_size(opts: &Opts, ulimit: rlimit::rlim) -> u16 {
// When the ulimit is higher than the batch size let the user know that the
// batch size can be increased unless they specified the ulimit themselves.
else if ulimit + 2 > batch_size && (opts.ulimit.is_none()) {
detail!(format!(
"File limit higher than batch size. Can increase speed by increasing batch size '-b {}'.",
ulimit - 100
), opts.quiet);
detail!(format!("File limit higher than batch size. Can increase speed by increasing batch size '-b {}'.", ulimit - 100),
opts.greppable, opts.accessible);
}
batch_size as u16
@@ -277,48 +365,22 @@ fn infer_batch_size(opts: &Opts, ulimit: rlimit::rlim) -> u16 {
#[cfg(test)]
mod tests {
use crate::{
adjust_ulimit_size, infer_batch_size, parse_addresses, print_opening, Opts, ScanOrder,
};
use crate::{adjust_ulimit_size, infer_batch_size, parse_addresses, print_opening, Opts};
use std::net::Ipv4Addr;
#[test]
fn batch_size_lowered() {
let opts = Opts {
addresses: vec!["127.0.0.1".to_owned()],
ports: None,
range: None,
quiet: true,
batch_size: 50_000,
timeout: 1_000,
ulimit: Some(2_000),
command: Vec::new(),
accessible: false,
scan_order: ScanOrder::Serial,
no_config: false,
no_nmap: false,
};
let mut opts = Opts::default();
opts.batch_size = 50_000;
let batch_size = infer_batch_size(&opts, 120);
assert!(batch_size < 50_000);
assert!(batch_size < opts.batch_size);
}
#[test]
fn batch_size_lowered_average_size() {
let opts = Opts {
addresses: vec!["127.0.0.1".to_owned()],
ports: None,
range: None,
quiet: true,
batch_size: 50_000,
timeout: 1_000,
ulimit: Some(2_000),
command: Vec::new(),
accessible: false,
scan_order: ScanOrder::Serial,
no_config: false,
no_nmap: false,
};
let mut opts = Opts::default();
opts.batch_size = 50_000;
let batch_size = infer_batch_size(&opts, 9_000);
assert!(batch_size == 3_000);
@@ -327,20 +389,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 {
addresses: vec!["127.0.0.1".to_owned()],
ports: None,
range: None,
quiet: true,
batch_size: 50_000,
timeout: 1_000,
ulimit: Some(2_000),
command: Vec::new(),
accessible: false,
scan_order: ScanOrder::Serial,
no_config: false,
no_nmap: false,
};
let mut opts = Opts::default();
opts.batch_size = 50_000;
let batch_size = infer_batch_size(&opts, 5_000);
assert!(batch_size == 4_900);
@@ -348,68 +398,36 @@ mod tests {
#[test]
fn batch_size_adjusted_2000() {
// ulimit == batch_size
let opts = Opts {
addresses: vec!["127.0.0.1".to_owned()],
ports: None,
range: None,
quiet: true,
batch_size: 50_000,
timeout: 1_000,
ulimit: Some(2_000),
command: Vec::new(),
accessible: false,
scan_order: ScanOrder::Serial,
no_config: false,
no_nmap: false,
};
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);
}
#[test]
fn test_print_opening_no_panic() {
let mut opts = Opts::default();
opts.ulimit = Some(2_000);
// print opening should not panic
print_opening();
print_opening(&opts);
assert!(1 == 1);
}
#[test]
fn test_high_ulimit_no_quiet_mode() {
let opts = Opts {
addresses: vec!["127.0.0.1".to_owned()],
ports: None,
range: None,
quiet: false,
batch_size: 10,
timeout: 1_000,
ulimit: None,
command: Vec::new(),
accessible: true,
scan_order: ScanOrder::Serial,
no_config: false,
no_nmap: false,
};
fn test_high_ulimit_no_greppable_mode() {
let mut opts = Opts::default();
opts.batch_size = 10;
opts.greppable = false;
infer_batch_size(&opts, 1_000_000);
let batch_size = infer_batch_size(&opts, 1_000_000);
assert!(1 == 1);
assert!(batch_size == opts.batch_size);
}
#[test]
fn parse_correct_addresses() {
let opts = Opts {
addresses: vec!["127.0.0.1".to_owned(), "192.168.0.0/30".to_owned()],
ports: None,
range: None,
quiet: true,
batch_size: 10,
timeout: 1_000,
ulimit: Some(2_000),
command: Vec::new(),
accessible: false,
scan_order: ScanOrder::Serial,
no_config: false,
no_nmap: false,
};
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!(
@@ -426,20 +444,8 @@ mod tests {
#[test]
fn parse_correct_host_addresses() {
let opts = Opts {
addresses: vec!["google.com".to_owned()],
ports: None,
range: None,
quiet: true,
batch_size: 10,
timeout: 1_000,
ulimit: Some(2_000),
command: Vec::new(),
accessible: false,
scan_order: ScanOrder::Serial,
no_config: false,
no_nmap: false,
};
let mut opts = Opts::default();
opts.addresses = vec!["google.com".to_owned()];
let ips = parse_addresses(&opts);
assert_eq!(ips.len(), 1);
@@ -447,20 +453,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()],
ports: None,
range: None,
quiet: true,
batch_size: 10,
timeout: 1_000,
ulimit: Some(2_000),
command: Vec::new(),
accessible: false,
scan_order: ScanOrder::Serial,
no_config: false,
no_nmap: false,
};
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),]);
@@ -468,22 +462,18 @@ mod tests {
#[test]
fn parse_incorrect_addresses() {
let opts = Opts {
addresses: vec!["im_wrong".to_owned(), "300.10.1.1".to_owned()],
ports: None,
range: None,
quiet: true,
batch_size: 10,
timeout: 1_000,
ulimit: Some(2_000),
command: Vec::new(),
accessible: false,
scan_order: ScanOrder::Serial,
no_config: false,
no_nmap: false,
};
let mut opts = Opts::default();
opts.addresses = vec!["im_wrong".to_owned(), "300.10.1.1".to_owned()];
let ips = parse_addresses(&opts);
assert_eq!(ips.is_empty(), true);
}
#[test]
fn parse_hosts_file_and_incorrect_hosts() {
// Host file contains IP, Hosts, incorrect IPs, incorrect hosts
let mut opts = Opts::default();
opts.addresses = vec!["fixtures/hosts.txt".to_owned()];
let ips = parse_addresses(&opts);
assert_eq!(ips.len(), 3);
}
}
+17 -10
View File
@@ -26,8 +26,9 @@ pub struct Scanner {
ips: Vec<IpAddr>,
batch_size: u16,
timeout: Duration,
quiet: bool,
greppable: bool,
port_strategy: PortStrategy,
accessible: bool,
}
impl Scanner {
@@ -35,15 +36,17 @@ impl Scanner {
ips: &[IpAddr],
batch_size: u16,
timeout: Duration,
quiet: bool,
greppable: bool,
port_strategy: PortStrategy,
accessible: bool,
) -> Self {
Self {
batch_size,
timeout,
quiet,
greppable,
port_strategy,
ips: ips.iter().map(|ip| ip.to_owned()).collect(),
accessible,
}
}
@@ -107,8 +110,12 @@ impl Scanner {
Err(e) => debug!("Shutdown stream error {}", &e),
_ => {}
}
if !self.quiet {
println!("Open {}", socket.to_string().purple());
if !self.greppable {
if self.accessible {
println!("Open {}", socket.to_string());
} else {
println!("Open {}", socket.to_string().purple());
}
}
Ok(socket)
@@ -167,7 +174,7 @@ mod tests {
end: 1_000,
};
let strategy = PortStrategy::pick(Some(range), None, ScanOrder::Random);
let scanner = Scanner::new(&addrs, 10, Duration::from_millis(100), true, strategy);
let scanner = Scanner::new(&addrs, 10, Duration::from_millis(100), true, strategy, true);
block_on(scanner.run());
// if the scan fails, it wouldn't be able to assert_eq! as it panicked!
assert_eq!(1, 1);
@@ -181,7 +188,7 @@ mod tests {
end: 1_000,
};
let strategy = PortStrategy::pick(Some(range), None, ScanOrder::Random);
let scanner = Scanner::new(&addrs, 10, Duration::from_millis(100), true, strategy);
let scanner = Scanner::new(&addrs, 10, Duration::from_millis(100), true, strategy, true);
block_on(scanner.run());
// if the scan fails, it wouldn't be able to assert_eq! as it panicked!
assert_eq!(1, 1);
@@ -194,7 +201,7 @@ mod tests {
end: 1_000,
};
let strategy = PortStrategy::pick(Some(range), None, ScanOrder::Random);
let scanner = Scanner::new(&addrs, 10, Duration::from_millis(100), true, strategy);
let scanner = Scanner::new(&addrs, 10, Duration::from_millis(100), true, strategy, true);
block_on(scanner.run());
assert_eq!(1, 1);
}
@@ -206,7 +213,7 @@ mod tests {
end: 445,
};
let strategy = PortStrategy::pick(Some(range), None, ScanOrder::Random);
let scanner = Scanner::new(&addrs, 10, Duration::from_millis(100), true, strategy);
let scanner = Scanner::new(&addrs, 10, Duration::from_millis(100), true, strategy, true);
block_on(scanner.run());
assert_eq!(1, 1);
}
@@ -221,7 +228,7 @@ mod tests {
end: 600,
};
let strategy = PortStrategy::pick(Some(range), None, ScanOrder::Random);
let scanner = Scanner::new(&addrs, 10, Duration::from_millis(100), true, strategy);
let scanner = Scanner::new(&addrs, 10, Duration::from_millis(100), true, strategy, true);
block_on(scanner.run());
assert_eq!(1, 1);
}
+17 -25
View File
@@ -1,12 +1,17 @@
use itertools::{iproduct, Product};
use std::net::{IpAddr, SocketAddr};
pub struct SocketIterator<'s> {
ips: &'s [IpAddr],
ports: &'s [u16],
ip_idx: usize,
ip_len: usize,
port_idx: usize,
port_len: usize,
// product_it is a cartesian product iterator over
// the slices of ports and IP addresses.
//
// The IP/port order is intentionally reversed here since we want
// the itertools::iproduct! macro below to generate the pairs with
// all the IPs for one port before moving on to the next one
// ("hold the port, go through all the IPs, then advance the port...").
// See also the comments in the iterator implementation for an example.
product_it:
Product<Box<std::slice::Iter<'s, u16>>, Box<std::slice::Iter<'s, std::net::IpAddr>>>,
}
/// An iterator that receives a slice of IPs and ports and returns a Socket
@@ -16,13 +21,10 @@ pub struct SocketIterator<'s> {
/// generating a vector containing all these combinations.
impl<'s> SocketIterator<'s> {
pub fn new(ips: &'s [IpAddr], ports: &'s [u16]) -> Self {
let ports_it = Box::new(ports.into_iter());
let ips_it = Box::new(ips.into_iter());
Self {
ip_idx: 0,
ip_len: ips.len(),
port_idx: 0,
port_len: ports.len(),
ips,
ports,
product_it: iproduct!(ports_it, ips_it),
}
}
}
@@ -41,20 +43,10 @@ impl<'s> Iterator for SocketIterator<'s> {
/// it.next(); // 192.168.0.1:443
/// it.next(); // None
fn next(&mut self) -> Option<Self::Item> {
if self.port_idx == self.port_len {
return None;
match self.product_it.next() {
None => None,
Some((port, ip)) => Some(SocketAddr::new(*ip, *port)),
}
self.ip_idx = self.ip_idx % self.ip_len;
let socket = SocketAddr::new(self.ips[self.ip_idx], self.ports[self.port_idx]);
self.ip_idx += 1;
if self.ip_idx == self.ip_len {
self.port_idx += 1;
}
Some(socket)
}
}
+30 -13
View File
@@ -7,11 +7,16 @@ macro_rules! warning {
use ansi_term::Colour::Red;
println!("{} {}", Red.bold().paint("[!]"), $name);
};
// quiet mode
($name:expr, $quiet:expr) => {
($name:expr, $greppable:expr, $accessible:expr) => {
use ansi_term::Colour::Red;
if !$quiet {
println!("{} {}", Red.bold().paint("[!]"), $name);
// if not greppable then print, otherwise no else statement so do not print.
if !$greppable {
if $accessible {
// Don't print the ascii art
println!("{}", $name);
} else {
println!("{} {}", Red.bold().paint("[!]"), $name);
}
}
};
}
@@ -22,26 +27,38 @@ macro_rules! detail {
use ansi_term::Colour::Blue;
println!("{} {}", Blue.bold().paint("[~]"), $name);
};
($name:expr, $quiet:expr) => {
($name:expr, $greppable:expr, $accessible:expr) => {
use ansi_term::Colour::Blue;
if !$quiet {
println!("{} {}", Blue.bold().paint("[~]"), $name);
// if not greppable then print, otherwise no else statement so do not print.
if !$greppable {
if $accessible {
// Don't print the ascii art
println!("{}", $name);
} else {
println!("{} {}", Blue.bold().paint("[~]"), $name);
}
}
};
}
#[macro_export]
macro_rules! output {
($name:expr, $quiet:expr) => {
use ansi_term::Colour::RGB;
if !$quiet {
println!("{} {}", RGB(0, 255, 9).bold().paint("[>]"), $name);
}
};
($name:expr) => {
use ansi_term::Colour::RGB;
println!("{} {}", RGB(0, 255, 9).bold().paint("[>]"), $name);
};
($name:expr, $greppable:expr, $accessible:expr) => {
use ansi_term::Colour::RGB;
// if not greppable then print, otherwise no else statement so do not print.
if !$greppable {
if $accessible {
// Don't print the ascii art
println!("{}", $name);
} else {
println!("{} {}", RGB(0, 255, 9).bold().paint("[>]"), $name);
}
}
};
}
#[macro_export]