diff --git a/tools/fofa_search.yaml b/tools/fofa_search.yaml new file mode 100644 index 00000000..4796cccd --- /dev/null +++ b/tools/fofa_search.yaml @@ -0,0 +1,285 @@ +name: "fofa_search" +command: "python3" +args: + - "-c" + - | + import sys + import json + import base64 + import requests + import os + + # ==================== FOFA配置 ==================== + # 请在此处配置您的FOFA账号信息 + # 您也可以在环境变量中设置:FOFA_EMAIL 和 FOFA_API_KEY + FOFA_EMAIL = "" # 请填写您的FOFA账号邮箱 + FOFA_API_KEY = "" # 请填写您的FOFA API密钥 + # ================================================== + + # FOFA API基础URL + base_url = "https://fofa.info/api/v1/search/all" + + # 解析参数(从JSON字符串或命令行参数) + def parse_args(): + # 尝试从第一个参数读取JSON配置 + if len(sys.argv) > 1: + try: + config = json.loads(sys.argv[1]) + return config + except (json.JSONDecodeError, TypeError): + # 如果不是JSON,使用传统的位置参数方式 + pass + + # 传统位置参数方式(向后兼容) + # 注意:email 和 api_key 已从参数中移除,现在从配置中读取 + config = {} + if len(sys.argv) > 1: + config['query'] = sys.argv[1] + if len(sys.argv) > 2: + try: + config['size'] = int(sys.argv[2]) + except (ValueError, TypeError): + pass + if len(sys.argv) > 3: + try: + config['page'] = int(sys.argv[3]) + except (ValueError, TypeError): + pass + if len(sys.argv) > 4: + config['fields'] = sys.argv[4] + if len(sys.argv) > 5: + val = sys.argv[5] + if isinstance(val, str): + config['full'] = val.lower() in ('true', '1', 'yes') + else: + config['full'] = bool(val) + return config + + try: + config = parse_args() + + # 从配置或环境变量获取email和api_key + email = os.getenv('FOFA_EMAIL', FOFA_EMAIL).strip() + api_key = os.getenv('FOFA_API_KEY', FOFA_API_KEY).strip() + query = config.get('query', '').strip() + + if not email: + error_result = { + "status": "error", + "message": "缺少FOFA配置: email(FOFA账号邮箱)", + "required_config": ["email", "api_key"], + "note": "请在YAML文件的FOFA_EMAIL配置项中填写您的FOFA账号邮箱,或在环境变量FOFA_EMAIL中设置" + } + print(json.dumps(error_result, ensure_ascii=False, indent=2)) + sys.exit(1) + + if not api_key: + error_result = { + "status": "error", + "message": "缺少FOFA配置: api_key(FOFA API密钥)", + "required_config": ["email", "api_key"], + "note": "请在YAML文件的FOFA_API_KEY配置项中填写您的API密钥,或在环境变量FOFA_API_KEY中设置。API密钥可在FOFA个人中心获取: https://fofa.info/userInfo" + } + 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) + + # 构建请求参数 + params = { + 'email': email, + 'key': api_key, + 'qbase64': base64.b64encode(query.encode('utf-8')).decode('utf-8') + } + + # 可选参数 + if 'size' in config and config['size'] is not None: + try: + size = int(config['size']) + if size > 0: + params['size'] = size + except (ValueError, TypeError): + pass + + if 'page' in config and config['page'] is not None: + try: + page = int(config['page']) + if page > 0: + params['page'] = page + except (ValueError, TypeError): + pass + + if 'fields' in config and config['fields']: + params['fields'] = str(config['fields']).strip() + + if 'full' in config and config['full'] is not None: + full_val = config['full'] + if isinstance(full_val, bool): + params['full'] = 'true' if full_val else 'false' + elif isinstance(full_val, str): + params['full'] = 'true' if full_val.lower() in ('true', '1', 'yes') else 'false' + elif isinstance(full_val, (int, float)): + params['full'] = 'true' if full_val != 0 else 'false' + + # 发送请求 + try: + response = requests.get(base_url, params=params, timeout=30) + response.raise_for_status() + + result_data = response.json() + + # 检查FOFA API返回的错误 + if result_data.get('error'): + error_result = { + "status": "error", + "message": f"FOFA API错误: {result_data.get('errmsg', '未知错误')}", + "error_code": result_data.get('error'), + "suggestion": "请检查API密钥是否正确,或查询语句是否符合FOFA语法" + } + print(json.dumps(error_result, ensure_ascii=False, indent=2)) + sys.exit(1) + + # 格式化输出结果 + output = { + "status": "success", + "query": query, + "size": result_data.get('size', 0), + "page": result_data.get('page', 1), + "total": result_data.get('total', 0), + "results_count": len(result_data.get('results', [])), + "results": result_data.get('results', []), + "message": f"成功获取 {len(result_data.get('results', []))} 条结果,共 {result_data.get('total', 0)} 条" + } + + print(json.dumps(output, ensure_ascii=False, indent=2)) + + except requests.exceptions.RequestException as e: + error_result = { + "status": "error", + "message": f"请求失败: {str(e)}", + "suggestion": "请检查网络连接或FOFA 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: "FOFA网络空间搜索引擎,支持灵活的查询参数配置" +description: | + FOFA是一个网络空间测绘搜索引擎,可以通过多种查询条件搜索互联网资产。 + + **主要功能:** + - 支持多种查询语法(app、title、domain、ip、port、country、city等) + - 灵活的字段返回配置 + - 分页查询支持 + - 完整数据模式(full参数) + + **使用场景:** + - 资产发现和枚举 + - 漏洞影响范围评估 + - 安全态势感知 + - 威胁情报收集 + - Bug bounty信息收集 + + **查询语法示例:** + - `app="Apache"` - 搜索Apache应用 + - `title="登录"` - 搜索标题包含"登录"的页面 + - `domain="example.com"` - 搜索特定域名 + - `ip="1.1.1.1"` - 搜索特定IP + - `port="80"` - 搜索开放80端口的资产 + - `country="CN"` - 搜索中国境内的资产 + - `city="Beijing"` - 搜索北京的资产 + - `app="Apache" && country="CN"` - 组合查询 + + + **注意事项:** + - API调用有频率限制,请合理使用 + - 查询结果数量受账户权限限制 + - full参数需要高级权限 +parameters: + + - name: "query" + type: "string" + description: | + FOFA查询语句(必需) + + 搜索查询语句,支持FOFA查询语法。 + 示例: + - app="Apache" + - title="登录" + - domain="example.com" + - ip="1.1.1.1" + - port="80" + - country="CN" + - app="Apache" && country="CN" + required: true + position: 2 + format: "positional" + - name: "size" + type: "int" + description: | + 返回结果数量(可选) + + 每页返回的结果数量,默认100。 + 取值范围:1-10000,受账户权限限制。 + required: false + position: 3 + format: "positional" + default: 100 + - name: "page" + type: "int" + description: | + 页码(可选) + + 要返回的页码,从1开始,默认1。 + 用于分页查询大量结果。 + required: false + position: 4 + format: "positional" + default: 1 + - name: "fields" + type: "string" + description: | + 返回字段列表(可选) + + 需要返回的字段,多个字段用逗号分隔。 + 默认字段:ip,port,domain + 可用字段:host,title,ip,domain,port,protocol,country,province,city,server,icp,header,banner,body,as_number,as_organization + required: false + position: 5 + format: "positional" + default: "ip,port,domain" + - name: "full" + type: "bool" + description: | + 是否返回完整数据(可选) + + 设置为true时返回完整数据,需要高级权限。 + 默认false,返回基础数据。 + required: false + position: 6 + format: "positional" + default: false