Skip to content

Commit

Permalink
new: added new cmd plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
evilsocket committed Dec 19, 2023
1 parent 61cb085 commit ab15307
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 3 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ paho-mqtt = { version = "0.12.3", optional = true }
csv = "1.3.0"
pavao = { version = "0.2.3", optional = true }
fast-socks5 = { version = "0.9.2", optional = true }
shell-words = "1.1.0"

[dev-dependencies]
tempfile = "3.8.0"
Expand Down
4 changes: 3 additions & 1 deletion src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ pub(crate) struct Options {
#[clap(short = 'Q', long, default_value_t = false)]
pub quiet: bool,

#[clap(flatten, next_help_heading = "COMMAND (CMD)")]
pub cmd: crate::plugins::cmd::options::Options,
#[cfg(feature = "amqp")]
#[clap(flatten, next_help_heading = "AMQP")]
pub amqp: crate::plugins::amqp::options::Options,
Expand All @@ -97,7 +99,7 @@ pub(crate) struct Options {
#[clap(flatten, next_help_heading = "TELNET")]
pub telnet: crate::plugins::telnet::options::Options,
#[cfg(feature = "samba")]
#[clap(flatten, next_help_heading = "SAMBA")]
#[clap(flatten, next_help_heading = "SAMBA (SMB)")]
pub smb: crate::plugins::samba::options::Options,
#[cfg(feature = "ssh")]
#[clap(flatten, next_help_heading = "SSH")]
Expand Down
110 changes: 110 additions & 0 deletions src/plugins/cmd/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use std::process::Stdio;
use std::time::Duration;

use async_trait::async_trait;
use ctor::ctor;

use crate::session::{Error, Loot};
use crate::Plugin;
use crate::{utils, Options};

use crate::creds::Credentials;

pub(crate) mod options;

#[ctor]
fn register() {
crate::plugins::manager::register("cmd", Box::new(Command::new()));
}

#[derive(Clone)]
pub(crate) struct Command {
opts: options::Options,
}

impl Command {
pub fn new() -> Self {
Command {
opts: options::Options::default(),
}
}

async fn run(&self, creds: &Credentials) -> Result<std::process::Output, Error> {
let (target, port) = utils::parse_target(&creds.target, 0)?;
let args = shell_words::split(
&self
.opts
.cmd_args
.replace("{USERNAME}", &creds.username)
.replace("{PASSWORD}", &creds.password)
.replace("{TARGET}", &target)
.replace("{PORT}", &format!("{}", port)),
)
.unwrap();

log::debug!("{} {}", &self.opts.cmd_binary, args.join(" "));

let child = std::process::Command::new(&self.opts.cmd_binary)
.args(&args)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.map_err(|e| e.to_string())?;

child.wait_with_output().map_err(|e| e.to_string())
}
}

#[async_trait]
impl Plugin for Command {
fn description(&self) -> &'static str {
"Command execution."
}

fn setup(&mut self, opts: &Options) -> Result<(), Error> {
self.opts = opts.cmd.clone();
Ok(())
}

async fn attempt(&self, creds: &Credentials, timeout: Duration) -> Result<Option<Loot>, Error> {
let output = tokio::time::timeout(timeout, self.run(creds))
.await
.map_err(|e| e.to_string())?;

if let Ok(out) = output {
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
if !stderr.is_empty() {
log::error!("{}", stderr);
}

log::debug!("{}", &stdout);

// check exit code first
if out.status.code().unwrap_or(-1) == self.opts.cmd_success_exit_code {
// then output if needed
let ok = if let Some(pattern) = &self.opts.cmd_success_match {
stdout.contains(pattern)
} else {
true
};

if ok {
return Ok(Some(Loot::new(
"command",
&creds.target,
[
("username".to_owned(), creds.username.to_owned()),
("password".to_owned(), creds.password.to_owned()),
],
)));
}
}

return Ok(None);
} else {
return Err(output.err().unwrap().to_string());
}
}
}
22 changes: 22 additions & 0 deletions src/plugins/cmd/options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use clap::Parser;
use serde::{Deserialize, Serialize};

#[derive(Parser, Debug, Serialize, Deserialize, Clone, Default)]
#[group(skip)]
pub(crate) struct Options {
#[clap(long)]
/// Command binary path.
pub cmd_binary: String,

#[clap(long, default_value = "")]
/// Command arguments. {USERNAME}, {PASSWORD}, {TARGET} and {PORT} can be used as placeholders.
pub cmd_args: String,

#[clap(long, default_value_t = 0)]
/// Process exit code to be considered as a positive match.
pub cmd_success_exit_code: i32,

#[clap(long)]
/// String to look for in the process standard output to be considered as a positive match.
pub cmd_success_match: Option<String>,
}
3 changes: 2 additions & 1 deletion src/plugins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ pub(crate) use plugin::Plugin;

// TODO: AFP
// TODO: SNMP
// TODO: SMB
// TODO: network discovery

pub(crate) mod cmd;

#[cfg(feature = "amqp")]
pub(crate) mod amqp;
#[cfg(feature = "cassandra")]
Expand Down
2 changes: 1 addition & 1 deletion src/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use memory_stats::memory_stats;

use crate::Session;

pub(crate) async fn statistics(session: Arc<Session>) {
pub(crate) fn statistics(session: Arc<Session>) {
let one_sec = time::Duration::from_millis(1000);
while !session.is_stop() {
std::thread::sleep(one_sec);
Expand Down

0 comments on commit ab15307

Please sign in to comment.