fixed a bug in project promotion where the folder for the project name
would be duplicated, and added rustbuster to the rust tools available!
This commit is contained in:
+61
-11
@@ -6,6 +6,7 @@ use crossterm::{
|
||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
|
||||
};
|
||||
use ratatui::{
|
||||
layout::{Constraint, Layout},
|
||||
prelude::*,
|
||||
widgets::{Block, Borders, List, ListItem, ListState, Paragraph},
|
||||
};
|
||||
@@ -248,19 +249,68 @@ pub fn run_tui(
|
||||
state.config.get(setting).unwrap()
|
||||
)));
|
||||
}
|
||||
let info_area_height = top_chunks[1].height.saturating_sub(2) as usize;
|
||||
if state.info_scroll as usize > info_lines.len().saturating_sub(info_area_height) {
|
||||
state.info_scroll = info_lines.len().saturating_sub(info_area_height) as u16;
|
||||
}
|
||||
let info_paragraph = Paragraph::new(info_lines)
|
||||
.block(
|
||||
if state.progress_bars.is_empty() {
|
||||
let info_area_height = top_chunks[2].height.saturating_sub(2) as usize;
|
||||
if state.info_scroll as usize > info_lines.len().saturating_sub(info_area_height) {
|
||||
state.info_scroll = info_lines.len().saturating_sub(info_area_height) as u16;
|
||||
}
|
||||
let info_paragraph = Paragraph::new(info_lines)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title("Selected Project Information"),
|
||||
)
|
||||
.scroll((state.info_scroll, 0))
|
||||
.wrap(ratatui::widgets::Wrap { trim: false });
|
||||
f.render_widget(info_paragraph, top_chunks[2]);
|
||||
} else {
|
||||
let progress_section = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Percentage(60), Constraint::Percentage(40)])
|
||||
.split(top_chunks[2]);
|
||||
let info_area_height = top_chunks[2].height.saturating_sub(2) as usize;
|
||||
if state.info_scroll as usize > info_lines.len().saturating_sub(info_area_height) {
|
||||
state.info_scroll = info_lines.len().saturating_sub(info_area_height) as u16;
|
||||
}
|
||||
let info_paragraph = Paragraph::new(info_lines)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title("Selected Project Information"),
|
||||
)
|
||||
.scroll((state.info_scroll, 0))
|
||||
.wrap(ratatui::widgets::Wrap { trim: false });
|
||||
f.render_widget(info_paragraph, progress_section[0]);
|
||||
let mut progress_lines = Vec::new();
|
||||
state.progress_bars.iter().for_each(|progress| {
|
||||
if let Ok(status) = progress.read() {
|
||||
let percentage = status.complete / status.total * 100;
|
||||
if percentage == 100 {
|
||||
progress_lines.push(Line::from(Span::styled(
|
||||
format!(
|
||||
"{} - {}/{} ({}%)",
|
||||
status.name, status.complete, status.total, percentage
|
||||
),
|
||||
Style::default().fg(Color::Green),
|
||||
)));
|
||||
} else {
|
||||
progress_lines.push(Line::from(Span::styled(
|
||||
format!(
|
||||
"{} - {}/{} ({}%)",
|
||||
status.name, status.complete, status.total, percentage
|
||||
),
|
||||
Style::default().fg(Color::Magenta),
|
||||
)));
|
||||
}
|
||||
}
|
||||
});
|
||||
let progress_paragraph = Paragraph::new(progress_lines).block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title("Selected Project Information"),
|
||||
)
|
||||
.scroll((state.info_scroll, 0))
|
||||
.wrap(ratatui::widgets::Wrap { trim: false });
|
||||
f.render_widget(info_paragraph, top_chunks[2]);
|
||||
.title("Operations in progress"),
|
||||
);
|
||||
f.render_widget(progress_paragraph, progress_section[1]);
|
||||
}
|
||||
let mut output_lines = Vec::new();
|
||||
state.output.iter().for_each(|text| {
|
||||
let line = Line::from(Span::raw(text));
|
||||
|
||||
+304
-4
@@ -1,3 +1,4 @@
|
||||
use crate::rust_tools::rustwitness;
|
||||
use clipboard::ClipboardContext;
|
||||
use clipboard::ClipboardProvider;
|
||||
use fs_extra::dir::{CopyOptions, copy};
|
||||
@@ -6,24 +7,26 @@ use keyring::Entry;
|
||||
use ratatui::crossterm::event;
|
||||
use rayon::prelude::*;
|
||||
use rhai::{AST, Dynamic, Engine, Scope};
|
||||
use rust_tools::{BusterTarget, rustbuster};
|
||||
use rustls::{ClientConfig, ClientConnection, RootCertStore, StreamOwned, pki_types::ServerName};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::fs::OpenOptions;
|
||||
use std::fs::{self, File, create_dir_all, read_dir, read_to_string, remove_dir, remove_dir_all};
|
||||
use std::io::{self, BufRead, BufReader, Read, Write};
|
||||
use std::net::IpAddr;
|
||||
use std::net::SocketAddr;
|
||||
use std::net::TcpStream;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::RwLock;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::{Arc, Mutex, mpsc::Sender, mpsc::channel};
|
||||
use std::time::Duration;
|
||||
use std::time::{self, Instant};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::rust_tools::rustwitness;
|
||||
|
||||
pub mod funcs;
|
||||
pub mod rust_tools;
|
||||
pub mod server;
|
||||
@@ -99,6 +102,7 @@ pub struct AppState {
|
||||
pub remoting: bool,
|
||||
pub help: Vec<String>,
|
||||
pub name: String,
|
||||
pub progress_bars: Vec<Arc<RwLock<ToolProgress>>>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
@@ -138,6 +142,7 @@ impl AppState {
|
||||
remoting: false,
|
||||
help: Vec::new(),
|
||||
name: String::new(),
|
||||
progress_bars: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,6 +256,10 @@ impl AppState {
|
||||
self.help
|
||||
.push("stop_db\nrestart this project's distrobox".to_string());
|
||||
self.help.push("rustwitness\nRun a scan on the hosts in the urls.txt file in yoru project files directory that captures screenshots of those urls, optionally through a proxy.".to_string());
|
||||
self.help.push("portscan\nRun a simple TCP portscan on either the scope for the project, or a given list of ips.\nportscan ip,ip,ip,ip port,port,port,port or portscan port,port,port".to_string());
|
||||
self.help.push("rustbuster\nRun the Subdomain/Subdirectory bruteforcetool RustBuster (included in tetanus now!) against a given domain name or url.\nrustbuster target,target,target subs=/path/to/subwordlist dirs=/path/to/dirwordlist".to_string());
|
||||
self.help
|
||||
.push("clear_operations\nClear the progress section of the tool.".to_string());
|
||||
self.help.push("exit\nquit the tool\n".to_string());
|
||||
self.initialize_modules();
|
||||
return Ok(());
|
||||
@@ -1077,6 +1086,271 @@ impl AppState {
|
||||
}
|
||||
}
|
||||
}
|
||||
"portscan" => {
|
||||
if let Some(project) = self.projects.get(self.selected_project) {
|
||||
let mut ports = Vec::new();
|
||||
let mut hosts = project.hosts.clone();
|
||||
if let Some(args) = command_args {
|
||||
let args_vec: Vec<&str> = args.split_whitespace().collect();
|
||||
match args_vec.len() {
|
||||
1 => {
|
||||
args_vec[0].split(",").into_iter().for_each(|p| {
|
||||
if let Ok(port) = p.trim().parse::<u16>() {
|
||||
ports.push(port);
|
||||
}
|
||||
});
|
||||
}
|
||||
2 => {
|
||||
args_vec[0].split(",").into_iter().for_each(|ip| {
|
||||
if let Ok(ip) = ip.parse::<IpAddr>() {
|
||||
let mut new_host = Host::new();
|
||||
new_host.ip = ip.to_string();
|
||||
hosts.push(new_host);
|
||||
} else if let Ok(net) = ip.parse::<IpNet>() {
|
||||
for host_addr in net.hosts() {
|
||||
let mut new_host = Host::new();
|
||||
new_host.ip = host_addr.to_string();
|
||||
hosts.push(new_host);
|
||||
}
|
||||
}
|
||||
});
|
||||
args_vec[1].split(",").into_iter().for_each(|p| {
|
||||
if p.contains("-") {
|
||||
if let Some((start, end)) = p.split_once("-") {
|
||||
if let Ok(start) = start.parse::<u16>() {
|
||||
if let Ok(end) = end.parse::<u16>() {
|
||||
for range in [start..end] {
|
||||
range.into_iter().for_each(|port| {
|
||||
ports.push(port.clone());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Ok(port) = p.trim().parse::<u16>() {
|
||||
ports.push(port);
|
||||
}
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if !ports.is_empty() && !hosts.is_empty() {
|
||||
let _ = self.main_tx.send(ToolMessage::Output((
|
||||
rid,
|
||||
format!("scanning {} ports on {} hosts", ports.len(), hosts.len()),
|
||||
)));
|
||||
let project_clone = Arc::new(Mutex::new(project.clone()));
|
||||
let id = self.selected_project.clone();
|
||||
let tx_clone = self.main_tx.clone();
|
||||
self.workers.spawn(move || {
|
||||
hosts.par_iter_mut().for_each(|h| {
|
||||
ports.par_iter().for_each(|p| {
|
||||
let mut host = h.clone();
|
||||
match host.check_port(p.clone(), tx_clone.clone()) {
|
||||
Ok(_) => {
|
||||
if let Ok(mut lock) = project_clone.lock() {
|
||||
lock.add_host(host.clone());
|
||||
}
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
});
|
||||
});
|
||||
if let Ok(lock) = project_clone.lock() {
|
||||
let _ =
|
||||
tx_clone.send(ToolMessage::UpdateProject(id, lock.clone()));
|
||||
let _ = tx_clone.send(ToolMessage::Output((
|
||||
0,
|
||||
"[success] portscan finished!".to_string(),
|
||||
)));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"rustbuster" => {
|
||||
if let Some(args) = command_args {
|
||||
let args: Vec<&str> = args.split(" ").collect();
|
||||
let mut dirs = Vec::new();
|
||||
let mut subs = Vec::new();
|
||||
let mut targets = Vec::new();
|
||||
if args
|
||||
.iter()
|
||||
.any(|a| a.contains("subs=") || a.contains("dirs="))
|
||||
{
|
||||
args.iter().for_each(|arg| {
|
||||
if arg.contains("subs=") {
|
||||
if let Some((_, sub_path)) = arg.split_once("=") {
|
||||
if let Ok(subtext) = read_to_string(sub_path) {
|
||||
subtext.lines().into_iter().for_each(|sub| {
|
||||
subs.push(sub.to_string());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if arg.contains("dirs=") {
|
||||
if let Some((_, dir_path)) = arg.split_once("=") {
|
||||
if let Ok(dir_text) = read_to_string(dir_path) {
|
||||
dir_text.lines().into_iter().for_each(|dir| {
|
||||
dirs.push(dir.to_string());
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if arg.contains(",") {
|
||||
arg.split(",").into_iter().for_each(|t| {
|
||||
targets.push(t.to_string());
|
||||
});
|
||||
} else if !arg.contains("=") {
|
||||
targets.push(arg.to_string());
|
||||
}
|
||||
}
|
||||
});
|
||||
let mut num_dir = 0;
|
||||
let mut num_sub = 0;
|
||||
let _ = self.main_tx.send(ToolMessage::Output((
|
||||
rid,
|
||||
format!("{} targets found scanning...", num_dir + num_sub),
|
||||
)));
|
||||
let tx_clone = self.main_tx.clone();
|
||||
let out_path = self.projects[self.selected_project].files.clone();
|
||||
let project_clone =
|
||||
Arc::new(Mutex::new(self.projects[self.selected_project].clone()));
|
||||
let mut buster_targets = Vec::new();
|
||||
for target in targets {
|
||||
if target.contains("://") {
|
||||
for dir in &dirs {
|
||||
num_dir += 1;
|
||||
buster_targets
|
||||
.push(BusterTarget::Dir(format!("{}/{}", target, dir)));
|
||||
}
|
||||
} else if target.contains(".") {
|
||||
for sub in &subs {
|
||||
num_sub += 1;
|
||||
buster_targets
|
||||
.push(BusterTarget::Sub(format!("{}.{}", sub, target)));
|
||||
}
|
||||
}
|
||||
}
|
||||
let new_progress = Arc::new(RwLock::new(ToolProgress {
|
||||
name: "rustbuster".to_string(),
|
||||
total: num_dir + num_sub,
|
||||
complete: 0,
|
||||
}));
|
||||
self.progress_bars.push(new_progress.clone());
|
||||
self.workers.spawn(move || {
|
||||
buster_targets.par_iter().for_each(|t| match t {
|
||||
BusterTarget::Dir(_) => {
|
||||
if let Some(res) = rustbuster(t.clone()) {
|
||||
let mut url_path = out_path.clone();
|
||||
url_path.push("urls.txt");
|
||||
if let Ok(mut url_file) = OpenOptions::new()
|
||||
.write(true)
|
||||
.append(true)
|
||||
.create(true)
|
||||
.open(&url_path)
|
||||
{
|
||||
if let Err(e) = writeln!(
|
||||
url_file,
|
||||
"{}",
|
||||
format!("[success] {}", res)
|
||||
) {
|
||||
let _ = tx_clone.send(ToolMessage::Output((
|
||||
rid,
|
||||
format!(
|
||||
"[error] saving url to {}: {e}",
|
||||
url_path.display()
|
||||
),
|
||||
)));
|
||||
}
|
||||
}
|
||||
let _ = tx_clone.send(ToolMessage::Output((
|
||||
rid,
|
||||
format!("[success] {}", res),
|
||||
)));
|
||||
}
|
||||
if let Ok(mut lock) = new_progress.write() {
|
||||
lock.complete += 1;
|
||||
}
|
||||
}
|
||||
BusterTarget::Sub(_) => {
|
||||
if let Some(res) = rustbuster(t.clone()) {
|
||||
let mut url_path = out_path.clone();
|
||||
url_path.push("domains.txt");
|
||||
if let Ok(mut url_file) = OpenOptions::new()
|
||||
.write(true)
|
||||
.append(true)
|
||||
.create(true)
|
||||
.open(&url_path)
|
||||
{
|
||||
if let Err(e) =
|
||||
writeln!(url_file, "{}", format!("{}", res))
|
||||
{
|
||||
let _ = tx_clone.send(ToolMessage::Output((
|
||||
rid,
|
||||
format!(
|
||||
"[error] saving domain to {}: {e}",
|
||||
url_path.display()
|
||||
),
|
||||
)));
|
||||
}
|
||||
}
|
||||
let _ = tx_clone.send(ToolMessage::Output((
|
||||
rid,
|
||||
format!("[success] {}", res),
|
||||
)));
|
||||
if let Some((domain, ip)) = res.split_once(":") {
|
||||
let mut new_host = Host::new();
|
||||
new_host.ip = ip.to_string();
|
||||
new_host.hostname = domain.to_string();
|
||||
if let Ok(mut project_lock) = project_clone.lock() {
|
||||
project_lock.add_host(new_host);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok(mut lock) = new_progress.write() {
|
||||
lock.complete += 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
drop(project_clone);
|
||||
});
|
||||
} else {
|
||||
let _ = self.main_tx.send(ToolMessage::Output((
|
||||
rid,
|
||||
"[error] no sub or dir wordlist provided!".to_string(),
|
||||
)));
|
||||
let _ = self
|
||||
.main_tx
|
||||
.send(ToolMessage::Output((rid, "USAGE:".to_string())));
|
||||
let _ = self.main_tx.send(ToolMessage::Output((
|
||||
rid,
|
||||
"rustbuster target,target,target subs=/path/to/subwordlist dirs=/path/to/dirwordlist".to_string(),
|
||||
)));
|
||||
let _ = self.main_tx.send(ToolMessage::Output((
|
||||
rid,
|
||||
"be sure to include the subs= or dirs= part of the command above!"
|
||||
.to_string(),
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
let _ = self.main_tx.send(ToolMessage::Output((
|
||||
rid,
|
||||
"[error] no arguments provided!".to_string(),
|
||||
)));
|
||||
let _ = self
|
||||
.main_tx
|
||||
.send(ToolMessage::Output((rid, "USAGE:".to_string())));
|
||||
let _ = self.main_tx.send(ToolMessage::Output((
|
||||
rid,
|
||||
"rustbuster target,target,target subs=/path/to/subwordlist dirs=/path/to/dirwordlist".to_string(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
"clear_operations" => {
|
||||
self.progress_bars.clear();
|
||||
}
|
||||
"exit" => {
|
||||
self.save_all()?;
|
||||
let _ = self.main_tx.send(ToolMessage::AppStateExit);
|
||||
@@ -1382,9 +1656,9 @@ impl AppState {
|
||||
let return_string = String::from("[success] Project promoted!");
|
||||
let mut new_project = self.projects[self.selected_project].clone();
|
||||
let new_files_path = PathBuf::from(self.config.get("current_files").unwrap())
|
||||
.join(format!("{}/{}", new_project.org_name, new_project.name));
|
||||
.join(format!("{}", new_project.org_name));
|
||||
let new_notes_path = PathBuf::from(self.config.get("current_notes").unwrap())
|
||||
.join(format!("{}/{}", new_project.org_name, new_project.name));
|
||||
.join(format!("{}", new_project.org_name));
|
||||
let mut options = CopyOptions::new();
|
||||
create_dir_all(&new_files_path)?;
|
||||
create_dir_all(&new_notes_path)?;
|
||||
@@ -2505,6 +2779,26 @@ id: {}",
|
||||
host_file.sync_all()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_port(&mut self, port: u16, tx: Sender<ToolMessage>) -> Result<(), Box<dyn Error>> {
|
||||
if let Ok(ip) = self.ip.parse::<IpAddr>() {
|
||||
let address = SocketAddr::new(ip, port);
|
||||
if let Ok(_) = TcpStream::connect_timeout(&address, Duration::from_secs(3)) {
|
||||
self.add_port(&format!("{}", port));
|
||||
let _ = tx.send(ToolMessage::Output((
|
||||
0,
|
||||
format!("[success] {} is open on {}", port, self.ip),
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
let _ = tx.send(ToolMessage::Output((
|
||||
0,
|
||||
format!("[error] {} is not a valid ip address!", self.ip),
|
||||
)));
|
||||
return Err("not valid IP".into());
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -3013,3 +3307,9 @@ struct PromptQuery {
|
||||
id: usize,
|
||||
query: String,
|
||||
}
|
||||
|
||||
struct ToolProgress {
|
||||
name: String,
|
||||
total: usize,
|
||||
complete: usize,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
use crate::*;
|
||||
use anyhow::Context;
|
||||
use dns_lookup::lookup_host;
|
||||
use headless_chrome::{Browser, LaunchOptions};
|
||||
use reqwest::StatusCode;
|
||||
use std::net::SocketAddr;
|
||||
use trust_dns_resolver::lookup::Ipv4Lookup;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum BusterTarget {
|
||||
Sub(String),
|
||||
Dir(String),
|
||||
}
|
||||
|
||||
pub fn rustwitness(
|
||||
input: String,
|
||||
@@ -61,3 +71,85 @@ pub fn rustwitness(
|
||||
capture_screenshot(&input, proxy.clone(), output.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn rustbuster(target: BusterTarget) -> Option<String> {
|
||||
match target {
|
||||
BusterTarget::Dir(url) => {
|
||||
if let Ok(resp) = reqwest::blocking::get(&url) {
|
||||
let status = resp.status();
|
||||
match status {
|
||||
StatusCode::OK => {
|
||||
return Some(format!("{}", url));
|
||||
}
|
||||
StatusCode::ACCEPTED => {
|
||||
return Some(format!("{}", url));
|
||||
}
|
||||
StatusCode::CONTINUE => {
|
||||
return Some(format!("{}", url));
|
||||
}
|
||||
StatusCode::CREATED => {
|
||||
return Some(format!("{}", url));
|
||||
}
|
||||
StatusCode::FOUND => {
|
||||
return Some(format!("{}", url));
|
||||
}
|
||||
StatusCode::IM_A_TEAPOT => {
|
||||
return Some(format!("{}", url));
|
||||
}
|
||||
StatusCode::MOVED_PERMANENTLY => {
|
||||
return Some(format!("{}", url));
|
||||
}
|
||||
StatusCode::PERMANENT_REDIRECT => {
|
||||
return Some(format!("{}", url));
|
||||
}
|
||||
StatusCode::TEMPORARY_REDIRECT => {
|
||||
return Some(format!("{}", url));
|
||||
}
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
BusterTarget::Sub(name) => {
|
||||
let mut base = String::new();
|
||||
if let Some((given_name, tld)) = name.rsplit_once(".") {
|
||||
if let Some((_, base_name)) = name.rsplit_once(".") {
|
||||
base = format!("{}.{}", base_name, tld);
|
||||
} else {
|
||||
base = format!("{}.{}", given_name, tld);
|
||||
}
|
||||
}
|
||||
let mut wildcards = Vec::new();
|
||||
if base.len() > 0 {
|
||||
if let Ok(ips) = lookup_host(&format!("burstpyrofoo.{}", base)) {
|
||||
ips.into_iter().for_each(|ip| {
|
||||
wildcards.push(ip);
|
||||
});
|
||||
}
|
||||
}
|
||||
if let Ok(mut ips) = lookup_host(&name) {
|
||||
let mut found_ips = Vec::new();
|
||||
let not_wild = !ips.all(|ip| wildcards.contains(&ip));
|
||||
if not_wild {
|
||||
ips.for_each(|ip| {
|
||||
found_ips.push(ip.to_string());
|
||||
});
|
||||
}
|
||||
if found_ips.len() > 0 {
|
||||
let mut out = String::new();
|
||||
found_ips.iter().for_each(|ip| {
|
||||
out.push_str(&format!("{}:{}\n", name, ip));
|
||||
});
|
||||
return Some(out);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user