Some re-working, adding security and fix handling shadowserver
This commit is contained in:
@ -7,6 +7,7 @@ use cidr::IpCidr;
|
|||||||
use event_bus::{EventBusSubscriber, EventBusWriter, EventBusWriterEvent};
|
use event_bus::{EventBusSubscriber, EventBusWriter, EventBusWriterEvent};
|
||||||
use rocket::{
|
use rocket::{
|
||||||
fairing::AdHoc,
|
fairing::AdHoc,
|
||||||
|
form::FromFormField,
|
||||||
futures::SinkExt,
|
futures::SinkExt,
|
||||||
http::Status,
|
http::Status,
|
||||||
request::{FromParam, FromRequest, Outcome, Request},
|
request::{FromParam, FromRequest, Outcome, Request},
|
||||||
@ -41,6 +42,7 @@ use worker::detection::{detect_scanner, get_dns_client, validate_ip, Scanners};
|
|||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
env, fmt,
|
env, fmt,
|
||||||
|
net::IpAddr,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
};
|
};
|
||||||
use std::{io::Write, net::SocketAddr};
|
use std::{io::Write, net::SocketAddr};
|
||||||
@ -111,6 +113,30 @@ impl IsStatic for Scanners {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Clone)]
|
||||||
|
struct SafeIpAddr {
|
||||||
|
pub addr: IpAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromFormField<'_> for SafeIpAddr {
|
||||||
|
fn from_value(field: rocket::form::ValueField<'_>) -> rocket::form::Result<'_, Self> {
|
||||||
|
let ip = field.value;
|
||||||
|
let query_address = IpAddr::from_str(ip);
|
||||||
|
match query_address {
|
||||||
|
Ok(ip) => {
|
||||||
|
if !validate_ip(ip) {
|
||||||
|
return Err(rocket::form::Error::validation(format!(
|
||||||
|
"Invalid IP address: {ip}"
|
||||||
|
))
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
Ok(SafeIpAddr { addr: ip })
|
||||||
|
}
|
||||||
|
Err(err) => Err(rocket::form::Error::validation(format!("Invalid IP: {err}")).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FromParam<'_> for Scanners {
|
impl FromParam<'_> for Scanners {
|
||||||
type Error = String;
|
type Error = String;
|
||||||
|
|
||||||
@ -163,7 +189,7 @@ impl fmt::Display for Scanners {
|
|||||||
Self::Binaryedge => "binaryedge",
|
Self::Binaryedge => "binaryedge",
|
||||||
Self::Censys => "censys",
|
Self::Censys => "censys",
|
||||||
Self::InternetMeasurement => "internet-measurement.com",
|
Self::InternetMeasurement => "internet-measurement.com",
|
||||||
Self::Shadowserver => "shadowserver.txt",
|
Self::Shadowserver => "shadowserver",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -176,7 +202,7 @@ impl serialize::ToSql<Text, Mysql> for Scanners {
|
|||||||
Self::Binaryedge => out.write_all(b"binaryedge")?,
|
Self::Binaryedge => out.write_all(b"binaryedge")?,
|
||||||
Self::Censys => out.write_all(b"censys")?,
|
Self::Censys => out.write_all(b"censys")?,
|
||||||
Self::InternetMeasurement => out.write_all(b"internet-measurement.com")?,
|
Self::InternetMeasurement => out.write_all(b"internet-measurement.com")?,
|
||||||
Self::Shadowserver => out.write_all(b"shadowserver.txt")?,
|
Self::Shadowserver => out.write_all(b"shadowserver")?,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(IsNull::No)
|
Ok(IsNull::No)
|
||||||
@ -203,14 +229,15 @@ impl TryInto<Scanners> for &str {
|
|||||||
"stretchoid" => Ok(Scanners::Stretchoid),
|
"stretchoid" => Ok(Scanners::Stretchoid),
|
||||||
"binaryedge" => Ok(Scanners::Binaryedge),
|
"binaryedge" => Ok(Scanners::Binaryedge),
|
||||||
"internet-measurement.com" => Ok(Scanners::InternetMeasurement),
|
"internet-measurement.com" => Ok(Scanners::InternetMeasurement),
|
||||||
|
"shadowserver" => Ok(Scanners::Shadowserver),
|
||||||
value => Err(format!("Invalid value: {value}")),
|
value => Err(format!("Invalid value: {value}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_ip(mut conn: DbConn, ip: String) -> Result<Scanner, Option<ResolvedResult>> {
|
async fn handle_ip(
|
||||||
let query_address = ip.parse().expect("To parse");
|
query_address: IpAddr,
|
||||||
|
) -> Result<(IpAddr, Option<Scanners>, ResolvedResult), ()> {
|
||||||
let ptr_result: Result<ResolvedResult, ()> = std::thread::spawn(move || {
|
let ptr_result: Result<ResolvedResult, ()> = std::thread::spawn(move || {
|
||||||
let client = get_dns_client();
|
let client = get_dns_client();
|
||||||
let ptr_result: ResolvedResult = if let Ok(res) = get_ptr(query_address, client) {
|
let ptr_result: ResolvedResult = if let Ok(res) = get_ptr(query_address, client) {
|
||||||
@ -223,27 +250,19 @@ async fn handle_ip(mut conn: DbConn, ip: String) -> Result<Scanner, Option<Resol
|
|||||||
.join()
|
.join()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if ptr_result.is_err() {
|
match ptr_result {
|
||||||
return Err(None);
|
Ok(result) => match detect_scanner(&result) {
|
||||||
}
|
Ok(Some(scanner_type)) => {
|
||||||
|
if !validate_ip(query_address) {
|
||||||
let result = ptr_result.unwrap();
|
error!("Invalid IP address: {query_address}");
|
||||||
|
return Err(());
|
||||||
match detect_scanner(&result) {
|
}
|
||||||
Ok(Some(scanner_type)) => {
|
Ok((query_address, Some(scanner_type), result))
|
||||||
if !validate_ip(query_address) {
|
|
||||||
error!("Invalid IP address: {ip}");
|
|
||||||
return Err(None);
|
|
||||||
}
|
}
|
||||||
match Scanner::find_or_new(query_address, scanner_type, result.result, &mut conn).await
|
Ok(None) => Ok((query_address, None, result)),
|
||||||
{
|
Err(err) => Err(err),
|
||||||
Ok(scanner) => Ok(scanner),
|
},
|
||||||
Err(_) => Err(None),
|
Err(err) => Err(err),
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None) => Err(None),
|
|
||||||
|
|
||||||
Err(_) => Err(Some(result)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,11 +301,15 @@ enum MultiReply {
|
|||||||
Error(ServerError),
|
Error(ServerError),
|
||||||
#[response(status = 422)]
|
#[response(status = 422)]
|
||||||
FormError(PlainText),
|
FormError(PlainText),
|
||||||
|
#[response(status = 422)]
|
||||||
|
HtmlFormError(HtmlContents),
|
||||||
#[response(status = 404)]
|
#[response(status = 404)]
|
||||||
NotFound(String),
|
NotFound(String),
|
||||||
#[response(status = 200)]
|
#[response(status = 200)]
|
||||||
Content(HtmlContents),
|
Content(HtmlContents),
|
||||||
#[response(status = 200)]
|
#[response(status = 200)]
|
||||||
|
TextContent(PlainText),
|
||||||
|
#[response(status = 200)]
|
||||||
FileContents(NamedFile),
|
FileContents(NamedFile),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,50 +368,89 @@ async fn handle_scan(
|
|||||||
MultiReply::Content(HtmlContents(format!("New task added: {} !", task_group_id)))
|
MultiReply::Content(HtmlContents(format!("New task added: {} !", task_group_id)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromForm, Serialize, Deserialize)]
|
#[derive(FromForm, Deserialize)]
|
||||||
pub struct ReportParams {
|
pub struct ReportParams {
|
||||||
ip: String,
|
ip: SafeIpAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reply_contents_for_scanner_found(scanner: Scanner) -> HtmlContents {
|
||||||
|
HtmlContents(match scanner.scanner_name {
|
||||||
|
Scanners::Binaryedge => match scanner.last_checked_at {
|
||||||
|
Some(date) => format!(
|
||||||
|
"Reported a binaryedge ninja! <b>{}</b> known as {} since {date}.",
|
||||||
|
scanner.ip,
|
||||||
|
scanner.ip_ptr.unwrap_or("".to_string())
|
||||||
|
),
|
||||||
|
None => format!(
|
||||||
|
"Reported a binaryedge ninja! <b>{}</b> known as {}.",
|
||||||
|
scanner.ip,
|
||||||
|
scanner.ip_ptr.unwrap_or("".to_string())
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Scanners::Stretchoid => match scanner.last_checked_at {
|
||||||
|
Some(date) => format!(
|
||||||
|
"Reported a stretchoid agent! <b>{}</b> known as {} since {date}.",
|
||||||
|
scanner.ip,
|
||||||
|
scanner.ip_ptr.unwrap_or("".to_string())
|
||||||
|
),
|
||||||
|
None => format!(
|
||||||
|
"Reported a stretchoid agent! <b>{}</b> known as {}.",
|
||||||
|
scanner.ip,
|
||||||
|
scanner.ip_ptr.unwrap_or("".to_string())
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Scanners::Shadowserver => match scanner.last_checked_at {
|
||||||
|
Some(date) => format!(
|
||||||
|
"Reported a cloudy shadowserver ! <b>{}</b> known as {} since {date}.",
|
||||||
|
scanner.ip,
|
||||||
|
scanner.ip_ptr.unwrap_or("".to_string())
|
||||||
|
),
|
||||||
|
None => format!(
|
||||||
|
"Reported a cloudy shadowserver ! <b>{}</b> known as {}.",
|
||||||
|
scanner.ip,
|
||||||
|
scanner.ip_ptr.unwrap_or("".to_string())
|
||||||
|
),
|
||||||
|
},
|
||||||
|
_ => format!("Not supported"),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/report", data = "<form>")]
|
#[post("/report", data = "<form>")]
|
||||||
async fn handle_report(db: DbConn, form: Form<ReportParams>) -> HtmlContents {
|
async fn handle_report(mut db: DbConn, form: Form<ReportParams>) -> MultiReply {
|
||||||
match handle_ip(db, form.ip.clone()).await {
|
match handle_ip(form.ip.addr).await {
|
||||||
Ok(scanner) => HtmlContents(match scanner.scanner_name {
|
Ok((query_address, scanner_type, result)) => match scanner_type {
|
||||||
Scanners::Binaryedge => match scanner.last_checked_at {
|
Some(scanner_type) => match Scanner::find_or_new(
|
||||||
Some(date) => format!(
|
query_address,
|
||||||
"Reported a binaryedge ninja! <b>{}</b> known as {} since {date}.",
|
scanner_type,
|
||||||
scanner.ip,
|
result.result.clone(),
|
||||||
scanner.ip_ptr.unwrap_or("".to_string())
|
&mut db,
|
||||||
),
|
)
|
||||||
None => format!(
|
.await
|
||||||
"Reported a binaryedge ninja! <b>{}</b> known as {}.",
|
{
|
||||||
scanner.ip,
|
Ok(scanner) => MultiReply::Content(reply_contents_for_scanner_found(scanner)),
|
||||||
scanner.ip_ptr.unwrap_or("".to_string())
|
Err(err) => MultiReply::Error(ServerError(format!(
|
||||||
),
|
"The IP {} resolved as {} could not be saved, server error: {err}.",
|
||||||
|
form.ip.addr,
|
||||||
|
match result.result {
|
||||||
|
Some(res) => res.to_string(),
|
||||||
|
None => "No value".to_string(),
|
||||||
|
}
|
||||||
|
))),
|
||||||
},
|
},
|
||||||
Scanners::Stretchoid => match scanner.last_checked_at {
|
None => MultiReply::HtmlFormError(HtmlContents(format!(
|
||||||
Some(date) => format!(
|
"The IP <b>{}</a> resolved as {:?} did not match known scanners patterns.",
|
||||||
"Reported a stretchoid agent! <b>{}</b> known as {} since {date}.",
|
form.ip.addr,
|
||||||
scanner.ip,
|
match result.result {
|
||||||
scanner.ip_ptr.unwrap_or("".to_string())
|
Some(res) => res.to_string(),
|
||||||
),
|
None => "No value".to_string(),
|
||||||
None => format!(
|
}
|
||||||
"Reported a stretchoid agent! <b>{}</b> known as {}.",
|
))),
|
||||||
scanner.ip,
|
},
|
||||||
scanner.ip_ptr.unwrap_or("".to_string())
|
|
||||||
),
|
|
||||||
},
|
|
||||||
_ => format!("Not supported"),
|
|
||||||
}),
|
|
||||||
|
|
||||||
Err(ptr_result) => HtmlContents(format!(
|
Err(_) => MultiReply::Error(ServerError(format!(
|
||||||
"The IP <b>{}</a> resolved as {:?} did not match known scanners patterns.",
|
"The IP <b>{}</a> did encounter en error at resolve time.",
|
||||||
form.ip,
|
form.ip.addr
|
||||||
match ptr_result {
|
))),
|
||||||
Some(res) => res.result,
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,7 +509,9 @@ async fn handle_list_scanners(
|
|||||||
path.push(static_data_dir);
|
path.push(static_data_dir);
|
||||||
path.push("scanners");
|
path.push("scanners");
|
||||||
path.push(match scanner_name {
|
path.push(match scanner_name {
|
||||||
Scanners::Stretchoid | Scanners::Binaryedge | Scanners::Shadowserver => panic!("This should not happen"),
|
Scanners::Stretchoid | Scanners::Binaryedge | Scanners::Shadowserver => {
|
||||||
|
panic!("This should not happen")
|
||||||
|
}
|
||||||
Scanners::Censys => "censys.txt".to_string(),
|
Scanners::Censys => "censys.txt".to_string(),
|
||||||
Scanners::InternetMeasurement => "internet-measurement.com.txt".to_string(),
|
Scanners::InternetMeasurement => "internet-measurement.com.txt".to_string(),
|
||||||
});
|
});
|
||||||
@ -464,7 +528,7 @@ async fn handle_list_scanners(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(scanners) = scanners_list {
|
if let Ok(scanners) = scanners_list {
|
||||||
MultiReply::Content(HtmlContents(scanners.join("\n")))
|
MultiReply::TextContent(PlainText(scanners.join("\n")))
|
||||||
} else {
|
} else {
|
||||||
MultiReply::Error(ServerError("Unable to list scanners".to_string()))
|
MultiReply::Error(ServerError("Unable to list scanners".to_string()))
|
||||||
}
|
}
|
||||||
@ -679,3 +743,35 @@ async fn main() -> Result<(), rocket::Error> {
|
|||||||
.await;
|
.await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use hickory_resolver::{
|
||||||
|
config::{NameServerConfigGroup, ResolverConfig, ResolverOpts},
|
||||||
|
Name, Resolver,
|
||||||
|
};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_ptr() {
|
||||||
|
let server = NameServerConfigGroup::google();
|
||||||
|
let config = ResolverConfig::from_parts(None, vec![], server);
|
||||||
|
let mut options = ResolverOpts::default();
|
||||||
|
options.timeout = Duration::from_secs(5);
|
||||||
|
options.attempts = 1; // One try
|
||||||
|
|
||||||
|
let resolver = Resolver::new(config, options).unwrap();
|
||||||
|
|
||||||
|
let query_address = "8.8.8.8".parse().expect("To parse");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
get_ptr(query_address, resolver).unwrap(),
|
||||||
|
ResolvedResult {
|
||||||
|
query: Name::from_str_relaxed("8.8.8.8.in-addr.arpa.").unwrap(),
|
||||||
|
result: Some(Name::from_str_relaxed("dns.google.").unwrap()),
|
||||||
|
error: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -28,12 +28,12 @@ impl Scanner {
|
|||||||
scanner_name: Scanners,
|
scanner_name: Scanners,
|
||||||
ptr: Option<Name>,
|
ptr: Option<Name>,
|
||||||
conn: &mut DbConn,
|
conn: &mut DbConn,
|
||||||
) -> Result<Scanner, ()> {
|
) -> Result<Scanner, DieselError> {
|
||||||
let ip_type = if query_address.is_ipv6() { 6 } else { 4 };
|
let ip_type = if query_address.is_ipv6() { 6 } else { 4 };
|
||||||
let scanner_row_result = Scanner::find(query_address.to_string(), ip_type, conn).await;
|
let scanner_row_result = Scanner::find(query_address.to_string(), ip_type, conn).await;
|
||||||
let scanner_row = match scanner_row_result {
|
let scanner_row = match scanner_row_result {
|
||||||
Ok(scanner_row) => scanner_row,
|
Ok(scanner_row) => scanner_row,
|
||||||
Err(_) => return Err(()),
|
Err(err) => return Err(err),
|
||||||
};
|
};
|
||||||
|
|
||||||
let scanner = if let Some(mut scanner) = scanner_row {
|
let scanner = if let Some(mut scanner) = scanner_row {
|
||||||
@ -58,7 +58,7 @@ impl Scanner {
|
|||||||
};
|
};
|
||||||
match scanner.save(conn).await {
|
match scanner.save(conn).await {
|
||||||
Ok(scanner) => Ok(scanner),
|
Ok(scanner) => Ok(scanner),
|
||||||
Err(_) => Err(()),
|
Err(err) => Err(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ use hickory_resolver::{Name, Resolver};
|
|||||||
|
|
||||||
use crate::worker::ip_addr::is_global_hardcoded;
|
use crate::worker::ip_addr::is_global_hardcoded;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, FromSqlRow)]
|
#[derive(Debug, Clone, Copy, FromSqlRow, PartialEq)]
|
||||||
pub enum Scanners {
|
pub enum Scanners {
|
||||||
Stretchoid,
|
Stretchoid,
|
||||||
Binaryedge,
|
Binaryedge,
|
||||||
@ -77,3 +77,34 @@ pub fn detect_scanner_from_name(name: &Name) -> Result<Option<Scanners>, ()> {
|
|||||||
&_ => Ok(None),
|
&_ => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_detect_scanner_from_name() {
|
||||||
|
let ptr = Name::from_str("scan-47e.shadowserver.org.").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
detect_scanner_from_name(&ptr).unwrap(),
|
||||||
|
Some(Scanners::Shadowserver)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_detect_scanner() {
|
||||||
|
let cname_ptr = Name::from_str("111.0-24.197.62.64.in-addr.arpa.").unwrap();
|
||||||
|
let ptr = Name::from_str("scan-47e.shadowserver.org.").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
detect_scanner(&ResolvedResult {
|
||||||
|
query: cname_ptr,
|
||||||
|
result: Some(ptr),
|
||||||
|
error: None
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
Some(Scanners::Shadowserver)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user