From 1caaea1d637dc0a7142851036afca7f00286b503 Mon Sep 17 00:00:00 2001 From: Kremilly Date: Thu, 12 Dec 2024 15:46:31 -0300 Subject: [PATCH] feat: implement share functionality with Pastebin API --- src/args_cli.rs | 18 ++++++ src/constants/global.rs | 3 +- src/core/dump.rs | 42 +------------- src/dump_sync.rs | 119 +++++++++++++++++++++------------------- src/plugins/mod.rs | 5 +- src/plugins/share.rs | 64 +++++++++++++++++++++ src/ui/mod.rs | 1 + src/ui/share_alerts.rs | 22 ++++++++ src/utils/file.rs | 5 ++ 9 files changed, 178 insertions(+), 101 deletions(-) create mode 100644 src/plugins/share.rs create mode 100644 src/ui/share_alerts.rs diff --git a/src/args_cli.rs b/src/args_cli.rs index 09b4472..30edf7d 100644 --- a/src/args_cli.rs +++ b/src/args_cli.rs @@ -41,6 +41,9 @@ pub enum Commands { /// Scan the table for xss prevention Scan(ScanOptions), + + /// Share the dump or scan results + Share(ShareOptions), } #[derive(Parser)] @@ -102,6 +105,21 @@ pub struct ScanOptions { /// Limit for scan pub limit: Option, + #[arg(short, long)] + /// File path for output + pub file: Option, +} + +#[derive(Parser)] +pub struct ShareOptions { + #[arg(short, long)] + /// Privacy level for share + pub privacy: Option, + + #[arg(short, long)] + /// Name for share + pub name: Option, + #[arg(short, long)] /// File path for output pub file: Option, diff --git a/src/constants/global.rs b/src/constants/global.rs index 5b5819e..37c4ad4 100644 --- a/src/constants/global.rs +++ b/src/constants/global.rs @@ -13,8 +13,9 @@ impl Global { pub const APP_ICON: &'static str = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAABGCAYAAABxLuKEAAAACXBIWXMAAC4jAAAuIwF4pT92AAADSklEQVR4nO2aP07jQBSHf95si0QQQgihIBJRUAfJgtocAY4ARwhHIFfIEcgRNjXVuqFBCGEkhBBCKC5ow5stHENY/Gb8Z5zYu++Tpkk8+jlfZuyZZztKKQjf+bHoE6gqIoZBxDCIGAYRwyBiGEQMg4hhEDEMIoZBxDCIGAYRwyBiGEQMg4hhSCPGA6AstTGAXwB6Gc+zCeB82jdv7gWAk7SBjqmCp5TypidkGx/AoeM4oSG/O81vWsodOo5zbDrIKOb9/b0sMQDgNxqNPU12G8Bv2JMS0280Gme6A4xTSSlVZutOJhN2eCulekqpZgm5vclk0tb97p8mMURkOqQoXU22V2KuB2DAfVkFMew/R0Taf7Ug2ulZBTGVzDaKSfncaQBgmPB5E9FtMhcpsgMAp7ZzAXsjJlhaWholffH29pb1nLJkl5ILzGEqFem/qL6AvalUSv+CfbXrFEQLTJa6j5jmeDxurqysfFs9Ly8v93MHo/5iugDGr6+vQ0QjYLi6uhrkDpyh7mJijqbt/OXlJQAwitva2pp2L8ZRdTEhsu+T2oh20ScA8Pz8/CFpfX1de12ZpepiRohGQhG8acPT01MA4GxjYyNpzfUF4yaSiFK1Iv01fftEFKY9hxStTUQXj4+P57UWs7m56RPRIREFFuWAiHoPDw/aDaq1skOR/jparZbfarU6SqlTpdRQKRVaKj1oxVT6GjPL1tbWANMywf39fXzd8KApWxjQ9quNmFm2t7fjOw3u7u7a+BR0BEvVvlqKmaXT6QT4LDid3t7exoKyFty/UHsxf7Ozs+MD8G9ubv5dMdfX16Yi/HB3dzexPFn67nrBCzxTzTexFlM0F6i+mIX0BezVY7yrq6ukzwvdIVJkH5WRC9gbMR/7EZukLDvkXcdokacEDFUQw5YCiCiA5rlTWbnAHGq+KWDvLEqpAaK3HOaaCyx+xAxc19XdcgeIVrG2ryPaXMBi2SFH67uum/Sw7APXdUMiOiYif565QLqpFMIw7DIQIprbo4ODg1Rlxv39/QDA3uXlZTxy8oyezLnG92P+V+QdPAYRwyBiGEQMg4hhEDEMIoZBxDCIGAYRwyBiGEQMg4hhEDEMIoZBxDCIGAYRw/AHZ4ENamm/WDkAAAAASUVORK5CYII="; + pub const PASTEBIN_API_URI: &'static str = "https://pastebin.com/api/api_post.php"; pub const CDN_BOOTSTRAP: &'static str = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"; - + pub fn app_config() -> String { format!("{}.yml", Self::APP_NAME) } diff --git a/src/core/dump.rs b/src/core/dump.rs index 972c8dc..8541e18 100644 --- a/src/core/dump.rs +++ b/src/core/dump.rs @@ -15,9 +15,8 @@ use std::{ }; use crate::{ - plugins::scan_xss::ScanXSS, handlers::dump_handlers::DumpHandlers, - + ui::{ normal_alerts::NormalAlerts, success_alerts::SuccessAlerts @@ -39,12 +38,6 @@ pub struct Dump { dbname: String, password: String, dump_file_path: String, - - table: Option, - payload: Option, - offset: Option, - limit: Option, - file: Option, } static DUMP_COUNT: AtomicUsize = AtomicUsize::new(0); @@ -60,12 +53,6 @@ impl Dump { backup_path: &str, interval: Option, path: &str, - - table: Option<&str>, - payload: Option<&str>, - offset: Option, - limit: Option, - file: Option<&str>, ) -> Self { Self { port, @@ -76,12 +63,6 @@ impl Dump { dump_file_path: backup_path.to_string(), interval: interval.unwrap_or(3600), path: path.to_string(), - - table: table.map(|s| s.to_string()), - payload: payload.map(|s| s.to_string()), - offset, - limit, - file: file.map(|s| s.to_string()), } } @@ -139,12 +120,6 @@ impl Dump { interval: interval_clone, dump_file_path: dump_file_path_clone.clone(), path: path_clone.clone(), - - table: None, - payload: None, - offset: None, - limit: None, - file: None, }; let dump_count = DUMP_COUNT.load(Ordering::SeqCst); @@ -183,19 +158,4 @@ impl Dump { ).dump().expect("Failed to transfer dump"); } - pub async fn scan_xss(&self) { - ScanXSS::new( - &self.host, - self.port as u16, - &self.user, - &self.password, - &self.dbname, - self.table.as_deref().unwrap_or(""), - self.payload.as_deref(), - self.offset, - self.limit, - self.file.as_deref(), - ).scan().await.expect("Failed to scan tables for XSS"); - } - } diff --git a/src/dump_sync.rs b/src/dump_sync.rs index 0f4770a..7f09f56 100644 --- a/src/dump_sync.rs +++ b/src/dump_sync.rs @@ -1,5 +1,8 @@ use clap::Parser; -use std::error::Error; +use std::{ + env, + error::Error, +}; use reqwest; @@ -12,10 +15,15 @@ use crate::{ args_cli::*, ui::ui_base::UI, - helpers::env::Env, core::dump::Dump, + helpers::env::Env, constants::global::Global, ui::success_alerts::SuccessAlerts, + + plugins::{ + share::Share, + scan_xss::ScanXSS, + }, }; pub struct DumpSync; @@ -38,17 +46,17 @@ impl DumpSync { UI::header(); let dbname = options.database.unwrap_or_else(|| { - std::env::var("DB_NAME").or_else(|_| std::env::var("DS_DB_NAME")).unwrap_or_default() + env::var("DB_NAME").or_else(|_| env::var("DS_DB_NAME")).unwrap_or_default() }); let backup_path = options.file.unwrap_or_else(|| Env::get_var("DS_DUMP_PATH")); - let host = std::env::var("DB_HOST").or_else(|_| std::env::var("DS_DB_HOST")).unwrap_or_default(); - let user = std::env::var("DB_USER").or_else(|_| std::env::var("DS_DB_USER")).unwrap_or_default(); - let password = std::env::var("DB_PASSWORD").or_else(|_| std::env::var("DS_DB_PASSWORD")).unwrap_or_default(); + let host = env::var("DB_HOST").or_else(|_| env::var("DS_DB_HOST")).unwrap_or_default(); + let user = env::var("DB_USER").or_else(|_| env::var("DS_DB_USER")).unwrap_or_default(); + let password = env::var("DB_PASSWORD").or_else(|_| env::var("DS_DB_PASSWORD")).unwrap_or_default(); - let port = std::env::var("DB_PORT") - .or_else(|_| std::env::var("DS_DB_PORT")) + let port = env::var("DB_PORT") + .or_else(|_| env::var("DS_DB_PORT")) .unwrap_or_default() .parse::() .expect("Invalid port"); @@ -64,12 +72,6 @@ impl DumpSync { &backup_path, None, &backup_path, - - None, - None, - None, - None, - None ).import(); } @@ -78,7 +80,7 @@ impl DumpSync { UI::header(); let dbname = options.database.unwrap_or_else(|| { - std::env::var("DB_NAME").or_else(|_| std::env::var("DS_DB_NAME")).unwrap_or_default() + env::var("DB_NAME").or_else(|_| env::var("DS_DB_NAME")).unwrap_or_default() }); let interval = options.interval.unwrap_or_else(|| { @@ -87,12 +89,12 @@ impl DumpSync { let backup_path = options.folder.unwrap_or_else(|| Env::get_var("DS_DUMP_PATH")); - let host = std::env::var("DB_HOST").or_else(|_| std::env::var("DS_DB_HOST")).unwrap_or_default(); - let user = std::env::var("DB_USER").or_else(|_| std::env::var("DS_DB_USER")).unwrap_or_default(); - let password = std::env::var("DB_PASSWORD").or_else(|_| std::env::var("DS_DB_PASSWORD")).unwrap_or_default(); + let host = env::var("DB_HOST").or_else(|_| env::var("DS_DB_HOST")).unwrap_or_default(); + let user = env::var("DB_USER").or_else(|_| env::var("DS_DB_USER")).unwrap_or_default(); + let password = env::var("DB_PASSWORD").or_else(|_| env::var("DS_DB_PASSWORD")).unwrap_or_default(); - let port = std::env::var("DB_PORT") - .or_else(|_| std::env::var("DS_DB_PORT")) + let port = env::var("DB_PORT") + .or_else(|_| env::var("DS_DB_PORT")) .unwrap_or_default() .parse::() .expect("Invalid port"); @@ -108,13 +110,7 @@ impl DumpSync { &dbname, &backup_path, Some(interval), - &backup_path, - - None, - None, - None, - None, - None + &backup_path, ).export(); } @@ -130,15 +126,15 @@ impl DumpSync { let file = options.file; let dbname = options.database.unwrap_or_else(|| { - std::env::var("DB_NAME").or_else(|_| std::env::var("DS_DB_NAME")).unwrap_or_default() + env::var("DB_NAME").or_else(|_| env::var("DS_DB_NAME")).unwrap_or_default() }); - let host = std::env::var("DB_HOST").or_else(|_| std::env::var("DS_DB_HOST")).unwrap_or_default(); - let user = std::env::var("DB_USER").or_else(|_| std::env::var("DS_DB_USER")).unwrap_or_default(); - let password = std::env::var("DB_PASSWORD").or_else(|_| std::env::var("DS_DB_PASSWORD")).unwrap_or_default(); + let host = env::var("DB_HOST").or_else(|_| env::var("DS_DB_HOST")).unwrap_or_default(); + let user = env::var("DB_USER").or_else(|_| env::var("DS_DB_USER")).unwrap_or_default(); + let password = env::var("DB_PASSWORD").or_else(|_| env::var("DS_DB_PASSWORD")).unwrap_or_default(); - let port = std::env::var("DB_PORT") - .or_else(|_| std::env::var("DS_DB_PORT")) + let port = env::var("DB_PORT") + .or_else(|_| env::var("DS_DB_PORT")) .unwrap_or_default() .parse::() .expect("Invalid port"); @@ -146,22 +142,19 @@ impl DumpSync { let header = format!("Scaning table: '{}'", table); UI::section_header(&header, "info"); - Dump::new( - &host, - port, - &user, - &password, - &dbname, - "", - None, - "", - - Some(table.as_str()), + ScanXSS::new( + &host, + port as u16, + &user, + &password, + &dbname, + &table, payload.as_deref(), Some(offset), Some(limit), file.as_deref(), - ).scan_xss().await; + ).scan().await.expect("Failed to scan tables for XSS"); + Ok(()) } @@ -170,14 +163,14 @@ impl DumpSync { UI::header(); let backup_path = options.file.unwrap(); - let dbname = std::env::var("DS_TRANSFER_DB_NAME").or_else(|_| std::env::var("DS_TRANSFER_DB_NAME")).unwrap_or_default(); + let dbname = env::var("DS_TRANSFER_DB_NAME").or_else(|_| env::var("DS_TRANSFER_DB_NAME")).unwrap_or_default(); - let host = std::env::var("DS_TRANSFER_HOST").or_else(|_| std::env::var("DS_TRANSFER_HOST")).unwrap_or_default(); - let user = std::env::var("DS_TRANSFER_USER").or_else(|_| std::env::var("DS_TRANSFER_USER")).unwrap_or_default(); - let password = std::env::var("DS_TRANSFER_PASSWORD").or_else(|_| std::env::var("DS_TRANSFER_PASSWORD")).unwrap_or_default(); + let host = env::var("DS_TRANSFER_HOST").or_else(|_| env::var("DS_TRANSFER_HOST")).unwrap_or_default(); + let user = env::var("DS_TRANSFER_USER").or_else(|_| env::var("DS_TRANSFER_USER")).unwrap_or_default(); + let password = env::var("DS_TRANSFER_PASSWORD").or_else(|_| env::var("DS_TRANSFER_PASSWORD")).unwrap_or_default(); - let port = std::env::var("DS_TRANSFER_PORT") - .or_else(|_| std::env::var("DS_TRANSFER_DB_PORT")) + let port = env::var("DS_TRANSFER_PORT") + .or_else(|_| env::var("DS_TRANSFER_DB_PORT")) .unwrap_or_default() .parse::() .expect("Invalid port"); @@ -193,15 +186,23 @@ impl DumpSync { &backup_path, None, &backup_path, - - None, - None, - None, - None, - None, ).transfer(); } + pub async fn share(&self, options: ShareOptions) -> Result<(), Box> { + Env::new(); + UI::header(); + + let file = options.file.unwrap(); + let api_key = env::var("PASTEBIN_API_KEY").unwrap_or_default(); + + let header = format!("Sharing file: '{}'", file); + UI::section_header(&header, "info"); + + Share::new(&file, &api_key).share().await?; + Ok(()) + } + pub async fn init(&self) -> Result<(), Box> { let cli = Cli::parse(); @@ -225,6 +226,10 @@ impl DumpSync { Commands::Scan(options) => { self.scan_xss(options).await?; }, + + Commands::Share(options) => { + self.share(options).await?; + }, } Ok(()) diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index 6205643..eff446d 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -1,2 +1,3 @@ -pub mod reports_xss; -pub mod scan_xss; \ No newline at end of file +pub mod share; +pub mod scan_xss; +pub mod reports_xss; \ No newline at end of file diff --git a/src/plugins/share.rs b/src/plugins/share.rs new file mode 100644 index 0000000..30ed8aa --- /dev/null +++ b/src/plugins/share.rs @@ -0,0 +1,64 @@ +use reqwest::Client; + +use std::{ + error::Error, + collections::HashMap, +}; + +use crate::{ + utils::file::FileUtils, + constants::global::Global, + ui::share_alerts::ShareAlerts, +}; + +pub struct Share { + file: String, + api_key: String, +} + +impl Share { + + pub fn new(file: &str, api_key: &str) -> Self { + Self { + file: file.to_string(), + api_key: api_key.to_string(), + } + } + + pub async fn share(&self) -> Result<(), Box> { + let ext = FileUtils::extension(&self.file); + + if ["sql", "txt", "csv", "json", "html"].iter().any(|&e| e == ext) { + let privacy = "1".to_string(); + let api_option = "paste".to_string(); + let name = format!("{}: {}", Global::APP_NAME, &self.file); + let content = FileUtils::content(&self.file); + + let mut params = HashMap::new(); + params.insert("api_dev_key", &self.api_key); + params.insert("api_option", &api_option); + params.insert("api_paste_code", &content); + params.insert("api_paste_private", &privacy); + params.insert("api_paste_name", &name); + params.insert("api_paste_format", &ext); + + let response = Client::new() + .post(Global::PASTEBIN_API_URI) + .form(¶ms) + .send() + .await?; + + let response_text = response.text().await?; + if response_text.starts_with("http") { + ShareAlerts::success(&response_text); + } else { + ShareAlerts::error(&response_text); + } + } else { + ShareAlerts::error("Invalid file extension"); + } + + Ok(()) + } + +} \ No newline at end of file diff --git a/src/ui/mod.rs b/src/ui/mod.rs index f9acd00..807e2ff 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,5 +1,6 @@ pub mod ui_base; pub mod scan_alerts; +pub mod share_alerts; pub mod report_alerts; pub mod normal_alerts; pub mod errors_alerts; diff --git a/src/ui/share_alerts.rs b/src/ui/share_alerts.rs new file mode 100644 index 0000000..2fc5614 --- /dev/null +++ b/src/ui/share_alerts.rs @@ -0,0 +1,22 @@ +extern crate colored; + +use colored::*; + +pub struct ShareAlerts; + +impl ShareAlerts { + + pub fn success(link: &str) { + println!( + "Success! Link: {}", + link.blue() + ); + } + + pub fn error(message: &str) { + println!( + "An error occurred: {}", message.red().bold(), + ); + } + +} diff --git a/src/utils/file.rs b/src/utils/file.rs index 2a53b5f..11dbd9b 100644 --- a/src/utils/file.rs +++ b/src/utils/file.rs @@ -23,4 +23,9 @@ impl FileUtils { extension.to_lowercase() } + pub fn content(file_path: &str) -> String { + fs::read_to_string(file_path) + .unwrap_or_default() + } + }