~fǕdZddlZddlZddlZddlZddlZddlZddlm Z ddl m Z ddl m Z ddlmZddlmZddlmZmZmZmZejeZGd 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_dictcountry_readercheZdZdZ 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.D===Rfbr c2|dr|ddndS)Nrr#)xs rz:IncidentRateLimiter._cleanup_old_records..Ss14ad1gg1r )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'     r rule_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)r rrFzRate limit exceeded for rule z from IP z: /z within z minutes)TOK)rrrr>rrr1r) rr?r@r7r.r9r: recent_countwindow_minutesr's @rcheck_rate_limitz$IncidentRateLimiter.check_rate_limitgsF 9;;* *T-B B B  % % ' ' 'ikkt'' $ $% % %,S1J====:===F !+1#C("6{{ ', L 43 3 3!-3N1G11#11$11'+'?11'111 zr ctj}||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)rrr1rpopr0)rr?r@r7r.r9s rrecord_incidentz#IncidentRateLimiter.record_incidentsikk $ d) ) )(+uD  $ $ $,S1J:$":::q!!!   c " " " " "r N)rrr) __name__ __module__ __qualname____doc__r4rr>strtupleboolrHrKr#r rr r s.1#&"' (('*(!( ((((0* * * X22),2 tSy 2222h#s#######r r 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 rUrparser)rrUs rrzIncidentCollector.__init__s))A,?,A,A(** r Tsitedelete_after_processingrAcKg} 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) rr5debugexists_get_incident_filesr1_get_site_username _process_fileextend Exceptionerrorinfo) rrXrYcollected_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))rlrar5rdr1)rrmrYall_collected_incidentsrXsite_incidentss rcollect_incidents_for_sitesz-IncidentCollector.collect_incidents_for_sitess#% ; ;D#'#B#B'$$N $ * *> : : : : "  KKD+,,E     '&r rfc|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 %sr[) r5r\r]is_diriterdiroslstatOSError stat_moduleS_ISREGst_mode_is_incident_filer0)clsrf incidents_dirrgfsts 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 )rR _FILE_PATTERNmatchname)r}rs rr|z#IncidentCollector._is_incident_fileMs%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_namerbr5rc)rrX user_inforks rr_z$IncidentCollector._get_site_usernamess  TX..I$ $    LL@     44444 s" A"AArsincident_file_namecg}d}t5}|D]}|dd} |dp|dd} |j| | \} } | s"td|| |dz }|j|j||jd} t|| | }| ||j | |  dddn #1swxYwYg}|rD t|}n3#t$r&}td ||Yd}~nd}~wwxYwtd |t!|||S) Nrr?unknown REMOTE_ADDRr@#Rate limit exceeded for site %s: %sr+domain site_pathrhuser_id) geo_readerz+Failed to bulk insert incidents from %s: %sz(Processed file %s: %d stored, %d dropped)r getrUrHr5r6rdocrootrr r0rKr rbrcrdr1)rrsrXrhrincidents_to_insert dropped_countrincidentr?r@allowedreason site_info incident_datacreated_incidentsrks rrz)IncidentCollector._process_file_incidentssZ!   ! H% H H",,y)<<&ll=99X\\!9>> #'"3"D"D## NN= "Q&M#k!% (#x  !4iJ!!! $**=999!11';GGGGA H! H! H! H! H! H! H! H! H! H! H! H! H! H! H! HH   $C'%%!!    A&   6  ! " "     ! s*C!DDDD!! E+E  Erc4|dd}|dp|dd}|j||\}}|std||dS|||||||S)Nr?rrr@r)rrUrHr5r6_store_incident) rrrXrhrr?r@rrs r_process_incidentz#IncidentCollector._process_incidents,,y)44ll=11 X\\ 96 6 +<<       NN5    4##          r r?r@c |j|j||jd}t||}|j|||S#t $r'}td||Yd}~dSd}~wwxYw)Nrz$Failed to store incident from %s: %s) rrrrrUrKrbr5rc) rrrXrhr?r@rrrks rrz!IncidentCollector._store_incidents +!\$8 I 1H   - -g{ C C CO    LL6"    44444  sAA A6A11A6)N)T)rLrMrNrOr rrrRrrlrq classmethodrr^recompilerr|rPr`r_dictrrrr#r rrTrTs++%84%?++++)-?#?#?#"&?#  ?#?#?#?#H)-''F|'"&'  ''''B$4$DJ$$$[$NBJ@AAM =$ =4 = = =[ =//* / "& /  ////b v #*    C!:C!C!* C!  C!  C!C!C!C!J      *       D*      r rT)rOloggingrvrstatryrrpathlibr collectionsrdefence360agent.model.wordpressrdefence360agent.wordpress.clir)defence360agent.wordpress.incident_parserr(defence360agent.model.wordpress_incidentrr r r getLoggerrLr5r rTr#r rrs[77  ######222222666666HHHHHH  8 $ $V#V#V#V#V#V#V#V#rkkkkkkkkkkr