From ba9b5c85e8e06ba4d3ba0941c3b435b23154212b Mon Sep 17 00:00:00 2001 From: Juan Jose Nicola Date: Thu, 7 Nov 2024 15:13:28 -0300 Subject: [PATCH 1/5] Add host nasl built-in functions --- rust/src/nasl/builtin/host/mod.rs | 195 ++++++++++++++++++++++++++- rust/src/nasl/utils/error.rs | 6 + rust/src/scannerctl/interpret/mod.rs | 11 +- 3 files changed, 204 insertions(+), 8 deletions(-) diff --git a/rust/src/nasl/builtin/host/mod.rs b/rust/src/nasl/builtin/host/mod.rs index 8a941ea79..a35dcf036 100644 --- a/rust/src/nasl/builtin/host/mod.rs +++ b/rust/src/nasl/builtin/host/mod.rs @@ -5,7 +5,12 @@ #[cfg(test)] mod tests; -use std::{net::IpAddr, str::FromStr}; +use std::{ + net::{IpAddr, SocketAddr, ToSocketAddrs}, + str::FromStr, +}; + +use dns_lookup::lookup_addr; use crate::function_set; use crate::nasl::utils::{error::FunctionErrorKind, lookup_keys::TARGET}; @@ -18,8 +23,6 @@ use crate::nasl::utils::{Context, ContextType, Register}; /// It does lookup TARGET and when not found falls back to 127.0.0.1 to resolve. /// If the TARGET is not a IP address than we assume that it already is a fqdn or a hostname and will return that instead. fn resolve_hostname(register: &Register) -> Result { - use std::net::ToSocketAddrs; - let default_ip = "127.0.0.1"; // currently we use shadow variables as _FC_ANON_ARGS; the original openvas uses redis for that purpose. let target = register.named(TARGET).map_or_else( @@ -79,6 +82,186 @@ fn nasl_get_host_ip( Ok(NaslValue::String(ip.to_string())) } +fn resolve( + mut hostname: String, +) -> Result>>, FunctionErrorKind> { + //std::net to_socket_addrs() requires a port. Therefore, using a dummy port + hostname.push_str(":5000"); + + match hostname.to_socket_addrs() { + Ok(addr) => Ok(Some(Box::new(addr))), + // assumes that target is already a hostname + Err(_) => Err(FunctionErrorKind::diagnostic_ret_null("Missing Hostname")), + } +} + +/// Get an IP address corresponding to the host name +fn resolve_host_name( + register: &Register, + _context: &Context, +) -> Result { + let hostname = match register.named("hostname") { + Some(ContextType::Value(NaslValue::String(x))) if !x.is_empty() => x.clone(), + _ => { + return Err(FunctionErrorKind::diagnostic_ret_null("Missing Hostname")); + } + }; + + match resolve(hostname)? { + Some(mut a) => { + let address = a.next().map_or_else(String::new, |x| x.to_string()); + let address = &address[..(address.len() - 5)]; + Ok(NaslValue::String(address.to_string())) + } + None => Ok(NaslValue::Null), + } +} +/// Resolve a hostname to all found addresses and return them in an NaslValue::Array +fn resolve_hostname_to_multiple_ips( + register: &Register, + _context: &Context, +) -> Result { + let hostname = match register.named("hostname") { + Some(ContextType::Value(NaslValue::String(x))) if !x.is_empty() => x.clone(), + _ => { + return Err(FunctionErrorKind::diagnostic_ret_null("Missing Hostname")); + } + }; + + match resolve(hostname)? { + Some(addr) => { + let ips = addr + .into_iter() + .map(|x| { + let address = x.to_string(); + let address = &address[..(address.len() - 5)]; + NaslValue::String(address.to_string()) + }) + .collect(); + Ok(NaslValue::Array(ips)) + } + // assumes that target is already a hostname + None => Ok(NaslValue::Null), + } +} + +/// Check if the currently scanned target is an IPv6 address. +/// Return TRUE if the current target is an IPv6 address, else FALSE. In case of an error, NULL is returned. +fn target_is_ipv6(_register: &Register, context: &Context) -> Result { + let target_ori = match context.target().is_empty() { + true => { + return Err(FunctionErrorKind::diagnostic_ret_null("Address is NULL!")); + } + false => context.target(), + }; + + let mut target = target_ori.to_string(); + // IPV6 must be between [] + if target.contains(":") { + let mut t_aux = String::from("["); + t_aux.push_str(target_ori); + t_aux.push(']'); + target = t_aux; + } + + // SocketAddr requires a socket, not only the IP addr. + target.push_str(":5000"); + match target.to_socket_addrs() { + Ok(addr) => { + let v = addr.into_iter().filter(|x| x.is_ipv6()).collect::>(); + Ok(NaslValue::Boolean(!v.is_empty())) + } + Err(_) => Err(FunctionErrorKind::diagnostic_ret_null("address is Null")), + } +} + +fn same_host(register: &Register, _: &Context) -> Result { + let positional = register.positional(); + if positional.len() != 2 { + return Err(FunctionErrorKind::diagnostic_ret_null( + "same_host needs two parameters!", + )); + } + + let h1 = match &positional[0] { + NaslValue::String(x) => { + if let Some(h) = resolve(x.to_string())? { + h + } else { + return Err(FunctionErrorKind::diagnostic_ret_null( + "Wrong parameter type", + )); + } + } + _ => { + return Err(FunctionErrorKind::diagnostic_ret_null( + "Wrong parameter type", + )); + } + }; + + let h2 = match &positional[1] { + NaslValue::String(x) => { + if let Some(h) = resolve(x.to_string())? { + h + } else { + return Err(FunctionErrorKind::diagnostic_ret_null( + "Wrong parameter type", + )); + } + } + _ => { + return Err(FunctionErrorKind::diagnostic_ret_null( + "Wrong parameter type", + )); + } + }; + + let cmp_hostname = match register.named("cmp_hostname") { + Some(ContextType::Value(NaslValue::Boolean(x))) => *x, + _ => false, + }; + + let addr1: Vec = h1.into_iter().map(|x| x.ip()).collect::>(); + let addr2: Vec = h2.into_iter().map(|x| x.ip()).collect::>(); + + let hostnames1 = addr1 + .clone() + .into_iter() + .map(|x| lookup_addr(&x)) + .collect::>(); + let hostnames2 = addr2 + .clone() + .into_iter() + .map(|x| lookup_addr(&x)) + .collect::>(); + + let mut flag = false; + for a1 in addr1.iter() { + for a2 in addr2.iter() { + if a1.eq(a2) { + flag = true; + } + } + } + + if cmp_hostname { + for hn1 in hostnames1.iter() { + for hn2 in hostnames2.iter() { + if hn1.is_ok() && hn2.is_ok() && hn1.as_ref().unwrap() == hn2.as_ref().unwrap() { + flag = true; + } + } + } + } + + if flag { + Ok(NaslValue::Boolean(true)) + } else { + Ok(NaslValue::Boolean(false)) + } +} + pub struct Host; function_set! { @@ -87,6 +270,10 @@ function_set! { ( get_host_name, get_host_names, - (nasl_get_host_ip, "get_host_ip") + (nasl_get_host_ip, "get_host_ip"), + resolve_host_name, + resolve_hostname_to_multiple_ips, + (target_is_ipv6, "TARGET_IS_IPV6"), + same_host ) } diff --git a/rust/src/nasl/utils/error.rs b/rust/src/nasl/utils/error.rs index 16cc74641..797d9fba9 100644 --- a/rust/src/nasl/utils/error.rs +++ b/rust/src/nasl/utils/error.rs @@ -98,6 +98,12 @@ impl FunctionErrorKind { pub fn missing_argument(val: &str) -> Self { Self::MissingArguments(vec![val.to_string()]) } + + /// Helper function to quickly construct a `MissingArguments` variant + /// for a single missing argument and returning a NaslValue::Null + pub fn diagnostic_ret_null(val: &str) -> Self { + Self::Diagnostic(val.to_string(), Some(NaslValue::Null)) + } } impl From<(&str, &str, &NaslValue)> for FunctionErrorKind { diff --git a/rust/src/scannerctl/interpret/mod.rs b/rust/src/scannerctl/interpret/mod.rs index d9fc5430c..83226432d 100644 --- a/rust/src/scannerctl/interpret/mod.rs +++ b/rust/src/scannerctl/interpret/mod.rs @@ -125,10 +125,13 @@ where } async fn run(&self, script: &str) -> Result<(), CliErrorKind> { - let context = self.context_builder.build(ContextKey::Scan( - self.scan_id.clone(), - Some(self.target.clone()), - )); + let target = match self.target.is_empty() { + true => None, + false => Some(self.target.clone()), + }; + let context = self + .context_builder + .build(ContextKey::Scan(self.scan_id.clone(), target)); let register = RegisterBuilder::build(); let code = self.load(script)?; let results: Vec<_> = CodeInterpreter::new(&code, register, &context) From b28e2ee03f41bf2afae88a151019765da9b75bc6 Mon Sep 17 00:00:00 2001 From: Juan Jose Nicola Date: Thu, 28 Nov 2024 08:29:27 -0300 Subject: [PATCH 2/5] fix conflicts and improve code Also implements get_host_names --- rust/src/feed/update/mod.rs | 6 +- rust/src/nasl/builtin/host/mod.rs | 152 ++++++++++++++++++------------ rust/src/nasl/builtin/mod.rs | 7 +- rust/src/nasl/builtin/ssh/mod.rs | 6 +- rust/src/nasl/utils/context.rs | 97 ++++++++++++++++--- rust/src/nasl/utils/hosts.rs | 20 ++++ rust/src/nasl/utils/mod.rs | 3 +- rust/src/scanner/scan_runner.rs | 3 +- rust/src/scanner/vt_runner.rs | 5 +- 9 files changed, 218 insertions(+), 81 deletions(-) create mode 100644 rust/src/nasl/utils/hosts.rs diff --git a/rust/src/feed/update/mod.rs b/rust/src/feed/update/mod.rs index 39f50246d..8503898da 100644 --- a/rust/src/feed/update/mod.rs +++ b/rust/src/feed/update/mod.rs @@ -15,6 +15,7 @@ use crate::nasl::interpreter::{CodeInterpreter, Interpreter}; use crate::nasl::nasl_std_functions; use crate::nasl::prelude::*; use crate::nasl::syntax::AsBufReader; +use crate::nasl::utils::context::Target; use crate::nasl::ContextType; use crate::storage::{item::NVTField, ContextKey, Dispatcher, NoOpRetriever}; @@ -48,7 +49,7 @@ pub async fn feed_version( let register = Register::default(); let k = ContextKey::default(); let fr = NoOpRetriever::default(); - let target = String::default(); + let target = Target::default(); // TODO add parameter to struct let functions = nasl_std_functions(); let context = Context::new(k, target, dispatcher, &fr, loader, &functions); @@ -147,9 +148,8 @@ where let register = Register::root_initial(&self.initial); let fr = NoOpRetriever::default(); - let target = String::default(); + let target = Target::default(); let functions = nasl_std_functions(); - let context = Context::new( key.clone(), target, diff --git a/rust/src/nasl/builtin/host/mod.rs b/rust/src/nasl/builtin/host/mod.rs index a35dcf036..c6f96479a 100644 --- a/rust/src/nasl/builtin/host/mod.rs +++ b/rust/src/nasl/builtin/host/mod.rs @@ -13,49 +13,11 @@ use std::{ use dns_lookup::lookup_addr; use crate::function_set; -use crate::nasl::utils::{error::FunctionErrorKind, lookup_keys::TARGET}; +use crate::nasl::utils::{error::FunctionErrorKind, hosts::resolve, lookup_keys::TARGET}; use crate::nasl::syntax::NaslValue; use crate::nasl::utils::{Context, ContextType, Register}; -/// Resolves IP address of target to hostname -/// -/// It does lookup TARGET and when not found falls back to 127.0.0.1 to resolve. -/// If the TARGET is not a IP address than we assume that it already is a fqdn or a hostname and will return that instead. -fn resolve_hostname(register: &Register) -> Result { - let default_ip = "127.0.0.1"; - // currently we use shadow variables as _FC_ANON_ARGS; the original openvas uses redis for that purpose. - let target = register.named(TARGET).map_or_else( - || default_ip.to_owned(), - |x| match x { - ContextType::Value(NaslValue::String(x)) => x.clone(), - _ => default_ip.to_owned(), - }, - ); - - match target.to_socket_addrs() { - Ok(mut addr) => Ok(addr.next().map_or_else(String::new, |x| x.to_string())), - // assumes that target is already a hostname - Err(_) => Ok(target), - } -} - -/// NASL function to get all stored vhosts -/// -/// As of now (2023-01-20) there is no vhost handling. -/// Therefore this function does load the registered TARGET and if it is an IP Address resolves it via DNS instead. -fn get_host_names(register: &Register, _: &Context) -> Result { - resolve_hostname(register).map(|x| NaslValue::Array(vec![NaslValue::String(x)])) -} - -/// NASL function to get the current hostname -/// -/// As of now (2023-01-20) there is no vhost handling. -/// Therefore this function does load the registered TARGET and if it is an IP Address resolves it via DNS instead. -fn get_host_name(register: &Register, _: &Context) -> Result { - resolve_hostname(register).map(NaslValue::String) -} - /// Return the target's IP address as IpAddr. pub fn get_host_ip(context: &Context) -> Result { let default_ip = "127.0.0.1"; @@ -73,26 +35,99 @@ pub fn get_host_ip(context: &Context) -> Result { } } -/// Return the target's IP address or 127.0.0.1 if not set. -fn nasl_get_host_ip( +pub fn add_host_name( + register: &Register, + context: &Context, +) -> Result { + let hostname = match register.named("hostname") { + Some(ContextType::Value(NaslValue::String(x))) if !x.is_empty() => x.clone(), + _ => { + return Err(FunctionErrorKind::diagnostic_ret_null("Empty Hostname")); + } + }; + let source = match register.named("source") { + Some(ContextType::Value(NaslValue::String(x))) if !x.is_empty() => x.clone(), + _ => "NASL".to_string(), + }; + + context.add_hostname(hostname, source); + Ok(NaslValue::Null) +} + +pub fn get_host_names( _register: &Register, context: &Context, ) -> Result { - let ip = get_host_ip(context)?; - Ok(NaslValue::String(ip.to_string())) + if context.target_vhosts().is_none() { + return Ok(NaslValue::Array(vec![NaslValue::String( + context.target_ip().to_string(), + )])); + } + + let vhosts = context + .target_vhosts() + .unwrap() + .iter() + .map(|(h, _s)| NaslValue::String(h.to_string())) + .collect(); + Ok(NaslValue::Array(vhosts)) +} +pub fn get_host_name( + _register: &Register, + context: &Context, +) -> Result { + let mut v = Vec::new(); + if let Some(vh) = context.target_vhosts() { + v = vh + .into_iter() + .map(|(v, _s)| NaslValue::String(v)) + .collect::>(); + } + + if !v.is_empty() { + return Ok(NaslValue::Fork(v)); + } + + if let Ok(ip) = get_host_ip(context) { + match lookup_addr(&ip) { + Ok(host) => Ok(NaslValue::String(host)), + Err(_) => Ok(NaslValue::String(ip.to_string())), + } + } else { + Ok(NaslValue::String(context.target().to_string())) + } } -fn resolve( - mut hostname: String, -) -> Result>>, FunctionErrorKind> { - //std::net to_socket_addrs() requires a port. Therefore, using a dummy port - hostname.push_str(":5000"); +pub fn get_host_name_source( + register: &Register, + context: &Context, +) -> Result { + let hostname = match register.named("hostname") { + Some(ContextType::Value(NaslValue::String(x))) if !x.is_empty() => x.clone(), + _ => { + return Err(FunctionErrorKind::diagnostic_ret_null("Empty Hostname")); + } + }; - match hostname.to_socket_addrs() { - Ok(addr) => Ok(Some(Box::new(addr))), - // assumes that target is already a hostname - Err(_) => Err(FunctionErrorKind::diagnostic_ret_null("Missing Hostname")), + if let Some(vh) = context.target_vhosts() { + if let Some(source) = + vh.into_iter() + .find_map(|(v, s)| if v == hostname { Some(s) } else { None }) + { + return Ok(NaslValue::String(source)); + }; } + + Ok(NaslValue::Null) +} + +/// Return the target's IP address or 127.0.0.1 if not set. +fn nasl_get_host_ip( + _register: &Register, + context: &Context, +) -> Result { + let ip = get_host_ip(context)?; + Ok(NaslValue::String(ip.to_string())) } /// Get an IP address corresponding to the host name @@ -175,6 +210,9 @@ fn target_is_ipv6(_register: &Register, context: &Context) -> Result Result { let positional = register.positional(); if positional.len() != 2 { @@ -255,11 +293,7 @@ fn same_host(register: &Register, _: &Context) -> Result Context { - let target = match &key { + let mut target = Target::default(); + target.set_target(match &key { ContextKey::Scan(_, Some(target)) => target.clone(), ContextKey::Scan(_, None) => String::default(), ContextKey::FileName(target) => target.clone(), - }; + }); Context::new( key, target, diff --git a/rust/src/nasl/builtin/ssh/mod.rs b/rust/src/nasl/builtin/ssh/mod.rs index a144d0e91..46d96d5a2 100644 --- a/rust/src/nasl/builtin/ssh/mod.rs +++ b/rust/src/nasl/builtin/ssh/mod.rs @@ -18,7 +18,7 @@ mod tests; pub use error::SshError; pub use sessions::SshSessions as Ssh; -use std::time::Duration; +use std::{borrow::BorrowMut, time::Duration}; use ::russh::{cipher, Preferred}; use russh_keys::key; @@ -153,9 +153,7 @@ impl Ssh { let port = port .filter(|_| socket.is_none()) .unwrap_or(DEFAULT_SSH_PORT); - let ip = ctx.target_ip().map_err(|e| { - SshError::from(SshErrorKind::InvalidIpAddr(ctx.target().to_string(), e)) - })?; + let ip = ctx.target_ip(); let timeout = timeout.map(Duration::from_secs); let keytype = keytype .map(|keytype| keytype.0) diff --git a/rust/src/nasl/utils/context.rs b/rust/src/nasl/utils/context.rs index 2c1457bb4..9108b4f38 100644 --- a/rust/src/nasl/utils/context.rs +++ b/rust/src/nasl/utils/context.rs @@ -7,6 +7,7 @@ use crate::nasl::syntax::{Loader, NaslValue, Statement}; use crate::storage::{ContextKey, Dispatcher, Retriever}; +use super::hosts::resolve; use super::{executor::Executor, lookup_keys::FC_ANON_ARGS}; /// Contexts are responsible to locate, add and delete everything that is declared within a NASL plugin @@ -290,6 +291,9 @@ impl Default for Register { } use std::collections::HashMap; use std::net::{AddrParseError, IpAddr}; +use std::str::FromStr; +use std::sync::Mutex; + type Named = HashMap; /// NaslContext is a struct to contain variables and if root declared functions @@ -329,6 +333,68 @@ impl NaslContext { } } +#[derive(Debug)] +pub struct Target { + /// The original target. IP or hostname + target: String, + /// The IP address. Always has a valid IP. It defaults to 127.0.0.1 if not possible to resolve target. + ip_addr: IpAddr, + // The shared state is guarded by a mutex. This is a `std::sync::Mutex` and + // not a Tokio mutex. This is because there are no asynchronous operations + // being performed while holding the mutex. Additionally, the critical + // sections are very small. + // + // A Tokio mutex is mostly intended to be used when locks need to be held + // across `.await` yield points. All other cases are **usually** best + // served by a std mutex. If the critical section does not include any + // async operations but is long (CPU intensive or performing blocking + // operations), then the entire operation, including waiting for the mutex, + // is considered a "blocking" operation and `tokio::task::spawn_blocking` + // should be used. + /// vhost list which resolve to the IP address and their sources. + vhosts: Mutex>, +} + +impl Target { + pub fn set_target(&mut self, target: String) -> &Target { + // Target can be an ip address or a hostname + self.target = target; + + // Store the IpAddr if possible, else default to localhost + if let Ok(host) = resolve(self.target.clone()) { + let t = match host { + Some(mut a) => { + let address = a.next().map_or_else(String::new, |x| x.to_string()); + address[..(address.len() - 5)].to_string() + } + None => "127.0.0.1".to_string(), + }; + + self.ip_addr = match t { + x if !x.is_empty() => x.to_string(), + _ => "127.0.0.1".to_string(), + } + .parse() + .unwrap(); + } + self + } + + pub fn add_hostname(&self, hostname: String, source: String) -> &Target { + self.vhosts.lock().unwrap().push((hostname, source)); + self + } +} + +impl Default for Target { + fn default() -> Self { + Self { + target: String::new(), + ip_addr: IpAddr::from_str("127.0.0.1").unwrap(), + vhosts: Mutex::new(vec![]), + } + } +} /// Configurations /// /// This struct includes all objects that a nasl function requires. @@ -337,7 +403,7 @@ pub struct Context<'a> { /// key for this context. A file name or a scan id key: ContextKey, /// target to run a scan against - target: String, + target: Target, /// Default Dispatcher dispatcher: &'a dyn Dispatcher, /// Default Retriever @@ -352,7 +418,7 @@ impl<'a> Context<'a> { /// Creates an empty configuration pub fn new( key: ContextKey, - target: String, + target: Target, dispatcher: &'a dyn Dispatcher, retriever: &'a dyn Retriever, loader: &'a dyn Loader, @@ -394,18 +460,27 @@ impl<'a> Context<'a> { &self.key } - /// Get the target host + /// Get the target IP as string pub fn target(&self) -> &str { - &self.target + &self.target.target } - /// Get the target host - pub fn target_ip(&self) -> Result { - match self.target() { - x if !x.is_empty() => x.to_string(), - _ => "127.0.0.1".to_string(), - } - .parse() + /// Get the target host as IpAddr enum member + pub fn target_ip(&self) -> IpAddr { + self.target.ip_addr + } + + /// Get the target VHost list + pub fn target_vhosts(&self) -> Option> { + Some(self.target.vhosts.lock().unwrap().clone()) + } + + pub fn set_target(&mut self, ori_target: String) { + self.target.target = ori_target; + } + + pub fn add_hostname(&self, hostname: String, source: String) { + self.target.add_hostname(hostname, source); } /// Get the storage diff --git a/rust/src/nasl/utils/hosts.rs b/rust/src/nasl/utils/hosts.rs new file mode 100644 index 000000000..c1baaca61 --- /dev/null +++ b/rust/src/nasl/utils/hosts.rs @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2024 Greenbone AG +// +// SPDX-License-Identifier: GPL-2.0-or-later WITH x11vnc-openssl-exception + +use std::net::{SocketAddr, ToSocketAddrs}; + +use crate::nasl::utils::error::FunctionErrorKind; + +pub fn resolve( + mut hostname: String, +) -> Result>>, FunctionErrorKind> { + //std::net to_socket_addrs() requires a port. Therefore, using a dummy port + hostname.push_str(":5000"); + + match hostname.to_socket_addrs() { + Ok(addr) => Ok(Some(Box::new(addr))), + // assumes that target is already a hostname + Err(_) => Err(FunctionErrorKind::diagnostic_ret_null("Missing Hostname")), + } +} diff --git a/rust/src/nasl/utils/mod.rs b/rust/src/nasl/utils/mod.rs index 701ce7fcd..afb7fa0ab 100644 --- a/rust/src/nasl/utils/mod.rs +++ b/rust/src/nasl/utils/mod.rs @@ -7,11 +7,12 @@ pub mod context; pub mod error; mod executor; pub mod function; +pub mod hosts; pub mod lookup_keys; use std::collections::HashMap; -pub use context::{Context, ContextType, Register}; +pub use context::{Context, ContextType, Register, Target}; pub use error::FunctionErrorKind; pub use executor::{Executor, IntoFunctionSet, StoredFunctionSet}; diff --git a/rust/src/scanner/scan_runner.rs b/rust/src/scanner/scan_runner.rs index b0b9081ff..8cc68a796 100644 --- a/rust/src/scanner/scan_runner.rs +++ b/rust/src/scanner/scan_runner.rs @@ -121,6 +121,7 @@ pub(super) mod tests { use crate::nasl::utils::Context; use crate::nasl::utils::Executor; use crate::nasl::utils::Register; + use crate::nasl::utils::Target as ContextTarget; use crate::nasl::{interpreter::CodeInterpreter, nasl_std_functions}; use crate::scanner::{ error::{ExecuteError, ScriptResult}, @@ -320,7 +321,7 @@ exit({rc}); let storage = DefaultDispatcher::new(); let register = Register::root_initial(&initial); - let target = String::default(); + let target = ContextTarget::default(); let functions = nasl_std_functions(); let loader = |_: &str| code.to_string(); let key = ContextKey::FileName(id.to_string()); diff --git a/rust/src/scanner/vt_runner.rs b/rust/src/scanner/vt_runner.rs index eae697909..6a9c78831 100644 --- a/rust/src/scanner/vt_runner.rs +++ b/rust/src/scanner/vt_runner.rs @@ -1,5 +1,6 @@ use crate::models::{Host, Parameter, Protocol, ScanId}; use crate::nasl::syntax::{Loader, NaslValue}; +use crate::nasl::utils::context::Target; use crate::nasl::utils::{Executor, Register}; use crate::scheduling::Stage; use crate::storage::item::Nvt; @@ -193,10 +194,12 @@ impl<'a, Stack: ScannerStack> VTRunner<'a, Stack> { if let Err(e) = self.check_keys(self.vt) { return e; } + let mut target = Target::default(); + target.set_target(self.target.clone()); let context = Context::new( self.generate_key(), - self.target.clone(), + target, self.storage.as_dispatcher(), self.storage.as_retriever(), self.loader, From 3a40fee96251e2e0d80108567e3f52f2a40ea156 Mon Sep 17 00:00:00 2001 From: Juan Jose Nicola Date: Tue, 19 Nov 2024 08:51:04 -0300 Subject: [PATCH 3/5] more host nasl built-in functions an some fixes --- rust/src/nasl/builtin/host/mod.rs | 56 +++++++++++++++++-------------- rust/src/nasl/utils/context.rs | 4 +-- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/rust/src/nasl/builtin/host/mod.rs b/rust/src/nasl/builtin/host/mod.rs index c6f96479a..076c118d9 100644 --- a/rust/src/nasl/builtin/host/mod.rs +++ b/rust/src/nasl/builtin/host/mod.rs @@ -14,12 +14,25 @@ use dns_lookup::lookup_addr; use crate::function_set; use crate::nasl::utils::{error::FunctionErrorKind, hosts::resolve, lookup_keys::TARGET}; +use crate::nasl::utils::error::FunctionErrorKind; use crate::nasl::syntax::NaslValue; use crate::nasl::utils::{Context, ContextType, Register}; +/// Get a list of found hostnames or a IP of the current target in case no hostnames were found yet. +fn get_host_names(_register: &Register, context: &Context) -> Result { + if let Some(hns) = context.target_vhosts() { + let hns = hns + .into_iter() + .map(|(h, _s)| NaslValue::String(h)) + .collect::>(); + return Ok(NaslValue::Array(hns)); + }; + Ok(NaslValue::String(context.target().to_string())) +} + /// Return the target's IP address as IpAddr. -pub fn get_host_ip(context: &Context) -> Result { +fn get_host_ip(context: &Context) -> Result { let default_ip = "127.0.0.1"; let r_sock_addr = match context.target() { x if !x.is_empty() => IpAddr::from_str(x), @@ -35,6 +48,9 @@ pub fn get_host_ip(context: &Context) -> Result { } } +///Expands the vHosts list with the given hostname. +///The mandatory parameter hostname is of type string. It contains the hostname which should be added to the list of vHosts +///Additionally a source, how the hostname was detected can be added with the named argument source as a string. If it is not given, the value NASL is set as default. pub fn add_host_name( register: &Register, context: &Context, @@ -54,24 +70,7 @@ pub fn add_host_name( Ok(NaslValue::Null) } -pub fn get_host_names( - _register: &Register, - context: &Context, -) -> Result { - if context.target_vhosts().is_none() { - return Ok(NaslValue::Array(vec![NaslValue::String( - context.target_ip().to_string(), - )])); - } - - let vhosts = context - .target_vhosts() - .unwrap() - .iter() - .map(|(h, _s)| NaslValue::String(h.to_string())) - .collect(); - Ok(NaslValue::Array(vhosts)) -} +/// Get the host name of the currently scanned target. If there is no host name available, the IP of the target is returned instead. pub fn get_host_name( _register: &Register, context: &Context, @@ -83,7 +82,9 @@ pub fn get_host_name( .map(|(v, _s)| NaslValue::String(v)) .collect::>(); } - + //TODO: store the current hostname being forked. + //TODO: don't fork if expand_vhost is disabled. + //TODO: don't fork if already in a vhost if !v.is_empty() { return Ok(NaslValue::Fork(v)); } @@ -98,6 +99,10 @@ pub fn get_host_name( } } +/// This function returns the source of detection of a given hostname. +/// The named parameter hostname is a string containing the hostname. +/// When no hostname is given, the current scanned host is taken. +/// If no virtual hosts are found yet this function always returns IP-address. pub fn get_host_name_source( register: &Register, context: &Context, @@ -110,15 +115,14 @@ pub fn get_host_name_source( }; if let Some(vh) = context.target_vhosts() { - if let Some(source) = - vh.into_iter() - .find_map(|(v, s)| if v == hostname { Some(s) } else { None }) + if let Some(source) = vh + .into_iter() + .find_map(|(v, s)| if v == hostname { Some(s) } else { None }) { return Ok(NaslValue::String(source)); }; } - - Ok(NaslValue::Null) + Ok(NaslValue::String(context.target().to_string())) } /// Return the target's IP address or 127.0.0.1 if not set. @@ -302,7 +306,6 @@ function_set! { Host, sync_stateless, ( - get_host_name, get_host_names, (nasl_get_host_ip, "get_host_ip"), resolve_host_name, @@ -310,6 +313,7 @@ function_set! { (target_is_ipv6, "TARGET_IS_IPV6"), same_host, add_host_name, + get_host_name, get_host_name_source ) } diff --git a/rust/src/nasl/utils/context.rs b/rust/src/nasl/utils/context.rs index 9108b4f38..6cf215cb1 100644 --- a/rust/src/nasl/utils/context.rs +++ b/rust/src/nasl/utils/context.rs @@ -475,8 +475,8 @@ impl<'a> Context<'a> { Some(self.target.vhosts.lock().unwrap().clone()) } - pub fn set_target(&mut self, ori_target: String) { - self.target.target = ori_target; + pub fn set_target(&mut self, target: String) { + self.target.target = target; } pub fn add_hostname(&self, hostname: String, source: String) { From e1319d996922e10fba0641aa66aa524e25985a76 Mon Sep 17 00:00:00 2001 From: Juan Jose Nicola Date: Tue, 19 Nov 2024 11:31:06 -0300 Subject: [PATCH 4/5] fix conflicts and improve code --- rust/src/nasl/builtin/host/README.md | 5 ----- rust/src/nasl/builtin/host/mod.rs | 5 ++--- rust/src/nasl/builtin/ssh/mod.rs | 2 +- rust/src/nasl/utils/context.rs | 15 +++------------ 4 files changed, 6 insertions(+), 21 deletions(-) diff --git a/rust/src/nasl/builtin/host/README.md b/rust/src/nasl/builtin/host/README.md index 1bb78031a..a28c3c343 100644 --- a/rust/src/nasl/builtin/host/README.md +++ b/rust/src/nasl/builtin/host/README.md @@ -1,10 +1,5 @@ ## Implements -- get_host_name -- get_host_names - -## Missing - - TARGET_IS_IPV6 - add_host_name - get_host_name diff --git a/rust/src/nasl/builtin/host/mod.rs b/rust/src/nasl/builtin/host/mod.rs index 076c118d9..8a6d148a6 100644 --- a/rust/src/nasl/builtin/host/mod.rs +++ b/rust/src/nasl/builtin/host/mod.rs @@ -6,15 +6,14 @@ mod tests; use std::{ - net::{IpAddr, SocketAddr, ToSocketAddrs}, + net::{IpAddr, ToSocketAddrs}, str::FromStr, }; use dns_lookup::lookup_addr; use crate::function_set; -use crate::nasl::utils::{error::FunctionErrorKind, hosts::resolve, lookup_keys::TARGET}; -use crate::nasl::utils::error::FunctionErrorKind; +use crate::nasl::utils::{error::FunctionErrorKind, hosts::resolve}; use crate::nasl::syntax::NaslValue; use crate::nasl::utils::{Context, ContextType, Register}; diff --git a/rust/src/nasl/builtin/ssh/mod.rs b/rust/src/nasl/builtin/ssh/mod.rs index 46d96d5a2..f3d5a6a0e 100644 --- a/rust/src/nasl/builtin/ssh/mod.rs +++ b/rust/src/nasl/builtin/ssh/mod.rs @@ -18,7 +18,7 @@ mod tests; pub use error::SshError; pub use sessions::SshSessions as Ssh; -use std::{borrow::BorrowMut, time::Duration}; +use std::time::Duration; use ::russh::{cipher, Preferred}; use russh_keys::key; diff --git a/rust/src/nasl/utils/context.rs b/rust/src/nasl/utils/context.rs index 6cf215cb1..60bc00cd2 100644 --- a/rust/src/nasl/utils/context.rs +++ b/rust/src/nasl/utils/context.rs @@ -290,7 +290,7 @@ impl Default for Register { } } use std::collections::HashMap; -use std::net::{AddrParseError, IpAddr}; +use std::net::IpAddr; use std::str::FromStr; use std::sync::Mutex; @@ -363,19 +363,10 @@ impl Target { // Store the IpAddr if possible, else default to localhost if let Ok(host) = resolve(self.target.clone()) { let t = match host { - Some(mut a) => { - let address = a.next().map_or_else(String::new, |x| x.to_string()); - address[..(address.len() - 5)].to_string() - } + Some(mut a) => a.next().map_or_else(String::new, |x| x.ip().to_string()), None => "127.0.0.1".to_string(), }; - - self.ip_addr = match t { - x if !x.is_empty() => x.to_string(), - _ => "127.0.0.1".to_string(), - } - .parse() - .unwrap(); + self.ip_addr = IpAddr::from_str(t.as_str()).unwrap(); } self } From 66a379fbafd6633317216978f5f9787f00a79f5c Mon Sep 17 00:00:00 2001 From: Juan Jose Nicola Date: Fri, 29 Nov 2024 09:02:31 -0300 Subject: [PATCH 5/5] apply suggestions --- rust/src/nasl/builtin/host/mod.rs | 280 +++++++++--------------------- rust/src/nasl/utils/context.rs | 15 +- rust/src/nasl/utils/hosts.rs | 11 +- 3 files changed, 97 insertions(+), 209 deletions(-) diff --git a/rust/src/nasl/builtin/host/mod.rs b/rust/src/nasl/builtin/host/mod.rs index 8a6d148a6..9d57a0bc8 100644 --- a/rust/src/nasl/builtin/host/mod.rs +++ b/rust/src/nasl/builtin/host/mod.rs @@ -6,28 +6,45 @@ mod tests; use std::{ - net::{IpAddr, ToSocketAddrs}, + net::{IpAddr, Ipv6Addr}, str::FromStr, }; use dns_lookup::lookup_addr; +use nasl_function_proc_macro::nasl_function; -use crate::function_set; use crate::nasl::utils::{error::FunctionErrorKind, hosts::resolve}; +use crate::{function_set, nasl::FromNaslValue}; use crate::nasl::syntax::NaslValue; -use crate::nasl::utils::{Context, ContextType, Register}; +use crate::nasl::utils::{Context, Register}; + +struct Hostname(String); +impl<'a> FromNaslValue<'a> for Hostname { + fn from_nasl_value(value: &'a NaslValue) -> Result { + let str = String::from_nasl_value(value)?; + if str.is_empty() { + Err(FunctionErrorKind::diagnostic_ret_null("Empty hostname.")) + } else { + Ok(Self(str)) + } + } +} /// Get a list of found hostnames or a IP of the current target in case no hostnames were found yet. -fn get_host_names(_register: &Register, context: &Context) -> Result { - if let Some(hns) = context.target_vhosts() { +#[nasl_function] +fn get_host_names(context: &Context) -> Result { + let hns = context.target_vhosts(); + if !hns.is_empty() { let hns = hns .into_iter() .map(|(h, _s)| NaslValue::String(h)) .collect::>(); return Ok(NaslValue::Array(hns)); }; - Ok(NaslValue::String(context.target().to_string())) + Ok(NaslValue::Array(vec![NaslValue::String( + context.target().to_string(), + )])) } /// Return the target's IP address as IpAddr. @@ -50,22 +67,14 @@ fn get_host_ip(context: &Context) -> Result { ///Expands the vHosts list with the given hostname. ///The mandatory parameter hostname is of type string. It contains the hostname which should be added to the list of vHosts ///Additionally a source, how the hostname was detected can be added with the named argument source as a string. If it is not given, the value NASL is set as default. +#[nasl_function(named(hostname, source))] pub fn add_host_name( - register: &Register, context: &Context, + hostname: Hostname, + source: Option<&str>, ) -> Result { - let hostname = match register.named("hostname") { - Some(ContextType::Value(NaslValue::String(x))) if !x.is_empty() => x.clone(), - _ => { - return Err(FunctionErrorKind::diagnostic_ret_null("Empty Hostname")); - } - }; - let source = match register.named("source") { - Some(ContextType::Value(NaslValue::String(x))) if !x.is_empty() => x.clone(), - _ => "NASL".to_string(), - }; - - context.add_hostname(hostname, source); + let source = source.filter(|x| !x.is_empty()).unwrap_or("NASL"); + context.add_hostname(hostname.0, source.into()); Ok(NaslValue::Null) } @@ -74,13 +83,15 @@ pub fn get_host_name( _register: &Register, context: &Context, ) -> Result { - let mut v = Vec::new(); - if let Some(vh) = context.target_vhosts() { - v = vh - .into_iter() - .map(|(v, _s)| NaslValue::String(v)) - .collect::>(); - } + let vh = context.target_vhosts(); + let v = if !vh.is_empty() { + vh.iter() + .map(|(v, _s)| NaslValue::String(v.to_string())) + .collect::>() + } else { + vec![] + }; + //TODO: store the current hostname being forked. //TODO: don't fork if expand_vhost is disabled. //TODO: don't fork if already in a vhost @@ -88,40 +99,29 @@ pub fn get_host_name( return Ok(NaslValue::Fork(v)); } - if let Ok(ip) = get_host_ip(context) { - match lookup_addr(&ip) { - Ok(host) => Ok(NaslValue::String(host)), - Err(_) => Ok(NaslValue::String(ip.to_string())), - } - } else { - Ok(NaslValue::String(context.target().to_string())) - } + let host = match get_host_ip(context) { + Ok(ip) => match lookup_addr(&ip) { + Ok(host) => host, + Err(_) => ip.to_string(), + }, + Err(_) => context.target().to_string(), + }; + Ok(NaslValue::String(host)) } /// This function returns the source of detection of a given hostname. /// The named parameter hostname is a string containing the hostname. /// When no hostname is given, the current scanned host is taken. /// If no virtual hosts are found yet this function always returns IP-address. -pub fn get_host_name_source( - register: &Register, - context: &Context, -) -> Result { - let hostname = match register.named("hostname") { - Some(ContextType::Value(NaslValue::String(x))) if !x.is_empty() => x.clone(), - _ => { - return Err(FunctionErrorKind::diagnostic_ret_null("Empty Hostname")); - } - }; - - if let Some(vh) = context.target_vhosts() { - if let Some(source) = vh - .into_iter() - .find_map(|(v, s)| if v == hostname { Some(s) } else { None }) - { - return Ok(NaslValue::String(source)); +#[nasl_function(named(hostname))] +pub fn get_host_name_source(context: &Context, hostname: Hostname) -> String { + let vh = context.target_vhosts(); + if !vh.is_empty() { + if let Some((_, source)) = vh.into_iter().find(|(v, _)| v == &hostname.0) { + return source; }; } - Ok(NaslValue::String(context.target().to_string())) + context.target().to_string() } /// Return the target's IP address or 127.0.0.1 if not set. @@ -134,169 +134,59 @@ fn nasl_get_host_ip( } /// Get an IP address corresponding to the host name -fn resolve_host_name( - register: &Register, - _context: &Context, -) -> Result { - let hostname = match register.named("hostname") { - Some(ContextType::Value(NaslValue::String(x))) if !x.is_empty() => x.clone(), - _ => { - return Err(FunctionErrorKind::diagnostic_ret_null("Missing Hostname")); - } - }; - - match resolve(hostname)? { - Some(mut a) => { - let address = a.next().map_or_else(String::new, |x| x.to_string()); - let address = &address[..(address.len() - 5)]; - Ok(NaslValue::String(address.to_string())) - } - None => Ok(NaslValue::Null), - } +#[nasl_function(named(hostname))] +fn resolve_host_name(hostname: Hostname) -> String { + resolve(hostname.0).map_or_else( + |_| "127.0.0.1".to_string(), + |x| x.first().map_or("127.0.0.1".to_string(), |v| v.to_string()), + ) } -/// Resolve a hostname to all found addresses and return them in an NaslValue::Array -fn resolve_hostname_to_multiple_ips( - register: &Register, - _context: &Context, -) -> Result { - let hostname = match register.named("hostname") { - Some(ContextType::Value(NaslValue::String(x))) if !x.is_empty() => x.clone(), - _ => { - return Err(FunctionErrorKind::diagnostic_ret_null("Missing Hostname")); - } - }; - match resolve(hostname)? { - Some(addr) => { - let ips = addr - .into_iter() - .map(|x| { - let address = x.to_string(); - let address = &address[..(address.len() - 5)]; - NaslValue::String(address.to_string()) - }) - .collect(); - Ok(NaslValue::Array(ips)) - } - // assumes that target is already a hostname - None => Ok(NaslValue::Null), - } +/// Resolve a hostname to all found addresses and return them in an NaslValue::Array +#[nasl_function(named(hostname))] +fn resolve_hostname_to_multiple_ips(hostname: Hostname) -> Result { + let ips = resolve(hostname.0)? + .into_iter() + .map(|x| NaslValue::String(x.to_string())) + .collect(); + Ok(NaslValue::Array(ips)) } /// Check if the currently scanned target is an IPv6 address. /// Return TRUE if the current target is an IPv6 address, else FALSE. In case of an error, NULL is returned. -fn target_is_ipv6(_register: &Register, context: &Context) -> Result { - let target_ori = match context.target().is_empty() { +#[nasl_function] +fn target_is_ipv6(context: &Context) -> Result { + let target = match context.target().is_empty() { true => { return Err(FunctionErrorKind::diagnostic_ret_null("Address is NULL!")); } false => context.target(), }; - - let mut target = target_ori.to_string(); - // IPV6 must be between [] - if target.contains(":") { - let mut t_aux = String::from("["); - t_aux.push_str(target_ori); - t_aux.push(']'); - target = t_aux; - } - - // SocketAddr requires a socket, not only the IP addr. - target.push_str(":5000"); - match target.to_socket_addrs() { - Ok(addr) => { - let v = addr.into_iter().filter(|x| x.is_ipv6()).collect::>(); - Ok(NaslValue::Boolean(!v.is_empty())) - } - Err(_) => Err(FunctionErrorKind::diagnostic_ret_null("address is Null")), - } + Ok(target.parse::().is_ok()) } /// Compare if two hosts are the same. /// The first two unnamed arguments are string containing the host to compare /// If the named argument cmp_hostname is set to TRUE, the given hosts are resolved into their hostnames -fn same_host(register: &Register, _: &Context) -> Result { - let positional = register.positional(); - if positional.len() != 2 { - return Err(FunctionErrorKind::diagnostic_ret_null( - "same_host needs two parameters!", - )); - } - - let h1 = match &positional[0] { - NaslValue::String(x) => { - if let Some(h) = resolve(x.to_string())? { - h - } else { - return Err(FunctionErrorKind::diagnostic_ret_null( - "Wrong parameter type", - )); - } - } - _ => { - return Err(FunctionErrorKind::diagnostic_ret_null( - "Wrong parameter type", - )); - } - }; - - let h2 = match &positional[1] { - NaslValue::String(x) => { - if let Some(h) = resolve(x.to_string())? { - h - } else { - return Err(FunctionErrorKind::diagnostic_ret_null( - "Wrong parameter type", - )); - } - } - _ => { - return Err(FunctionErrorKind::diagnostic_ret_null( - "Wrong parameter type", - )); - } - }; - - let cmp_hostname = match register.named("cmp_hostname") { - Some(ContextType::Value(NaslValue::Boolean(x))) => *x, - _ => false, - }; - - let addr1: Vec = h1.into_iter().map(|x| x.ip()).collect::>(); - let addr2: Vec = h2.into_iter().map(|x| x.ip()).collect::>(); - - let hostnames1 = addr1 - .clone() - .into_iter() - .map(|x| lookup_addr(&x)) +#[nasl_function(named(cmp_hostname))] +fn same_host(h1: &str, h2: &str, cmp_hostname: Option) -> Result { + let h1 = resolve(h1.to_string())?; + let h2 = resolve(h2.to_string())?; + + let hostnames1 = h1 + .iter() + .filter_map(|x| lookup_addr(x).ok()) .collect::>(); - let hostnames2 = addr2 - .clone() - .into_iter() - .map(|x| lookup_addr(&x)) + let hostnames2 = h2 + .iter() + .filter_map(|x| lookup_addr(x).ok()) .collect::>(); - let mut flag = false; - for a1 in addr1.iter() { - for a2 in addr2.iter() { - if a1.eq(a2) { - flag = true; - } - } - } - - if cmp_hostname { - for hn1 in hostnames1.iter() { - for hn2 in hostnames2.iter() { - if hn1.is_ok() && hn2.is_ok() && hn1.as_ref().unwrap() == hn2.as_ref().unwrap() { - flag = true; - } - } - } - } + let any_ip_address_matches = h1.iter().any(|a1| h2.contains(a1)); + let any_hostname_matches = hostnames1.iter().any(|h1| hostnames2.contains(h1)); + let cmp_hostname = cmp_hostname.filter(|x| *x).unwrap_or(false); - Ok(NaslValue::Boolean(flag)) + Ok(any_ip_address_matches || (cmp_hostname && any_hostname_matches)) } pub struct Host; diff --git a/rust/src/nasl/utils/context.rs b/rust/src/nasl/utils/context.rs index 60bc00cd2..cc8d39d0f 100644 --- a/rust/src/nasl/utils/context.rs +++ b/rust/src/nasl/utils/context.rs @@ -361,13 +361,10 @@ impl Target { self.target = target; // Store the IpAddr if possible, else default to localhost - if let Ok(host) = resolve(self.target.clone()) { - let t = match host { - Some(mut a) => a.next().map_or_else(String::new, |x| x.ip().to_string()), - None => "127.0.0.1".to_string(), - }; - self.ip_addr = IpAddr::from_str(t.as_str()).unwrap(); - } + self.ip_addr = match resolve(self.target.clone()) { + Ok(a) => *a.first().unwrap_or(&IpAddr::from_str("127.0.0.1").unwrap()), + Err(_) => IpAddr::from_str("127.0.0.1").unwrap(), + }; self } @@ -462,8 +459,8 @@ impl<'a> Context<'a> { } /// Get the target VHost list - pub fn target_vhosts(&self) -> Option> { - Some(self.target.vhosts.lock().unwrap().clone()) + pub fn target_vhosts(&self) -> Vec<(String, String)> { + self.target.vhosts.lock().unwrap().clone() } pub fn set_target(&mut self, target: String) { diff --git a/rust/src/nasl/utils/hosts.rs b/rust/src/nasl/utils/hosts.rs index c1baaca61..f3d105128 100644 --- a/rust/src/nasl/utils/hosts.rs +++ b/rust/src/nasl/utils/hosts.rs @@ -2,18 +2,19 @@ // // SPDX-License-Identifier: GPL-2.0-or-later WITH x11vnc-openssl-exception -use std::net::{SocketAddr, ToSocketAddrs}; +use std::net::{IpAddr, ToSocketAddrs}; use crate::nasl::utils::error::FunctionErrorKind; -pub fn resolve( - mut hostname: String, -) -> Result>>, FunctionErrorKind> { +pub fn resolve(mut hostname: String) -> Result, FunctionErrorKind> { //std::net to_socket_addrs() requires a port. Therefore, using a dummy port hostname.push_str(":5000"); match hostname.to_socket_addrs() { - Ok(addr) => Ok(Some(Box::new(addr))), + Ok(addr) => { + let ips = addr.into_iter().map(|x| x.ip()).collect::>(); + Ok(ips) + } // assumes that target is already a hostname Err(_) => Err(FunctionErrorKind::diagnostic_ret_null("Missing Hostname")), }