Skip to content

Commit

Permalink
new: added samba module (ref evilsocket#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
evilsocket committed Dec 18, 2023
1 parent a704326 commit f8f3a02
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 7 deletions.
3 changes: 2 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
/.vscode
/data
/test-servers
/release.py
/release.py
/target
21 changes: 21 additions & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ rdp-rs = { version = "0.1.0", optional = true }
scylla = { version = "0.10.1", optional = true }
paho-mqtt = { version = "0.12.3", optional = true }
csv = "1.3.0"
pavao = { version = "0.2.3", optional = true }

[dev-dependencies]
tempfile = "3.8.0"
Expand Down Expand Up @@ -107,6 +108,7 @@ default = [
"redis",
"scylla",
"tcp_ports",
"samba",
]
http = ["dep:url", "dep:reqwest", "dep:base64", "dep:ntlmclient"]
dns = ["dep:trust-dns-resolver"]
Expand Down Expand Up @@ -134,6 +136,7 @@ amqp = []
redis = []
scylla = ["dep:scylla"]
tcp_ports = []
samba = ["dep:pavao"]

[profile.release]
lto = true # Enable link-time optimization
Expand Down
6 changes: 4 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
FROM rust:bullseye as builder

RUN apt-get update && apt-get install -y libssl-dev ca-certificates cmake git
RUN apt-get update && apt-get install -y libsmbclient-dev libssl-dev ca-certificates cmake git

WORKDIR /app
ADD . /app
RUN cargo build --release
RUN ls -la /usr/lib/

FROM gcr.io/distroless/cc-debian11
FROM debian:bullseye
RUN apt-get update && apt-get install -y libsmbclient libssl-dev ca-certificates
COPY --from=builder /app/target/release/legba /usr/bin/legba
ENTRYPOINT ["/usr/bin/legba"]
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ For the building instructions, usage and the complete list of options [check the

## Supported Protocols/Features:

AMQP (ActiveMQ, RabbitMQ, Qpid, JORAM and Solace), Cassandra/ScyllaDB, DNS subdomain enumeration, FTP, HTTP (basic authentication, NTLMv1, NTLMv2, multipart form, custom requests with CSRF support, files/folders enumeration, virtual host enumeration), IMAP, Kerberos pre-authentication and user enumeration, LDAP, MongoDB, MQTT, Microsoft SQL, MySQL, Oracle, PostgreSQL, POP3, RDP, Redis, SSH / SFTP, SMTP, STOMP (ActiveMQ, RabbitMQ, HornetQ and OpenMQ), TCP port scanning, Telnet, VNC.
AMQP (ActiveMQ, RabbitMQ, Qpid, JORAM and Solace), Cassandra/ScyllaDB, DNS subdomain enumeration, FTP, HTTP (basic authentication, NTLMv1, NTLMv2, multipart form, custom requests with CSRF support, files/folders enumeration, virtual host enumeration), IMAP, Kerberos pre-authentication and user enumeration, LDAP, MongoDB, MQTT, Microsoft SQL, MySQL, Oracle, PostgreSQL, POP3, RDP, Redis, Samba, SSH / SFTP, SMTP, STOMP (ActiveMQ, RabbitMQ, HornetQ and OpenMQ), TCP port scanning, Telnet, VNC.

## Benchmark

Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ fn setup() -> Result<Options, session::Error> {

if env::var_os("RUST_LOG").is_none() {
// set `RUST_LOG=debug` to see debug logs
env::set_var("RUST_LOG", "info,blocking=off");
env::set_var("RUST_LOG", "info,blocking=off,pavao=off");
}

env_logger::builder()
Expand Down
3 changes: 3 additions & 0 deletions src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ pub(crate) struct Options {
#[cfg(feature = "telnet")]
#[clap(flatten, next_help_heading = "TELNET")]
pub telnet: crate::plugins::telnet::options::Options,
#[cfg(feature = "samba")]
#[clap(flatten, next_help_heading = "SAMBA")]
pub smb: crate::plugins::samba::options::Options,
#[cfg(feature = "ssh")]
#[clap(flatten, next_help_heading = "SSH")]
pub ssh: crate::plugins::ssh::options::Options,
Expand Down
6 changes: 4 additions & 2 deletions src/plugins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ pub(crate) mod kerberos;
pub(crate) mod ldap;
#[cfg(feature = "mongodb")]
pub(crate) mod mongodb;
#[cfg(feature = "mssql")]
mod mssql;
#[cfg(feature = "mqtt")]
pub(crate) mod mqtt;
#[cfg(feature = "mssql")]
mod mssql;
#[cfg(feature = "oracle")]
pub(crate) mod oracle; // optional as it requires libclntsh that's a pain to install and configure
#[cfg(feature = "pop3")]
Expand All @@ -39,6 +39,8 @@ pub(crate) mod pop3;
pub(crate) mod rdp;
#[cfg(feature = "redis")]
pub(crate) mod redis;
#[cfg(feature = "samba")]
pub(crate) mod samba;
#[cfg(feature = "scylla")]
pub(crate) mod scylla;
#[cfg(feature = "smtp")]
Expand Down
152 changes: 152 additions & 0 deletions src/plugins/samba/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
use std::collections::HashMap;
use std::time::Duration;

use async_trait::async_trait;
use ctor::ctor;
use pavao::{SmbClient, SmbCredentials, SmbDirentType, SmbOptions};
use tokio::sync::Mutex;

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

use lazy_static::lazy_static;

pub(crate) mod options;

lazy_static! {
static ref SHARE_CACHE: Mutex<HashMap<String, String>> = Mutex::new(HashMap::new());
static ref PAVAO_LOCK: Mutex<bool> = tokio::sync::Mutex::new(true);
}

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

#[derive(Clone)]
pub(crate) struct SMB {
share: Option<String>,
workgroup: String,
}

impl SMB {
pub fn new() -> Self {
SMB {
share: None,
workgroup: String::default(),
}
}

fn get_samba_client(
&self,
server: &str,
workgroup: &str,
share: &str,
username: &str,
password: &str,
) -> Result<SmbClient, Error> {
SmbClient::new(
SmbCredentials::default()
.server(server)
.share(share)
.username(username)
.password(password)
.workgroup(workgroup),
SmbOptions::default()
.no_auto_anonymous_login(false)
.one_share_per_server(true),
)
.map_err(|e| format!("error creating client for {}: {}", share, e.to_string()))
}

async fn get_share_for(&self, target: &str) -> Result<String, Error> {
if let Some(share) = self.share.as_ref() {
// return from arguments
return Ok(share.clone());
}

let mut guard = SHARE_CACHE.lock().await;
if let Some(share) = guard.get(target) {
// return from cache
return Ok(share.clone());
}

// get from listing
log::info!("searching private share for {} ...", target);

let server = format!("smb://{}", target);
let root_cli = self.get_samba_client(&server, &self.workgroup, "", "", "")?;
for entry in root_cli.list_dir("").unwrap() {
match entry.get_type() {
SmbDirentType::FileShare | SmbDirentType::Dir => {
let share = format!("/{}", entry.name());
// if share is private we expect an error
let sub_cli =
self.get_samba_client(&server, &self.workgroup, &share, "", "")?;
let listing = sub_cli.list_dir("");
if listing.is_err() {
log::info!("{}{} found", &server, &share);
// found a private share, update the cache and return.
guard.insert(target.to_owned(), share.clone());
return Ok(share);
}
}
_ => {}
}
}

return Err(format!(
"could not find private share for {}, provide one with --smb-share",
target
));
}
}

#[async_trait]
impl Plugin for SMB {
fn description(&self) -> &'static str {
"Samba password authentication."
}

fn setup(&mut self, opts: &Options) -> Result<(), Error> {
self.share = opts.smb.smb_share.clone();
self.workgroup = opts.smb.smb_workgroup.clone();
Ok(())
}

async fn attempt(&self, creds: &Credentials, timeout: Duration) -> Result<Option<Loot>, Error> {
let address = utils::parse_target_address(&creds.target, 445)?;
let server = format!("smb://{}", &address);
let share = tokio::time::timeout(timeout, self.get_share_for(&address))
.await
.map_err(|e: tokio::time::error::Elapsed| e.to_string())?
.map_err(|e| e.to_string())?;

// HACK: pavao doesn't seem to be thread safe, so we need to acquire this lock here.
// Sadly this decreases performances, but it appears that there are no alternatives
// for rust :/
let _guard = PAVAO_LOCK.lock().await;
let client = self.get_samba_client(
&server,
&self.workgroup,
&share,
&creds.username,
&creds.password,
)?;

return if client.list_dir("/").is_ok() {
Ok(Some(Loot::new(
"smb",
&address,
[
("username".to_owned(), creds.username.to_owned()),
("password".to_owned(), creds.password.to_owned()),
],
)))
} else {
Ok(None)
};
}
}
13 changes: 13 additions & 0 deletions src/plugins/samba/options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use clap::Parser;
use serde::{Deserialize, Serialize};

#[derive(Parser, Debug, Serialize, Deserialize, Clone, Default)]
#[group(skip)]
pub(crate) struct Options {
#[clap(long, default_value = "WORKGROUP", help_heading = "SMB")]
/// Samba workgroup name.
pub smb_workgroup: String,
#[clap(long, help_heading = "SMB")]
/// Expicitly set Samba private share to test.
pub smb_share: Option<String>,
}

0 comments on commit f8f3a02

Please sign in to comment.