wafrift_evolution/differential/
probe.rs1include!(concat!(env!("OUT_DIR"), "/differential_data.rs"));
5
6#[derive(Debug, Clone, PartialEq)]
8pub struct Probe {
9 pub payload: String,
11 pub tests: ProbeTarget,
13 pub description: String,
15 pub expected_blocked: bool,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum ProbeTarget {
22 SqlKeyword(String),
24 SqlOperator(String),
26 SqlComment(String),
28 SqlQuote,
30 SqlTautology(String),
32 XssTag(String),
34 XssEvent(String),
36 XssExecFunction(String),
38 CmdSeparator(String),
40 CmdCommand(String),
42 CmdPath(String),
44 Baseline,
46}
47
48#[must_use]
68pub fn generate_probes() -> Vec<Probe> {
69 let mut probes = Vec::new();
70 probes.push(baseline_probe(BASELINE_PAYLOAD, BASELINE_DESCRIPTION));
71 probes.extend(sql_keyword_probes());
72 probes.extend(sql_operator_probes());
73 probes.extend(sql_comment_probes());
74 probes.push(Probe {
75 payload: "'".into(),
76 tests: ProbeTarget::SqlQuote,
77 description: "SQL single quote".into(),
78 expected_blocked: true,
79 });
80 probes.extend(sql_tautology_probes());
81 probes.extend(xss_tag_probes());
82 probes.extend(xss_event_probes());
83 probes.extend(xss_function_probes());
84 probes.extend(command_separator_probes());
85 probes.extend(command_name_probes());
86 probes.extend(command_path_probes());
87 probes
88}
89
90pub(crate) fn baseline_probe(payload: &str, description: &str) -> Probe {
91 Probe {
92 payload: payload.into(),
93 tests: ProbeTarget::Baseline,
94 description: description.into(),
95 expected_blocked: false,
96 }
97}
98
99pub(crate) fn sql_keyword_probes() -> Vec<Probe> {
100 SQL_KEYWORDS
101 .iter()
102 .map(|&keyword| Probe {
103 payload: format!("test {keyword} value"),
104 tests: ProbeTarget::SqlKeyword(keyword.to_string()),
105 description: format!("SQL keyword: {keyword}"),
106 expected_blocked: true,
107 })
108 .collect()
109}
110
111pub(crate) fn sql_operator_probes() -> Vec<Probe> {
112 SQL_OPERATORS
113 .iter()
114 .map(|&operator| Probe {
115 payload: format!("test{operator}test"),
116 tests: ProbeTarget::SqlOperator(operator.to_string()),
117 description: format!("SQL operator: {operator}"),
118 expected_blocked: true,
119 })
120 .collect()
121}
122
123pub(crate) fn sql_comment_probes() -> Vec<Probe> {
124 SQL_COMMENTS
125 .iter()
126 .map(|&comment| Probe {
127 payload: format!("test{comment}test"),
128 tests: ProbeTarget::SqlComment(comment.to_string()),
129 description: format!("SQL comment: {comment}"),
130 expected_blocked: true,
131 })
132 .collect()
133}
134
135pub(crate) fn sql_tautology_probes() -> Vec<Probe> {
136 SQL_TAUTOLOGIES
137 .iter()
138 .map(|&tautology| Probe {
139 payload: tautology.to_string(),
140 tests: ProbeTarget::SqlTautology(tautology.to_string()),
141 description: format!("SQL tautology: {tautology}"),
142 expected_blocked: true,
143 })
144 .collect()
145}
146
147pub(crate) fn xss_tag_probes() -> Vec<Probe> {
148 XSS_TAGS
149 .iter()
150 .map(|&(name, payload, expected_blocked)| Probe {
151 payload: payload.into(),
152 tests: ProbeTarget::XssTag(name.into()),
153 description: format!("XSS tag: {name}"),
154 expected_blocked,
155 })
156 .collect()
157}
158
159pub(crate) fn xss_event_probes() -> Vec<Probe> {
160 XSS_EVENTS
161 .iter()
162 .map(|&event| Probe {
163 payload: format!("<x {event}=1>"),
164 tests: ProbeTarget::XssEvent(event.to_string()),
165 description: format!("XSS event: {event}"),
166 expected_blocked: true,
167 })
168 .collect()
169}
170
171pub(crate) fn xss_function_probes() -> Vec<Probe> {
172 XSS_FUNCTIONS
173 .iter()
174 .map(|&(name, payload, expected_blocked)| Probe {
175 payload: payload.into(),
176 tests: ProbeTarget::XssExecFunction(name.into()),
177 description: format!("XSS function: {name}"),
178 expected_blocked,
179 })
180 .collect()
181}
182
183pub(crate) fn command_separator_probes() -> Vec<Probe> {
184 COMMAND_SEPARATORS
185 .iter()
186 .map(|&separator| Probe {
187 payload: format!("test{separator}test"),
188 tests: ProbeTarget::CmdSeparator(separator.to_string()),
189 description: format!("CMD separator: {separator}"),
190 expected_blocked: true,
191 })
192 .collect()
193}
194
195pub(crate) fn command_name_probes() -> Vec<Probe> {
196 COMMAND_NAMES
197 .iter()
198 .map(|&command| Probe {
199 payload: command.to_string(),
200 tests: ProbeTarget::CmdCommand(command.to_string()),
201 description: format!("CMD command: {command}"),
202 expected_blocked: false,
203 })
204 .collect()
205}
206
207pub(crate) fn command_path_probes() -> Vec<Probe> {
208 COMMAND_PATHS
209 .iter()
210 .map(|&path| Probe {
211 payload: path.to_string(),
212 tests: ProbeTarget::CmdPath(path.to_string()),
213 description: format!("CMD path: {path}"),
214 expected_blocked: true,
215 })
216 .collect()
217}
218
219#[cfg(test)]
220mod tests {
221 use super::{ProbeTarget, generate_probes};
222
223 #[test]
224 fn generate_probes_has_baseline() {
225 let probes = generate_probes();
226 assert!(
227 probes
228 .iter()
229 .any(|probe| probe.tests == ProbeTarget::Baseline)
230 );
231 }
232
233 #[test]
234 fn generate_probes_covers_all_categories() {
235 let probes = generate_probes();
236 assert!(
237 probes
238 .iter()
239 .any(|probe| matches!(probe.tests, ProbeTarget::SqlKeyword(_)))
240 );
241 assert!(
242 probes
243 .iter()
244 .any(|probe| matches!(probe.tests, ProbeTarget::SqlOperator(_)))
245 );
246 assert!(
247 probes
248 .iter()
249 .any(|probe| matches!(probe.tests, ProbeTarget::SqlComment(_)))
250 );
251 assert!(
252 probes
253 .iter()
254 .any(|probe| matches!(probe.tests, ProbeTarget::SqlQuote))
255 );
256 assert!(
257 probes
258 .iter()
259 .any(|probe| matches!(probe.tests, ProbeTarget::SqlTautology(_)))
260 );
261 assert!(
262 probes
263 .iter()
264 .any(|probe| matches!(probe.tests, ProbeTarget::XssTag(_)))
265 );
266 assert!(
267 probes
268 .iter()
269 .any(|probe| matches!(probe.tests, ProbeTarget::XssEvent(_)))
270 );
271 assert!(
272 probes
273 .iter()
274 .any(|probe| matches!(probe.tests, ProbeTarget::XssExecFunction(_)))
275 );
276 assert!(
277 probes
278 .iter()
279 .any(|probe| matches!(probe.tests, ProbeTarget::CmdSeparator(_)))
280 );
281 assert!(
282 probes
283 .iter()
284 .any(|probe| matches!(probe.tests, ProbeTarget::CmdCommand(_)))
285 );
286 assert!(
287 probes
288 .iter()
289 .any(|probe| matches!(probe.tests, ProbeTarget::CmdPath(_)))
290 );
291 }
292
293 #[test]
294 fn generate_probes_has_many() {
295 let probes = generate_probes();
296 assert!(
297 probes.len() >= 60,
298 "expected 60+ probes, got {}",
299 probes.len()
300 );
301 }
302
303 #[test]
304 fn probes_have_descriptions() {
305 let probes = generate_probes();
306 for probe in &probes {
307 assert!(
308 !probe.description.is_empty(),
309 "probe should have description"
310 );
311 assert!(
312 !probe.payload.is_empty() || probe.tests == ProbeTarget::Baseline,
313 "probe should have payload"
314 );
315 }
316 }
317
318 #[test]
319 fn sql_quote_expected_blocked() {
320 let probes = generate_probes();
321 let quote = probes
322 .iter()
323 .find(|p| matches!(p.tests, ProbeTarget::SqlQuote));
324 assert!(quote.is_some());
325 assert!(
326 quote.unwrap().expected_blocked,
327 "SQL quote should be expected blocked"
328 );
329 }
330}