From 642ae6e714e3f01de74f5651b4af083eb608d873 Mon Sep 17 00:00:00 2001 From: William Desportes Date: Sat, 22 Jun 2024 20:15:40 +0200 Subject: [PATCH] First working version with reporting --- snow-scanner/.gitignore | 3 + snow-scanner/Cargo.toml | 4 + snow-scanner/src/main.rs | 187 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 190 insertions(+), 4 deletions(-) diff --git a/snow-scanner/.gitignore b/snow-scanner/.gitignore index 6985cf1..16dfb9f 100644 --- a/snow-scanner/.gitignore +++ b/snow-scanner/.gitignore @@ -12,3 +12,6 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb + +# Database files +*.sqlite* diff --git a/snow-scanner/Cargo.toml b/snow-scanner/Cargo.toml index f5e159d..96f8e67 100644 --- a/snow-scanner/Cargo.toml +++ b/snow-scanner/Cargo.toml @@ -34,3 +34,7 @@ rouille = "3.6.2" hmac = "0.12.1" sha2 = "0.10.8" hex = "0.4.3" +rusqlite = { version = "0.31.0", features = ["bundled"] } +dns-ptr-resolver = "1.2.0" +hickory-client = { version = "0.24.1", default-features = false } +chrono = "0.4.38" diff --git a/snow-scanner/src/main.rs b/snow-scanner/src/main.rs index abf3028..4d9493b 100644 --- a/snow-scanner/src/main.rs +++ b/snow-scanner/src/main.rs @@ -1,15 +1,103 @@ #[macro_use] extern crate rouille; -use rouille::Response; -use std::io; - +use chrono::Utc; use hmac::{Hmac, Mac}; +use rouille::Response; +use rouille::ResponseBody; +use rusqlite::types::ToSqlOutput; +use rusqlite::{named_params, Connection, OpenFlags, Result, ToSql}; use sha2::Sha256; +use std::fmt; +use std::io; +use std::str::FromStr; + +use hickory_client::client::SyncClient; +use hickory_client::rr::Name; +use hickory_client::tcp::TcpClientConnection; + +use dns_ptr_resolver::{get_ptr, ResolvedResult}; // Create alias for HMAC-SHA256 type HmacSha256 = Hmac; +#[derive(Debug, Clone)] +enum Scanners { + Strechoid, + Binaryedge, +} + +impl fmt::Display for Scanners { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + Self::Strechoid => "strechoid", + Self::Binaryedge => "binaryedge", + } + ) + } +} +impl ToSql for Scanners { + /// Converts Rust value to SQLite value + fn to_sql(&self) -> Result> { + match self { + Self::Strechoid => Ok("strechoid".into()), + Self::Binaryedge => Ok("binaryedge".into()), + } + } +} + +#[derive(Debug)] +struct Scanner { + ip: String, + ip_type: u8, + scanner_name: Scanners, + created_at: String, + updated_at: String, + last_seen_at: String, + last_checked_at: String, +} + +fn save_scanner(conn: &Connection, scanner: &Scanner) -> Result<(), ()> { + match conn.execute( + "INSERT INTO scanners (ip, ip_type, scanner_name, created_at, updated_at, last_seen_at, last_checked_at) + VALUES (:ip, :ip_type, :scanner_name, :created_at, :updated_at, :last_seen_at, :last_checked_at) + ON CONFLICT(ip, ip_type) DO UPDATE SET updated_at = :updated_at, last_seen_at = :last_seen_at, last_checked_at = :last_checked_at;", + named_params! { + ":ip": &scanner.ip, + ":ip_type": &scanner.ip_type, + ":scanner_name": &scanner.scanner_name, + ":created_at": &scanner.created_at, + ":updated_at": &scanner.updated_at, + ":last_seen_at": &scanner.last_seen_at, + ":last_checked_at": &scanner.last_checked_at + }, + ) { + Ok(_) => Ok(()), + Err(_) => Err(()), + } +} + +fn detect_scanner(ptr_result: &ResolvedResult) -> Result { + match ptr_result.result { + Some(ref x) + if x.trim_to(2) + .eq_case(&Name::from_str("binaryedge.ninja.").expect("Should parse")) => + { + Ok(Scanners::Binaryedge) + }, + Some(ref x) + if x.trim_to(2) + .eq_case(&Name::from_str("stretchoid.com").expect("Should parse")) => + { + Ok(Scanners::Strechoid) + } + _ => Err(()), + } +} + // The HTML document of the home page. static FORM: &str = r#" @@ -21,14 +109,47 @@ static FORM: &str = r#"

