diff --git a/tools/zoomeye_search.yaml b/tools/zoomeye_search.yaml new file mode 100644 index 00000000..9669229e --- /dev/null +++ b/tools/zoomeye_search.yaml @@ -0,0 +1,358 @@ +name: "zoomeye_search" +command: "python3" +args: + - "-c" + - | + import sys + import json + import base64 + import requests + import os + + # ==================== ZoomEye配置 ==================== + # 请在此处配置您的ZoomEye账号信息 + # 您也可以在环境变量中设置:ZOOMEYE_API_KEY + ZOOMEYE_API_KEY = "" # 请填写您的ZoomEye API密钥 + # ================================================== + + # ZoomEye API基础URL + base_url = "https://api.zoomeye.org/v2/search" + + # 解析参数(从JSON字符串或命令行参数) + def parse_args(): + # 尝试从第一个参数读取JSON配置 + if len(sys.argv) > 1: + try: + # 确保 sys.argv[1] 是字符串 + arg1 = str(sys.argv[1]) + # 尝试解析为JSON + config = json.loads(arg1) + # 确保返回的是字典类型 + if isinstance(config, dict): + return config + except (json.JSONDecodeError, TypeError, ValueError): + # 如果不是JSON,使用传统的位置参数方式 + pass + + # 传统位置参数方式(向后兼容) + # 参数位置:query=1, page=2, pagesize=3, fields=4, sub_type=5 + config = {} + if len(sys.argv) > 1: + config['query'] = str(sys.argv[1]) + if len(sys.argv) > 2: + try: + config['page'] = int(sys.argv[2]) + except (ValueError, TypeError): + pass + if len(sys.argv) > 3: + try: + config['pagesize'] = int(sys.argv[3]) + except (ValueError, TypeError): + pass + if len(sys.argv) > 4: + config['fields'] = str(sys.argv[4]) + if len(sys.argv) > 5: + config['sub_type'] = str(sys.argv[5]) + return config + + try: + config = parse_args() + + # 确保 config 是字典类型 + if not isinstance(config, dict): + error_result = { + "status": "error", + "message": f"参数解析错误: 期望字典类型,但得到 {type(config).__name__}", + "type": "TypeError" + } + print(json.dumps(error_result, ensure_ascii=False, indent=2)) + sys.exit(1) + + # 从配置或环境变量获取api_key + api_key = os.getenv('ZOOMEYE_API_KEY', ZOOMEYE_API_KEY).strip() + query = config.get('query', '').strip() + + if not api_key: + error_result = { + "status": "error", + "message": "缺少ZoomEye配置: api_key(ZoomEye API密钥)", + "required_config": ["api_key"], + "note": "请在YAML文件的ZOOMEYE_API_KEY配置项中填写您的API密钥,或在环境变量ZOOMEYE_API_KEY中设置。API密钥可在ZoomEye个人中心获取: https://www.zoomeye.org/user" + } + print(json.dumps(error_result, ensure_ascii=False, indent=2)) + sys.exit(1) + + if not query: + error_result = { + "status": "error", + "message": "缺少必需参数: query(搜索查询语句)", + "required_params": ["query"], + "examples": [ + 'app:"Apache"', + 'title:"登录"', + 'domain:"example.com"', + 'ip:"1.1.1.1"', + 'port:80', + 'country:"CN"', + 'city:"Beijing"' + ] + } + print(json.dumps(error_result, ensure_ascii=False, indent=2)) + sys.exit(1) + + # 构建请求参数(ZoomEye API 使用 POST 请求,参数在请求体中) + # ZoomEye API 使用 qbase64 参数(base64编码的查询) + data = { + 'qbase64': base64.b64encode(query.encode('utf-8')).decode('utf-8') + } + + # 可选参数 + if 'page' in config and config['page'] is not None: + try: + page = int(config['page']) + if page > 0: + data['page'] = page + except (ValueError, TypeError): + pass + + if 'pagesize' in config and config['pagesize'] is not None: + try: + pagesize = int(config['pagesize']) + if pagesize > 0 and pagesize <= 10000: + data['pagesize'] = pagesize + except (ValueError, TypeError): + pass + + if 'fields' in config and config['fields']: + data['fields'] = str(config['fields']).strip() + + if 'sub_type' in config and config['sub_type']: + sub_type = str(config['sub_type']).strip().lower() + if sub_type in ('v4', 'v6', 'web'): + data['sub_type'] = sub_type + + if 'facets' in config and config['facets']: + data['facets'] = str(config['facets']).strip() + + if 'ignore_cache' in config and config['ignore_cache'] is not None: + ignore_cache_val = config['ignore_cache'] + if isinstance(ignore_cache_val, bool): + data['ignore_cache'] = ignore_cache_val + elif isinstance(ignore_cache_val, str): + data['ignore_cache'] = ignore_cache_val.lower() in ('true', '1', 'yes') + elif isinstance(ignore_cache_val, (int, float)): + data['ignore_cache'] = ignore_cache_val != 0 + + # 设置请求头(ZoomEye API 使用 API Key 认证) + headers = { + 'API-KEY': api_key, + 'Content-Type': 'application/json' + } + + # 发送请求(ZoomEye API 使用 POST 方法) + try: + response = requests.post(base_url, json=data, headers=headers, timeout=30) + response.raise_for_status() + + result_data = response.json() + + # 检查ZoomEye API返回的错误 + # ZoomEye API 成功时 code 为 60000 + if result_data.get('code') != 60000: + error_code = result_data.get('code', 'unknown') + error_msg = result_data.get('message', '未知错误') + error_result = { + "status": "error", + "message": f"ZoomEye API错误: {error_msg}", + "error_code": error_code, + "suggestion": "请检查API密钥是否正确,或查询语句是否符合ZoomEye语法" + } + print(json.dumps(error_result, ensure_ascii=False, indent=2)) + sys.exit(1) + + # 格式化输出结果 + # ZoomEye API 返回的数据在 data 字段中 + data_list = result_data.get('data', []) + output = { + "status": "success", + "query": result_data.get('query', query), + "total": result_data.get('total', 0), + "page": result_data.get('page', 1), + "pagesize": result_data.get('pagesize', 10), + "results_count": len(data_list), + "results": data_list, + "message": f"成功获取 {len(data_list)} 条结果" + } + + print(json.dumps(output, ensure_ascii=False, indent=2)) + + except requests.exceptions.RequestException as e: + error_result = { + "status": "error", + "message": f"请求失败: {str(e)}", + "suggestion": "请检查网络连接或ZoomEye API服务状态" + } + print(json.dumps(error_result, ensure_ascii=False, indent=2)) + sys.exit(1) + + except Exception as e: + error_result = { + "status": "error", + "message": f"执行出错: {str(e)}", + "type": type(e).__name__ + } + print(json.dumps(error_result, ensure_ascii=False, indent=2)) + sys.exit(1) +enabled: true +short_description: "ZoomEye网络空间搜索引擎,支持灵活的查询参数配置" +description: | + ZoomEye(钟馗之眼)是一个网络空间测绘搜索引擎,可以通过多种查询条件搜索互联网资产。 + + **主要功能:** + - 支持多种查询语法(app、title、domain、ip、port、country、city等) + - 灵活的字段返回配置 + - 分页查询支持(最多10000条/页) + - 支持IPv4、IPv6和Web资产搜索 + - 支持统计项(facets)查询 + - 丰富的资产信息返回 + + **使用场景:** + - 资产发现和枚举 + - 漏洞影响范围评估 + - 安全态势感知 + - 威胁情报收集 + - Bug bounty信息收集 + + **查询语法说明:** + + **搜索规则:** + - 搜索范围包括设备(IPv4、IPv6)和网站(域名) + - 搜索字符串不区分大小写,使用引号包裹(如 `"Cisco System"`) + - 使用 `==` 进行精确匹配(区分大小写) + + **逻辑运算符:** + - `=` - 搜索包含关键词的资产,如 `title="knownsec"` + - `==` - 精确匹配(区分大小写),如 `title=="knownsec"` + - `||` - 或运算,如 `service="ssh" || service="http"` + - `&&` - 与运算,如 `device="router" && after="2020-01-01"` + - `!=` - 非运算,如 `country="US" && subdivisions!="new york"` + - `()` - 优先级处理,如 `(country="US" && port!=80) || (country="US" && title!="404 Not Found")` + - `*` - 模糊搜索,如 `title="*google"` + + **语法关键字:** + + **地理位置:** `country="CN"`、`subdivisions="beijing"`、`city="changsha"` + + **IP/域名:** `ip="8.8.8.8"`、`cidr="52.2.254.36/24"`、`domain="baidu.com"`、`hostname="google.com"`、`port=80`、`asn=42893`、`org="Stanford University"`、`isp="China Mobile"` + + **指纹识别:** `app="Cisco ASA SSL VPN"`、`service="ssh"`、`device="router"`、`os="RouterOS"`、`title="Cisco"`、`product="Cisco"`、`protocol="TCP"`、`industry="government"`、`is_honeypot="True"` + + **证书搜索:** `ssl="google"`、`ssl.cert.fingerprint="..."`、`ssl.jarm="..."`、`ssl.ja3s=...`、`ssl.version="TLSv1.3"`、`ssl.cert.subject.cn="example.com"` + + **HTTP相关:** `banner="FTP"`、`http.header="http"`、`http.header_hash="..."`、`http.header.server="Nginx"`、`http.header.status_code="200"`、`http.body="document"`、`http.body_hash="..."` + + **时间搜索:** `after="2020-01-01"`、`before="2020-01-01"`(需与其他条件组合使用) + + **其他:** `dig="baidu.com 220.181.38.148"`、`iconhash="f3418a443e7d841097c714d69ec4bcb8"`、`filehash="0b5ce08db7fb8fffe4e14d05588d49d9"`、`is_ipv4=true`、`is_ipv6=true`、`is_domain=true` + + **查询示例:** + - `port=80 && service="http"` - 搜索80端口运行HTTP服务的网络设备 + - `city=nagoya && port=443 && service=ssl` - 搜索名古屋443端口运行SSL的设备 + - `country=us && os=windows` - 搜索美国运行Windows系统的设备 + - `app="Microsoft NTP"` - 搜索运行Microsoft NTP应用的设备 + - `city=tokyo && device=webcam` - 搜索东京的摄像头 + - `after="2020-01-01" && port=50050` - 搜索2020-01-01之后索引的50050端口资产 + - `os=linux && port=22 && country=PL` - 搜索波兰Linux系统22端口资产 + - `service=ftp && hostname=example` - 搜索主机名为example的FTP服务 + - `http.body="Knownsec"` - 搜索body中包含"Knownsec"的资产 + + **注意事项:** + - API调用有频率限制,请合理使用 + - 查询结果数量受账户权限限制 + - 需要有效的ZoomEye API密钥 +parameters: + - name: "query" + type: "string" + description: | + ZoomEye查询语句(必需) + + 搜索查询语句,支持ZoomEye查询语法。使用引号包裹字符串,使用逻辑运算符连接条件。 + 示例: + - app="Apache" + - title="登录" + - domain="example.com" + - ip="1.1.1.1" + - port=80 + - country="CN" + - app="Apache" && country="CN" + - service="ssh" || service="http" + - port=80 && service="http" + required: true + position: 1 + format: "positional" + - name: "page" + type: "int" + description: | + 页码(可选) + + 要返回的页码,从1开始,默认1。 + 用于分页查询大量结果。 + required: false + position: 2 + format: "positional" + default: 1 + - name: "pagesize" + type: "int" + description: | + 每页结果数量(可选) + + 每页返回的结果数量,默认10。 + 取值范围:1-10000,受账户权限限制。 + required: false + position: 3 + format: "positional" + default: 5 + - name: "fields" + type: "string" + description: | + 返回字段列表(可选) + + 需要返回的字段,多个字段用逗号分隔。 + 默认字段:ip,port,domain,update_time + 可用字段:ip,port,domain,url,hostname,os,service,title,version,device,rdns,product,header,body,banner,update_time,country,province,city,isp,organization,asn,protocol,ssl,ssl.jarm,ssl.ja3s等 + required: false + position: 4 + format: "positional" + default: "ip,port,domain,update_time" + - name: "sub_type" + type: "string" + description: | + 数据类型(可选) + + 指定搜索的数据类型。 + 可选值:v4(IPv4设备,默认)、v6(IPv6设备)、web(网站资产) + required: false + position: 5 + format: "positional" + default: "v4" + - name: "facets" + type: "string" + description: | + 统计项(可选) + + 需要统计的字段,多个字段用逗号分隔。 + 支持:country、subdivisions、city、product、service、device、os、port + required: false + position: 6 + format: "positional" + - name: "ignore_cache" + type: "bool" + description: | + 是否忽略缓存(可选) + + 设置为true时忽略缓存,获取最新数据。 + 默认false,支持商业版及以上用户。 + required: false + position: 7 + format: "positional" + default: false