From 889bc86c50af91396f79fdf7721cf4418dee8ddd Mon Sep 17 00:00:00 2001 From: pyro Date: Wed, 17 Jun 2026 12:01:25 -0500 Subject: [PATCH] wrote the tool --- .gitignore | 5 ++++ Cargo.toml | 9 +++++++ src/main.rs | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore index 0b188bc..e2aab4f 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,8 @@ target/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + + +# Added by cargo + +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e09b3fe --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "own-users" +version = "0.1.0" +edition = "2024" + +[dependencies] +clap = { version = "4.6.1", features = ["derive"] } +neo4rs = "0.8.0" +tokio = { version = "1.52.3", features = ["full"] } diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..de9ff76 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,75 @@ +use clap::Parser; +use neo4rs::{Graph, query}; +use std::fs::File; +use std::io::{self, BufRead}; +use std::path::Path; +use tokio; + +#[derive(Parser, Debug)] +#[command( + name = "BloodHound Owned Marker", + version = "1.0", + about = "Marks a list of usernames as owned inside a BloodHound Neo4j database" +)] +struct Args { + #[arg(short, long)] + input: String, + + #[arg(short, long, default_value = "bolt://localhost:7687")] + address: String, + + #[arg(short, long, default_value = "neo4j")] + user: String, + + #[arg(short, long, default_value = "neo4j")] + pass: String, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let args = Args::parse(); + if !Path::new(&args.input).exists() { + eprintln!("Error: Input file '{}' does not exist.", args.input); + std::process::exit(1); + } + println!("Connecting to Neo4j database at {}...", args.address); + let graph = match Graph::new(&args.address, &args.user, &args.pass).await { + Ok(g) => g, + Err(e) => { + eprintln!("Failed to initialize Neo4j connection configuration: {}", e); + std::process::exit(1); + } + }; + if let Err(e) = graph.run(query("RETURN 1")).await { + eprintln!( + "Database connection error: {}. Check your URI and credentials.", + e + ); + std::process::exit(1); + } + println!("Connected successfully!"); + let file = File::open(&args.input)?; + let reader = io::BufReader::new(file); + let mut count = 0; + for line in reader.lines() { + let raw_user = line?; + let username = raw_user.trim(); + if username.is_empty() { + continue; + } + let cypher_query = + query("MATCH (u:User) WHERE toUpper(u.name) = toUpper($name) SET u.owned = true") + .param("name", username); + match graph.run(cypher_query).await { + Ok(_) => { + println!("[-] Marked as owned: {}", username); + count += 1; + } + Err(e) => { + eprintln!("[!] Error marking user '{}': {}", username, e); + } + } + } + println!("\nTask completed. Total users marked as owned: {}", count); + Ok(()) +}