+
+

+

+
"#; -fn main() { +fn main() -> Result<()> { println!("Now listening on localhost:8000"); + let server = "1.1.1.1:53".parse().expect("To parse"); + let conn = TcpClientConnection::with_timeout(server, std::time::Duration::new(5, 0)).unwrap(); + rouille::start_server("localhost:8000", move |request| { + let client = SyncClient::new(conn); + let path = "./snow-scanner.sqlite"; + let conn = Connection::open_with_flags( + path, + OpenFlags::SQLITE_OPEN_READ_WRITE + | OpenFlags::SQLITE_OPEN_CREATE + | OpenFlags::SQLITE_OPEN_FULL_MUTEX, + ) + .unwrap(); + conn.execute( + "CREATE TABLE IF NOT EXISTS scanners ( + ip VARCHAR(255), + ip_type TINYINT(1), + scanner_name VARCHAR(255), + created_at DATETIME, + updated_at DATETIME, + last_seen_at DATETIME, + last_checked_at DATETIME, + PRIMARY KEY (ip, ip_type) + )", + (), // empty list of parameters. + ) + .unwrap(); + conn.pragma_update_and_check(None, "journal_mode", &"WAL", |_| Ok(())) + .unwrap(); + router!(request, (GET) (/) => { rouille::Response::html(FORM) @@ -38,6 +159,49 @@ fn main() { rouille::Response::text("pong") }, + (POST) (/report) => { + let data = try_or_400!(post_input!(request, { + ip: String, + })); + + // We just print what was received on stdout. Of course in a real application + // you probably want to process the data, eg. store it in a database. + println!("Received data: {:?}", data); + let query_address = data.ip.parse().expect("To parse"); + + let ptr_result = get_ptr(query_address, client).unwrap(); + + match detect_scanner(&ptr_result) { + Ok(scanner_name) => { + let ip_type = if data.ip.contains(':') {6} else {4}; + let scanner = Scanner { + ip: data.ip, + ip_type: ip_type, + scanner_name: scanner_name.clone(), + created_at: Utc::now().to_string(), + updated_at: Utc::now().to_string(), + last_seen_at: Utc::now().to_string(), + last_checked_at: Utc::now().to_string(), + }; + save_scanner(&conn, &scanner).unwrap(); + rouille::Response::html( + match scanner_name { + Scanners::Binaryedge => + format!("Reported an escaped ninja! {} {:?}.", scanner.ip, ptr_result.result.unwrap()), + Scanners::Strechoid => + format!("Reported a stretchoid agent! {} {:?}.", scanner.ip, ptr_result.result.unwrap()) + } + ) + + }, + + Err(_) => + rouille::Response::html( + format!("The IP {} resolved as {:?} did not match known scanners patterns.", data.ip, ptr_result.result) + ) + } + }, + (POST) (/register) => { let data = try_or_400!(post_input!(request, { email: String, @@ -62,6 +226,21 @@ fn main() { rouille::Response::html(format!("Success! {}.", hex::encode(code_bytes))) }, + (GET) (/scanners/{scanner_name: String}) => { + let mut stmt = conn.prepare("SELECT ip FROM scanners WHERE scanner_name = :scanner_name ORDER BY ip_type, created_at").unwrap(); + let mut rows = stmt.query(named_params! { ":scanner_name": scanner_name }).unwrap(); + let mut ips: Vec = vec!(); + while let Some(row) = rows.next().unwrap() { + ips.push(row.get(0).unwrap()); + } + + Response { + status_code: 200, + headers: vec![("Content-Type".into(), "text/plain; charset=utf-8".into())], + data: ResponseBody::from_string(ips.join("\n")), + upgrade: None, + } + }, (GET) (/{api_key: String}/scanners/{scanner_name: String}) => { let mut mac = HmacSha256::new_from_slice(b"my secret and secure key") .expect("HMAC can take key of any size");