3.3.5. 脚本定制

July 25, 2026 · View on GitHub

什么是TH-Nebula脚本?

TH-Nebula脚本是TH-Nebula系统配合策略, 触发策略, 参与策略定义变量计算的程序.

为何需要定制TH-Nebula脚本?

因为业务千变万化, TH-Nebula并不能预知到所有的业务场景, 所以需要自定义化, 可以对一个接口写一个, 甚至多个脚本去触发策略.

如何定制TH-Nebula脚本?

TH-Nebula脚本的总体流程

21.脚本定制1

概要:

  1. 首先取得所需要监控网站的接口以及页面(page)
  2. 对接口的参数需要收集起来,并对参数要明确了解
  3. 对接口的类型分类为所属的策略, 根据接口参数所提供的, 在定制脚本上定制相应的类,也就是处理的函数,分配到策略上, 就可以在触发事件的时候, 通过处理函数,将事件分配到策略上, 在详情页面拿到所定制的流量数据.

简要的例子

在需要监控网站的订单页面, 某个用户提交订单, 那么在定制脚本通过监控订单接口, 拿到订单流量, 把所有的订单参数作为策略字段存到事件中去, 那么事件就会根据配备上线的策略去触发策略, 这事件就会在TH-Nebula中例如总览, 风险名单管理, 风险事件管理位置出现, 在详情页面就能拿到这订单的所有信息.

完整的例子

根据之前的例子, 新建了一个事件叫做账号-实名验证的策略, 那么它的类型如图所示它的类型为: ACCOUNT_CERTIFICATION

22.脚本定制2

已经定制好策略, 然后在脚本中定义此策略相应的类, 以下是测试的类 例如在/etc/nebula/sniffer/sniffer.conf中配置脚本名, 以下testparser即是脚本名, 脚本的所在位置在于TH-Nebula_sniffer项目:

sniffer.conf位置:

/etc/nebula/sniffer/sniffer.conf

配置:

sources: [eth0]
en0:
    driver: bro
    interface: en0
    ports: [80, 81, 1080, 3128, 8000, 8080, 8888, 9001, 8081-8083]
    start_port: 48880
    instances: 1
    parser:
        name: test
        module: testparser

testparser.py位置:

TH-Nebula_sniffer\TH-Nebula_sniffer\customparsers\testparser.py

配置:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import re

import logging

from threathunter_common.util import millis_now
from threathunter_common.event import Event

from ..parser import Parser, extract_common_properties, extract_http_log_event
from ..parserutil import extract_value_from_body, get_md5, get_json_obj
from ..msg import HttpMsg

logger = logging.getLogger("sniffer.parser.{}".format("testparser"))

def arg_from_get(data):
    d = data.split('?')[1].split('&')
    result = {}
    for e in d:
        a, b = e.split('=')
        result[a] = b
    return result

def account_test(httpmsg):
    if not isinstance(httpmsg, HttpMsg):
        return
    if "/loign" not in httpmsg.uri:
        return
    # get请求通过httpmsg的uri字段获得请求参数
    data = httpmsg.get_dict()    
    data = arg_from_get(data['uri'])
    properties = extract_common_properties(httpmsg)
    properties["result"] = "T"
    properties["new_password"] = data.get('new_password')
    properties["old_password"] = data.get('old_password')
    properties["register_realname"] = ""
    properties["register_channel"] = "not"
    properties["email"] = data.get('email')
    properties["user_name"] = extract_value_from_body(r_mobile_pattern, httpmsg.req_body)
    properties["password"] = data.get('password')
    properties["captcha"] = ""
    properties["register_verification_token"] = "none"
    properties["register_verification_token_type"] = "null"
    return Event("nebula", "ACCOUNT_CERTIFICATION", "", millis_now(), properties)


#############Parser############################
class TestParser(Parser):
    def __init__(self):
        super(TestParser, self).__init__()
        self.http_msg_parsers = [
            account_test,
        ]

    def name(self):
        return "test customparsers"

    def get_logbody_config(self):
        return ["login", "user"]

    def get_events_from_http_msg(self, http_msg):
        if not http_msg:
            return []

        result = list()
        for p in self.http_msg_parsers:
            try:
                ev = p(http_msg)
                if ev:
                    result.append(ev)
            except:
                logger.debug("fail to parse with {}".format(p))

        return result

    def get_events_from_text_msg(self, text_msg):
        return []

    def filter(self, msg):
        if not isinstance(msg, HttpMsg):
            return False

        return False


