wafrift_evolution/differential/
analysis.rs1use super::probe::{Probe, ProbeTarget};
2
3#[derive(Debug, Clone)]
5pub struct DifferentialResult {
6 pub blocked_sql_keywords: Vec<String>,
8 pub blocked_sql_operators: Vec<String>,
10 pub blocked_sql_comments: Vec<String>,
12 pub blocks_sql_quotes: bool,
14 pub blocked_tautologies: Vec<String>,
16 pub blocked_xss_tags: Vec<String>,
18 pub blocked_xss_events: Vec<String>,
20 pub blocked_xss_functions: Vec<String>,
22 pub blocked_cmd_separators: Vec<String>,
24 pub blocked_cmd_commands: Vec<String>,
26 pub blocked_cmd_paths: Vec<String>,
28 pub baseline_blocked: bool,
30 pub total_probes: usize,
32 pub total_blocked: usize,
34}
35
36impl DifferentialResult {
37 #[must_use]
39 pub fn new() -> Self {
40 Self {
41 blocked_sql_keywords: Vec::new(),
42 blocked_sql_operators: Vec::new(),
43 blocked_sql_comments: Vec::new(),
44 blocks_sql_quotes: false,
45 blocked_tautologies: Vec::new(),
46 blocked_xss_tags: Vec::new(),
47 blocked_xss_events: Vec::new(),
48 blocked_xss_functions: Vec::new(),
49 blocked_cmd_separators: Vec::new(),
50 blocked_cmd_commands: Vec::new(),
51 blocked_cmd_paths: Vec::new(),
52 baseline_blocked: false,
53 total_probes: 0,
54 total_blocked: 0,
55 }
56 }
57
58 pub fn record(&mut self, probe: &Probe, was_blocked: bool) {
60 self.total_probes += 1;
61 if was_blocked {
62 self.total_blocked += 1;
63 }
64 match &probe.tests {
65 ProbeTarget::Baseline => self.baseline_blocked = was_blocked,
66 ProbeTarget::SqlKeyword(keyword) => {
67 if was_blocked && !self.blocked_sql_keywords.contains(keyword) {
68 self.blocked_sql_keywords.push(keyword.clone());
69 }
70 }
71 ProbeTarget::SqlOperator(operator) => {
72 if was_blocked && !self.blocked_sql_operators.contains(operator) {
73 self.blocked_sql_operators.push(operator.clone());
74 }
75 }
76 ProbeTarget::SqlComment(comment) => {
77 if was_blocked && !self.blocked_sql_comments.contains(comment) {
78 self.blocked_sql_comments.push(comment.clone());
79 }
80 }
81 ProbeTarget::SqlQuote => self.blocks_sql_quotes = was_blocked,
82 ProbeTarget::SqlTautology(tautology) => {
83 if was_blocked && !self.blocked_tautologies.contains(tautology) {
84 self.blocked_tautologies.push(tautology.clone());
85 }
86 }
87 ProbeTarget::XssTag(tag) => {
88 if was_blocked && !self.blocked_xss_tags.contains(tag) {
89 self.blocked_xss_tags.push(tag.clone());
90 }
91 }
92 ProbeTarget::XssEvent(event) => {
93 if was_blocked && !self.blocked_xss_events.contains(event) {
94 self.blocked_xss_events.push(event.clone());
95 }
96 }
97 ProbeTarget::XssExecFunction(function) => {
98 if was_blocked && !self.blocked_xss_functions.contains(function) {
99 self.blocked_xss_functions.push(function.clone());
100 }
101 }
102 ProbeTarget::CmdSeparator(separator) => {
103 if was_blocked && !self.blocked_cmd_separators.contains(separator) {
104 self.blocked_cmd_separators.push(separator.clone());
105 }
106 }
107 ProbeTarget::CmdCommand(command) => {
108 if was_blocked && !self.blocked_cmd_commands.contains(command) {
109 self.blocked_cmd_commands.push(command.clone());
110 }
111 }
112 ProbeTarget::CmdPath(path) => {
113 if was_blocked && !self.blocked_cmd_paths.contains(path) {
114 self.blocked_cmd_paths.push(path.clone());
115 }
116 }
117 }
118 }
119}
120impl Default for DifferentialResult {
121 fn default() -> Self {
122 Self::new()
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::DifferentialResult;
129 use crate::differential::{ProbeTarget, generate_quick_probes};
130
131 #[test]
132 fn record_basic_results() {
133 let probes = generate_quick_probes();
134 let mut result = DifferentialResult::new();
135 for probe in &probes {
136 let blocked = match &probe.tests {
137 ProbeTarget::SqlTautology(_) | ProbeTarget::SqlKeyword(_) => true,
138 ProbeTarget::XssTag(tag) if tag == "script" => true,
139 _ => false,
140 };
141 result.record(probe, blocked);
142 }
143 assert_eq!(result.total_probes, probes.len());
144 assert!(result.total_blocked > 0);
145 assert!(!result.baseline_blocked);
146 }
147
148 #[test]
149 fn record_deduplicates() {
150 let mut result = DifferentialResult::new();
151 let probe = crate::differential::Probe {
152 payload: "test".into(),
153 tests: ProbeTarget::SqlKeyword("SELECT".into()),
154 description: "test".into(),
155 expected_blocked: true,
156 };
157 result.record(&probe, true);
158 result.record(&probe, true);
159 assert_eq!(result.blocked_sql_keywords.len(), 1);
160 assert_eq!(result.blocked_sql_keywords[0], "SELECT");
161 }
162
163 #[test]
164 fn default_impl_equivalent_to_new() {
165 let first = DifferentialResult::new();
166 let second = DifferentialResult::default();
167 assert_eq!(first.total_probes, second.total_probes);
168 assert_eq!(first.total_blocked, second.total_blocked);
169 }
170}