å®æ¹å ¬ä¼å· ä¼ä¸å®å ¨ æ°æµªå¾®å
FreeBuf.COMç½ç»å®å ¨è¡ä¸é¨æ·ï¼æ¯æ¥åå¸ä¸ä¸çå®å ¨èµè®¯ãææ¯åæã
FreeBuf+å°ç¨åº
*æ¬æä½è
ï¼bigfaceï¼æ¬æå± FreeBuf ååå¥å±è®¡åï¼æªç»è®¸å¯ç¦æ¢è½¬è½½ã
elastalert æ¯ ä¸æ¬¾åºäºelasticsearchçå¼æºåè¦äº§åï¼ å®æ¹è¯´æææ¡£ ï¼ãç¸ä¿¡è®¸å¤äººé½ä¼ä½¿ç¨ELKåæ¥å¿æ¶éç³»ç»ï¼ä½æ¯äº§çä¸ä¸ªåºäºæ¥å¿çâä¼ç§âçå®å ¨åè¦ç¡®æ¯ä¸ä¸ªé¾é¢ãåè¦è§åé¾ç¼åï¼åè¦è§åé¾ç®¡ççãæ¬ææ¯ä½è æ¢ç´¢çå®å ¨åè¦çä¸äºæè·¯ï¼å¸æè½å¸®å©å°æéè¦ç人ã
æ¬äººå¯¹ELKåè¦å¤çæè·¯ï¼
elastalert éè¿postçåè¦æ¨¡å¼ï¼postä¸ä¸ªåè¦æ°æ®å å°æå¡ç«¯ï¼éè¿æå¡ç«¯å¹é éè¦åè¦ç对象ï¼åè¦çæ¹å¼ï¼æç»å°å®å ¨åè¦ååºã
åè¦å¯¹è±¡ï¼ä¼ä¸äººåï¼Â æä¹æ¥ï¼ æ¥æºè°ç¨ééAPIãCMDBãLDAPã
åè¦æ¹å¼Â æä¹éæ©ï¼æ ¹æ®åè¦çº§å«ãåè¦æ¥æºï¼wazuhãé©é¾HIDSãelastalertè§åï¼éç¨ä¸åçåè¦æ¹å¼ã
ç¯å¢è¯´æ
Elastic Stack v6.2.2 ï¼éç¨äº6.0+ï¼
Elastalert v0.1.29
elastalert æºç é¨ç½²
ä¸è½½
elastalert
æºç
git clone https://github.com/Yelp/elastalert.git
å®è£ ä¾èµ
pip install -r requirements.txt
pip install "elasticsearch>=6.0.0"
å建elastalertç´¢å¼ï¼Indexï¼&æ å°ï¼Mappingï¼
python elastalert/create_index.py --host localhost --port 9200 --index elastalert
å建elastalertçé
ç½®æ件
config.yaml
ï¼
# åè¦è§ååæ¾çæ件夹
rules_folder: myrules
# æ¯2åéæ¥è¯¢ä¸æ¬¡elasticsearch
run_every:
minutes: 2
# æ¥è¯¢æ¶é´èå´5åé
buffer_time:
minutes: 5
# è¿æ¥elasticsearché
ç½®
es_host: localhost
es_port: 9200
# elasticsearch认è¯ï¼å¦ææªä½¿ç¨å¯æ³¨é
es_username: kibana
es_password: kibana
# elastalertç¶æç´¢å¼
writeback_index: elastalert
å¼å¯elastalert
python elastalert/elastalert.py --config config.yaml
elastalertè§åç±»å
å®æ¹è§åç±»åæ述并ä¸æ¯å¤ªæ¸ æ°ï¼ä»¥ä¸ç»åºalertæ¹å¼ä¸ºpostçjsonæ°æ®ï¼ä¾¿äºåç»å¤§å®¶éæ¥éåã
以ä¸çè§åç±»åå使ç¨ä»¥ä¸ææ¡£æ ·æ¬ä½è§¦ååè¦ï¼
doc = {
"@timestamp": get_now(),
"codec": "nodejs",
"tags": "31",
"level": "high",
"server": "nginx",
"status": "anystatus",
"message": ">>> [ xxx ]: valid id error ."
elastalertç´¢å¼ä¸ï¼
hits
表示è§åå½ä¸æ¡æ°ï¼matches
表示è§åå½ä¸æ¡æ°ï¼å¹¶ä¸å¹é è§å触ååè¦æ°éã
anyç±»å
说æï¼ä»»ä½è§åé½ä¼å¹é ï¼ æ¥è¯¢è¿åçæ¯ä¸ªå½ä¸å°çæä¸ä¸ªè¦æ¥ã
è§åï¼å½å¹é
statuså段为
anystatus
ï¼è§¦ååè¦ã
# ruleå称
name: any_rule
# è§åç±»å
type: any
# çæ§ç´¢å¼
index: testalert
# çæ§æ¶é´1åéå
timeframe:
minutes: 1
# Elastic DSLè¯æ³
filter:
- term:
status: "anystatus"
# åè¦æ¹å¼
alert: post
# æå¡ç«¯æ¥å£
http_post_url: "http://localhost:8088/alertapi"
http_post_static_payload:
# æ·»å å°postå
ä¸çæ°æ®ï¼è§åå称
rule_name: any_rule
# æ·»å å°postå
ä¸çæ°æ®ï¼åè¦çº§å«
rule_level: medium
postç»æï¼
{
"status": "anystatus",
"_type": "mydata",
"level": "high",
"num_hits": 5,
"@timestamp": "2018-01-31T02:26:52.268477Z",
"rule_level": "medium",
"server": "nginx",
"rule_name": "any_rule",
"_index": "testalert",
"num_matches": 5,
"message": ">>> [ xxx ]: valid id error .",
"_id": "AWFKCd4a5xzN_sFQhZgO",
"codec": "nodejs",
"tags": "31"
blacklistç±»å
说æï¼é»ååè§åå°æ£æ¥é»ååä¸çæ个å段ï¼å¦æå®å¨é»ååä¸åå¹é ã
è§åï¼å½å段statuså¹é å°å ³é®åhackerãhuahuaï¼è§¦ååè¦
name: blacklist_rule
type: blacklist
index: testalert
timeframe:
minutes: 1
compare_key: status
blacklist:
- "hacker"
- "huahua"
alert: post
http_post_url: "http://localhost:8088/alertapi"
http_post_static_payload:
rule_name: blacklist_rule
rule_level: medium
è¥å ³é®åå¨æ件ä¸ï¼å¯ç¨
- "!file /path/to/file"
ï¼ç®æµå ³é®åä¸æ¯ææ£åï¼æªæµè¿ï¼ã
postç»æï¼
{
"status": "huahua",
"_type": "mydata",
"level": "high",
"num_hits": 2,
"@timestamp": "2018-01-31T02:37:46.071850Z",
"rule_level": "medium",
"server": "nginx",
"rule_name": "blacklist_rule",
"_index": "testalert",
"num_matches": 1,
"message": ">>> [ xxx ]: valid id error .",
"_id": "AWFKE9gM5xzN_sFQhZg2",
"codec": "nodejs",
"tags": "31"
whitelistç±»å
说æï¼ä¸é»åå类似ï¼æ¤è§åå°æ个å段ä¸ç½ååè¿è¡æ¯è¾ï¼å¦æå表ä¸ä¸å å«è¯¥åè¯ï¼åå¹é ã
changeç±»å
说æï¼æ¤è§åå°çè§æ个å段ï¼å¹¶å¨è¯¥å段æ´æ¹æ¶è¿è¡å¹é ï¼è¯¥é¢åå¿ é¡»ç¸å¯¹äºæåä¸ä¸ªäºä»¶åçç¸åçååã
è§åï¼å½serverå段å¼ç¸åï¼codecå段å¼ä¸åæ¶ï¼è§¦ååè¦ã
name: change_rule
type: change
index: testalert
timeframe:
minutes: 1
compare_key: codec
ignore_null: true
query_key: server
alert: post
http_post_url: "http://localhost:8088/alertapi"
http_post_static_payload:
rule_name: change_rule
rule_level: medium
å段解æï¼
compare_key
ï¼ä¸ä¸ä¸æ¡è®°å½å对æ¯çå段
query_key
ï¼ä¸ä¸ä¸æ¡è®°å½ç¸åçå段
ignore_null
ï¼å¿½ç¥è®°å½ä¸åå¨compare_keyå段çæ
åµ
postç»æï¼
{
"status": "up",
"_type": "mydata",
"_id": "AWFKIgZA5xzN_sFQhZh5",
"tags": "31",
"num_hits": 4,
"@timestamp": "2018-01-31T02:53:15.413240Z",
"rule_level": "medium",
"old_value": [
"nodejs"
"server": "nginx",
"rule_name": "change_rule",
"_index": "testalert",
"new_value": [
"java"
"num_matches": 1,
"message": ">>> [ xxx ]: valid id error .",
"level": "high",
"codec": "java"
frequencyç±»å
说æï¼å½ç»å®æ¶é´èå´å è³å°æä¸å®æ°éçäºä»¶æ¶ï¼æ¤è§åå¹é ã è¿å¯ä»¥æç §æ¯ä¸ªquery_keyæ¥è®¡æ°ã
è§åï¼å½å段statuså¹é å°å ³é®åfrequencyè¶ è¿3次ï¼å æ¬3次ï¼ï¼è§¦ååè¦
name: frequency_rule
type: frequency
index: testalert
num_events: 3
timeframe:
minutes: 1
filter:
- term:
status: "frequency"
alert: post
http_post_url: "http://localhost:8088/alertapi"
http_post_static_payload:
rule_name: frequency_rule
rule_level: medium
postç»æï¼
{
"status": "frequency",
"_type": "mydata",
"level": "high",
"num_hits": 3,
"@timestamp": "2018-01-31T03:28:00.793290Z",
"rule_level": "medium",
"server": "nginx",
"rule_name": "frequency_rule",
"_index": "testalert",
"num_matches": 1,
"message": ">>> [ xxx ]: valid id error .",
"_id": "AWFKQdg_5xzN_sFQhZjW",
"codec": "java",
"tags": "31"
spikeç±»å
说æï¼å½æ个æ¶é´æ®µå çäºä»¶éæ¯ä¸ä¸ä¸ªæ¶é´æ®µçspike_heightæ¶é´å¤§æå°æ¶ï¼è¿ä¸ªè§åæ¯å¹é çãå®ä½¿ç¨ä¸¤ä¸ªæ»å¨çªå£æ¥æ¯è¾äºä»¶çå½åååèé¢çã æ们å°è¿ä¸¤ä¸ªçªå£ç§°ä¸ºâåèâåâå½åâã
è§åï¼å½åçªå£æ°æ®é为3ï¼å½åçªå£è¶ è¿åèçªå£æ°æ®é次æ°1次ï¼è§¦ååè¦ã
name: spike_rule
type: spike
index: testalert
timeframe:
minutes: 1
threshold_cur: 3
spike_height: 1
spike_type: "up"
filter:
- term:
status: "spike"
alert: post
http_post_url: "http://localhost:8088/alertapi"
http_post_static_payload:
rule_name: spike_rule
rule_level: medium
å段解æï¼
threshold_cur
ï¼å½åçªå£åå§å¼
spike_height
ï¼å½åçªå£æ°æ®éè¿ç»æ¯åèçªå£æ°æ®éé«(/ä½)ç次æ°
spike_type
ï¼é«æä½
postç»æï¼
{
"status": "spike",
"_type": "mydata",
"_id": "AWFLMbye5xzN_sFQhZlk",
"tags": "31",
"num_hits": 13,
"@timestamp": "2018-01-31T07:50:02.382708Z",
"rule_level": "medium",
"server": "nginx",
"rule_name": "spike_rule",
"_index": "testalert",
"spike_count": 8,
"reference_count": 0,
"num_matches": 1,
"message": ">>> [ xxx ]: valid id error .",
"level": "high",
"codec": "java"
flatlineç±»å
说æï¼å½ä¸ä¸ªæ¶é´æ®µå çäºä»¶æ»æ°ä½äºä¸ä¸ªç»å®çéå¼æ¶ï¼å¹é è§åã
è§åï¼å½ä¿¡æ¯éä½äº3æ¡æ¶ï¼è§¦ååè¦ã
name: flatline_rule
type: flatline
index: testalert
timeframe:
minutes: 1
threshold: 3
alert: post
http_post_url: "http://localhost:8088/alertapi"
http_post_static_payload:
rule_name: flatline_rule
rule_level: medium
postç»æï¼
{
"count": 1,
"num_hits": 1,
"@timestamp": "2018-01-31T09:02:35.720517Z",
"rule_level": "medium",
"rule_name": "flatline_rule",
"key": "all",
"num_matches": 1
cardinalityç±»å
说æï¼å½ä¸ä¸ªæ¶é´èå´å çç¹å®å段çå¯ä¸å¼çæ»æ°é«äºæä½äºéå¼æ¶ï¼è¯¥è§åå¹é
è§åï¼1åéå ï¼levelçå¯ä¸æ°éè¶ è¿2个(ä¸å æ¬2个)ï¼è§¦ååè¦ã
name: test_rule
index: testalert
type: cardinality
timeframe:
minutes: 1
cardinality_field: level
max_cardinality: 2
alert: post
http_post_url: "http://localhost:8088/api/alert"
http_post_static_payload:
rule_name: test_rule
rule_level: medium
postç»æï¼
{
"status": "cardinality",
"_type": "mydata",
"level": "info",
"num_hits": 3,
"@timestamp": "2018-01-31T09:17:02.276937Z",
"rule_level": "medium",
"server": "nginx",
"rule_name": "cardinality_rule",
"_index": "testalert",
"num_matches": 1,
"message": ">>> [ xxx ]: valid id error .",
"_id": "AWFLgWKw5xzN_sFQhZvg",
"codec": "java",
"tags": "31"
percentage matchç±»å
说æï¼å½è®¡ç®çªå£å çå¹é 桶ä¸çææ¡£çç¾åæ¯é«äºæä½äºéå¼æ¶ï¼æ¤è§åå¹é ã计ç®çªå£é»è®¤ä¸ºbuffer_timeã
è§åï¼å½levelå段æªhighï¼æ¶é´çªå£å æ¥å¿éé«äºåä¸ä¸ªæ¶é´çªå£95%ï¼è§¦ååè¦ãï¼æªå®æ´æµè¯ï¼
name: percentage_match_rule
type: percentage_match
index: testalert
# description: "test description"
buffer_time:
minutes: 1
max_percentage: 95
match_bucket_filter:
- term:
level: high
doc_type: mydata
alert: post
http_post_url: "http://localhost:8088/alertapi"
http_post_static_payload:
rule_name: percentage_match_rule
rule_level: medium
postç»æï¼
{
"num_hits": 10,
"@timestamp": "2018-01-31T09:39:05.199394Z",
"rule_level": "medium",
"rule_name": "percentage_match_rule",
"num_matches": 1,
"percentage": 100.0
åè¦æ¹å¼
elastalertå ç½®çåè¦æ¹å¼å¹¶ä¸å¤ªä½¿ç¨ä¸å½äººçä¹ æ¯ï¼æ以è¿å建议èªè¡åæå¡ç«¯éæ°å®ä¹ã
为ä»ä¹ä¸å¨elastalertæºç alerts.pyä¸ç´æ¥å ç±»ï¼èéè¿poståºæ¥èªå·±åæå¡ç«¯æ¥æ¶åè¦ï¼ 主è¦èèå°elastalert项ç®æ´æ°ã
ç®åæ¯è¾å¸¸ç¨çåè¦æ¨¡å¼æï¼ééã微信ãé®ä»¶ãçä¿¡ã
é¦å 设计好çåè¦å 容ï¼äºæ¯æ们å¯ä»¥å建好4ç§åè¦ç±»åï¼å¹¶éæ¥å®ç°åè½ã
ééåè¦
ç®åééæ两ç§åè¦æ¹æ³ï¼ä¸ç§æ¯è·å¾ç®¡çåtokenï¼å¯ä»¥è°ç¨ä¼ä¸éç¥äº§çåè¦ï¼è¿ç§æ¹å¼ç好å¤æ¯å¯ä»¥éç¥å°ä¼ä¸ä¸å¯¹åºç人ï¼å¯¹åºé¨é¨ä¸ææ人çã
è¿éå享ä¸ä¸å®ç°ç大è´æè·¯ï¼
def send(self, post_alert_content):
# åè¦å
容
msgcontent = {
"title": post_alert_content["name"],
"text": "## è§åï¼{0} \n ## 级å«ï¼{1} \n ## æ¶é´ï¼{2} \n ## å
容ï¼{3}".format(
post_alert_content["name"],post_alert_content["level"],post_alert_content["create_at"],post_alert_content["content"]
# è·åéè¦éç¥çç¨æ·å表
userid_list = users.getDingDingUserIdByName(post_alert_content["contact_users"])
msgtype = "markdown"
agent_id = DD_AgentId
dept_id_list = None
try:
msgcontent = json.dumps(msgcontent)
except JSONDecodeError:
args = locals().copy()
payload = {}
for k, v in args.items():
if k in ('msgtype', 'agent_id', 'msgcontent', 'userid_list', 'dept_id_list'):
if v is not None:
payload.update({k: v})
# åéééåè¦ä¿¡æ¯
resp = self.callDingDingWebApi(self.access_token, 'dingtalk.corp.message.corpconversation.asyncsend', **payload)
if "error_response" in resp.json().keys():
self.getAccessToken()
self.send(post_alert_content)
ææï¼åè¦åºç°å¨ä¼ä¸éç¥ä¸ã
å¦ä¸ç§åæ¯éè¿ééå建群ï¼æ·»å ééæºå¨äººåè¦ã
def sendByRobot(self, post_alert_content):
DD_level = post_alert_content.get("level", "")
DD_name = post_alert_content.get("name", "")
DD_content = post_alert_content.get("content", "")
DD_url = post_alert_content.get("url", "")
headers = {"Content-Type": "application/json"}
message = {
"msgtype": "markdown",
"markdown": {
"title": "ã" + DD_level + "ã" + DD_name,
"text": "### æ¶é´ï¼" + datetime.now().strftime("%Y-%m-%d %X") + "\n" \
"### è§åï¼" + "ã" + DD_level + "ã" + DD_name + "\n" \
"### å
容ï¼" + DD_content + "\n"
r = requests.post(url=DD_url, headers=headers, data=json.dumps(message))
return True
çä¿¡åè¦
çæ¯åè¦çå ·ä½å®ç°ä¸ä¼ä¸éç¨ççä¿¡ééæå ³ï¼ä½æ¯æ¹å¼åºæ¬ç¸ä¼¼ã
def send(self, post_alert_content):
param:
phone @string
raw_content @string
return:
@bool
self.params['phone'] = post_alert_content["users_phone"]
self.params['report'] = True
content = self.getContent(post_alert_content)
self.params['msg'] = urllib.quote(content)
response = requests.post(SMS_SEND_MSG_URL, json=self.params)
rv = response.json()
微信åè¦
微信åè¦ï¼å®ç°ç大è´æè·¯ï¼
def send(self, users, subject, content):
params:
users @string
subject @string
content @string
return:
@bool
# 微信API
post_url = WECHAT_MSG_URL + self.token
for user in users.split(","):
message = {
# ä¼ä¸å·ä¸çç¨æ·å¸å·
"touser": user,
# æ¶æ¯ç±»å
"msgtype": "text",
# ä¼ä¸å·ä¸çåºç¨id
"agentid": WECHAT_AGENTID,
"text": {
"content": subject + '\n' + content
"safe": "0"
# 触ååè¦
r = requests.post(url=post_url, data=json.dumps(message), verify=False)
print r.text
return True
é®ä»¶åè¦
é®ç®±åè¦è¦æ³¨æ使ç¨SSLï¼ä¸ç¶é®ç®±è´¦å¯è¢«æ¸äºå°±åµåµäºã
def send(self, post_alert_content):
to_addrs = "{}".format(post_alert_content["to_addrs"])
subject = "ãè§åã {}".format(post_alert_content["name"])
message = "ãæ¶é´ã{} \n ãå
容ã{}".format(post_alert_content["create_at"], post_alert_content["content"])
# to_addr = to_addrs.split(",")
for to_addr in to_addrs.split(","):
msg = self.format_msg(self.from_addr, to_addr, subject, message)
s = smtplib.SMTP_SSL(Mail_Host, Mail_Port)
s.login(Mail_User, Mail_Pass)
s.sendmail(self.from_addr, [to_addr], msg.as_string())
s.quit()
return True
è§å管ç
为äºæ¹ä¾¿è¿ç¨ç®¡çè§åï¼æ们éè¦æ°æ®åºåå¨è§åä¿¡æ¯ï¼ç¶åéè¿æå¡ç«¯æ¥å£æ¥çå½åè§åä¿¡æ¯ï¼æ°éï¼æä½YAMLè§åæ件å®ç°è§å管çã
å¦ææ们éè¦æ·»å è§åï¼é£ä¹å¨è§åç®å½ä¸ï¼å建对åºçyamlè§åæ件å³å¯ã
def insertElastRule(params):
# æ¥çæ°æ®åºä¸æ¯å¦åå¨ååè§å
_es_rule = ElastRule.query.filter_by(rule_esalert_name=rule_esalert_name).first()
if _es_rule:
return False
else:
now = datetime.now()
insertRule = ElastRule(
rule_name=params["rule_name"],
rule_type=params["rule_type"],
rule_index=params["rule_index"],
rule_num_events=params["rule_num_events"],
rule_timeframe=params["rule_timeframe"],
rule_filter=params["rule_filter"],
rule_level=params["rule_level"],
rule_content=params["rule_content"],
create_at=now,
end_at=now
db.session.add(insertRule)
db.session.commit()
# å建yamlè§åæ件
createRuleYAML(params["rule_name"])
return True
å建YAMLå½æ°ï¼
def createRuleYAML(rule_esalert_name):
_rule = ElastRule.query.filter_by(rule_esalert_name=rule_esalert_name).first()
ruleJson = {
"name": _rule.rule_esalert_name,
"type": _rule.rule_type,
"index": _rule.rule_index,
"num_events": int(_rule.rule_num_events),
"timeframe": {'minutes': int(_rule.rule_timeframe)},
"filter": _rule.rule_filter,
"alert": "post",
"http_post_url": "http://localhost:8088/api/alert",
"http_post_static_payload":{"rule_name": _rule.rule_esalert_name, "rule_level": _rule.rule_level}
with open('/easywatch/elastalert_rules/{}.yaml'.format(rule_esalert_name),'w') as fw:
yaml.safe_dump(ruleJson, stream=fw, allow_unicode=True, default_flow_style=False)
åè¦æè
æ¸ éç使ç¨ï¼éè¿çº§å«ç»å使ç¨åè¦æ¹å¼ï¼
é«çº§å«åè¦ä½¿ç¨3个æ以ä¸çæ¹å¼åè¦ - çä¿¡ãééï¼å¾®ä¿¡ï¼ãé®ä»¶
ä¸çº§å«åè¦ä½¿ç¨2个æ以ä¸çæ¹å¼åè¦ - ééï¼å¾®ä¿¡ï¼ãé®ä»¶
ä½çº§å«åè¦ä½¿ç¨1个æ以ä¸çæ¹å¼åè¦ - é®ä»¶
ELKå±ç¤ºåè¦ææï¼
éè¿æ建è§å¾ãé¢æ¿ï¼æ¥çå ·ä½åè¦æå¿
*æ¬æä½è ï¼bigfaceï¼æ¬æå± FreeBuf ååå¥å±è®¡åï¼æªç»è®¸å¯ç¦æ¢è½¬è½½ã
- 0 æç« æ°
- 0 è¯è®ºæ°
- 0 å ³æ³¨è
请 ç»å½ / 注å åå¨FreeBufåå¸å 容å¦