Parser.add_parser("test", TestParser())

要注意最后return中Event第二个参数是: ACCOUNT_CERTIFICATION 这个参数决定了触发了策略的类型, 系统会根据这个类型去匹配策略, 如果触发了策略, 那么会根据策略的处理流程处理.

httpmsg是什么?

httpmsgTH-Nebula系统中的sniffer定义的请求所包含的数据, 设备信息, IP等等所有能抓到的所有信息

流量中的请求如何体现在httpmsg中?

GET请求在httpmsg的体现

注意请求参数order=88888888888888888888 在uri中, 可以以此找到所有请求参数,这是定制脚本重点!

{
	'uid': '',
	'status_code': 404,
	'resp_content_type': 'text/plain; charset=utf-8',
	'resp_headers': {
		'CONTENT-LENGTH': '356',
		'VARY': 'Accept-Encoding',
		'SERVER': 'openresty',
		'CONNECTION': 'close',
		'DATE': 'Fri, 26 Oct 2018 03:58:30 GMT',
		'CONTENT-TYPE': 'text/plain; charset=utf-8'
	},
	'id': ObjectId('5bd290e612a3650c2c1c440f'),
	'dest_port': 9001,
	'resp_body_len': 356,
	'log_body': False,
	'resp_body': '',
	'req_content_type': '',
	'debug_processing': False,
	'req_headers': {
		'ACCEPT-ENCODING': 'gzip, deflate',
		'HOST': 'nebula.example.com:9001',
		'ACCEPT': '*/*',
		'USER-AGENT': 'python-requests/2.11.1',
		'CONNECTION': 'close',
		'COOKIE': '<SESSION_COOKIE>'
	},
	'method': 'GET',
	'req_body': '',
	'req_body_len': 0,
	'host': 'nebula.example.com',
	'referer': '',
	'xforward': '',
	'did': '',
	'status_msg': 'not found',
	'source_ip': '198.51.100.28',
	 // 这里需要注意 uri 字段值
	'uri': 'nebula.example.com/history?order_id=88888888888888888888',
	'user_agent': 'python-requests/2.11.1',
	'source_port': 60563,
	'dest_ip': '172.18.16.169'
}

POST请求在httpmsg的体现

注意请求字段 在req_body字段中,可以以此找到所有请求参数,这是定制脚本重点

{
	'uid': '',
	'status_code': 404,
	'resp_content_type': 'text/plain;charset=utf-8',
	'resp_headers': {
		'CONTENT-LENGTH': '356',
		'VARY': 'Accept-Encoding',
		'SERVER': 'openresty',
		'CONNECTION': 'close',
		'DATE': 'Fri,26Oct201808:13:52GMT',
		'CONTENT-TYPE': 'text/plain;charset=utf-8'
	},
	'id': ObjectId('5bd2ccc012a365682aee1fbd'),
	'dest_port': 9001,
	'resp_body_len': 356,
	'log_body': False,
	'resp_body': 'Traceback(mostrecentcalllast):\nFile"/home/threathunter/nebula/nebula_web/venv/lib/python2.7/site-packages/tornado/web.py",line1422,in_execute\nresult=self.prepare()\nFile"/home/threathunter/nebula/nebula_web/venv/lib/python2.7/site-packages/tornado/web.py",line2149,inprepare\nraiseHTTPError(self._status_code)\nHTTPError:HTTP404:NotFound\n',
	'req_content_type': 'application/json',
	'debug_processing': False,
	'req_headers': {
		'CONTENT-LENGTH': '78',
		'ACCEPT-ENCODING': 'gzip,deflate',
		'HOST': 'nebula.example.com:9001',
		'ACCEPT': '*/*',
		'USER-AGENT': 'python-requests/2.11.1',
		'CONNECTION': 'close',
		'COOKIE': '<SESSION_COOKIE>',
		'CONTENT-TYPE': 'application/json'
	},
	'method': 'POST',
	 // 这里需要注意 req_body 字段值
	 'req_body': '{"password":"777777777","order":"999999999999999999","user":"weihong999"}',
	'req_body_len': 78,
	'host': 'nebula.example.com',
	'referer': '',
	'xforward': '',
	'did': '',
	'status_msg': 'notfound',
	'source_ip': '198.51.100.141',
	'uri': 'nebula.example.com/history',
	'user_agent': 'python-requests/2.11.1',
	'source_port': 62840,
	'dest_ip': '172.18.16.169'
}