ÿØÿà JFIF    ÿÛ „ !.%+&8&+/1555$;@;4?.451 4,$,44444444444414444444444444444444444444444444444444ÿÀ  á á" ÿÄ     ÿÄ ?    !1AQaq"2‘¡±ÁðBRbrÑá#‚’¢²3S CñÿÄ   ÿÄ !    !1QAa‘2ÿÚ   ? 5˜Z¯V¦cø)›t/? z¨±>Õ5€¶‹Á¤·¼z¼Ü¬+ñ®v¤¨_ˆR­BFn©—˜ý®ç̝P8gýt·ÉSTŦˆìät?þé¼íìN/Þa)ì–í6ô… Ï¿øÃj´¿KÇü]ÿ ªô¹-eKànëÕHTx}ýSÜ›ÿ ”7Ø×&µ<¦  ¥ÑO¶[Ù¯ä¨ÞÃÿ PZ-¬;#õ|•oaÿ ©CìÞz3˜öː/¤­ñTûIØ}š^ mÓ%ªxˆ¥ÉŸu=Z+ISe¿45™¼u;ú&WØ÷€æßQ™®{|íx*TC“#ZŠìZ§²‹ 6pv…³¿¡äª*áZÐ%ÒOáˆo"x«OHk w±æ+¬V(kMúŸ5Vö«$ ÁrÏbàb57/luR ¸ÑÛj Òµì`Мq­û žICÀÊ•©4€Âcà¨Ï€O´<èÐ:›ù(Ë^L8þ‘ÍÌ#¸Ð_Ì©ÙK(Öz 4¬û+¸;ü’V’84‘¬ÃŽ:[â‡ÔÌáõp¢~§ªlæ£ö{®G>J¼"°‡7¯ÆÉèßû ‹É‹§ÁòÃýâßî ^ƾÙõ‹×óH#«LP½ïX=xÑÍ$|W?•~• îëÔ©ª‹ {ÝT…Kÿ ”hûâá)J*ö˜–ÔU;iÇ€/ ÆþjóZ\ýwØ=Ìm ºèËL9 ýèÆð/¨’¥öo=nË.%Îì ŽÕ¯È|{Oj²ƒE6e/ßdÄõ²Ìâ1O®ò×TsəԸhOMýíMˆ¿¼H˜l²,7Â¥#MF/Úf°Ö½± ¸–dr‹NýÊ íjqx{œÉ ä-È ¦ øÄër¨q°ð †nцýÑÄÆ’mä…n<0È™;ÁÝá¯ÁZƒ7FÀmì­ É&9ˆîéi¶ùN§Y• ÃZãAâ?•‡©‰ , ó¾IŸŠc1 4â&y­&pŠ­6;M À 0¹qç»p.á …ŸÅáK@%6·y6ƒ‰3?”úºŽ‰éX5ªPT §µ!=Mž«Ú½‹ÅgÂSâÉaþÓoö–¯ÁÔìR>5éÿ üs¶ÆUcÌ kÇR ]ÿ ù¬¼«VŽ;Â|‡~¢¦”ÏŰæ {L™Õ°Óv¹ò¸írޡעCÃ!íVÕ {¶»sŒNPg/ "uÕbkm²“$ďå¿é¹§°½æz¯6 †s¿!s–wÚÝ“™Œ °.ûj>·+™Òa…©Œ&rÝÎtÛë긪Ît’LAVp%c Úý[ÄzJ¾ÇàXXç@˜ó<êL]·T˜¾¥1Ó©V‡g´æ½¦Ý@¹óø!_@´ÞâSÁ —S3™•& ]@JHÚý©ZŽ €×æÔr»Áf!‡yÞ4Mv*èÓã_{‘åóUuљØ«Oïé*®EvÑ Œ÷‡U \"㪒ÍK+À 4“M¡ï:0¥5í!'<@î´”>Ç»&Z–ïCCV˜Ì5Šo&îhè.žû |ÓK©h$s6KìŒëã)¹hI¦GïOåóI;ììü#É$Š0…Ææ¥TØ.5­¾gn´ “ÂÖ\:hœ89G)J@„}œ:’Ò{/Š"¦_Æ×7Æ3VÇŠÊa]ÚŒÙ€Ä–=®uÁßâACZƒ§§£ Qnâ:«,×{tyø¬iÛcœÜÄ€H½ÄÍCk´÷šß .W'b¤Íåh]÷€=,Žv×cÚEÚHXJX¶îo¨FÒtèöŸ>ªª6[J®Fµ£sGÁeqõfe\íjÒÐïÄÐGˆe1Ø‹.Ø”‘Ëuø Y­ˆÜ ŽG|zùªüMpDnQWÄ”%JŠ™)â*p@Örš«ÕT2Ð%ˆG#ª„ ·¤!°ŸOTÂT¸aÚ%4&h™LµšØüÐ.F¿²ÐÞ_Ç‚¾ÅÃaÜ÷09Æ q€öy˜v‡85õN÷]¬äѼóS{°_MެúÔ#°Ç¸0åÞè2ëôPcvÆw9®ií1Ä8F™˜à‰´+‰Ik1òÝ7“Ñ×ÒsÝ\x‚h`ÞÑ`ó"|µEcý£n˜h`}GÞ !±ù²Ápü²ß6 0ïi󜵩SÈÇ7˜-ÕURO˜¦´f$ªž-Í6(œ}<„ éc øs]ŽŽ„*—¾ ìdŽ„)méª\¿êÎIg¾ØÞ~I#C/¼¼´EÁÈŽi8“©õådô·>euä ƒ'Ê×लR1ÉJE1ÐAát`t;ÇР%Ý<‡¥„ÍÆ`×Oyó)õiI€ñQaŸ4Ûù\áàaÃÔ¹HÃu¹*k€¦<„e S‡&õÏ B!ŽhüÞ`yj}mªf×\¿ Ç~æ­9‡û\՞Ǖg²1Žû5V7 !àöšm° c`ܬøÇìµÒ'P"?…´Ö,"§^•õލsÔ)6˜sæéÍR¼ ò|Sl”‹7 nPW Gòú÷½§O¯‡„l¡kSÞŒr½PÊ@æ¢pŽ-mÿ #Ÿ˜Àº¶Áä¦;ïÔæ$1££`“Õ>„—·ž)ßð³ñ#Ï Ô$¶œ‰ÊE‹À;÷º ¯«P:Ñ”8–IÊtpÞ3ª“>ê“þës4ò2OÏÕ­±zô†Õ§‰.÷ä¸;¿˜“'œ›žª}«Œ{ª±Ì 9ÔóÞÕ‡0 $íWV3Üì¬ —@kÝ4@¿r¼±½¬™›?øØæ´'Áé®CË3-g$˜ö‡×auÚi´Žp/êÛ æF›Ú2v‹ã¿¿,nB1̨ƃqÞa5͝@&Æû“él÷ \C²½UÍc ¯k×¢U ÖéQå™—-r wô ÞÏ<Ò=&=ÿ Ôê Òêˈt,i—;LîÜ á¸*ÚÃ1$êL•LÍ <É)ýÐà’ ;F™{ƒ™˜€&'}‚ãÄK`¡ÞT@I;®žZóè‚s’7®°›+§O­Åq©é»²9<Ô J ¼9O’HL»Ùïì¸rk¼Ž_ý‘TŸu[²ßÚŒ·ü÷B%¯E ŸÔX5êO´ Ç•€’I0 ÉJX` ñ¹õ%;µŸD‘«´€àwÒ™U ûئžÖö\×®×´8 ½‡ºÐÆÓ§?Àkmœ=;d5*@-ì0F Rªýš[Ü6âö̃ڸr*KA9· u*µæ£?U¸Âêí†8@¦X4 e-ò„0s{ HâUpU?¼mñRa°®a%Ð'tÉ×’\¾ÊÉ]t›h>·(Ë@R¼¡Ãt h}’O÷au<+nT…Ö…MӐ??Óe95 q>í/;&JSû °¯ÊéÞ øƒ*Ã2½Ài&:nôUl=¾¿5eˆ3”ñc|Ú2V”>„»&eE;«ÚäC p¢Û úy 9š[ŒÌx¼擼A&DåÒ¯ˆ¤ÀÌ;"˜ ÏQä¸åhÊ}Ûq«Û0WžÒ|»€ø®öCm5•\ÇÀ§Pe3£]0ÃàLDÉ‰1øªxjgwT‚÷¿LΨK‹›ùs—xˆÜ±µ kæ¸f‰‰ÜGk/LÛØ6d9ò¶ùA{ƒA3š/¬D¬khÓk‰`˜"㯒r¿±Óã jx‡°e}<Ñø\3y:'À•/h½Í€Ç4~g ?Û(¼]v‘ªlKÎâ~?O‚W%{Ì:“'©úNq¾›úo(X’¥¯ˆ nFê{Ç€ü?º'ë ø‹ì Þ09ŒÌç9Æ —ËC`j@ÓÄ(+a‹un¸#ÂꟋ{K`‘ÑÍÍ'à´»/Û,KW;Þ4²þð ï Nm|~fGÏ(…³Ã)«1ö­Õ ¥‡¨©ƒÃ™ü-s=à=U66Ï«Ýc蓦W¹íž®›nÔ%êÇìŒ<#Ü×84ån®Ð ÒåOC` ñânÑs‡¢ç 1õ%Îhì½Ã½® e:ݼUZo™`  ÅZŸŒÊ«ê1ÏÄo$q¹Þ€©ˆhÐÉä¯ñ[!…Ú˜àJ:x2$Íß&PåT£6ç— ‡Í*4Ýšçjÿ ‰É nófÐ ó(L5C•åÆ\rMÒ@ò }y-W}™üýVù—ú¢=Ù”c®‘< M ž ´Phr ¦©TD ‘ù.$´÷O‡‘V2Æò.=IUŒ=ž‡â¬i™aþÓåÙ?òUø'ØÖ•.~* šTŒ!•-×áºTâ®ä#õü'´ eýlYÅÓeÕKÂrT"CÚ@u!Óxƒ{š3€}1¿(r}%«nËamjÑ%ÑNEò v ˜à  σöK³,*º.àzù¨™Ó ÚçâU¦*¿ 9{%Ö¹ njûdaXöb) kÛÆ±ûÓ\°M7ˆÂ=û›ç¿Ã‚­V»Cg–8ÙêE- j)k$º`Ã-ùEýeBÆÇ]c¡°ñty&Òd0nõ'¡W+ƒ*|–øµFa\GQªEAÔp5\Ǽ·¼Ç8·õ -â§Ú[ ‡ uZeÖ 3}×d'+¹:ð+K†Û®s!Ï$úe€<Û”x)1»a­¡LC]¸µík…ÚàA»AYº{†ªS[¦5HÒ7ù --,ísòDØ€èk ÞÀîÜ ò@â( ËNˆë›4ô½•/¦o‡€Û7 ê•ÆêòðÜy'Án½µ á˜ݦ ndeo…[ì¶Ê,¥R³Ä=À±—–ß;£™´ñSâ*g§”ïaið‘Jå~™ÓÞ ß³Õ¢»8x埒²52>AÊb&-÷\7´éÄù€T˜,w;3{ï˜k…à¹ÄqÀ«œ{€\ ˆ¾[´¨јr &Úé„Ívˆ±8†¿]|¬ņ4I×pÞS1ÈÖz‰#Ìv‡G!YNògñ:màTz¢Ý1ô©^O=~ë|5Bã™ç•¼µõ•bÆ@úÕS¬ÈŒ#¬zünrŸ û” Z²•èðV"ÁHÚý©wÝ €7¼Ìu1hÑa3Éä û f$o¿É ™Ú›ÝçnpÒ3äÌ3†Í§,Äï]$‰/pê †«À¼¸e9­Æê_C]žƒ·ý·frÁN«, E=›Çq -‰öŒ:aÏ¿±í&£Í:-} 84‘ÿ eƒQÑeëSsuiA ³g㟥ú£?ÿ ʼn*”“÷aühe:ÊWa@ÒÞk±eØ] F Ô—r.åä˜ @ö¥ªZoÐýYL·¥S²G/‡ñ <~*ZÆ´è>JlòàÛÆ½ÿ 窘ìGN¢:I®KšJp/`íIÁÀõ#Ä-€ö­šµŒoF4|ÆQØÆ@Ì|£Ô…¢À{9˜è½Üó›€ôYÒÎYsið;ís¤€à²ˆ‚4qÉVŒI$ ‰"° æµ8cXGjœˏ¡Aâý•ËÜ¢ûï e·çLx']á"oÅÎê3¯Ç—¹”ó0nå‚âg{Œñ> S´˜îè°g238‚ãköÝfÚd´6Ò€;ò÷±¢™¼›º ¢Æ'¥Ðx'e¬ç ]bÈÆV¢ó‹kýBO ðÊâ$Ÿ!×T 3Mýמ žìٍàÌü‘8÷€àæØ8æ©6‰©L´«…oãpð„~Çk‰!ñ;‹”ÛžÍ àž±z Ÿôû øŸÝužÏ;ÿ #|u6™Þ¬ÚˆÐõA4¶â|ôl|Ê2ŽÇ¤ÝÅÇY.<#Aí.k§hóF‚”Y; M½Ö4hŸ4&›­¿tès´%FìL¥£Ãk‰ÇT¤haÁ¤ÚxfÉ`ÑìË›>i 3t‚:,–+^÷´–{Û–Nxi"x‘Ûg î¨>¥Õ܁ùZH,2Û“:8xÊ¢Çí9.É-Ìâã-=çjwµS˜dütžçwýGòú®®ûº_ˆýx$–¡ãøO EÚÛÏ÷R„×w+3£Á£öUMyR²¹âŒ°š›¸Ñãò9§Ó_Dl+Ùßc›úšGÅÌc†Ž!Ko=¶.‘Îÿ c²(2®V mª.ÿ ¹B›¹å ù„öŸSV>™ü¯$y:G¢Z×àøúdî¹û­·ýÇ´:•c LÍõi_‹ö+ÎæGÊè>OŠ•äž´§Þ{X}¨1ÚTc›»Qþ•êô°t¿OP?eæ~É{5]•ÙR£r5†nZ\ã@ &îJõ ¾àC°þV>fé¥/ü5ñÊIº_é5 ;e­h<@ Ä&æÃëE%;X,ÒãÆÞ`Oò¦kŸm#˜!ÀyÄ¢| óLšò¥Ä` ¶R=|ÈCâh5ò3DˆïF†ðÒ#ÅìÛœ?¸yhBãœí ZxßÎÄhºRK„`Þödvײ™ÀÈÑÒgŒuY w³%†ƒÓzõ ÖÏp‚dH®¦A´ù§»ÓÇMæ~)ˆð‡û:ù&Ä •vGD´À n ݇¼Ö8Fö óáà£~Ë¥x`oK|Ä?fxiØü%pìR>éò+Û±éÎ>núlFŤ'tq8LZÏvÃ?„¡ß±È⽆¯³íü@x|PöUäèØã¡ð‚ŒAìÏ"vÍwóŸÍ{ ý0.z È•Ö{,N¡£¡ŸKÕÙž>Ýœþ ÍÀ°<×EA!Å‚D™IúOÍ¡>ôG}Â` ÍßkÜL™Ž Þð™ {IøF²¹òQ3&!ÃÂÞz.d&Ï-sH¸,Ôõ˜ŽP€ 77ˆÝ¼ÊëÜw =cÕ Ú,ØÐ5ÎYÐ)ì´öœgŒ[¤ßv㙑8心>h]§µháYš£²ºÑ.{Ï7Sð•?´~×SÃKýJÛ˜ ™Íäiúu<µX¶1õ^kâçIÑ£sZ4h>j*ÔšD:4­¿_ ÷¸ Õxæÿ ¸?Mù _•­ÊÐ ä ÷ý ÑwL œ­ïnTkÛUÍN©ë:¦fV ¶ÜÔÜMªÅâA½–¿R×TXš-%iTÊT•‡Ù‚JôϐZxWÑè‰f‰òG º ×Õû2aZ7OU3[“×AT–ÞŒ…-‘¤”Ì ì&(ˆ¿­•ƒkï’:ðY¦W‘ Å)“†‘˜³Åtcø˜ñTÂwÚÇ4|üLÇªí–v- qˆèU qPE.†â‘˜µ Æ,ÐÅs]8¾„oúÑ i>ÜxxÈó)ƒ ´æÁâØ$À‰vžŸf$Ž |ãw;ÀÁIJ»b` {¦Ó¤Ú$©YÀ‘n@Óïž«9J¼êG m¤ ܯ¹ÌW4€ÐÒÅÛ‡#褕Ÿn-?í|с¥÷Ú¹¬'´ÞÜ9ÓK `hê£SÄSà?7—Wí_´…óB›»:=Ãïq`<8ñÓŒÑlú2d¬ê³£hÖ[l|$vÝro~'R®‰§°ñmY ͧäP |PUª¹·:3Œ[Û{Xÿ ºâ@‚W–Äé u‚ ¯´*=íή.pûÒdt @G‰¬ s¸ ëÉücr ÞæÑ¨Ê@>¤¢Ö±. Þ'¯°ÌME[YéïĵÂCå½ Ué©Áû'Ê9%eÔðNU”ë‘ÌsD3/®+UI˜9h.WC”빓$#:pz:YÓ ¿xž* ³$Í +$kñAŠ‹†¢ Uê>¸)_š¬÷©ßAÂÔb9ÇU ¯¾á•9¯ÏÏ÷O÷¼¼Fähal1‰3Ì[Ïr•´UCksNÐ] R‘¸¥H+§Šé†c©vÖÞ0iÓ76s†î!§=ß ¼~Ô'°Ãmäoäš³ªøi1úÉ)³yV8 CLÄØÁ‘WYïi€H6ÖÑiámø^ÈY´°Ñ7¥Û*—Ñ©L«Qƒï—Ùrÿ ›£Ð*š¸ˆL©ˆ$ˆ ÷¾D§9È®«qbqC)–ˆïv´çñsÑVT­Ø, <àïºÀO«Jý·õ àfPìð .wFšir´þ’2_Y *Æ€x\« ì€9š@ Ž|F⇥ˆkZ@hÖÄ0t¿-<“‹qµ¾*ZL¤Ú)&BJpÓF5=$„at*Zš$’ÑtdûÝRI1 2މ$€$I$#‰SÞ’Hë¬ï;Á$¡t$’`<(ñÇt)$‡Ð.Êf¢X’Kt=Éé$‚ˆªè¢oÝëòI%Rgcª÷ŠyI%¡‰ÿ !ñ)´õ $¤ Ô’IIGÿÙ""" Countermeasures driver program - executes one or more countermeasures Copyright 2023 Fortinet, Inc. All Rights Reserved. fm-ops@fortinet.com TODO: - global block file to prevent all countermeasures from running? - sudo access validation - run time limits """ try: import argparse except: import p_argparse as argparse try: # Python 2.x import ConfigParser as configparser except: # Python 3.x import configparser try: import json except ImportError: import simplejson as json import p_importlib import logging import logging.handlers import os import os.path import subprocess import sys import tarfile import tempfile import time import traceback import types from datetime import datetime try: # Python 2.x import urllib2 except: import urllib.request as urllib2 try: # Python 2.x import urlparse except: import urllib.parse as urlparse import zipfile import aggregator import agent_util from .plugins.CountermeasurePlugin import JsonPlugin MAX_OUTPUT_LENGTH = 100 * 1024 # Backported for Python 2.4 support def any(iterable): for element in iterable: if element: return True return False def load_plugins(): """ Dynamically load all available Countermeasure plugins, in both the application default and the customer's local custom plugin directory """ log = setup_logging("countermeasure") plugins = {} for directory in ( os.path.join(LIB_DIR, "countermeasures", "plugins"), BASE_CUSTOM_PLUGIN_DIR, ): if not os.path.exists(directory): continue log.info("Loading Countermeasure plugins from %s" % directory) sys.path.append(directory) for mod_name in os.listdir(directory): if mod_name.endswith(".py") and not mod_name.startswith("__"): try: mod = p_importlib.import_module(mod_name[:-3]) except: log.error( "Unable to import plugin %s: %s" % (mod_name, traceback.format_exc()) ) continue for name, obj in list(mod.__dict__.items()): if ( (sys.version_info[0] == 3 and type(obj) == type) or (sys.version_info[0] == 2 and type(obj) == types.ClassType) ) and name.endswith("Countermeasure"): try: plugin = obj() plugins[plugin.textkey] = plugin except: log.error( "Unable to instantiate plugin %s: %s" % (mod_name, traceback.format_exc()) ) if mod_name.endswith(".json"): try: json_counter = open(os.path.join(directory, mod_name)) except Exception: log.error("Unable to open %s" % os.path.join(directory, mod_name)) log.error(traceback.format_exc()) continue file_content = json_counter.read() json_counter.close() try: counter_data = json.loads(file_content) except Exception: log.error("%s file is not a valid json file to be read" % mod_name) log.error(traceback.format_exc()) continue required_fields = ["name", "textkey", "command", "author"] existing_keys = counter_data.keys() success = True for key in required_fields: if key not in existing_keys or not counter_data.get(key): log.error( "%s is missing from the countermeasure declaration of %s" % (key, mod_name) ) success = False break if not success: continue try: max_runtime = int(counter_data.get("max_runtime")) except: log.error( "max runtime %s is not valid" % (counter_data.get("max_runtime")) ) max_runtime = None try: max_frequency = int(counter_data.get("max_frequency")) except: log.error( "max frequency %s is not valid" % (counter_data.get("max_frequency")) ) max_frequency = None textkey = counter_data.get("textkey") plugin = JsonPlugin(counter_data.get("command")) plugin.textkey = counter_data.get("textkey") plugin.name = counter_data.get("name") plugin.description = counter_data.get("description") plugin.wall_announce_delay = counter_data.get("wall_announce_delay") plugin.author = counter_data.get("author") plugin.max_runtime = counter_data.get("max_runtime") plugin.max_frequency = counter_data.get("max_frequency") plugins[textkey] = plugin return plugins def load_agent_config(): agent_config_file = os.path.join(BASE_CONFIG_DIR, PKG_DIR, "%s_agent.cfg" % BRAND) config_file = configparser.ConfigParser() config_file.read(agent_config_file) return config_file def setup_logging(name): log_file = os.path.join(BASE_LOG_DIR, PKG_DIR, "countermeasure.log") root_logger = logging.getLogger(name) agg_logger = logging.getLogger("Client") handler = logging.handlers.RotatingFileHandler( log_file, "a", maxBytes=5 * 1024**2, backupCount=5 ) handler.setFormatter( logging.Formatter( "%(process)d) %(asctime)s - %(name)s - %(levelname)s - %(message)s" ) ) root_logger.addHandler(handler) agg_logger.addHandler(handler) # If we have a TTY, add a stdout handler if sys.stdin.isatty(): root_logger.addHandler(logging.StreamHandler()) root_logger.setLevel(logging.INFO) agg_logger.setLevel(logging.INFO) return root_logger def set_last_execution(textkey): last_execution_directory = os.path.join( BASE_DATA_DIR, PKG_DIR, "countermeasures/last_execution" ) if not os.path.exists(last_execution_directory): os.makedirs(last_execution_directory) os.system("touch %s" % os.path.join(last_execution_directory, textkey)) def get_last_execution(textkey): last_execution_directory = os.path.join( BASE_DATA_DIR, PKG_DIR, "countermeasures/last_execution" ) if not os.path.exists(last_execution_directory): os.makedirs(last_execution_directory) filename = os.path.join(last_execution_directory, textkey) if not os.path.exists(filename): return 0 stat = os.stat(filename) return stat.st_mtime def install_plugins(url): log = setup_logging("countermeasure") log.info("\nFetching remote plugins from %s" % url) f = tempfile.NamedTemporaryFile(delete=False) num_installed = 0 ext = url.split(".")[-1] fname = url.split("/")[-1] if not os.path.exists(BASE_CUSTOM_PLUGIN_DIR): os.system("mkdir %s" % BASE_CUSTOM_PLUGIN_DIR) try: r = urllib2.urlopen(url) if "content-disposition" in r.info(): ext = r.info().getheader("content-disposition").split(".")[-1] f.write(r.read()) f.close() except: log.error("Unable to download URL: %s" % traceback.format_exc()) return if ext in ("tar", "tgz"): try: t = tarfile.open(f.name) for file in t.getnames(): if file.endswith(".py") or file.endswith(".json"): log.info(" Installing plugin %s" % file) t.extract(file, BASE_CUSTOM_PLUGIN_DIR) num_installed += 1 except: log.error("Unable to extract tar contents: %s" % traceback.format_exc()) elif ext == "zip": try: z = zipfile.ZipFile(f.name) for file in z.namelist(): if file.endswith(".py") or file.endswith(".json"): log.info(" Installing plugin %s" % file) z.extract(file, BASE_CUSTOM_PLUGIN_DIR) num_installed += 1 except: log.error("Unable to extract zip contents: %s" % traceback.format_exc()) elif ext == "py" or ext == "json": log.info(" Installing plugin %s" % fname) os.system("cp %s %s" % (f.name, os.path.join(BASE_CUSTOM_PLUGIN_DIR, fname))) num_installed += 1 else: log.error("Unable to install Countermeasure, unknown extension: %s" % ext) if num_installed: log.info("\nInstalled %s Countermeasure plugins" % num_installed) os.system("rm -f %s" % f.name) def list_plugins(): plugins = load_plugins() pairs = [] for textkey, plugin in plugins.items(): pairs.append((plugin.name, plugin.author or "", plugin.description)) pairs.sort() max_name_len = max([len(p[0]) for p in pairs]) max_author_len = max([len(p[1]) for p in pairs]) print("\nAvailable Countermeasures") print("=========================\n") fmt = "%-" + str(max_name_len + 3) + "s %-" + str(max_author_len + 3) + "s %s" print(fmt % ("Name", "Author", "Description")) print("-" * 80) for textkey, author, description in pairs: print(fmt % (textkey, author, description)) print("") def validate_plugins(): plugins = load_plugins() pairs = [] for textkey, plugin in plugins.items(): output = plugin.validate() if output: pairs.append((plugin.name, output)) if pairs: pairs.sort() max_len = max([len(p[0]) for p in pairs]) print("\nInvalid Countermeasures") print("=========================") fmt = "%-" + str(max_len + 3) + "s %s" for textkey, description in pairs: print(fmt % (textkey, description)) else: print("\nAll plugins are valid.") print("") def validate_sudo(): plugins = load_plugins() for textkey, plugin in plugins.items(): if plugin.sudo_requirements: print("\nVerifying sudo requirements for %s" % textkey) devnull = open("/dev/null", "w") for app in plugin.sudo_requirements: # If the app is a list, then we just need at least one of these to be available if type(app) == list: valid = any( [ subprocess.call( ("sudo -n -l %s" % subapp).split(), stdout=devnull, stderr=devnull, ) == 0 for subapp in app ] ) # If not a list, just check one item else: valid = ( subprocess.call( ("sudo -n -l %s" % app).split(), stdout=devnull, stderr=devnull, ) == 0 ) print( " %-30s %s" % ( ("%s:%s" % (textkey, app)), valid and "Pass" or "Missing Permissions", ) ) print("") devnull.close() def execute(hash, textkeys, metadata_file=None): # Setup logging and get config file log = setup_logging("countermeasure %s" % hash) config = load_agent_config() # Read and parse any metadata if given cm_metadata = {} if metadata_file: try: f = open(metadata_file, "r") metadata_contents = f.read() f.close() cm_metadata = json.loads(metadata_contents) os.remove(metadata_file) except Exception: log.error("Problem while proccessing metadata:") log.error(traceback.format_exc()) cm_metadata = {} # Get available plugins plugins = load_plugins() proxy_info = {} if config.has_section("agent_proxy"): proxy_info = config["agent_proxy"] # Iterate through the plugin textkeys, executing each one in succession for textkey in textkeys: if textkey not in plugins: print("Unable to execute unknown plugin %s" % textkey) log.error("Unable to execute unknown plugin %s" % textkey) current_time = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") report_output( plugin_textkey=textkey, server_key=config.get("agent", "server_key"), aggregator_url=config.get("agent", "aggregator_url").lower(), agent_version=config.get("agent", "version"), output=[ { "timestamp": current_time, "format": "text", "output": "Unable to execute plugin %s" % textkey, } ], hash=hash, return_code=1, status="error", proxy_info=proxy_info, ) continue plugin = plugins[textkey] plugin.set_metadata(cm_metadata) # Check how recently the countermeasure has been run last_execution = time.time() - get_last_execution(textkey) if plugin.max_frequency and last_execution < plugin.max_frequency: log.warning( "%s countermeasure skipped, ran too %d seconds ago" % (textkey, last_execution) ) continue print("Executing %s" % textkey) log.info("Executing %s" % textkey) # Set last execution time to current time set_last_execution(textkey) # Run prepare operation plugin.prepare() # Post wall message if needed if plugin.wall_announce_delay is not None: os.system("wall 'Executing %s countermeasure'" % textkey) if plugin.wall_announce_delay: time.sleep(plugin.wall_announce_delay) # Execute the plugin try: plugin.run() plugin.status = "success" except: plugin.status = "error" plugin.output = [ { "timestamp": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"), "format": "text", "output": "Exception executing plugin: %s" % traceback.format_exc(), } ] # Truncate output if it's too long if len(plugin.output) > MAX_OUTPUT_LENGTH: plugin.output = plugin.output[:MAX_OUTPUT_LENGTH] proxy_info = {} if config.has_section("agent_proxy"): proxy_info = config["agent_proxy"] # Report the output report_output( plugin_textkey=plugin.textkey, server_key=config.get("agent", "server_key"), aggregator_url=config.get("agent", "aggregator_url").lower(), agent_version=config.get("agent", "version"), output=plugin.output, return_code=plugin.return_code, hash=hash, status=plugin.status, proxy_info=proxy_info, ) log.info("Completed countermeasure %s" % textkey) def report_output(**kwargs): aggregator_url = kwargs.get("aggregator_url") server_key = kwargs.get("server_key") agent_version = kwargs.get("agent_version") if not aggregator_url.startswith("http"): try: host, port = aggregator_url.split(":") if port in ("443", "8443"): aggregator_url = "https://" + aggregator_url else: aggregator_url = "http://" + aggregator_url except: aggregator_url = "https://" + aggregator_url aggregator_url = urlparse.urljoin(aggregator_url, "/v2/countermeasure") payload = { "countermeasure_textkey": kwargs.get("plugin_textkey"), "server_key": server_key, "incident_hash": kwargs.get("hash"), "output": kwargs.get("output"), "return_code": kwargs.get("return_code"), "status": kwargs.get("status"), } proxy_info = kwargs.get("proxy_info") aggregator_client = aggregator.Client( aggregator_url, agent_version, server_key, proxy_config=proxy_info ) aggregator_client.call("countermeasure", payload) def main(): parser = argparse.ArgumentParser() parser.add_argument( "action", choices=[ "execute", "list_plugins", "install_plugins", "validate_plugins", "validate_sudo", ], ) parser.add_argument("--url", required=False, help="URL to download plugins from") parser.add_argument("--hash", required=False, help="Incident hash") parser.add_argument( "--textkeys", required=False, nargs="+", help="CounterMeasure plugin textkeys to execute", ) parser.add_argument( "--metadata-file", required=False, help="Path to file containing JSON metadata that will be passed to plugin", ) args = parser.parse_args() # Perform logical validation to make sure we got the correct arguments based on the action type if args.action == "execute" and (args.hash == None or args.textkeys == None): print("ERROR: Need to provide --hash and --textkey arguments\n") parser.print_help() sys.exit(1) elif args.action == "install_plugins" and args.url == None: print("ERROR: Need to provide --url argument\n") parser.print_help() sys.exit(1) # Dispatch to action methods if args.action == "execute": execute(args.hash, args.textkeys, args.metadata_file) elif args.action == "list_plugins": list_plugins() elif args.action == "install_plugins": install_plugins(args.url) elif args.action == "validate_plugins": validate_plugins() elif args.action == "validate_sudo": validate_sudo()