Validate IP addresses before insert
This commit is contained in:
@ -37,6 +37,12 @@ members = [
|
|||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
|
||||||
|
# Enable unstable features, requires nightly
|
||||||
|
# Currently only used to enable rusts official ip support
|
||||||
|
unstable = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { git = "https://github.com/rwf2/Rocket/", rev = "3bf9ef02d6e803fe9f753777f5a829dda6d2453d"}
|
rocket = { git = "https://github.com/rwf2/Rocket/", rev = "3bf9ef02d6e803fe9f753777f5a829dda6d2453d"}
|
||||||
rocket_ws = { git = "https://github.com/rwf2/Rocket/", rev = "3bf9ef02d6e803fe9f753777f5a829dda6d2453d"}
|
rocket_ws = { git = "https://github.com/rwf2/Rocket/", rev = "3bf9ef02d6e803fe9f753777f5a829dda6d2453d"}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
use std::{net::IpAddr, str::FromStr};
|
use std::{net::IpAddr, str::FromStr};
|
||||||
|
|
||||||
use crate::{worker::detection::detect_scanner_from_name, DbConnection, SnowDb};
|
use crate::{
|
||||||
|
worker::detection::{detect_scanner_from_name, validate_ip},
|
||||||
|
DbConnection, SnowDb,
|
||||||
|
};
|
||||||
use hickory_resolver::Name;
|
use hickory_resolver::Name;
|
||||||
use rocket::futures::channel::mpsc as rocket_mpsc;
|
use rocket::futures::channel::mpsc as rocket_mpsc;
|
||||||
use rocket::futures::StreamExt;
|
use rocket::futures::StreamExt;
|
||||||
@ -49,12 +52,15 @@ impl EventBus {
|
|||||||
}
|
}
|
||||||
match event {
|
match event {
|
||||||
EventBusWriterEvent::ScannerFoundResponse { name, address } => {
|
EventBusWriterEvent::ScannerFoundResponse { name, address } => {
|
||||||
|
let ip: IpAddr = address.into();
|
||||||
|
if !validate_ip(ip) {
|
||||||
|
error!("Invalid IP address: {ip}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
let name = Name::from_str(name.as_str()).unwrap();
|
let name = Name::from_str(name.as_str()).unwrap();
|
||||||
match detect_scanner_from_name(&name) {
|
match detect_scanner_from_name(&name) {
|
||||||
Ok(Some(scanner_type)) => {
|
Ok(Some(scanner_type)) => {
|
||||||
match Scanner::find_or_new(address.into(), scanner_type, Some(name), db)
|
match Scanner::find_or_new(ip, scanner_type, Some(name), db).await {
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(scanner) => {
|
Ok(scanner) => {
|
||||||
let _ = scanner.save(db).await;
|
let _ = scanner.save(db).await;
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ use rocket_db_pools::Database;
|
|||||||
|
|
||||||
use rocket_ws::WebSocket;
|
use rocket_ws::WebSocket;
|
||||||
use server::Server;
|
use server::Server;
|
||||||
use worker::detection::{detect_scanner, get_dns_client, Scanners};
|
use worker::detection::{detect_scanner, get_dns_client, validate_ip, Scanners};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
env, fmt,
|
env, fmt,
|
||||||
@ -209,6 +209,10 @@ async fn handle_ip(mut conn: DbConn, ip: String) -> Result<Scanner, Option<Resol
|
|||||||
|
|
||||||
match detect_scanner(&result) {
|
match detect_scanner(&result) {
|
||||||
Ok(Some(scanner_type)) => {
|
Ok(Some(scanner_type)) => {
|
||||||
|
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
|
match Scanner::find_or_new(query_address, scanner_type, result.result, &mut conn).await
|
||||||
{
|
{
|
||||||
Ok(scanner) => Ok(scanner),
|
Ok(scanner) => Ok(scanner),
|
||||||
|
@ -8,6 +8,8 @@ use dns_ptr_resolver::ResolvedResult;
|
|||||||
use hickory_resolver::config::{NameServerConfigGroup, ResolverConfig, ResolverOpts};
|
use hickory_resolver::config::{NameServerConfigGroup, ResolverConfig, ResolverOpts};
|
||||||
use hickory_resolver::{Name, Resolver};
|
use hickory_resolver::{Name, Resolver};
|
||||||
|
|
||||||
|
use crate::worker::ip_addr::is_global_hardcoded;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, FromSqlRow)]
|
#[derive(Debug, Clone, Copy, FromSqlRow)]
|
||||||
pub enum Scanners {
|
pub enum Scanners {
|
||||||
Stretchoid,
|
Stretchoid,
|
||||||
@ -33,6 +35,14 @@ pub fn get_dns_client() -> Resolver {
|
|||||||
Resolver::new(config, options).unwrap()
|
Resolver::new(config, options).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn validate_ip(ip: IpAddr) -> bool {
|
||||||
|
// unspecified => 0.0.0.0
|
||||||
|
if ip.is_loopback() || ip.is_multicast() || ip.is_unspecified() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return is_global_hardcoded(ip);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn detect_scanner(ptr_result: &ResolvedResult) -> Result<Option<Scanners>, ()> {
|
pub fn detect_scanner(ptr_result: &ResolvedResult) -> Result<Option<Scanners>, ()> {
|
||||||
match &ptr_result.result {
|
match &ptr_result.result {
|
||||||
Some(name) => detect_scanner_from_name(&name),
|
Some(name) => detect_scanner_from_name(&name),
|
||||||
|
126
snow-scanner/src/worker/ip_addr.rs
Normal file
126
snow-scanner/src/worker/ip_addr.rs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
//
|
||||||
|
// Port of the official Rust implementation
|
||||||
|
// Source: https://github.com/dani-garcia/vaultwarden/blob/1.32.1/src/util.rs
|
||||||
|
//
|
||||||
|
|
||||||
|
/// TODO: This is extracted from IpAddr::is_global, which is unstable:
|
||||||
|
/// https://doc.rust-lang.org/nightly/std/net/enum.IpAddr.html#method.is_global
|
||||||
|
/// Remove once https://github.com/rust-lang/rust/issues/27709 is merged
|
||||||
|
#[allow(clippy::nonminimal_bool)]
|
||||||
|
#[cfg(any(not(feature = "unstable"), test))]
|
||||||
|
pub fn is_global_hardcoded(ip: std::net::IpAddr) -> bool {
|
||||||
|
match ip {
|
||||||
|
std::net::IpAddr::V4(ip) => {
|
||||||
|
!(ip.octets()[0] == 0 // "This network"
|
||||||
|
|| ip.is_private()
|
||||||
|
|| (ip.octets()[0] == 100 && (ip.octets()[1] & 0b1100_0000 == 0b0100_0000)) //ip.is_shared()
|
||||||
|
|| ip.is_loopback()
|
||||||
|
|| ip.is_link_local()
|
||||||
|
// addresses reserved for future protocols (`192.0.0.0/24`)
|
||||||
|
||(ip.octets()[0] == 192 && ip.octets()[1] == 0 && ip.octets()[2] == 0)
|
||||||
|
|| ip.is_documentation()
|
||||||
|
|| (ip.octets()[0] == 198 && (ip.octets()[1] & 0xfe) == 18) // ip.is_benchmarking()
|
||||||
|
|| (ip.octets()[0] & 240 == 240 && !ip.is_broadcast()) //ip.is_reserved()
|
||||||
|
|| ip.is_broadcast())
|
||||||
|
}
|
||||||
|
std::net::IpAddr::V6(ip) => {
|
||||||
|
!(ip.is_unspecified()
|
||||||
|
|| ip.is_loopback()
|
||||||
|
// IPv4-mapped Address (`::ffff:0:0/96`)
|
||||||
|
|| matches!(ip.segments(), [0, 0, 0, 0, 0, 0xffff, _, _])
|
||||||
|
// IPv4-IPv6 Translat. (`64:ff9b:1::/48`)
|
||||||
|
|| matches!(ip.segments(), [0x64, 0xff9b, 1, _, _, _, _, _])
|
||||||
|
// Discard-Only Address Block (`100::/64`)
|
||||||
|
|| matches!(ip.segments(), [0x100, 0, 0, 0, _, _, _, _])
|
||||||
|
// IETF Protocol Assignments (`2001::/23`)
|
||||||
|
|| (matches!(ip.segments(), [0x2001, b, _, _, _, _, _, _] if b < 0x200)
|
||||||
|
&& !(
|
||||||
|
// Port Control Protocol Anycast (`2001:1::1`)
|
||||||
|
u128::from_be_bytes(ip.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0001
|
||||||
|
// Traversal Using Relays around NAT Anycast (`2001:1::2`)
|
||||||
|
|| u128::from_be_bytes(ip.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0002
|
||||||
|
// AMT (`2001:3::/32`)
|
||||||
|
|| matches!(ip.segments(), [0x2001, 3, _, _, _, _, _, _])
|
||||||
|
// AS112-v6 (`2001:4:112::/48`)
|
||||||
|
|| matches!(ip.segments(), [0x2001, 4, 0x112, _, _, _, _, _])
|
||||||
|
// ORCHIDv2 (`2001:20::/28`)
|
||||||
|
|| matches!(ip.segments(), [0x2001, b, _, _, _, _, _, _] if (0x20..=0x2F).contains(&b))
|
||||||
|
))
|
||||||
|
|| ((ip.segments()[0] == 0x2001) && (ip.segments()[1] == 0xdb8)) // ip.is_documentation()
|
||||||
|
|| ((ip.segments()[0] & 0xfe00) == 0xfc00) //ip.is_unique_local()
|
||||||
|
|| ((ip.segments()[0] & 0xffc0) == 0xfe80)) //ip.is_unicast_link_local()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "unstable"))]
|
||||||
|
pub use is_global_hardcoded as is_global;
|
||||||
|
|
||||||
|
#[cfg(feature = "unstable")]
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_global(ip: std::net::IpAddr) -> bool {
|
||||||
|
ip.is_global()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// These are some tests to check that the implementations match
|
||||||
|
/// The IPv4 can be all checked in 30 seconds or so and they are correct as of nightly 2023-07-17
|
||||||
|
/// The IPV6 can't be checked in a reasonable time, so we check over a hundred billion random ones, so far correct
|
||||||
|
/// Note that the is_global implementation is subject to change as new IP RFCs are created
|
||||||
|
///
|
||||||
|
/// To run while showing progress output:
|
||||||
|
/// cargo +nightly test --release --features sqlite,unstable -- --nocapture --ignored
|
||||||
|
#[cfg(test)]
|
||||||
|
#[cfg(feature = "unstable")]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn test_ipv4_global() {
|
||||||
|
for a in 0..u8::MAX {
|
||||||
|
println!("Iter: {}/255", a);
|
||||||
|
for b in 0..u8::MAX {
|
||||||
|
for c in 0..u8::MAX {
|
||||||
|
for d in 0..u8::MAX {
|
||||||
|
let ip = IpAddr::V4(std::net::Ipv4Addr::new(a, b, c, d));
|
||||||
|
assert_eq!(
|
||||||
|
ip.is_global(),
|
||||||
|
is_global_hardcoded(ip),
|
||||||
|
"IP mismatch: {}",
|
||||||
|
ip
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn test_ipv6_global() {
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
std::thread::scope(|s| {
|
||||||
|
for t in 0..16 {
|
||||||
|
let handle = s.spawn(move || {
|
||||||
|
let mut v = [0u8; 16];
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
for i in 0..20 {
|
||||||
|
println!("Thread {t} Iter: {i}/50");
|
||||||
|
for _ in 0..500_000_000 {
|
||||||
|
rng.fill(&mut v);
|
||||||
|
let ip = IpAddr::V6(std::net::Ipv6Addr::from(v));
|
||||||
|
assert_eq!(
|
||||||
|
ip.is_global(),
|
||||||
|
is_global_hardcoded(ip),
|
||||||
|
"IP mismatch: {ip}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,2 +1,3 @@
|
|||||||
pub mod detection;
|
pub mod detection;
|
||||||
|
pub mod ip_addr;
|
||||||
pub mod modules;
|
pub mod modules;
|
||||||
|
@ -8,6 +8,7 @@ use tungstenite::stream::MaybeTlsStream;
|
|||||||
use tungstenite::{connect, Error, Message, WebSocket};
|
use tungstenite::{connect, Error, Message, WebSocket};
|
||||||
|
|
||||||
pub mod detection;
|
pub mod detection;
|
||||||
|
pub mod ip_addr;
|
||||||
pub mod modules;
|
pub mod modules;
|
||||||
|
|
||||||
use crate::detection::get_dns_client;
|
use crate::detection::get_dns_client;
|
||||||
|
Reference in New Issue
Block a user