
1. 网络空间测绘与FOFA的定位思考1.1 为什么需要网络空间测绘很多刚接触安全或者资产梳理的朋友第一次听到“网络空间测绘”这个词会觉得有点玄乎。其实把它翻译成人话就是把互联网上公开可访问的设备、服务、组件信息像地图一样索引起来让你能按条件去检索。传统搜索引擎索引的是网页文本内容而网络空间测绘引擎索引的是主机、端口、协议、证书、指纹这些“资产属性”。我最早接触这类工具是因为做资产梳理。当时手里有一个目标单位只知道主域名但对方到底有多少对外暴露的系统、用了哪些中间件、有没有测试环境不小心开到公网完全是一笔糊涂账。靠人工一个个扫效率低还容易漏。后来用上FOFA这类测绘引擎思路一下就打开了——先测绘、再收敛、后验证这是资产梳理最顺的一条链路。FOFA在国内做网络空间测绘算是比较早的一批它的核心价值在于数据覆盖面广、语法灵活、支持API批量调用。对于做资产梳理、攻击面管理、漏洞影响面排查的人来说它是一个绕不开的工具。这篇内容我就按自己的实际使用经验从语法规则讲到API调用再到指纹识别和实战场景把能踩的坑和能抄的作业都摊开讲。1.2 FOFA到底能解决什么问题先明确一下适用人群和场景免得有人看完觉得“这玩意儿跟我没关系”。FOFA主要面向这几类人安全从业者做攻击面梳理、暴露面排查、漏洞影响范围评估。运维和资产管理员盘点自己单位到底有多少资产挂在公网上。渗透测试人员在授权范围内做信息收集快速定位目标资产。安全研究者做组件分布统计、指纹特征研究。它解决的问题可以归纳成一句话给定一个条件快速找出互联网上符合这个条件的所有资产。比如“找出所有使用某版本组件的资产”“找出某个域名下所有返回200的页面”“找出某个证书特征关联的主机”。这些在FOFA里都是一条语法的事。提示网络空间测绘工具的使用必须建立在合法合规的前提下。只对自己拥有或已获得明确授权的资产进行检索和测试这是底线没有商量余地。2. FOFA核心语法规则拆解2.1 基础字段与查询逻辑FOFA的语法本质上是“字段值”的组合查询。理解字段是第一步。常用的核心字段我整理成了一张表方便对照字段名含义示例domain网站根域名domainexample.comhost主机名或IPhost1.1.1.1ipIP地址ip1.1.1.0/24port端口port8080protocol协议protocolhttpstitle网页标题title后台管理headerHTTP响应头headerServerbody网页正文内容body登录cert证书内容certexamplestatus_codeHTTP状态码status_code200server服务器类型servernginxos操作系统osLinux这些字段可以单独用也可以用逻辑运算符组合。FOFA支持与、||或、!非、()分组。举个实际例子我想找某个域名下所有返回200且端口是80或443的资产domainexample.com status_code200 (port80 || port443)这条语法拆开看就是先锁定域名再筛状态码最后用括号把端口条件分组。括号在这里很关键因为的优先级高于||不加括号结果会跑偏。我见过不少人写语法时忽略优先级查出来的结果跟预期差十万八千里排查半天才发现是括号的问题。2.2 逻辑运算符的优先级陷阱说到优先级这里必须展开讲一下因为这是新手最容易翻车的地方。FOFA的运算符优先级从高到低大致是()!||。也就是说会先于||结合。假设你写domaina.com || domainb.com port443你以为它是“a.com或b.com且端口443”但实际上它等价于domaina.com || (domainb.com port443)结果就是a.com的所有端口都会出来只有b.com被限制在443。正确写法应该是(domaina.com || domainb.com) port443注意写复杂语法时养成“只要涉及或运算就加括号”的习惯能省掉大量排查时间。2.3 模糊匹配与精确匹配FOFA的等号有几种写法含义不同精确匹配值必须完全相等。也是精确匹配部分场景下与等价。*模糊匹配包含即可。比如title后台只会匹配标题完全等于“后台”的而title*后台会匹配“后台管理系统”“登录后台”等所有包含“后台”的标题。实际做资产梳理时模糊匹配用得更多因为目标的标题往往带各种后缀。但模糊匹配也有代价——结果集会变大可能引入噪音。我的经验是先用模糊匹配扩大范围再用精确条件逐步收敛。比如先title*管理找出所有管理类页面再叠加status_code200和countryCN缩小范围。2.4 组合查询的实战写法把字段、运算符、匹配方式组合起来就能写出很精准的查询。分享几条我常用的“语法糖”查找某域名下所有非标准端口的Web服务domainexample.com protocolhttp port!80 port!443查找使用特定组件的资产bodystruts status_code200查找证书中包含特定关键词的资产certexample.com port443查找某个C段下的所有存活主机ip192.168.1.0/24 status_code200这几条覆盖了资产梳理中最常见的需求。你可以把它们当成模板把里面的值替换成自己的目标即可。3. 从零到一的实操流程3.1 账号准备与查询界面熟悉FOFA的使用门槛不高注册账号后就能用基础查询。免费账号有查询条数限制做小规模梳理够用如果要批量调用API或者跑大规模任务需要升级会员。注册流程这里不展开重点说界面。登录后主界面就是一个搜索框加结果列表。搜索框输入语法回车出结果。结果列表里每条记录包含IP、端口、协议、标题、域名、更新时间等信息。点进去能看到更详细的响应头、正文快照、证书信息。右侧通常有筛选和导出选项。我建议新手先别急着上API在界面上手动跑几十条语法把字段和结果对应关系摸熟。这个过程就像学开车先在空地练别一上来就上高速。3.2 一条完整查询的拆解演示拿一个具体需求来演示找出某高校域名下所有返回200的Web资产且排除掉非标准端口。第一步锁定域名范围domainedu.com第二步加上状态码条件domainedu.com status_code200第三步限定端口为80或443domainedu.com status_code200 (port80 || port443)第四步如果还想进一步筛出使用特定中间件的domainedu.com status_code200 (port80 || port443) servernginx每一步都在收敛结果集。实际跑下来第一条约几万条第二条降到几千第三条几百第四条可能就几十条。这种逐步收敛的思路比一次性写一条巨长的语法更容易调试哪一步结果不对一眼就能看出问题出在哪个条件。3.3 结果导出与二次处理界面查询的结果可以导出成CSV或JSON。导出后我一般用Python做二次处理比如去重、按IP段归类、提取标题做指纹统计。这里给一段简单的处理脚本import csv from collections import Counter with open(fofa_result.csv, r, encodingutf-8) as f: reader csv.DictReader(f) rows list(reader) # 按服务器类型统计 servers Counter(row.get(server, unknown) for row in rows) for server, count in servers.most_common(10): print(f{server}: {count}) # 按C段归类 c_segments Counter(..join(row[ip].split(.)[:3]) for row in rows) for seg, count in c_segments.most_common(10): print(f{seg}.0/24: {count})这段脚本干的事很简单统计服务器类型分布和C段分布。别小看这两个统计服务器类型分布能帮你判断目标的技术栈偏好C段分布能帮你发现资产聚集的区域对后续深入梳理很有价值。4. API调用与自动化实战4.1 为什么必须掌握API界面查询适合探索和调试但真正做规模化资产梳理时API是绕不开的。原因有三个一是界面查询有条数上限API可以分页拉取二是API能嵌入自动化流程跟其他工具串联三是API返回结构化数据方便程序处理。FOFA的API基于HTTP协议返回JSON格式。核心接口是搜索接口传入查询语法和分页参数返回结果列表。调用前需要在个人中心获取API Key这个Key相当于你的身份凭证绝对不能泄露或提交到公开仓库。4.2 API调用的完整代码示例下面是一段Python调用FOFA API的完整示例包含分页和错误处理import requests import base64 import time API_KEY your_api_key_here BASE_URL https://fofa.info/api/v1/search/all def fofa_search(query, size100, page1): # FOFA要求query做base64编码 qbase64 base64.b64encode(query.encode()).decode() params { key: API_KEY, qbase64: qbase64, size: size, page: page, fields: ip,port,protocol,title,domain,server } try: resp requests.get(BASE_URL, paramsparams, timeout30) resp.raise_for_status() data resp.json() if data.get(error): print(fAPI返回错误: {data.get(errmsg)}) return [] return data.get(results, []) except requests.exceptions.RequestException as e: print(f请求异常: {e}) return [] def fetch_all(query, max_pages10): all_results [] for page in range(1, max_pages 1): results fofa_search(query, size100, pagepage) if not results: break all_results.extend(results) print(f第{page}页获取{len(results)}条) time.sleep(1) # 控制频率避免触发限流 return all_results if __name__ __main__: query domainexample.com status_code200 results fetch_all(query, max_pages5) print(f共获取{len(results)}条记录)这段代码有几个关键点值得说明。第一qbase64参数要求查询语法先做Base64编码这是FOFA API的硬性要求不做编码会直接报错。第二fields参数指定返回哪些字段不指定会返回默认字段集指定后数据更干净。第三分页之间加了time.sleep(1)这是为了控制请求频率。免费或低等级账号的API有频率限制跑太快会触发限流返回429错误加个延时是最省事的规避方式。4.3 分页与限流的处理经验关于分页FOFA的API一般单页最大返回100条具体以官方文档为准要拿全量数据必须循环翻页。但翻页有个坑如果查询结果在翻页过程中发生变化比如新资产上线可能导致漏数据或重复数据。做严谨的资产梳理时我会记录每次拉取的时间戳最后做一次去重合并。限流方面除了加延时还可以用指数退避策略。遇到429错误时不要立即重试而是等待一段时间再试等待时间逐次翻倍def fetch_with_backoff(query, page, retries3): wait 2 for i in range(retries): results fofa_search(query, pagepage) if results: return results print(f重试第{i1}次等待{wait}秒) time.sleep(wait) wait * 2 return []这个策略在API不稳定或者限流严格时特别管用。我实测下来配合基础延时基本不会因为限流中断任务。4.4 把API嵌入资产梳理流水线单独调API只是第一步真正的价值在于把它嵌入流水线。我的常规做法是用API拉取目标域名的全量资产。用Python做去重和归类。对归类后的资产做指纹识别下一节展开。输出成报告或导入到资产管理平台。这条流水线跑通后原本需要几天的人工梳理压缩到几小时。关键是把每一步都脚本化让流程可重复执行这样资产有变化时重跑一遍就能拿到最新结果。5. 指纹识别与资产画像5.1 Web指纹识别的基本原理指纹识别说白了就是通过资产暴露的特征判断它用的是什么组件、什么框架、什么版本。特征来源主要有几类HTTP响应头比如Server: nginx、X-Powered-By: PHP/7.4。HTML正文特征比如特定框架会在页面里留下独有的JS路径或meta标签。特定文件路径比如某些组件有固定的静态资源路径。Cookie名称不同框架设置的Cookie名有差异。证书信息证书里的组织名、通用名也能作为辅助特征。FOFA本身在结果里就带了一些指纹字段比如server、header、title。但它的指纹库不可能覆盖所有组件所以实际工作中我经常需要结合FOFA的查询能力和自建指纹规则来做识别。5.2 用FOFA语法做指纹筛选FOFA的body、header、title字段可以直接用来做指纹筛选。举几个实际例子筛选使用某OA系统的资产body/seeyon/common/ status_code200筛选使用某中间件的资产headerServer: Apache-Coyote status_code200筛选标题包含特定关键词的后台title*管理后台 status_code200 countryCN这些语法的思路是找到目标组件独有的字符串特征用它作为查询条件。特征选得越独特结果越精准。比如选“login”这种通用词噪音会非常大选某个组件独有的路径片段结果就干净得多。5.3 自建指纹库的思路当FOFA内置字段不够用时就需要自建指纹库。我的做法是维护一个JSON文件每条记录包含组件名、匹配位置header/body/title、匹配规则字符串或正则、置信度。然后用Python对API拉回来的资产逐条匹配。import re FINGERPRINTS [ {name: Nginx, location: header, pattern: rServer:\s*nginx, confidence: high}, {name: Tomcat, location: body, pattern: rApache Tomcat, confidence: high}, {name: 某OA, location: body, pattern: r/seeyon/common/, confidence: high}, ] def match_fingerprint(asset): matched [] for fp in FINGERPRINTS: target asset.get(fp[location], ) if re.search(fp[pattern], target, re.IGNORECASE): matched.append(fp[name]) return matched这个指纹库可以持续积累。每遇到一个新组件就把它的特征加进去时间长了就是一笔宝贵的资产。我自己的指纹库从最初的十几条积累到现在几百条覆盖了大部分常见中间件、框架和国产OA系统。5.4 指纹识别的准确率问题指纹识别不是百分百准确的常见误报来源有几个一是特征字符串太通用被其他组件复用二是资产做了伪装或改造特征被抹掉三是版本更新后特征变化。降低误报的办法多特征交叉验证。比如一个资产同时满足“header里有Tomcat特征”和“body里有Tomcat管理页面特征”那基本可以确定。只满足单一特征的标记为“疑似”人工复核。提示指纹识别结果用于资产梳理时建议保留置信度字段。高置信度的直接采信低置信度的进入人工复核队列这样既保证效率又控制风险。6. 常见问题与排查技巧实录6.1 语法报错与结果异常排查新手最常遇到的问题就是语法报错或者结果跟预期不符。我整理了一张速查表问题现象可能原因解决办法语法报错字段名拼写错误对照字段表检查拼写结果为空条件太严格逐步删减条件定位问题结果过多用了模糊匹配改用精确匹配或叠加条件结果不符预期运算符优先级问题给或运算加括号API返回400qbase64未编码对query做Base64编码API返回429请求频率过高加延时或指数退避API返回401Key无效或过期检查Key是否正确这张表覆盖了我遇到的大部分问题。排查的核心思路是“二分法”把复杂语法拆成两半分别跑看哪一半出问题再继续拆直到定位到具体条件。6.2 API调用的典型错误处理API调用中除了上面表格里的错误还有几个坑值得单独说。第一个是编码问题。FOFA要求query做Base64编码但有些语言的Base64实现会带换行符导致编码结果不对。Python里用base64.b64encode(query.encode()).decode()是安全的注意最后要.decode()转成字符串。第二个是超时问题。网络不稳定时请求可能卡住。一定要设置timeout参数我一般设30秒。超时后走重试逻辑不要无限等待。第三个是返回字段缺失。API返回的字段可能因为资产本身没有该属性而为空。处理数据时要做空值判断别直接对空值做字符串操作否则会抛异常。6.3 大规模梳理的性能优化当目标资产量很大时串行调用API会非常慢。优化方向有两个一是并发请求用线程池同时拉多个分页二是增量更新只拉取最近变化的资产。并发请求要注意控制并发数太高会触发限流。我一般用5到10个并发配合每请求之间的短延时。增量更新则依赖FOFA返回的更新时间字段只拉取某个时间点之后的数据。from concurrent.futures import ThreadPoolExecutor def parallel_fetch(query, pages, workers5): with ThreadPoolExecutor(max_workersworkers) as executor: futures [executor.submit(fofa_search, query, 100, p) for p in range(1, pages1)] results [] for f in futures: results.extend(f.result()) return results这段代码用线程池并发拉取多个分页。实测下来5个并发配合基础延时既能提速又不容易触发限流。并发数不是越高越好找到限流阈值以下的平衡点才是关键。6.4 几个容易忽略的实操心得最后分享几个我在实际使用中总结的小心得都是文档里不会写的。第一查询语法先在小范围验证再放大。写一条新语法时先用一个已知的小目标跑确认结果符合预期再换成大目标。这样能避免在大目标上跑出一条错误语法浪费查询额度。第二善用收藏和标签功能。FOFA界面支持保存常用查询把高频语法存起来下次直接调用省去重复输入。第三定期备份API拉取的数据。测绘数据是动态变化的今天能查到的资产明天可能就下线了。做长期资产跟踪时每次拉取的数据都要存档方便做历史对比。第四关注资产的“变化”而非“快照”。单次测绘只是一个时间点的快照真正有价值的是资产的变化趋势。比如某个端口突然开放、某个组件版本突然更新这些变化往往意味着运维动作或安全事件值得重点关注。这套流程我从最早的界面手动查询到后来的API自动化再到现在的指纹库加流水线是一步步迭代出来的。每一步都踩过坑也都沉淀成了可复用的经验。你如果刚开始接触建议从界面查询和基础语法入手把字段和逻辑摸熟再逐步上API和自动化。别一上来就追求大而全先把一条链路跑通比什么都重要。