sentinel_modsec/engine/
scoring.rs1use crate::variables::MutableCollection;
4
5pub const ANOMALY_SCORE: &str = "anomaly_score";
7pub const INBOUND_ANOMALY_SCORE_THRESHOLD: &str = "inbound_anomaly_score_threshold";
8pub const OUTBOUND_ANOMALY_SCORE_THRESHOLD: &str = "outbound_anomaly_score_threshold";
9pub const SQL_INJECTION_SCORE: &str = "sql_injection_score";
10pub const XSS_SCORE: &str = "xss_score";
11pub const RFI_SCORE: &str = "rfi_score";
12pub const LFI_SCORE: &str = "lfi_score";
13pub const RCE_SCORE: &str = "rce_score";
14pub const PHP_INJECTION_SCORE: &str = "php_injection_score";
15pub const SESSION_FIXATION_SCORE: &str = "session_fixation_score";
16
17#[derive(Debug, Clone)]
19pub struct ScoringConfig {
20 pub paranoia_level: u8,
22 pub inbound_threshold: i32,
24 pub outbound_threshold: i32,
26 pub critical_score: i32,
28 pub error_score: i32,
30 pub warning_score: i32,
32 pub notice_score: i32,
34}
35
36impl Default for ScoringConfig {
37 fn default() -> Self {
38 Self {
39 paranoia_level: 1,
40 inbound_threshold: 5,
41 outbound_threshold: 4,
42 critical_score: 5,
43 error_score: 4,
44 warning_score: 3,
45 notice_score: 2,
46 }
47 }
48}
49
50impl ScoringConfig {
51 pub fn for_paranoia_level(level: u8) -> Self {
53 match level {
54 1 => Self::default(),
55 2 => Self {
56 paranoia_level: 2,
57 inbound_threshold: 10,
58 outbound_threshold: 8,
59 ..Default::default()
60 },
61 3 => Self {
62 paranoia_level: 3,
63 inbound_threshold: 15,
64 outbound_threshold: 12,
65 ..Default::default()
66 },
67 _ => Self {
68 paranoia_level: 4,
69 inbound_threshold: 20,
70 outbound_threshold: 16,
71 ..Default::default()
72 },
73 }
74 }
75
76 pub fn score_for_severity(&self, severity: u8) -> i32 {
78 match severity {
79 0 | 1 | 2 => self.critical_score,
80 3 => self.error_score,
81 4 => self.warning_score,
82 5 => self.notice_score,
83 _ => 0,
84 }
85 }
86}
87
88#[derive(Debug, Clone, Default)]
90pub struct AnomalyScore {
91 pub inbound: i32,
93 pub outbound: i32,
95 pub sqli: i32,
97 pub xss: i32,
99 pub rfi: i32,
101 pub lfi: i32,
103 pub rce: i32,
105 pub php: i32,
107 pub session_fixation: i32,
109}
110
111impl AnomalyScore {
112 pub fn new() -> Self {
114 Self::default()
115 }
116
117 pub fn add_inbound(&mut self, score: i32) {
119 self.inbound += score;
120 }
121
122 pub fn add_outbound(&mut self, score: i32) {
124 self.outbound += score;
125 }
126
127 pub fn inbound_exceeded(&self, threshold: i32) -> bool {
129 self.inbound >= threshold
130 }
131
132 pub fn outbound_exceeded(&self, threshold: i32) -> bool {
134 self.outbound >= threshold
135 }
136
137 pub fn sync_to_tx<C: MutableCollection>(&self, tx: &mut C) {
139 tx.set(ANOMALY_SCORE.to_string(), self.inbound.to_string());
140 tx.set(SQL_INJECTION_SCORE.to_string(), self.sqli.to_string());
141 tx.set(XSS_SCORE.to_string(), self.xss.to_string());
142 tx.set(RFI_SCORE.to_string(), self.rfi.to_string());
143 tx.set(LFI_SCORE.to_string(), self.lfi.to_string());
144 tx.set(RCE_SCORE.to_string(), self.rce.to_string());
145 tx.set(PHP_INJECTION_SCORE.to_string(), self.php.to_string());
146 tx.set(SESSION_FIXATION_SCORE.to_string(), self.session_fixation.to_string());
147 }
148
149 pub fn sync_from_tx<C: crate::variables::Collection>(&mut self, tx: &C) {
151 if let Some(values) = tx.get(ANOMALY_SCORE) {
152 if let Some(v) = values.first() {
153 self.inbound = v.parse().unwrap_or(0);
154 }
155 }
156 if let Some(values) = tx.get(SQL_INJECTION_SCORE) {
157 if let Some(v) = values.first() {
158 self.sqli = v.parse().unwrap_or(0);
159 }
160 }
161 if let Some(values) = tx.get(XSS_SCORE) {
162 if let Some(v) = values.first() {
163 self.xss = v.parse().unwrap_or(0);
164 }
165 }
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172 use crate::variables::{Collection, HashMapCollection};
173
174 #[test]
175 fn test_scoring_config() {
176 let config = ScoringConfig::default();
177 assert_eq!(config.score_for_severity(2), 5); assert_eq!(config.score_for_severity(3), 4); assert_eq!(config.score_for_severity(4), 3); }
181
182 #[test]
183 fn test_anomaly_score() {
184 let mut score = AnomalyScore::new();
185 score.add_inbound(5);
186 score.add_inbound(3);
187 assert_eq!(score.inbound, 8);
188 assert!(score.inbound_exceeded(5));
189 assert!(!score.inbound_exceeded(10));
190 }
191
192 #[test]
193 fn test_sync_to_tx() {
194 let mut score = AnomalyScore::new();
195 score.inbound = 15;
196 score.sqli = 10;
197
198 let mut tx = HashMapCollection::new();
199 score.sync_to_tx(&mut tx);
200
201 let anomaly_val = tx.get(ANOMALY_SCORE).and_then(|v| v.first().map(|s| s.to_string()));
202 let sqli_val = tx.get(SQL_INJECTION_SCORE).and_then(|v| v.first().map(|s| s.to_string()));
203 assert_eq!(anomaly_val, Some("15".to_string()));
204 assert_eq!(sqli_val, Some("10".to_string()));
205 }
206}