6NFdZddlZddlZddlZddlZddlZddlZddlm Z ddl m Z ddl m Z ddlmZddlmZddlmZmZmZejeZGd d ZGd d ZdS) z1Collector for WordPress CVE protection incidents.N)Path) defaultdict)WPSite) get_data_dir)IncidentFileParser)upsert_wordpress_incidentbulk_create_wordpress_incidentsbuild_incident_dictcheZdZdZ ddededefdZd Zd ed ed ee effd Z d ed efdZ dS)IncidentRateLimitera Rate limiter to prevent DoS attacks via incident flooding. Implements per-rule-per-IP rate limiting as per spec: - Maximum 100 incidents for each rule from the same IP within 15 minutes Memory-optimized implementation with bounded entry count using LRU eviction. d'max_incidents_per_rule_per_iptime_window_secondsmax_unique_entriesc||_||_||_tt|_d|_tj|_dS)aI Initialize the rate limiter. Args: max_incidents_per_rule_per_ip: Max incidents per rule per IP (default: 100) time_window_seconds: Time window in seconds (default: 900 = 15 minutes) max_unique_entries: Max unique (rule_id, IP) combinations to track (default: 10000) <N) max_per_rule_per_ip time_windowrrlistincident_timescleanup_intervaltime last_cleanup)selfrrrs a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/incident_collector.py__init__zIncidentRateLimiter.__init__"sK$A ."4*$// " IKKc~ tj}||jz g}|jD]5\}} fd|D}|r ||j|< ||6|D] }|j|= t |j|jkrt|jd}tdt |jt|jdzz }|d|D] \}}|j|=t d|j|||_ dS)zHRemove records older than the time window and enforce max entries limit.c g|] }|k| Sr".0tscutoffs r z.C===Rfbrc2|dr|ddndS)Nrr")xs rz:IncidentRateLimiter._cleanup_old_records..Rs14ad1gg1r)keyr*g?NzARate limiter exceeded max entries (%d), removed %d oldest entries) rrritemsappendlenrsortedmaxintloggerwarningr) rnowkeys_to_deleter- timestampsrecententries_by_age num_to_remove_r&s @r_cleanup_old_recordsz(IncidentRateLimiter._cleanup_old_records:sikkt''#288:: + +OC====:===F ++1#C((%%c****! ) )C#C(( t" # #d&= = =##))++44N  D'((3t/F/L+M+MMM)-8 - -Q',, NN'     rrule_id attacker_ipreturnc  tj|jz |jkr|tj}||jz ||f}||jvrB|j|} fd|D}|r||j|<t |}n |j|=d}nd}||jkr#|jdz}dd|d|d|d|jd |d fSd S) z Check if adding an incident would exceed rate limits. Args: rule_id: Rule identifier attacker_ip: IP address of the attacker Returns: Tuple of (allowed: bool, reason: str) c g|] }|k| Sr"r"r#s rr'z8IncidentRateLimiter.check_rate_limit..r(rrrFzRate limit exceeded for rule z from IP z: /z within z minutes)TOK)rrrr=rrr0r) rr>r?r6r-r8r9 recent_countwindow_minutesr&s @rcheck_rate_limitz$IncidentRateLimiter.check_rate_limitfsF 9;;* *T-B B B  % % ' ' 'ikkt'' $ $% % %,S1J====:===F !+1#C("6{{ ', L 43 3 3!-3N1G11#11$11'+'?11'111 zrctj}||f}||jvr |g|j|<dS|j|}t||jkr|d||dS)z Record that an incident was added. Args: rule_id: Rule identifier attacker_ip: IP address rN)rrr0rpopr/)rr>r?r6r-r8s rrecord_incidentz#IncidentRateLimiter.record_incidentsikk $ d) ) )(+uD  $ $ $,S1J:$":::q!!!   c " " " " "rN)r rr) __name__ __module__ __qualname____doc__r3rr=strtupleboolrGrJr"rrr r s.1#&"' (('*(!( ((((0* * * X22),2 tSy 2222h#s#######rr c teZdZdZddedzfdZ ddededefd Z dd eededefd Z e d e dee fd Z ejdZe de defdZdededzdedefdZdededzfdZdeedededzdedef dZdedededzdefdZdedededzdededef dZdS)IncidentCollectorzM Collect and persist WordPress incidents from plugin incident files. N rate_limitercV|p t|_t|_dS)z Initialize the incident collector. Args: rate_limiter: Optional rate limiter (creates default if not provided) N)r rTrparser)rrTs rrzIncidentCollector.__init__s))A,?,A,A(** rTsitedelete_after_processingr@cKg} t|d{V}td|||std|gS||}td|||std|gStdt ||||}|D]5}|||||d{V}||6n3#t$r&} t d|| Yd} ~ nd} ~ wwxYwt dt |||S) ab Collect incidents from a single WordPress site. Args: site: WordPress site to collect incidents from ruleset_version: Version of the ruleset being used delete_after_processing: Whether to delete incident files after processing Returns: List of collected Incident objects NzData directory for site %s: %sz)Data directory does not exist for site %sIncident files for site %s: %sz#No incident files found for site %sz%Found %d incident file(s) for site %sz*Error collecting incidents for site %s: %sz$Collected %d incident(s) for site %s) rr4debugexists_get_incident_filesr0_get_site_username _process_fileextend Exceptionerrorinfo) rrWrXcollected_incidentsdata_dirincident_filesusername incident_filefile_incidentses rcollect_incidents_for_sitez,IncidentCollector.collect_incidents_for_sites !% )$////////H LL94 J J J??$$  H$OOO !55h??N LL0$   "  BDIII LL7N##    ..t44H!/ ; ; '+'9'9!+ (("""""" $**>:::: ;    LL<           2 # $ $    #"s&A!D/(AD/8A6D// E9EEsitescKg}|D]3}|||d{V}||4|r6tdt |t ||S)a Collect incidents from multiple WordPress sites. Args: sites: List of WordPress sites delete_after_processing: Whether to delete incident files after processing Returns: List of collected Incident objects Nz2Collected %d WordPress incident(s) from %d site(s))rkr`r4rcr0)rrlrXall_collected_incidentsrWsite_incidentss rcollect_incidents_for_sitesz-IncidentCollector.collect_incidents_for_sitess#% ; ;D#'#B#B'$$N $ * *> : : : : "  KKD+,,E     '&rrec|dz }td|||r|std|gSg}|D]k} t j|}n#t$rY$wxYwtj |j r*| |r| |ltd|||S)a Get all incident files in the incidents directory. Only returns files older than one hour to give the WordPress plugin time to process and finalize the incident data before collection. Args: data_dir: Path to the imunify-security data directory Returns: List of incident file paths, sorted by modification time incidentsz#Incidents directory for site %s: %sz.Incidents directory does not exist for site %srZ) r4r[r\is_diriterdiroslstatOSError stat_moduleS_ISREGst_mode_is_incident_filer/)clsre incidents_dirrffsts rr]z%IncidentCollector._get_incident_files"s0!;.  18]   ##%% ]-A-A-C-C  LL@(   I&&(( ) )A Xa[[    "2:.. )33H3H3K3K )%%a((( ,h   sB B"!B"z^\d{4}-\d{2}-\d{2}-\d{2}\.php$ file_pathcZt|j|jS)z Check if a file is an incident file based on naming pattern. Args: file_path: Path to the file to check Returns: True if file matches pattern yyyy-mm-dd-hh.php )rQ _FILE_PATTERNmatchname)r|rs rr{z#IncidentCollector._is_incident_fileLs%C%++IN;;<<">" ## ' N$$$555 8-:LMMM& &    LLC"     IIIIII s%AC BC D*#D DDc tj|j}|jS#t$r-}t d|j||Yd}~dSd}~wwxYw)Nz.Failed to get username for uid=%d, site %s: %s)pwdgetpwuiduidpw_namerar4rb)rrW user_inforjs rr^z$IncidentCollector._get_site_usernamess  TX..I$ $    LL@     44444 s" A"AArrincident_file_namecg}d}|D]}|dd}|dp|dd} |j|| \} } | s"td|| |dz }|j|j||jd} t|| } | | |j || g}|rD t|}n3#t$r&}t d ||Yd}~nd}~wwxYwtd |t|||S) Nrr>unknown REMOTE_ADDRr?#Rate limit exceeded for site %s: %sr*domain site_pathrguser_idz+Failed to bulk insert incidents from %s: %sz(Processed file %s: %d stored, %d dropped)getrTrGr4r5rdocrootrr r/rJr rarbrcr0)rrrrWrgrincidents_to_insert dropped_countincidentr>r?allowedreason site_info incident_datacreated_incidentsrjs rrz)IncidentCollector._process_file_incidentss! " D DHll9i88G",,}55y::K#/@@OGV  9 " +!\$8 I 0)DDM  & &} 5 5 5   - -g{ C C C C   $C'%%!!    A&   6  ! " "     ! s(C88 D(D##D(rc4|dd}|dp|dd}|j||\}}|std||dS|||||||S)Nr>rrr?r)rrTrGr4r5_store_incident) rrrWrgrr>r?rrs r_process_incidentz#IncidentCollector._process_incidents,,y)44ll=11 X\\ 96 6 +<<       NN5    4##          rr>r?c |j|j||jd}t||}|j|||S#t $r'}td||Yd}~dSd}~wwxYw)Nrz$Failed to store incident from %s: %s) rrrrrTrJrar4rb) rrrWrgr>r?rrrjs rrz!IncidentCollector._store_incidents +!\$8 I 1H   - -g{ C C CO    LL6"    44444  sAA A6A11A6)N)T)rKrLrMrNr rrrQrrkrp classmethodrr]recompilerr{rOr_r^dictrrrr"rrrSrSs++%84%?++++)-?#?#?#"&?#  ?#?#?#?#H)-''F|'"&'  ''''B$4$DJ$$$[$NBJ@AAM =$ =4 = = =[ =//* / "& /  ////b v #*    @!:@!@!* @!  @!  @!@!@!@!D      *       D*      rrS)rNloggingrurstatrxrrpathlibr collectionsrdefence360agent.model.wordpressrdefence360agent.wordpress.clir)defence360agent.wordpress.incident_parserr(defence360agent.model.wordpress_incidentrr r getLoggerrKr4r rSr"rrrsQ77  ######222222666666HHHHHH  8 $ $V#V#V#V#V#V#V#V#rhhhhhhhhhhr