1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
use serde_xml_rs;
use regex::Regex;
pub mod report;
pub mod policy;
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct NessusClientDatav2 {
#[serde(rename="Report")]
pub report: report::Report,
#[serde(rename="Policy")]
pub policy: policy::Policy,
}
mod errors {
use serde_xml_rs;
error_chain! {
foreign_links {
Xml(serde_xml_rs::Error);
}
}
}
pub use self::errors::*;
pub fn parse<I: Into<String>>(buffer: I) -> Result<NessusClientDatav2> {
let report = serde_xml_rs::deserialize(buffer.into().as_bytes())?;
Ok(report)
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct PatchAdvice {
old_version: String,
new_version: String,
severity: u64,
}
impl PatchAdvice {
pub fn new<I: Into<String>>(old_version: I, new_version: I, severity: u64) -> PatchAdvice {
PatchAdvice {
old_version: old_version.into(),
new_version: new_version.into(),
severity: severity,
}
}
}
impl report::ReportItem {
pub fn patch_needed(&self) -> Option<Vec<PatchAdvice>> {
lazy_static! {
static ref RE: Vec<Regex> = vec![
Regex::new("Remote package installed : (?P<old>.*)\nShould be : (?P<new>.*)").unwrap(),
Regex::new("The following vulnerable instance of Java is installed on the\nremote host :\n\n Path : (?P<path>.*)\n Installed version : (?P<old>.*)\n Fixed version : (?P<new>.*)").unwrap(),
Regex::new("- Installed package : (?P<old>.*)\n Fixed package : (?P<new>.*)").unwrap(),
];
}
match self.plugin_output {
Some(ref output) => {
let advice: Vec<PatchAdvice> = RE.iter()
.flat_map(|re| {
re.captures_iter(output)
})
.map(|caps| {
PatchAdvice {
old_version: caps["old"].to_owned(),
new_version: caps["new"].to_owned(),
severity: self.severity.parse().unwrap(),
}
})
.collect();
if advice.len() > 0 {
Some(advice)
} else {
None
}
},
None => None
}
}
}
impl report::ReportHost {
pub fn patch_needed(&self) -> Option<Vec<PatchAdvice>> {
let advice: Vec<PatchAdvice> = self.report_items.iter()
.flat_map(|item| {
match item.patch_needed() {
Some(advice) => advice,
None => Vec::new(),
}
})
.collect();
if advice.len() > 0 {
Some(advice)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::parse;
use super::PatchAdvice;
use super::report::ReportItem;
#[test]
fn test_parse() {
let reports = vec![
("nessus_report_local2.nessus", include_str!("../../files/nessus_report_local2.nessus")),
("nessus_report_local_3.nessus", include_str!("../../files/nessus_report_local_3.nessus")),
("nessus_report_localpci.nessus", include_str!("../../files/nessus_report_localpci.nessus")),
("nessus_report_test_local.nessus", include_str!("../../files/nessus_report_test_local.nessus")),
];
for (name, report) in reports {
let report = parse(report);
println!("report {:?}: {:?}", name, report);
assert!(report.is_ok());
}
}
#[test]
fn test_debian_patch_advice() {
let item = ReportItem {
port: "0".to_owned(),
svc_name: "general".to_owned(),
protocol: "tcp".to_owned(),
severity: "3".to_owned(),
plugin_id: "101322".to_owned(),
plugin_name: "Debian DSA-3904-1 : bind9 - security update".to_owned(),
plugin_family: "Debian Local Security Checks".to_owned(),
description: "Clement Berthaux from Synaktiv discovered two vulnerabilities in BIND, a DNS server implementation. They allow an attacker to bypass TSIG authentication by sending crafted DNS packets to a server.\n\n - CVE-2017-3142 An attacker who is able to send and receive messages to an authoritative DNS server and who has knowledge of a valid TSIG key name may be able to circumvent TSIG authentication of AXFR requests via a carefully constructed request packet. A server that relies solely on TSIG keys for protection with no other ACL protection could be manipulated into :\n\n - providing an AXFR of a zone to an unauthorized recipient\n - accepting bogus NOTIFY packets\n\n - CVE-2017-3143 An attacker who is able to send and receive messages to an authoritative DNS server and who has knowledge of a valid TSIG key name for the zone and service being targeted may be able to manipulate BIND into accepting an unauthorized dynamic update.".to_owned(),
fname: "debian_DSA-3904.nasl".to_owned(),
plugin_modification_date: "2017/07/12".to_owned(),
plugin_publication_date: "2017/07/10".to_owned(),
plugin_type: "local".to_owned(),
risk_factor: "High".to_owned(),
script_version: "$Revision: 3.3 $".to_owned(),
solution: "Upgrade the bind9 packages.\n\nFor the oldstable distribution (jessie), these problems have been fixed in version 1:9.9.5.dfsg-9+deb8u12.\n\nFor the stable distribution (stretch), these problems have been fixed in version 1:9.10.3.dfsg.P4-12.3+deb9u1.".to_owned(),
synopsis: "The remote Debian host is missing a security-related update.".to_owned(),
plugin_output: Some("Remote package installed : bind9-host_1:9.9.5.dfsg-9+deb8u11\nShould be : bind9-host_1:9.9.5.dfsg-9+deb8u12\nRemote package installed : dnsutils_1:9.9.5.dfsg-9+deb8u11\nShould be : dnsutils_1:9.9.5.dfsg-9+deb8u12\nRemote package installed : libbind9-90_1:9.9.5.dfsg-9+deb8u11\nShould be : libbind9-90_1:9.9.5.dfsg-9+deb8u12\nRemote package installed : libdns-export100_1:9.9.5.dfsg-9+deb8u11\nShould be : libdns-export100_1:9.9.5.dfsg-9+deb8u12\nRemote package installed : libdns100_1:9.9.5.dfsg-9+deb8u11\nShould be : libdns100_1:9.9.5.dfsg-9+deb8u12\nRemote package installed : libirs-export91_1:9.9.5.dfsg-9+deb8u11\nShould be : libirs-export91_1:9.9.5.dfsg-9+deb8u12\nRemote package installed : libisc-export95_1:9.9.5.dfsg-9+deb8u11\nShould be : libisc-export95_1:9.9.5.dfsg-9+deb8u12\nRemote package installed : libisc95_1:9.9.5.dfsg-9+deb8u11\nShould be : libisc95_1:9.9.5.dfsg-9+deb8u12\nRemote package installed : libisccc90_1:9.9.5.dfsg-9+deb8u11\nShould be : libisccc90_1:9.9.5.dfsg-9+deb8u12\nRemote package installed : libisccfg-export90_1:9.9.5.dfsg-9+deb8u11\nShould be : libisccfg-export90_1:9.9.5.dfsg-9+deb8u12\nRemote package installed : libisccfg90_1:9.9.5.dfsg-9+deb8u11\nShould be : libisccfg90_1:9.9.5.dfsg-9+deb8u12\nRemote package installed : liblwres90_1:9.9.5.dfsg-9+deb8u11\nShould be : liblwres90_1:9.9.5.dfsg-9+deb8u12".to_owned())
};
let patch_advice = item.patch_needed();
assert_eq!(patch_advice, Some(vec![
PatchAdvice::new("bind9-host_1:9.9.5.dfsg-9+deb8u11", "bind9-host_1:9.9.5.dfsg-9+deb8u12", 3),
PatchAdvice::new("dnsutils_1:9.9.5.dfsg-9+deb8u11", "dnsutils_1:9.9.5.dfsg-9+deb8u12", 3),
PatchAdvice::new("libbind9-90_1:9.9.5.dfsg-9+deb8u11", "libbind9-90_1:9.9.5.dfsg-9+deb8u12", 3),
PatchAdvice::new("libdns-export100_1:9.9.5.dfsg-9+deb8u11", "libdns-export100_1:9.9.5.dfsg-9+deb8u12", 3),
PatchAdvice::new("libdns100_1:9.9.5.dfsg-9+deb8u11", "libdns100_1:9.9.5.dfsg-9+deb8u12", 3),
PatchAdvice::new("libirs-export91_1:9.9.5.dfsg-9+deb8u11", "libirs-export91_1:9.9.5.dfsg-9+deb8u12", 3),
PatchAdvice::new("libisc-export95_1:9.9.5.dfsg-9+deb8u11", "libisc-export95_1:9.9.5.dfsg-9+deb8u12", 3),
PatchAdvice::new("libisc95_1:9.9.5.dfsg-9+deb8u11", "libisc95_1:9.9.5.dfsg-9+deb8u12", 3),
PatchAdvice::new("libisccc90_1:9.9.5.dfsg-9+deb8u11", "libisccc90_1:9.9.5.dfsg-9+deb8u12", 3),
PatchAdvice::new("libisccfg-export90_1:9.9.5.dfsg-9+deb8u11", "libisccfg-export90_1:9.9.5.dfsg-9+deb8u12", 3),
PatchAdvice::new("libisccfg90_1:9.9.5.dfsg-9+deb8u11", "libisccfg90_1:9.9.5.dfsg-9+deb8u12", 3),
PatchAdvice::new("liblwres90_1:9.9.5.dfsg-9+deb8u11", "liblwres90_1:9.9.5.dfsg-9+deb8u12", 3)
]));
}
}