Skip to content

Commit 2567648

Browse files
committed
Add: Notus related built-in functions
This includes: - update_table_driven_lsc_data - security_notus - notus_error - notus
1 parent ad26d96 commit 2567648

File tree

4 files changed

+200
-4
lines changed

4 files changed

+200
-4
lines changed

rust/src/nasl/builtin/error.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use thiserror::Error;
66

77
use crate::nasl::prelude::*;
88
use crate::nasl::utils::error::FnErrorKind;
9+
use crate::notus::NotusError;
910

1011
use super::KBError;
1112
use super::cert::CertError;
@@ -28,6 +29,8 @@ pub enum BuiltinError {
2829
#[error("{0}")]
2930
Http(HttpError),
3031
#[error("{0}")]
32+
Notus(NotusError),
33+
#[error("{0}")]
3134
String(StringError),
3235
#[error("{0}")]
3336
Misc(MiscError),
@@ -101,6 +104,6 @@ builtin_error_variant!(CertError, Cert);
101104
builtin_error_variant!(SysError, Sys);
102105
builtin_error_variant!(FindServiceError, FindService);
103106
builtin_error_variant!(SnmpError, Snmp);
104-
107+
builtin_error_variant!(NotusError, Notus);
105108
#[cfg(feature = "nasl-builtin-raw-ip")]
106109
builtin_error_variant!(RawIpError, RawIp);

rust/src/nasl/builtin/mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ mod isotime;
1616
mod knowledge_base;
1717
pub mod misc;
1818
pub mod network;
19-
mod snmp;
20-
19+
mod notus;
2120
mod preferences;
2221
#[cfg(feature = "nasl-builtin-raw-ip")]
2322
pub mod raw_ip;
2423
mod regex;
2524
mod report_functions;
25+
mod snmp;
2626
mod ssh;
2727
mod string;
2828
mod sys;
@@ -64,7 +64,8 @@ pub fn nasl_std_functions() -> Executor {
6464
.add_set(find_service::FindService)
6565
.add_set(wmi::Wmi)
6666
.add_set(snmp::Snmp)
67-
.add_set(cert::NaslCerts::default());
67+
.add_set(cert::NaslCerts::default())
68+
.add_set(notus::NaslNotus::default());
6869

6970
#[cfg(feature = "nasl-builtin-raw-ip")]
7071
executor.add_set(raw_ip::RawIp);

rust/src/nasl/builtin/notus/mod.rs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
// SPDX-FileCopyrightText: 2023 Greenbone AG
2+
//
3+
// SPDX-License-Identifier: GPL-2.0-or-later WITH x11vnc-openssl-exception
4+
5+
use std::{
6+
collections::HashMap,
7+
io::{Read, Write},
8+
net::{SocketAddr, TcpStream},
9+
};
10+
11+
use greenbone_scanner_framework::models::FixedVersion;
12+
use nasl_function_proc_macro::nasl_function;
13+
use serde::{Deserialize, Serialize};
14+
use serde_json;
15+
16+
use crate::{
17+
function_set,
18+
nasl::{
19+
ArgumentError, FnError, NaslValue, ScanCtx, builtin::http::HttpError,
20+
utils::scan_ctx::NotusCtx,
21+
},
22+
notus::{HashsumProductLoader, Notus},
23+
storage::items::kb,
24+
};
25+
26+
#[derive(Serialize, Deserialize, Debug)]
27+
struct NotusResult {
28+
oid: String,
29+
message: String,
30+
}
31+
32+
#[nasl_function(named(pkg_list, os_release))]
33+
fn update_table_driven_lsc_data(
34+
context: &ScanCtx,
35+
pkg_list: &str,
36+
os_release: &str,
37+
) -> Result<(), FnError> {
38+
context.set_kb_item(
39+
kb::KbKey::Ssh(kb::Ssh::Login(kb::Login::PackageListNotus)),
40+
kb::KbItem::String(pkg_list.to_string()),
41+
)?;
42+
43+
context.set_kb_item(
44+
kb::KbKey::Ssh(kb::Ssh::Login(kb::Login::ReleaseNotus)),
45+
kb::KbItem::String(os_release.to_string()),
46+
)?;
47+
48+
Ok(())
49+
}
50+
51+
#[nasl_function]
52+
fn security_notus() {
53+
todo!()
54+
}
55+
56+
impl NaslNotus {
57+
fn notus_self(
58+
&self,
59+
notus: &mut Notus<HashsumProductLoader>,
60+
pkg_list: &[String],
61+
product: &str,
62+
) -> Result<NaslValue, FnError> {
63+
let res = notus.scan(product, pkg_list)?;
64+
65+
let mut ret = HashMap::new();
66+
for (oid, vuls) in res {
67+
let message = vuls.into_iter().map(|vul| match vul.fixed_version {
68+
FixedVersion::Single { version, specifier } => format!("Vulnerable package: {}\nInstalled version: {}\nFixed version: {:2}{}", vul.name, vul.installed_version, specifier.to_string(), version),
69+
FixedVersion::Range { start, end } => format!("Vulnerable package: {}\nInstalled version: {}\nFixed version: < {}\nFixed version: >={}", vul.name, vul.installed_version, start, end),
70+
}).collect::<Vec<String>>().join("\n\n");
71+
ret.insert(oid, NaslValue::String(message));
72+
}
73+
74+
Ok(NaslValue::Dict(ret))
75+
}
76+
77+
fn notus_extern(
78+
&self,
79+
addr: &SocketAddr,
80+
pkg_list: &[String],
81+
product: &str,
82+
) -> Result<NaslValue, FnError> {
83+
let mut sock = TcpStream::connect(addr).map_err(|e| HttpError::IO(e.kind()))?;
84+
let pkg_json = serde_json::to_string(pkg_list).unwrap();
85+
86+
let request = format!(
87+
"POST /notus/{} HTTP/1.1\r\nContent-Length: {}\r\n\r\n{}",
88+
product,
89+
pkg_json.len(),
90+
pkg_json
91+
);
92+
sock.write_all(request.as_bytes())
93+
.map_err(|e| HttpError::IO(e.kind()))?;
94+
let mut response = Vec::new();
95+
sock.read_to_end(&mut response)
96+
.map_err(|e| HttpError::IO(e.kind()))?;
97+
let response_str = String::from_utf8(response).unwrap();
98+
99+
// Split headers and body
100+
let parts: Vec<&str> = response_str.split("\r\n\r\n").collect();
101+
let body = if parts.len() > 1 {
102+
parts[1]
103+
} else {
104+
&response_str
105+
};
106+
107+
// Parse JSON array of results
108+
let results: Vec<NotusResult> = serde_json::from_str(body).unwrap();
109+
110+
// Convert to NaslValue (Dict mapping oid -> message)
111+
let mut ret = HashMap::new();
112+
for result in results {
113+
ret.insert(result.oid, NaslValue::String(result.message));
114+
}
115+
116+
Ok(NaslValue::Dict(ret))
117+
}
118+
119+
#[nasl_function]
120+
fn notus_error(&self) -> Option<String> {
121+
self.last_error.clone()
122+
}
123+
124+
#[nasl_function(named(pkg_list, product))]
125+
fn notus(
126+
&self,
127+
context: &ScanCtx,
128+
pkg_list: NaslValue,
129+
product: &str,
130+
) -> Result<NaslValue, FnError> {
131+
let notus = if let Some(notus) = &context.notus {
132+
notus
133+
} else {
134+
panic!("Notus context is not set, this is a toolkit error");
135+
};
136+
let pkg_list: Vec<String> = match pkg_list {
137+
NaslValue::String(s) => s.split(',').map(|s| s.trim().to_string()).collect(),
138+
NaslValue::Array(arr) => arr.iter().map(|v| v.to_string()).collect(),
139+
x => {
140+
return Err(ArgumentError::wrong_argument(
141+
"pkg_list",
142+
"String as Comma Separated List or Array of Strings",
143+
&format!("{:?}", x),
144+
)
145+
.into());
146+
}
147+
};
148+
match notus {
149+
NotusCtx::Direct(notus) => {
150+
self.notus_self(&mut notus.lock().unwrap(), &pkg_list, product)
151+
}
152+
NotusCtx::Address(addr) => self.notus_extern(addr, &pkg_list, product),
153+
}
154+
}
155+
}
156+
157+
#[derive(Default)]
158+
pub struct NaslNotus {
159+
last_error: Option<String>,
160+
}
161+
162+
function_set! {
163+
NaslNotus,
164+
(
165+
update_table_driven_lsc_data,
166+
security_notus,
167+
NaslNotus::notus_error,
168+
NaslNotus::notus,
169+
)
170+
}

rust/src/storage/items/kb.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ pub enum KbKey {
1717
/// Contains SSL/TLS Kb keys
1818
Ssl(Ssl),
1919

20+
Ssh(Ssh),
21+
2022
/// Contains Port related Kb keys
2123
Port(Port),
2224

@@ -73,6 +75,19 @@ pub enum Ssl {
7375
Ca,
7476
}
7577

78+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
79+
#[serde(rename_all = "snake_case")]
80+
pub enum Ssh {
81+
Login(Login),
82+
}
83+
84+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
85+
#[serde(rename_all = "snake_case")]
86+
pub enum Login {
87+
PackageListNotus,
88+
ReleaseNotus,
89+
}
90+
7691
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
7792
#[serde(rename_all = "snake_case")]
7893
pub enum Port {
@@ -175,6 +190,13 @@ impl Display for KbKey {
175190
KbKey::Ssl(Ssl::Password) => write!(f, "SSL/password"),
176191
KbKey::Ssl(Ssl::Ca) => write!(f, "SSL/ca"),
177192

193+
KbKey::Ssh(Ssh::Login(Login::PackageListNotus)) => {
194+
write!(f, "ssh/login/package_list_notus")
195+
}
196+
KbKey::Ssh(Ssh::Login(Login::ReleaseNotus)) => {
197+
write!(f, "ssh/login/release_notus")
198+
}
199+
178200
KbKey::Port(Port::Tcp(port)) => write!(f, "Ports/tcp/{port}"),
179201
KbKey::Port(Port::Udp(port)) => write!(f, "Ports/udp/{port}"),
180202

0 commit comments

Comments
 (0)