feat: reworked command processing and storage
This commit is contained in:
parent
ceb4aa808e
commit
5ed8efca94
44
Cargo.lock
generated
44
Cargo.lock
generated
@ -990,6 +990,30 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum_delegate"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8ea75f31022cba043afe037940d73684327e915f88f62478e778c3de914cd0a"
|
||||
dependencies = [
|
||||
"enum_delegate_lib",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum_delegate_lib"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e1f6c3800b304a6be0012039e2a45a322a093539c45ab818d9e6895a39c90fe"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rand 0.8.5",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
@ -3453,15 +3477,30 @@ dependencies = [
|
||||
name = "sparse-actions"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bindgen",
|
||||
"chrono",
|
||||
"enum_delegate",
|
||||
"http",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"leptos",
|
||||
"pcap-sys",
|
||||
"rmp-serde",
|
||||
"rustls",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"smoltcp",
|
||||
"sqlx",
|
||||
"thiserror 2.0.11",
|
||||
"tokio",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sparse-beacon"
|
||||
version = "0.7.0"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
@ -3515,7 +3554,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sparse-server"
|
||||
version = "0.1.0"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@ -4565,6 +4604,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93d59ca99a559661b96bf898d8fce28ed87935fd2bea9f05983c1464dd6c71b1"
|
||||
dependencies = [
|
||||
"getrandom 0.3.1",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@ -7,6 +7,27 @@ version.workspace = true
|
||||
chrono = { version = "0.4.39", features = ["serde"] }
|
||||
serde = { version = "1.0.218", features = ["derive"] }
|
||||
serde_bytes = "0.11.15"
|
||||
uuid = { version = "1.14.0", features = ["serde"] }
|
||||
enum_delegate = "0.2.0"
|
||||
async-trait = "0.1.86"
|
||||
serde_json = "1.0.139"
|
||||
|
||||
leptos = { version = "0.7.7", optional = true }
|
||||
thiserror = { version = "2.0.11", optional = true }
|
||||
pcap-sys = { path = "../pcap-sys", optional = true }
|
||||
tokio = { version = "1.43.0", features = ["fs", "io-std", "io-util", "net", "process", "rt", "sync", "time", "tokio-macros"], optional = true }
|
||||
smoltcp = { version = "0.12.0", default-features = false, features = ["proto-ipv4", "socket", "socket-tcp", "medium-ethernet", "std"], optional = true }
|
||||
http = { version = "1.2.0", optional = true }
|
||||
rmp-serde = { version = "1.3.0", optional = true }
|
||||
hyper-util = { version = "0.1.10", features = ["client", "client-legacy", "http1", "http2", "service", "tokio"], optional = true }
|
||||
hyper = { version = "1.6.0", features = ["client", "http1", "http2"], optional = true }
|
||||
rustls = { version = "0.23.23", default-features = false, features = ["std"], optional = true }
|
||||
sqlx = { version = "0.8", default-features = false, features = ["chrono", "macros", "migrate", "runtime-tokio", "sqlite", "sqlx-sqlite", "uuid"], optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.69"
|
||||
|
||||
[features]
|
||||
beacon = ["dep:thiserror", "dep:pcap-sys", "dep:tokio", "dep:smoltcp", "dep:http", "dep:hyper-util", "dep:rustls", "dep:hyper", "dep:rmp-serde", "uuid/v4"]
|
||||
server-ssr = ["uuid/v4", "dep:sqlx"]
|
||||
server = ["dep:leptos"]
|
||||
|
||||
@ -1 +1,98 @@
|
||||
pub trait Action {}
|
||||
#[cfg(feature = "server")]
|
||||
use leptos::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::version::Version;
|
||||
|
||||
mod ls;
|
||||
mod update;
|
||||
mod exec;
|
||||
mod upload;
|
||||
mod install;
|
||||
mod download;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct FileId(pub uuid::Uuid);
|
||||
|
||||
/// Macro used to enforce the invariant that struct names are used to identify
|
||||
/// the enum branch as well
|
||||
macro_rules! define_actions_enum {
|
||||
($(($mod:ident, $act:ident)),+) => {
|
||||
#[derive(::serde::Serialize, ::serde::Deserialize)]
|
||||
#[serde(tag = "cmd_type")]
|
||||
pub enum Actions {
|
||||
$($act($mod::$act)),+,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
define_actions_enum! {
|
||||
(ls, Ls),
|
||||
(update, Update),
|
||||
(exec, Exec),
|
||||
(upload, Upload),
|
||||
(install, Install),
|
||||
(download, Download)
|
||||
}
|
||||
|
||||
pub const ACTION_BUILDERS: &'static [&'static (dyn ActionBuilder + Send + Sync)] = &[
|
||||
&ActionBuilderImpl::<ls::Ls>::new(),
|
||||
&ActionBuilderImpl::<update::Update>::new(),
|
||||
&ActionBuilderImpl::<exec::Exec>::new(),
|
||||
&ActionBuilderImpl::<upload::Upload>::new(),
|
||||
&ActionBuilderImpl::<install::Install>::new(),
|
||||
&ActionBuilderImpl::<download::Download>::new(),
|
||||
];
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait Action: Serialize + for<'a> Deserialize<'a> {
|
||||
const REQ_VERSION: Version;
|
||||
const REQ_OS: Option<&'static str>;
|
||||
const REQ_FIELDS: &'static [(&'static str, &'static str, Option<&'static str>)];
|
||||
|
||||
type ActionData: Serialize + for<'a> Deserialize<'a>;
|
||||
|
||||
#[cfg(feature = "beacon")]
|
||||
async fn execute(&self) -> Self::ActionData;
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn render_data(&self, data: Self::ActionData) -> impl IntoView;
|
||||
}
|
||||
|
||||
pub trait ActionBuilder {
|
||||
fn name(&self) -> &'static str;
|
||||
fn required_version(&self) -> Version;
|
||||
fn required_os(&self) -> Option<&'static str>;
|
||||
fn form_elements(&self) -> &'static [(&'static str, &'static str, Option<&'static str>)];
|
||||
fn verify_json_body(&self, body: serde_json::Value) -> Result<(), serde_json::Error>;
|
||||
}
|
||||
|
||||
pub struct ActionBuilderImpl<T>(std::marker::PhantomData<T>);
|
||||
|
||||
impl<T> ActionBuilderImpl<T> {
|
||||
pub const fn new() -> Self {
|
||||
Self(std::marker::PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ActionBuilder for ActionBuilderImpl<T>
|
||||
where
|
||||
T: Action
|
||||
{
|
||||
fn name(&self) -> &'static str {
|
||||
let tname = std::any::type_name::<T>();
|
||||
tname.split(":").last().unwrap()
|
||||
}
|
||||
fn required_version(&self) -> Version {
|
||||
T::REQ_VERSION
|
||||
}
|
||||
fn required_os(&self) -> Option<&'static str> {
|
||||
T::REQ_OS
|
||||
}
|
||||
fn form_elements(&self) -> &'static [(&'static str, &'static str, Option<&'static str>)] {
|
||||
T::REQ_FIELDS
|
||||
}
|
||||
fn verify_json_body(&self, body: serde_json::Value) -> Result<(), serde_json::Error> {
|
||||
serde_json::from_value::<T>(body).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
35
sparse-actions/src/actions/download.rs
Normal file
35
sparse-actions/src/actions/download.rs
Normal file
@ -0,0 +1,35 @@
|
||||
#[cfg(feature = "server")]
|
||||
use leptos::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::version::Version;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Download {
|
||||
download_src: super::FileId,
|
||||
download_path: String,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl super::Action for Download {
|
||||
const REQ_VERSION: Version = Version::new(2, 0);
|
||||
const REQ_OS: Option<&'static str> = None;
|
||||
const REQ_FIELDS: &'static [(&'static str, &'static str, Option<&'static str>)] = &[
|
||||
("File path to download/place", "download_path", None),
|
||||
("File to download", "download_src", Some("file")),
|
||||
];
|
||||
|
||||
type ActionData = ();
|
||||
|
||||
#[cfg(feature = "beacon")]
|
||||
async fn execute(&self) -> Self::ActionData {
|
||||
"Hi".to_string();
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn render_data(&self, _data: Self::ActionData) -> impl IntoView {
|
||||
view! {
|
||||
"ls ran"
|
||||
}
|
||||
}
|
||||
}
|
||||
33
sparse-actions/src/actions/exec.rs
Normal file
33
sparse-actions/src/actions/exec.rs
Normal file
@ -0,0 +1,33 @@
|
||||
#[cfg(feature = "server")]
|
||||
use leptos::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::version::Version;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Exec {
|
||||
exec_cmd: String
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl super::Action for Exec {
|
||||
const REQ_VERSION: Version = Version::new(2, 0);
|
||||
const REQ_OS: Option<&'static str> = None;
|
||||
const REQ_FIELDS: &'static [(&'static str, &'static str, Option<&'static str>)] = &[
|
||||
("Command to execute", "exec_cmd", None)
|
||||
];
|
||||
|
||||
type ActionData = String;
|
||||
|
||||
#[cfg(feature = "beacon")]
|
||||
async fn execute(&self) -> Self::ActionData {
|
||||
"Execute".to_string()
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn render_data(&self, _data: Self::ActionData) -> impl IntoView {
|
||||
view! {
|
||||
"execute command"
|
||||
}
|
||||
}
|
||||
}
|
||||
33
sparse-actions/src/actions/install.rs
Normal file
33
sparse-actions/src/actions/install.rs
Normal file
@ -0,0 +1,33 @@
|
||||
#[cfg(feature = "server")]
|
||||
use leptos::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::version::Version;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Install {
|
||||
install_target: std::path::PathBuf
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl super::Action for Install {
|
||||
const REQ_VERSION: Version = Version::new(2, 0);
|
||||
const REQ_OS: Option<&'static str> = None;
|
||||
const REQ_FIELDS: &'static [(&'static str, &'static str, Option<&'static str>)] = &[
|
||||
("Binary to infect", "install_target", None)
|
||||
];
|
||||
|
||||
type ActionData = ();
|
||||
|
||||
#[cfg(feature = "beacon")]
|
||||
async fn execute(&self) -> Self::ActionData {
|
||||
"Hi".to_string();
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn render_data(&self, _data: Self::ActionData) -> impl IntoView {
|
||||
view! {
|
||||
"ls ran"
|
||||
}
|
||||
}
|
||||
}
|
||||
29
sparse-actions/src/actions/ls.rs
Normal file
29
sparse-actions/src/actions/ls.rs
Normal file
@ -0,0 +1,29 @@
|
||||
#[cfg(feature = "server")]
|
||||
use leptos::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::version::Version;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Ls;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl super::Action for Ls {
|
||||
const REQ_VERSION: Version = Version::new(2, 0);
|
||||
const REQ_OS: Option<&'static str> = None;
|
||||
const REQ_FIELDS: &'static [(&'static str, &'static str, Option<&'static str>)] = &[];
|
||||
|
||||
type ActionData = ();
|
||||
|
||||
#[cfg(feature = "beacon")]
|
||||
async fn execute(&self) -> Self::ActionData {
|
||||
"Hi".to_string();
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn render_data(&self, _data: Self::ActionData) -> impl IntoView {
|
||||
view! {
|
||||
"ls ran"
|
||||
}
|
||||
}
|
||||
}
|
||||
29
sparse-actions/src/actions/update.rs
Normal file
29
sparse-actions/src/actions/update.rs
Normal file
@ -0,0 +1,29 @@
|
||||
#[cfg(feature = "server")]
|
||||
use leptos::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::version::Version;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Update;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl super::Action for Update {
|
||||
const REQ_VERSION: Version = Version::new(2, 0);
|
||||
const REQ_OS: Option<&'static str> = None;
|
||||
const REQ_FIELDS: &'static [(&'static str, &'static str, Option<&'static str>)] = &[];
|
||||
|
||||
type ActionData = ();
|
||||
|
||||
#[cfg(feature = "beacon")]
|
||||
async fn execute(&self) -> Self::ActionData {
|
||||
"Hello".to_string();
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn render_data(&self, _data: Self::ActionData) -> impl IntoView {
|
||||
view! {
|
||||
"update ran"
|
||||
}
|
||||
}
|
||||
}
|
||||
33
sparse-actions/src/actions/upload.rs
Normal file
33
sparse-actions/src/actions/upload.rs
Normal file
@ -0,0 +1,33 @@
|
||||
#[cfg(feature = "server")]
|
||||
use leptos::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::version::Version;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Upload {
|
||||
upload_src: String
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl super::Action for Upload {
|
||||
const REQ_VERSION: Version = Version::new(2, 0);
|
||||
const REQ_OS: Option<&'static str> = None;
|
||||
const REQ_FIELDS: &'static [(&'static str, &'static str, Option<&'static str>)] = &[
|
||||
("File path to upload/exfil", "upload_src", None)
|
||||
];
|
||||
|
||||
type ActionData = ();
|
||||
|
||||
#[cfg(feature = "beacon")]
|
||||
async fn execute(&self) -> Self::ActionData {
|
||||
"Hi".to_string();
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn render_data(&self, _data: Self::ActionData) -> impl IntoView {
|
||||
view! {
|
||||
"ls ran"
|
||||
}
|
||||
}
|
||||
}
|
||||
38
sparse-actions/src/adapter.rs
Normal file
38
sparse-actions/src/adapter.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use crate::error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BeaconRoute {
|
||||
pub network: (Ipv4Addr, u8),
|
||||
pub gateway: (Ipv4Addr, u8),
|
||||
pub interface_index: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BeaconNetworkingInfo {
|
||||
pub routes: Vec<BeaconRoute>,
|
||||
pub interfaces: Vec<BeaconInterface>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BeaconInterface {
|
||||
pub name: Vec<u8>,
|
||||
pub mtu: u16,
|
||||
pub mac_addr: [u8; 6],
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait BeaconAdapter {
|
||||
type Error: error::AdapterError + Send + Sync;
|
||||
|
||||
const OPERATING_SYSTEM: &'static str;
|
||||
|
||||
fn interface_name_from_interface(interface: &BeaconInterface) -> Vec<u8>;
|
||||
|
||||
fn networking_info(&self) -> Result<BeaconNetworkingInfo, error::BeaconError<Self::Error>>;
|
||||
|
||||
async fn get_username(&self) -> Result<String, error::BeaconError<Self::Error>>;
|
||||
|
||||
async fn get_hostname(&self) -> Result<String, error::BeaconError<Self::Error>>;
|
||||
}
|
||||
40
sparse-actions/src/error.rs
Normal file
40
sparse-actions/src/error.rs
Normal file
@ -0,0 +1,40 @@
|
||||
use thiserror::Error;
|
||||
|
||||
pub trait AdapterError: std::error::Error {}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum BeaconError<T>
|
||||
where
|
||||
T: AdapterError,
|
||||
{
|
||||
#[error("io error")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("pcap error")]
|
||||
Pcap(#[from] pcap_sys::error::Error),
|
||||
#[error("utf8 decoding error")]
|
||||
Utf8(#[from] std::str::Utf8Error),
|
||||
#[error("task join error")]
|
||||
Join(#[from] tokio::task::JoinError),
|
||||
#[error("could not find default route")]
|
||||
NoDefaultRoute,
|
||||
#[error("connection error")]
|
||||
Connect(#[from] smoltcp::socket::tcp::ConnectError),
|
||||
#[error("http comms error")]
|
||||
Http(#[from] http::Error),
|
||||
#[error("uri parse error")]
|
||||
InvalidUri(#[from] http::uri::InvalidUri),
|
||||
#[error("hyper http error")]
|
||||
HyperError(#[from] hyper_util::client::legacy::Error),
|
||||
#[error("rustls")]
|
||||
Rustls(#[from] rustls::Error),
|
||||
#[error("adapter error")]
|
||||
Adapter(#[from] T),
|
||||
#[error("http error from server")]
|
||||
SparseServerHttpError(http::StatusCode),
|
||||
#[error("message pack encode error")]
|
||||
RmpSerdeEncode(#[from] rmp_serde::encode::Error),
|
||||
#[error("message pack decode error")]
|
||||
RmpSerdeDecode(#[from] rmp_serde::decode::Error),
|
||||
#[error("http error")]
|
||||
Hyper(#[from] hyper::Error),
|
||||
}
|
||||
@ -5,4 +5,9 @@ pub mod payload_types {
|
||||
}
|
||||
|
||||
pub mod actions;
|
||||
#[cfg(feature = "beacon")]
|
||||
pub mod adapter;
|
||||
#[cfg(feature = "beacon")]
|
||||
pub mod error;
|
||||
pub mod messages;
|
||||
pub mod version;
|
||||
|
||||
7
sparse-actions/src/main.rs
Normal file
7
sparse-actions/src/main.rs
Normal file
@ -0,0 +1,7 @@
|
||||
use sparse_actions::actions::ActionBuilder;
|
||||
|
||||
fn main() {
|
||||
for b in sparse_actions::actions::ACTION_BUILDERS {
|
||||
println!("{}", b.name());
|
||||
}
|
||||
}
|
||||
64
sparse-actions/src/version.rs
Normal file
64
sparse-actions/src/version.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use std::{
|
||||
cmp::{Ord, Ordering, PartialOrd},
|
||||
fmt::{self, Display},
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
|
||||
#[repr(transparent)]
|
||||
#[cfg_attr(feature = "server-ssr", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "server-ssr", sqlx(transparent))]
|
||||
pub struct Version(u16);
|
||||
|
||||
impl Version {
|
||||
pub const fn new(maj: u8, min: u8) -> Self {
|
||||
Self(((maj as u16) << 8u16) | (min as u16))
|
||||
}
|
||||
|
||||
pub const fn major(&self) -> u8 {
|
||||
(self.0 >> 8) as u8
|
||||
}
|
||||
|
||||
pub const fn minor(&self) -> u8 {
|
||||
(self.0 & 0xFF) as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Version {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Version {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
if self.major() == other.major() {
|
||||
self.minor().cmp(&other.minor())
|
||||
} else {
|
||||
self.major().cmp(&other.major())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Version {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Version({}.{})", self.major(), self.minor())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Version {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}.{}", self.major(), self.minor())
|
||||
}
|
||||
}
|
||||
|
||||
/*#[cfg(feature = "server-ssr")]
|
||||
impl<'r> sqlx::Decode<'r, sqlx::Sqlite> for Version {
|
||||
fn decode(
|
||||
value: sqlx::Sqlite::ValueRef<'r>,
|
||||
) -> Result<Self, Box<dyn Error + 'static + Send + Sync>> {
|
||||
let value = <u16 as sqlx::Decode<sqlx::Sqlite>>::decode(value)?;
|
||||
Ok(Self(value))
|
||||
}
|
||||
}*/
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "sparse-beacon"
|
||||
version = "0.7.0"
|
||||
version.workspace = true
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
@ -29,9 +29,9 @@ http-body = "1.0.1"
|
||||
rmp-serde = "1.3.0"
|
||||
cron = "0.13.0"
|
||||
|
||||
pcap-sys = { version = "0.1.0", path = "../pcap-sys" }
|
||||
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
|
||||
packets = { version = "0.1.0", path = "../packets" }
|
||||
pcap-sys = { path = "../pcap-sys" }
|
||||
sparse-actions = { path = "../sparse-actions", features = ["beacon"] }
|
||||
packets = { path = "../packets" }
|
||||
|
||||
[features]
|
||||
openssl = ["dep:rustls-openssl"]
|
||||
|
||||
@ -12,13 +12,13 @@ use hyper_util::{
|
||||
use rustls::RootCertStore;
|
||||
use tower_service::Service;
|
||||
|
||||
use sparse_actions::payload_types::Parameters;
|
||||
|
||||
use crate::{
|
||||
use sparse_actions::{
|
||||
adapter, error,
|
||||
tcp::{self, setup_network},
|
||||
payload_types::Parameters
|
||||
};
|
||||
|
||||
use crate::tcp::{self, setup_network};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ServerConnector<T>
|
||||
where
|
||||
|
||||
@ -3,17 +3,13 @@ use sparse_actions::payload_types::Parameters;
|
||||
use http_body_util::{BodyExt, Full};
|
||||
use hyper::{Request, Method};
|
||||
|
||||
use sparse_actions::messages;
|
||||
use sparse_actions::{adapter, error::BeaconError, messages};
|
||||
|
||||
mod callback;
|
||||
mod socket;
|
||||
mod tcp;
|
||||
mod params;
|
||||
|
||||
pub mod adapter;
|
||||
pub mod error;
|
||||
pub use error::BeaconError;
|
||||
|
||||
pub fn install_rustls() {
|
||||
#[cfg(feature = "openssl")]
|
||||
let _ = rustls_openssl::default_provider().install_default();
|
||||
@ -63,7 +59,7 @@ where
|
||||
let hostname = host_adapter.get_hostname().await.unwrap_or("(unknown)".to_string());
|
||||
let userent = host_adapter.get_username().await.unwrap_or("(unknown)".to_string());
|
||||
|
||||
let mut config: messages::BeaconConfig = {
|
||||
let _config: messages::BeaconConfig = {
|
||||
let client = callback::obtain_https_client(&host_adapter, ¶ms).await?;
|
||||
|
||||
make_request(
|
||||
@ -83,15 +79,15 @@ where
|
||||
loop {
|
||||
// let client = callback::obtain_https_client(&host_adapter, ¶ms).await?;
|
||||
|
||||
use messages::RuntimeConfig as RC;
|
||||
let target_wake_time = match &config.runtime_config {
|
||||
RC::Oneshot => { break; },
|
||||
RC::Random { interval_min, interval_max } => {},
|
||||
RC::Regular { interval } => {},
|
||||
RC::Cron { schedule, timezone } => {
|
||||
//use messages::RuntimeConfig as RC;
|
||||
//let target_wake_time = match &config.runtime_config {
|
||||
// RC::Oneshot => { break; },
|
||||
// RC::Random { interval_min, interval_max } => {},
|
||||
// RC::Regular { interval } => {},
|
||||
// RC::Cron { schedule, timezone } => {
|
||||
|
||||
}
|
||||
};
|
||||
// }
|
||||
//};
|
||||
}
|
||||
|
||||
// for _ in 1..5 {
|
||||
@ -106,6 +102,4 @@ where
|
||||
// let body = body.collect().await;
|
||||
// println!("{:?}", body);
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
use sparse_actions::payload_types::Parameters;
|
||||
|
||||
use crate::adapter::BeaconAdapter;
|
||||
use crate::error::BeaconError;
|
||||
use sparse_actions::{adapter::BeaconAdapter, error::BeaconError, payload_types::Parameters};
|
||||
|
||||
pub fn domain_name<'a, T>(params: &'a Parameters) -> Result<&'a str, BeaconError<T::Error>>
|
||||
where
|
||||
|
||||
@ -2,7 +2,7 @@ use smoltcp::phy::{self, Device, DeviceCapabilities, Medium};
|
||||
|
||||
use pcap_sys::Interface;
|
||||
|
||||
use crate::{adapter, error};
|
||||
use sparse_actions::{adapter, error};
|
||||
|
||||
struct SocketInner {
|
||||
lower: Interface,
|
||||
|
||||
@ -18,9 +18,10 @@ use tokio::{
|
||||
task::{spawn_blocking, JoinHandle},
|
||||
};
|
||||
|
||||
use sparse_actions::payload_types::Parameters;
|
||||
|
||||
use crate::{adapter, error};
|
||||
use sparse_actions::{
|
||||
adapter, error,
|
||||
payload_types::Parameters
|
||||
};
|
||||
|
||||
pub struct NetInterfaceHandle {
|
||||
net: Arc<Mutex<(SocketSet<'static>, crate::socket::RawSocket, Interface)>>,
|
||||
|
||||
@ -18,5 +18,6 @@ rustls = { version = "0.23", default-features = false, features = ["ring", "std"
|
||||
rcgen = { version = "0.13.2", features = ["pem", "x509-parser", "crypto"] }
|
||||
rustls-pki-types = "1.11.0"
|
||||
axum-msgpack = "0.4.0"
|
||||
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
|
||||
chrono = { version = "0.4.39", features = ["serde"] }
|
||||
|
||||
sparse-actions = { path = "../sparse-actions" }
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "sparse-server"
|
||||
version = "0.1.0"
|
||||
version.workspace = true
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@ -51,7 +51,7 @@ server_fn = { version = "0.7.7", features = ["multipart"] }
|
||||
multer = { version = "3.1.0", optional = true }
|
||||
uuid = { version = "1.14.0", features = ["v4"], optional = true }
|
||||
|
||||
sparse-actions = { path = "../sparse-actions", optional = true }
|
||||
sparse-actions = { path = "../sparse-actions", features = ["server"] }
|
||||
sparse-handler = { path = "../sparse-handler", optional = true }
|
||||
|
||||
[features]
|
||||
@ -79,14 +79,14 @@ ssr = [
|
||||
"dep:cron",
|
||||
"dep:sparse-handler",
|
||||
"dep:rustls-pki-types",
|
||||
"dep:sparse-actions",
|
||||
"dep:rand",
|
||||
"dep:multer",
|
||||
"dep:uuid",
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
"leptos-use/ssr"
|
||||
"leptos-use/ssr",
|
||||
"sparse-actions/server-ssr"
|
||||
]
|
||||
|
||||
[package.metadata.leptos]
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
-- Add migration script here
|
||||
|
||||
ALTER TABLE beacon_instance ADD COLUMN version int NOT NULL DEFAULT 512;
|
||||
27
sparse-server/migrations/20250223215600_commands_v2.sql
Normal file
27
sparse-server/migrations/20250223215600_commands_v2.sql
Normal file
@ -0,0 +1,27 @@
|
||||
-- Add migration script here
|
||||
DROP TABLE beacon_command_invocation;
|
||||
DROP TABLE beacon_command;
|
||||
|
||||
CREATE TABLE beacon_command (
|
||||
command_id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
|
||||
cmd_parameters varchar NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE beacon_command_invocation (
|
||||
beacon_id varchar NOT NULL,
|
||||
command_id int NOT NULL,
|
||||
|
||||
issue_date int NOT NULL,
|
||||
|
||||
invoker_id int NOT NULL,
|
||||
|
||||
invocation_date int,
|
||||
invocation_result varchar,
|
||||
|
||||
PRIMARY KEY (beacon_id, command_id),
|
||||
|
||||
FOREIGN KEY (command_id) REFERENCES beacon_command,
|
||||
FOREIGN KEY (beacon_id) REFERENCES beacon_instance,
|
||||
FOREIGN KEY (invoker_id) REFERENCES users
|
||||
);
|
||||
@ -1,66 +1,17 @@
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use leptos::server_fn::codec::{MultipartData, MultipartFormData};
|
||||
use web_sys::FormData;
|
||||
use send_wrapper::SendWrapper;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
use {
|
||||
sparse_actions::version::Version,
|
||||
leptos::server_fn::error::NoCustomError,
|
||||
sqlx::{sqlite::SqliteRow, FromRow, Row},
|
||||
tokio::io::AsyncWriteExt,
|
||||
};
|
||||
|
||||
use crate::beacons::{BeaconResources, categories::Category};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub enum CommandBody {
|
||||
Update,
|
||||
Exec { command: String },
|
||||
Install { target_binary: String },
|
||||
Upload { target_file: String },
|
||||
Download { src_file: String, target_path: String },
|
||||
Chdir { target_dir: String },
|
||||
Ls
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
impl FromRow<'_, SqliteRow> for CommandBody {
|
||||
fn from_row(row: &SqliteRow) -> sqlx::Result<Self> {
|
||||
match row.try_get("cmd_type")? {
|
||||
"update" => Ok(Self::Update),
|
||||
"exec" => Ok(Self::Exec {
|
||||
command: row.try_get("exec_command")?
|
||||
}),
|
||||
"install" => Ok(Self::Install {
|
||||
target_binary: row.try_get("install_target")?
|
||||
}),
|
||||
"upload" => Ok(Self::Upload {
|
||||
target_file: row.try_get("upload_src")?
|
||||
}),
|
||||
"download" => Ok(Self::Download {
|
||||
src_file: row.try_get("download_src")?,
|
||||
target_path: row.try_get("download_path")?
|
||||
}),
|
||||
"chdir" => Ok(Self::Chdir {
|
||||
target_dir: row.try_get("chdir_target")?
|
||||
}),
|
||||
"ls" => Ok(Self::Ls),
|
||||
type_name => Err(sqlx::Error::TypeNotFound {
|
||||
type_name: type_name.to_string()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "ssr", derive(FromRow))]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct BeaconCommand {
|
||||
pub command_id: i64,
|
||||
#[cfg_attr(feature = "ssr", sqlx(flatten))]
|
||||
pub body: CommandBody
|
||||
}
|
||||
|
||||
#[server(
|
||||
prefix = "/api/commands",
|
||||
endpoint = "issue_command",
|
||||
@ -75,138 +26,19 @@ pub async fn issue_command(
|
||||
));
|
||||
};
|
||||
|
||||
let mut fields = std::collections::HashMap::<String, String>::new();
|
||||
let mut download_src = None::<multer::Field>;
|
||||
let mut fields = serde_json::Map::new();
|
||||
let db = crate::db::get_db()?;
|
||||
|
||||
let mut data = data.into_inner().ok_or(ServerFnError::<NoCustomError>::ServerError(
|
||||
"No form data was provided".to_owned(),
|
||||
))?;
|
||||
while let Ok(Some(field)) = data.next_field().await {
|
||||
let name = field.name().unwrap_or_default().to_string();
|
||||
tracing::trace!("Found field {}", &name);
|
||||
if name != "download_src" {
|
||||
fields.insert(name.clone(), field.text().await.unwrap_or_default());
|
||||
} else {
|
||||
download_src = Some(field);
|
||||
}
|
||||
}
|
||||
while let Ok(Some(mut field)) = data.next_field().await {
|
||||
tracing::debug!("Processing field {:?}", field.name());
|
||||
let Some(name) = field.name().map(|f| f.to_string()) else { continue; };
|
||||
|
||||
enum Target {
|
||||
Beacon(String),
|
||||
Category(i64)
|
||||
}
|
||||
let file_name = field.file_name().map(str::to_string);
|
||||
|
||||
let Some(target_beacons) = fields
|
||||
.get("beacon_id")
|
||||
.map(Clone::clone)
|
||||
.map(Target::Beacon)
|
||||
.or(fields
|
||||
.get("category_id")
|
||||
.map(|id| id.parse::<i64>().ok())
|
||||
.flatten()
|
||||
.map(Target::Category)) else {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"A beacon command cannot be made without a target".to_owned(),
|
||||
));
|
||||
};
|
||||
|
||||
let db = crate::db::get_db()?;
|
||||
|
||||
let command_id = match fields.get("cmd_type").map(Clone::clone).as_deref() {
|
||||
Some("update") => {
|
||||
Ok(sqlx::query!(
|
||||
"INSERT INTO beacon_command (cmd_type) VALUES ('update')"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?
|
||||
.last_insert_rowid())
|
||||
}
|
||||
Some("ls") => {
|
||||
Ok(sqlx::query!(
|
||||
"INSERT INTO beacon_command (cmd_type) VALUES ('ls')"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?
|
||||
.last_insert_rowid())
|
||||
}
|
||||
Some("chdir") => {
|
||||
let chdir_target = fields
|
||||
.get("chdir_target")
|
||||
.filter(|s| !s.is_empty())
|
||||
.ok_or(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Directory target cannot be empty".to_owned(),
|
||||
))?;
|
||||
|
||||
Ok(sqlx::query!(
|
||||
"INSERT INTO beacon_command (cmd_type, chdir_target) VALUES ('chdir', ?)",
|
||||
chdir_target
|
||||
)
|
||||
.execute(&db)
|
||||
.await?
|
||||
.last_insert_rowid())
|
||||
}
|
||||
Some("exec") => {
|
||||
let exec_command = fields
|
||||
.get("exec_command")
|
||||
.filter(|s| !s.is_empty())
|
||||
.ok_or(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Command cannot be empty".to_owned(),
|
||||
))?;
|
||||
|
||||
Ok(sqlx::query!(
|
||||
"INSERT INTO beacon_command (cmd_type, exec_command) VALUES ('exec', ?)",
|
||||
exec_command
|
||||
)
|
||||
.execute(&db)
|
||||
.await?
|
||||
.last_insert_rowid())
|
||||
}
|
||||
Some("install") => {
|
||||
let install_target = fields
|
||||
.get("install_target")
|
||||
.filter(|s| !s.is_empty())
|
||||
.ok_or(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Install target cannot be empty".to_owned(),
|
||||
))?;
|
||||
|
||||
Ok(sqlx::query!(
|
||||
"INSERT INTO beacon_command (cmd_type, install_target) VALUES ('install', ?)",
|
||||
install_target
|
||||
)
|
||||
.execute(&db)
|
||||
.await?
|
||||
.last_insert_rowid())
|
||||
}
|
||||
Some("upload") => {
|
||||
let upload_src = fields
|
||||
.get("upload_src")
|
||||
.filter(|s| !s.is_empty())
|
||||
.ok_or(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Upload file request path cannot be empty".to_owned(),
|
||||
))?;
|
||||
|
||||
Ok(sqlx::query!(
|
||||
"INSERT INTO beacon_command (cmd_type, upload_src) VALUES ('upload', ?)",
|
||||
upload_src
|
||||
)
|
||||
.execute(&db)
|
||||
.await?
|
||||
.last_insert_rowid())
|
||||
}
|
||||
Some("download") => {
|
||||
let download_path = fields
|
||||
.get("download_path")
|
||||
.filter(|s| !s.is_empty())
|
||||
.ok_or(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Upload file request path cannot be empty".to_owned(),
|
||||
))?;
|
||||
|
||||
let mut download_src = download_src
|
||||
.ok_or(ServerFnError::<NoCustomError>::ServerError(
|
||||
"File to upload cannot be empty".to_owned(),
|
||||
))?;
|
||||
|
||||
let file_name = download_src.file_name().unwrap_or_default().to_string();
|
||||
if let Some(file_name) = file_name {
|
||||
let file_id = uuid::Uuid::new_v4();
|
||||
|
||||
let mut target_file_path = expect_context::<std::path::PathBuf>();
|
||||
@ -220,7 +52,7 @@ pub async fn issue_command(
|
||||
.open(target_file_path)
|
||||
.await?;
|
||||
|
||||
while let Ok(Some(chunk)) = download_src.chunk().await {
|
||||
while let Ok(Some(chunk)) = field.chunk().await {
|
||||
target_file.write(&chunk).await?;
|
||||
}
|
||||
|
||||
@ -232,24 +64,83 @@ pub async fn issue_command(
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
Ok(sqlx::query!(
|
||||
"INSERT INTO beacon_command (cmd_type, download_src, download_path) VALUES ('download', ?, ?)",
|
||||
file_id,
|
||||
download_path
|
||||
)
|
||||
.execute(&db)
|
||||
.await?
|
||||
.last_insert_rowid())
|
||||
fields.insert(name, serde_json::to_value(sparse_actions::actions::FileId(file_id))?);
|
||||
} else {
|
||||
let Ok(value) = field.text().await else { continue; };
|
||||
let json = match serde_json::from_str(&value) {
|
||||
Ok(v) => v,
|
||||
Err(_) => serde_json::Value::String(value.clone())
|
||||
};
|
||||
fields.insert(name, json);
|
||||
}
|
||||
_ => Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"Unknown command type".to_owned(),
|
||||
))
|
||||
}?;
|
||||
}
|
||||
|
||||
enum Target {
|
||||
Beacon(String),
|
||||
Category(i64)
|
||||
}
|
||||
|
||||
tracing::debug!("Parameters provided: {:?}", serde_json::to_string(&fields));
|
||||
|
||||
let Some(target_beacons) = fields
|
||||
.get("target_beacon_id")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.map(str::to_string)
|
||||
.map(Target::Beacon)
|
||||
.or(fields
|
||||
.get("target_category_id")
|
||||
.and_then(serde_json::Value::as_i64)
|
||||
.map(Target::Category)) else {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"A beacon command cannot be made without a target".to_owned(),
|
||||
));
|
||||
};
|
||||
|
||||
fields.remove("target_beacon_id");
|
||||
fields.remove("target_category_id");
|
||||
|
||||
let Some(command_builder) = sparse_actions::actions::ACTION_BUILDERS
|
||||
.iter()
|
||||
.find(|builder| Some(builder.name()) == fields.get("cmd_type").and_then(serde_json::Value::as_str)) else {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
"No command type provided".to_owned(),
|
||||
));
|
||||
};
|
||||
|
||||
let fields = serde_json::Value::Object(fields);
|
||||
|
||||
command_builder.verify_json_body(fields.clone())?;
|
||||
serde_json::from_value::<sparse_actions::actions::Actions>(fields.clone())?;
|
||||
|
||||
let serialized_fields = serde_json::to_string(&fields)?;
|
||||
|
||||
let command_id = sqlx::query!(
|
||||
"INSERT INTO beacon_command (cmd_parameters) VALUES (?)",
|
||||
serialized_fields
|
||||
)
|
||||
.execute(&db)
|
||||
.await?
|
||||
.last_insert_rowid();
|
||||
|
||||
let now = chrono::Utc::now();
|
||||
|
||||
match target_beacons {
|
||||
Target::Beacon(bid) => {
|
||||
let beacon_instance = sqlx::query!(
|
||||
r#"SELECT version as "version: Version" FROM beacon_instance WHERE beacon_id = ?"#,
|
||||
bid
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
|
||||
if beacon_instance.version < command_builder.required_version() {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(format!(
|
||||
"Beacon does not meet the minimum required version to run that command ({} vs {})",
|
||||
beacon_instance.version,
|
||||
command_builder.required_version()
|
||||
)));
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO beacon_command_invocation (command_id, issue_date, invoker_id, beacon_id)
|
||||
VALUES (?, ?, ?, ?)",
|
||||
@ -262,16 +153,20 @@ pub async fn issue_command(
|
||||
.await?;
|
||||
}
|
||||
Target::Category(cid) => {
|
||||
let version = command_builder.required_version();
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO beacon_command_invocation (command_id, issue_date, invoker_id, beacon_id)
|
||||
SELECT ?, ?, ?, bi.beacon_id FROM beacon_category bc
|
||||
INNER JOIN beacon_category_assignment bca ON bc.category_id = bca.category_id
|
||||
INNER JOIN beacon_instance bi ON bca.beacon_id = bi.beacon_id
|
||||
WHERE bc.category_id = ?",
|
||||
WHERE bc.category_id = ?
|
||||
AND bi.version >= ?",
|
||||
command_id,
|
||||
now,
|
||||
user.user_id,
|
||||
cid
|
||||
cid,
|
||||
version
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
@ -290,6 +185,8 @@ pub fn CommandForm(categories: Vec<Category>, beacon_id: Option<String>) -> impl
|
||||
}
|
||||
});
|
||||
|
||||
let (current_cmd, set_current_cmd) = signal("Exec".to_owned());
|
||||
|
||||
view! {
|
||||
{(categories.is_empty() && beacon_id.is_none())
|
||||
.then(|| view! {
|
||||
@ -318,14 +215,14 @@ pub fn CommandForm(categories: Vec<Category>, beacon_id: Option<String>) -> impl
|
||||
<legend>"Issue new command"</legend>
|
||||
{if let Some(bid) = beacon_id.clone() {
|
||||
Either::Left(view! {
|
||||
<input name="beacon_id" type="hidden" value=bid />
|
||||
<input name="target_beacon_id" type="hidden" value=bid />
|
||||
})
|
||||
} else {
|
||||
Either::Right(view! {
|
||||
<label>
|
||||
"Select a category to command"
|
||||
</label>
|
||||
<select name="category_id">
|
||||
<select name="target_category_id">
|
||||
{categories
|
||||
.iter()
|
||||
.map(|cat| view! {
|
||||
@ -338,27 +235,34 @@ pub fn CommandForm(categories: Vec<Category>, beacon_id: Option<String>) -> impl
|
||||
})
|
||||
}}
|
||||
<label>"Type of command"</label>
|
||||
<select name="cmd_type">
|
||||
<option value="exec">"Execute command"</option>
|
||||
<option value="upload">"Upload/exfil file"</option>
|
||||
<option value="download">"Download/place file"</option>
|
||||
<option value="update">"Update beacon"</option>
|
||||
<option value="chdir">"Change directory"</option>
|
||||
<option value="ls">"List files"</option>
|
||||
<option value="install">"Install to a new binary"</option>
|
||||
<select
|
||||
name="cmd_type"
|
||||
on:change:target=move |ev| {
|
||||
set_current_cmd(ev.target().value().to_string())
|
||||
}
|
||||
prop:value=move || current_cmd.get()
|
||||
>
|
||||
{sparse_actions::actions::ACTION_BUILDERS
|
||||
.iter()
|
||||
.map(|b| view! {
|
||||
<option value=b.name()>{b.name()}</option>
|
||||
})
|
||||
.collect_view()}
|
||||
</select>
|
||||
<label class="cmd-exec">"Command"</label>
|
||||
<input class="cmd-exec" name="exec_command" />
|
||||
<label class="cmd-upload">"File to upload/exfil"</label>
|
||||
<input class="cmd-upload" name="upload_src" />
|
||||
<label class="cmd-chdir">"Directory to change to"</label>
|
||||
<input class="cmd-chdir" name="chdir_target" />
|
||||
<label class="cmd-install">"Binary to infect"</label>
|
||||
<input class="cmd-install" name="install_target" />
|
||||
<label class="cmd-download">"Target location for file"</label>
|
||||
<input class="cmd-download" name="download_path"/>
|
||||
<label class="cmd-download">"File to download/place"</label>
|
||||
<input class="cmd-download" name="download_src" type="file" />
|
||||
{move || sparse_actions::actions::ACTION_BUILDERS
|
||||
.iter()
|
||||
.find(|b| b.name() == *current_cmd.read())
|
||||
.map(|b| b
|
||||
.form_elements()
|
||||
.iter()
|
||||
.map(|(label, name, itype)| view! {
|
||||
<label>{label.to_string()}</label>
|
||||
<input
|
||||
name=name.to_string()
|
||||
type=itype.unwrap_or("text").to_string()
|
||||
/>
|
||||
})
|
||||
.collect_view())}
|
||||
<div></div>
|
||||
<input type="submit" value="Submit" disabled=move ||command_action.pending().get()/>
|
||||
</fieldset>
|
||||
|
||||
@ -52,6 +52,7 @@ pub struct CurrentBeaconInstance {
|
||||
pub operating_system: String,
|
||||
pub userent: String,
|
||||
pub hostname: String,
|
||||
pub version: sparse_actions::version::Version,
|
||||
pub last_checkin: chrono::DateTime<chrono::Utc>,
|
||||
pub config_id: Option<i64>,
|
||||
pub template_id: i64,
|
||||
@ -588,6 +589,9 @@ pub fn BeaconSidebar() -> impl IntoView {
|
||||
<div class="beacon-instance-os">
|
||||
<span>"OS: "</span> {beacon.operating_system.clone()}
|
||||
</div>
|
||||
<div class="beacon-instance-vers">
|
||||
<span>"Version: "</span> {beacon.version.to_string()}
|
||||
</div>
|
||||
{(sort_method.get() != Some(SortMethod::Category))
|
||||
.then(|| -> Vec<String> {
|
||||
let BeaconResources {
|
||||
|
||||
@ -13,6 +13,7 @@ use serde::Deserialize;
|
||||
use sqlx::sqlite::SqlitePool;
|
||||
use tokio::signal;
|
||||
|
||||
use sparse_actions::version::Version;
|
||||
use sparse_server::app::*;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
@ -362,7 +363,8 @@ async fn handle_listener_events(
|
||||
|
||||
{
|
||||
let beacons = sqlx::query!(
|
||||
"SELECT beacon_id, template_id, peer_ip, nickname, cwd, operating_system, beacon_userent, hostname, config_id FROM beacon_instance"
|
||||
r#"SELECT beacon_id, template_id, peer_ip, nickname, cwd, operating_system,
|
||||
beacon_userent, hostname, config_id, version as "version: Version" FROM beacon_instance"#
|
||||
)
|
||||
.fetch_all(&state.db)
|
||||
.await?;
|
||||
@ -407,6 +409,7 @@ async fn handle_listener_events(
|
||||
userent: b.beacon_userent,
|
||||
hostname: b.hostname,
|
||||
config_id: b.config_id,
|
||||
version: b.version,
|
||||
last_checkin: last_checkin
|
||||
.iter()
|
||||
.find(|ch| ch.beacon_id == b.beacon_id)
|
||||
@ -440,8 +443,8 @@ async fn handle_listener_events(
|
||||
}
|
||||
Ok(BeaconEvent::NewBeacon(bid)) => {
|
||||
let beacon = sqlx::query!(
|
||||
"SELECT template_id, peer_ip, nickname, cwd, operating_system, beacon_userent, hostname, config_id FROM beacon_instance
|
||||
WHERE beacon_id = ?",
|
||||
r#"SELECT template_id, peer_ip, nickname, cwd, operating_system, beacon_userent, hostname, config_id, version as "version: Version" FROM beacon_instance
|
||||
WHERE beacon_id = ?"#,
|
||||
bid
|
||||
)
|
||||
.fetch_one(&state.db)
|
||||
@ -465,6 +468,7 @@ async fn handle_listener_events(
|
||||
userent: beacon.beacon_userent,
|
||||
hostname: beacon.hostname,
|
||||
config_id: beacon.config_id,
|
||||
version: beacon.version,
|
||||
last_checkin: chrono::Utc::now(),
|
||||
category_ids: category_ids.iter().map(|r| r.category_id).collect()
|
||||
};
|
||||
|
||||
@ -11,28 +11,4 @@ main.beacons div.commands {
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.cmd-exec, .cmd-install, .cmd-upload, .cmd-download, .cmd-chdir {
|
||||
display: none;
|
||||
}
|
||||
|
||||
select[name="cmd_type"]:has(> option[value="exec"]:checked) ~ .cmd-exec {
|
||||
display: block;
|
||||
}
|
||||
|
||||
select[name="cmd_type"]:has(> option[value="upload"]:checked) ~ .cmd-upload {
|
||||
display: block;
|
||||
}
|
||||
|
||||
select[name="cmd_type"]:has(> option[value="download"]:checked) ~ .cmd-download {
|
||||
display: block;
|
||||
}
|
||||
|
||||
select[name="cmd_type"]:has(> option[value="chdir"]:checked) ~ .cmd-chdir {
|
||||
display: block;
|
||||
}
|
||||
|
||||
select[name="cmd_type"]:has(> option[value="install"]:checked) ~ .cmd-install {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,8 +10,8 @@ async-trait = "0.1.86"
|
||||
tokio = { version = "1.43.0", features = ["fs", "macros", "rt"] }
|
||||
thiserror = "2.0.11"
|
||||
|
||||
sparse-beacon = { version = "0.7.0", path = "../sparse-beacon", features = ["ring"] }
|
||||
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
|
||||
sparse-beacon = { path = "../sparse-beacon", features = ["ring"] }
|
||||
sparse-actions = { path = "../sparse-actions", features = ["beacon"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
nl-sys = { version = "0.1.0", path = "../nl-sys" }
|
||||
|
||||
@ -2,7 +2,7 @@ use std::net::Ipv4Addr;
|
||||
|
||||
use nl_sys::netlink;
|
||||
|
||||
use sparse_beacon::{
|
||||
use sparse_actions::{
|
||||
adapter::{BeaconAdapter, BeaconInterface, BeaconNetworkingInfo, BeaconRoute},
|
||||
error,
|
||||
};
|
||||
@ -13,7 +13,7 @@ pub enum LinuxAdapterError {
|
||||
Nl(#[from] nl_sys::error::Error),
|
||||
}
|
||||
|
||||
impl sparse_beacon::error::AdapterError for LinuxAdapterError {}
|
||||
impl sparse_actions::error::AdapterError for LinuxAdapterError {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LinuxAdapter;
|
||||
|
||||
@ -2,8 +2,7 @@ use std::io::SeekFrom;
|
||||
|
||||
use tokio::io::{AsyncReadExt, AsyncSeekExt};
|
||||
|
||||
use sparse_actions::payload_types::{Parameters, XOR_KEY};
|
||||
use sparse_beacon::adapter::BeaconAdapter;
|
||||
use sparse_actions::{adapter::BeaconAdapter, payload_types::{Parameters, XOR_KEY}};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
@ -16,7 +15,7 @@ mod freebsd;
|
||||
use freebsd::FreeBsdAdapter as Adapter;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), sparse_beacon::BeaconError<<Adapter as BeaconAdapter>::Error>> {
|
||||
async fn main() -> Result<(), sparse_actions::error::BeaconError<<Adapter as BeaconAdapter>::Error>> {
|
||||
#[cfg(target_os = "linux")]
|
||||
let mut binary_file = tokio::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
|
||||
@ -5,6 +5,6 @@ version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.169"
|
||||
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
|
||||
sparse-actions = { path = "../sparse-actions" }
|
||||
errno = "0.3"
|
||||
cfg-if = "1.0.0"
|
||||
|
||||
@ -8,6 +8,6 @@ errno = "0.3.10"
|
||||
hex = "0.4.3"
|
||||
libc = "0.2.169"
|
||||
rand = "0.9.0"
|
||||
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
|
||||
sparse-unix-infector = { version = "2.0.0", path = "../sparse-unix-infector" }
|
||||
sparse-actions = { path = "../sparse-actions" }
|
||||
sparse-unix-infector = { path = "../sparse-unix-infector" }
|
||||
structopt = "0.3.26"
|
||||
|
||||
@ -16,8 +16,8 @@ windows-result = "0.3.0"
|
||||
windows-strings = "0.3.0"
|
||||
winreg = "0.55"
|
||||
|
||||
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
|
||||
sparse-beacon = { version = "0.7.0", path = "../sparse-beacon", features = ["openssl"] }
|
||||
sparse-actions = { path = "../sparse-actions" }
|
||||
sparse-beacon = { path = "../sparse-beacon", features = ["openssl"] }
|
||||
|
||||
[features]
|
||||
service = []
|
||||
|
||||
@ -5,4 +5,4 @@ version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
errno = "0.3.10"
|
||||
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
|
||||
sparse-actions = { path = "../sparse-actions" }
|
||||
|
||||
@ -7,9 +7,10 @@ version.workspace = true
|
||||
errno = "0.3.10"
|
||||
hex = "0.4.3"
|
||||
rand = "0.9.0"
|
||||
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
|
||||
sparse-windows-infector = { version = "2.0.0", path = "../sparse-windows-infector" }
|
||||
structopt = "0.3.26"
|
||||
windows = { version = "0.59.0", features = ["Win32_System_Services"] }
|
||||
windows-strings = "0.3.0"
|
||||
winreg = "0.55.0"
|
||||
|
||||
sparse-actions = { path = "../sparse-actions" }
|
||||
sparse-windows-infector = { path = "../sparse-windows-infector" }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user