@@ -121,6 +121,7 @@ impl DetectorRegistry {
121121 self . register ( SsrfMetadataDetector ) ;
122122 self . register ( ExfiltrationChainDetector ) ;
123123 self . register ( SecretLeakageDetector ) ;
124+ self . register ( WebArchiveProbeDetector ) ;
124125 }
125126
126127 pub fn detect_log_anomalies ( & self , entries : & [ LogEntry ] ) -> Vec < LogAnomaly > {
@@ -203,6 +204,7 @@ struct SensitiveFileAccessDetector;
203204struct SsrfMetadataDetector ;
204205struct ExfiltrationChainDetector ;
205206struct SecretLeakageDetector ;
207+ struct WebArchiveProbeDetector ;
206208
207209impl LogDetector for SqlInjectionProbeDetector {
208210 fn id ( & self ) -> & ' static str {
@@ -640,6 +642,50 @@ impl LogDetector for SecretLeakageDetector {
640642 }
641643}
642644
645+ impl LogDetector for WebArchiveProbeDetector {
646+ fn id ( & self ) -> & ' static str {
647+ "web.archive-probe"
648+ }
649+
650+ fn family ( & self ) -> DetectorFamily {
651+ DetectorFamily :: Web
652+ }
653+
654+ fn detect ( & self , entries : & [ LogEntry ] ) -> Vec < DetectorFinding > {
655+ let matches = matching_entries (
656+ entries,
657+ & [
658+ ".zip" ,
659+ ".tar.gz" ,
660+ ".tgz" ,
661+ ".sql" ,
662+ ".7z" ,
663+ ".rar" ,
664+ ".tar.bz2" ,
665+ ".tar.xz" ,
666+ ".dump" ,
667+ ".bak" ,
668+ ] ,
669+ ) ;
670+
671+ if matches. len ( ) < 3 {
672+ return Vec :: new ( ) ;
673+ }
674+
675+ vec ! [ DetectorFinding {
676+ detector_id: self . id( ) . to_string( ) ,
677+ family: self . family( ) ,
678+ description: format!(
679+ "Suspicious archive/backup file probing detected in {} HTTP requests" ,
680+ matches. len( )
681+ ) ,
682+ severity: threshold_severity( matches. len( ) , 3 , 8 ) ,
683+ confidence: 82 ,
684+ sample_line: matches[ 0 ] . line. clone( ) ,
685+ } ]
686+ }
687+ }
688+
643689fn matching_entries < ' a > ( entries : & ' a [ LogEntry ] , patterns : & [ & str ] ) -> Vec < & ' a LogEntry > {
644690 entries
645691 . iter ( )
0 commit comments