简介:这份 PDF 面向准备红队 / Java Web 安全面试的安全从业者与进阶学习者,以问答形式梳理常见漏洞与攻击链知识点,可用于面试前快速复盘、查漏补缺和搭建知识框架。压缩包内仅 1 个 PDF 文件,大小约 184KB,轻量便携,适合在电脑或移动端随时翻阅。内容围绕 Java 反序列化、Shiro、Fastjson、Weblogic 等漏洞展开,并延伸到 IIOP、T3、SSRF、权限绕过、RMI/JRMP 通信机制,以及 DNS 出网、横向渗透、命令执行、密码抓取和域内攻击方法等实战话题。已有 268 人学习,问答式结构便于按主题定位答案,也能帮助读者理解漏洞检测、利用思路与面试表达之间的对应关系。
1. 从红队面试题的「第一步」说起:授权边界比漏洞清单更先被追问
一份流传很广的红队面试题整理,能被背下来的往往是技术点:Kerberos 票据怎么用、域内委派怎么查、权限维持有哪些手法。但我带人复盘过很多次模拟项目,发现真正把人问住的地方不在这些。面试官听完你的攻击链,通常会插一句:“你拿到授权之后,第一步做了什么?”这一问筛掉的是只练过工具、没走过完整流程的人。红队面试考的是一条可复现、可留痕、可解释的攻击链,以及你在链上每一环对边界的判断。适合两类人看:正在准备红队岗面试的从业者,和想把团队内部演练做得更规范的安全工程师。下面按“靶场怎么搭、链路怎么跑、坑在哪”讲清楚。
2. 把红队面试题拆成四层:从侦察到清场的判定点
2.1 面试官先问边界,再问技术的原因
红队工作本质上是一次有范围、有时限、有委托方的授权测试。范围之外的一切操作,无论技术多漂亮,都是负分。面试官先问边界,是因为这一问能同时验证三件事:你有没有读过授权文档、你知不知道自己的操作会产生什么副作用、你会不会在出事时说得清。
举个很常见的场景:某开发者在自建靶场里把一条链路跑通了,从外网 Web 一路到内网数据库。面试时讲得很流畅,被问到“你扫内网的时候,怎么保证没扫到办公段”时卡住了。他确实没扫,但他讲不出为什么没扫——靶场里恰好没有办公段而已。这就是“碰巧正确”,不是“工程上正确”。
我一般会建议准备面试的人在脑子里固化一个顺序:范围确认 → 资产测绘 → 验证性测试 → 横向 → 目标达成 → 清场 → 报告。这个顺序不是流程表演,每一环都有对应的产出物,面试时你说的每一句结论都应该能指回一个文件或一条记录。
工具选型在这个阶段反而不重要。同一个端口扫描,用哪款工具面试官不会追问,但“你为什么只扫这几个端口”“你为什么用全连接而不是半连接”“你扫的时候并发开到多少”这类问题,答不上来就暴露了你只是照抄命令。
2.2 四个分层:侦察、初始访问、横向、维持与清场
把一份红队面试题按技术点归类,会得到几十个零散条目;按攻击链阶段归类,就只有四层。这个归类方式的好处是,面试官问任何一道题,你都能定位到它属于哪一层,然后顺着这一层的前置条件和后续影响作答。
第一层是侦察与资产测绘。目标是搞清楚“有什么”,产出是一张带端口、服务、版本、归属段的资产表。这一层的判定点是覆盖率和准确性,不是发现了多少“高危”。
第二层是初始访问。目标是在授权范围内拿到第一个执行权限,产出是一个可复现的最小验证步骤。判定点是“这个漏洞是否真的存在”,而不是“这个工具跑出了结果”。
第三层是横向移动。目标是在拿到第一个立足点后,验证内网隔离是否真的有效。判定点是路径最短、副作用最小,而不是跳板机数量。
第四层是权限维持与清场。目标是验证检测能力,以及测试结束后环境能否回到初始状态。判定点是清场清单是否执行完毕。
这四层里,最容易被忽略的是第四层。大部分人把“打穿”当成终点,实际上在真实项目里,清场和报告的分量不比攻击链轻。面试时主动讲清场,反而是加分项。
2.3 每一层的典型追问与判断标准
下面这张表是我整理面试复盘时常用的对照表,左边是面试官可能问的方向,右边是我认为能过关的回答结构。注意这里列的是“回答结构”,不是标准答案,具体技术细节要按你自己的靶场环境填。
| 攻击链层级 | 常见追问方向 | 合格回答应包含的要素 |
|---|---|---|
| 侦察 | 你怎么确定扫全了 | 扫描范围来源、端口策略、误报剔除方法、资产表字段 |
| 侦察 | 扫到非授权段怎么办 | 前置白名单校验、发现越界立即停止、记录并上报 |
| 初始访问 | 你怎么确认漏洞真实存在 | 最小验证步骤、响应特征、与误报的区分依据 |
| 初始访问 | 验证过程会不会影响业务 | 只读型验证、请求频率控制、避开高峰时段 |
| 横向 | 为什么选这条路径 | 可达性验证、最小权限原则、副作用评估 |
| 横向 | 内网隔离失效你怎么证明 | 连通性证据、时间戳、源目地址记录 |
| 维持与清场 | 你留下了什么 | 账号、计划任务、文件、配置变更的完整清单 |
| 维持与清场 | 怎么证明清干净了 | 清场后复扫结果、差异比对、委托方确认 |
这张表的价值在于,它把“我会什么工具”转换成了“我在每一层的判断依据是什么”。面试时被问到任何一层,你都能从表里拉出一行来展开。
2.4 用一张答题卡把四层串成 90 秒叙述
面试时间有限,面试官不会给你半小时讲完整条链。我习惯让准备面试的人写一张“90 秒答题卡”:四句话,每句对应一层,每句都必须带一个可验证的细节。
比如这样组织:范围来自某份授权文档,划定了三个网段;侦察阶段产出了一张 42 条记录的资产表,其中 3 条是误报已剔除;初始访问用的是一个只读型验证,响应特征和误报有明显差异;横向只走了一条最短路径,全程有操作记录;清场后复扫结果与初始基线一致。
这四句话里没有任何工具名,但每一句都能被追问,而且追问下去你都有东西可答。这就是“可复现”的叙述方式。反过来,如果你的答题卡里全是工具名和参数,面试官随便挑一个问“为什么用这个参数”,你就得现编。
答题卡不需要写得漂亮,写在便签上,面试前看两遍就行。真正要花时间的是把这张卡背后的东西做出来——也就是后面的靶场、扫描、留痕三件事。
3. 搭一套可复现的授权靶场:三段网络与容器编排
3.1 三段网络怎么切,为什么这么切
靶场拓扑不用复杂,但网络必须分段,否则你练不出隔离判断的能力。我一般切三段:工作台段、对外服务段、内网段。
工作台段放攻击者工作机,只连这一段,模拟真实项目里你从外部进入的位置。对外服务段放模拟业务系统,同时连工作台段和内网段,它是唯一的内外桥梁。内网段放数据服务和内部应用,只连内网段,并且设置为不出网。
这个结构的关键在第三段。真实内网里,大部分主机是不能直接访问外网的,如果你的靶场内网段可以随便出网,你会养成一个坏习惯:遇到不通就想着从外部绕。而在真实项目里,这条路往往不存在,你得老老实实做横向。
分段的另一个好处是让“越界”这件事变得可检测。当你只在工作台段操作时,任何指向内网段以外的流量都是异常的,你自己就能发现。面试时被问到“你怎么保证没越界”,你可以直接说:靶场里我把非授权段做成了不可达,越界会立刻失败,而不是悄悄成功。
具体到实现,我用容器的 bridge 网络加子网划分,内网段加一个不出网开关。这样不需要虚拟化平台,一台开发机就能跑起来,重启也快。
3.2 用容器编排起一套最小靶场
下面这份编排文件是我常用的骨架,三个服务、两个网络。实际用的时候按需加服务,但网络结构不要动。
# lab/docker-compose.yml # 两段网络:edge 为工作台入口段,internal 为不出网的内网段 networks: edge: driver: bridge ipam: config: - subnet: 172.28.10.0/24 internal: driver: bridge internal: true # 关键开关:该网段无法访问外部网络 ipam: config: - subnet: 172.28.20.0/24 services: workbench: # 攻击者工作台,只接 edge image: python:3.11-slim container_name: lab-workbench command: ["sleep", "infinity"] networks: edge: ipv4_address: 172.28.10.10 volumes: - ./work:/work # 扫描结果、留痕文件都落在这里 dmz-web: # 模拟对外业务系统,同时接两段,是唯一桥梁 image: nginx:1.25-alpine container_name: lab-dmz-web networks: edge: ipv4_address: 172.28.10.20 internal: ipv4_address: 172.28.20.20 volumes: - ./dmz/nginx.conf:/etc/nginx/nginx.conf:ro intranet-db: # 内网数据服务,只在内网段,不出网 image: postgres:16-alpine container_name: lab-intranet-db environment: POSTGRES_PASSWORD: lab_only_password # 仅靶场使用,勿复用 networks: internal: ipv4_address: 172.28.20.30逻辑说明:internal: true是这个文件里最重要的一行,它让内网段无法访问宿主机以外的网络。dmz-web同时挂在两个网络上,扮演内外之间的跳板,这也是真实环境里最常见的形态——一台对外提供服务的机器,往往同时持有内网地址。
参数说明:subnet决定网段,建议选 172.28.x.x 这种不和公司办公网冲突的段,避免启动后路由打架。ipv4_address是固定地址,方便你在扫描和留痕文件里写死目标,不用每次查 IP。volumes把宿主机的./work挂进工作台,扫描结果和记录文件直接落在本地,容器删了也不丢。
启动和验证:
# 启动靶场,-d 后台运行 docker compose -f lab/docker-compose.yml up -d # 查看三个容器是否都起来了 docker compose -f lab/docker-compose.yml ps # 从工作台测一下到对外服务的连通性,应当通 docker exec lab-workbench python -c "import socket;s=socket.create_connection(('172.28.10.20',80),3);print('edge ok');s.close()" # 从工作台测一下到内网数据服务,应当不通(不在同一网段) docker exec lab-workbench python -c "import socket;socket.create_connection(('172.28.20.30',5432),3)"最后一个命令预期是超时或连接被拒,这就是隔离生效的证据。我第一次搭的时候这条命令直接通了,查了半天才发现是内网段没加internal: true,容器之间通过宿主机的路由绕过去了。这个细节面试时可以作为“你怎么验证隔离有效”的答案。
3.3 用扫描结果生成一张能被追问的资产表
扫描本身不是难点,难点是扫描结果怎么变成一张能拿给面试官看的表。我一般用端口扫描工具导出 XML,再用脚本转成 CSV。这样做的原因是 XML 结构化,字段稳定,而逐行正则匹配文本输出很容易被版本号里的特殊字符搞崩。
#!/usr/bin/env python3 # lab/work/collect.py # 读取扫描导出的 XML,输出资产清单 CSV import csv import sys import xml.etree.ElementTree as ET def parse(xml_path, out_path): tree = ET.parse(xml_path) root = tree.getroot() rows = [] for host in root.findall("host"): # 只保留状态为 up 的主机,down 的记录不进表 state = host.find("status") if state is None or state.get("state") != "up": continue addr = "" for a in host.findall("address"): if a.get("addrtype") == "ipv4": addr = a.get("addr") for port in host.findall("./ports/port"): # 端口状态过滤:只保留 open,closed 和 filtered 不入表 st = port.find("state") if st is None or st.get("state") != "open": continue svc = port.find("service") rows.append({ "ip": addr, "port": port.get("portid"), "proto": port.get("protocol"), "service": svc.get("name", "") if svc is not None else "", "product": svc.get("product", "") if svc is not None else "", "version": svc.get("version", "") if svc is not None else "", }) with open(out_path, "w", newline="", encoding="utf-8") as f: w = csv.DictWriter(f, fieldnames=["ip", "port", "proto", "service", "product", "version"]) w.writeheader() w.writerows(rows) print(f"写入 {len(rows)} 条记录 -> {out_path}") if __name__ == "__main__": parse(sys.argv[1], sys.argv[2])逻辑说明:脚本只处理state="up"的主机和state="open"的端口,这两个过滤条件决定了资产表的准确率。很多人把 filtered 也写进去,结果表里一堆“看着有其实不可达”的条目,横向阶段白跑一趟。
参数说明:第一个参数是扫描导出的 XML 路径,第二个是输出 CSV 路径。字段里我特意保留了 product 和 version,因为面试追问经常是“你怎么判断这个服务是什么版本”,版本信息就来自这里。注意版本识别本身会有一到两个版本的误差,面试时主动说清这个误差,比被问出来强。
3.4 进场前的三次连通性验证
在开始任何测试动作之前,我会做三次验证,每次只验证一件事:工作台到对外服务是否可达、对外服务到内网服务是否可达、工作台到内网服务是否不可达。三次结果记进留痕文件,作为“隔离有效”的基线证据。
这个习惯来自一次血泪经验:靶场重搭之后,内网段的隔离配置没生效,我在工作台直接就把内网数据服务扫了。流程上看起来没问题,但如果换成真实项目,这就是明确的越界。从那以后我把三次验证写进了固定步骤,不验证不开始。
验证命令可以是一条循环,把三个目标依次打一遍,输出结果带时间戳追加到文件里。这类小脚本不复杂,但能省掉后面很多解释成本。
4. 侦察与初始访问的落地:把过程写成可验证的步骤
4.1 资产测绘的输出物应该长什么样
资产表不是越全越好,而是要能被追问。我一般要求表里有五个必填字段:IP、端口、协议、服务名、归属段。归属段这一列很多人不写,但它恰恰是面试时最容易被追问的一列——“这台机器属于哪个段,你怎么知道的”。
归属段的信息来源是你的范围文档,不是扫描结果。扫描器只能告诉你“这个 IP 有服务”,不能告诉你“这个 IP 在授权范围内”。这两件事必须分开,混在一起就是越界的开始。
表里还可以加一列“验证状态”,取值就三种:已确认、待复核、已排除。已排除的条目要在备注里写清排除理由,比如端口开放但服务返回统一错误页,判定为无效资产。这一列的作用是在面试时证明你不是“扫到就算数”。
4.2 扫描参数怎么设才不会把靶场打崩
扫描参数的选择取决于目标数量和网络质量,但有三条经验值:端口范围按需、并发别开满、超时别设太短。
全端口扫描在靶场里可以跑,但在真实项目里要先确认业务时段。我一般先用常用端口扫一轮,拿到初步资产表,再针对可疑主机做全端口。这样做的原因是全端口扫描的流量特征明显,容易被目标侧的防护设备记录。
下面这条命令是我在靶场里常用的写法,可以对照参数看:
# 从工作台容器内执行,扫对外服务段 # -sT 全连接扫描:容器环境下半连接容易被网络层丢弃 # -Pn 跳过主机存活探测:靶场主机不响应 ICMP # -p- 全端口:仅在靶场用小范围目标时使用 # --min-rate 限速:避免把容器网络打满导致误判 # -oA 同时导出三种格式,XML 供脚本解析 docker exec lab-workbench \ nmap -sT -Pn -p- --min-rate 500 -oA /work/scan_dmz 172.28.10.20参数说明:-sT在容器网络里比半连接稳定,因为容器网络栈对原始套接字的处理有差异,半连接扫描经常出现大量误报的 filtered。--min-rate 500是每秒发包下限,设太高会让目标侧响应不过来,出现假阴性。-oA会同时生成 .nmap、.xml、.gnmap 三个文件,脚本只读 .xml。
扫描结束后先看汇总行,重点看 open 的数量和 filtered 的数量。如果 filtered 占比超过一半,多半是限速或网络策略的问题,不是目标真的关着门。
4.3 从“打了”到“能证明我打了”的操作留痕
留痕这件事,平时觉得麻烦,出事时是唯一的后悔药。我要求每个操作都落一条记录,记录里至少有四个字段:时间戳、目标、动作、结果摘要。
下面这个脚本把命令执行和记录写在一起,避免“做了但忘了记”:
#!/usr/bin/env python3 # lab/work/trace.py # 执行命令并记录到带哈希链的 jsonl,防止事后被篡改 import hashlib import json import subprocess import sys import time from pathlib import Path TRACE = Path("/work/trace.jsonl") def last_hash(): if not TRACE.exists(): return "0" * 64 lines = TRACE.read_text(encoding="utf-8").strip().splitlines() if not lines: return "0" * 64 return json.loads(lines[-1])["hash"] def run(cmd, target, note=""): start = time.strftime("%Y-%m-%dT%H:%M:%S") proc = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=300) # 结果摘要只留前 200 字符,完整输出另存文件 digest = (proc.stdout or proc.stderr or "").strip()[:200] record = { "ts": start, "target": target, "action": cmd, "note": note, "rc": proc.returncode, "digest": digest, "prev": last_hash(), } # 链式哈希:每条记录包含上一条的哈希,改动任意一条都会断链 record["hash"] = hashlib.sha256( json.dumps(record, sort_keys=True, ensure_ascii=False).encode() ).hexdigest() with TRACE.open("a", encoding="utf-8") as f: f.write(json.dumps(record, ensure_ascii=False) + "\n") return proc.returncode if __name__ == "__main__": # 用法:trace.py <目标> <命令> <备注> sys.exit(run(sys.argv[2], sys.argv[1], sys.argv[3] if len(sys.argv) > 3 else ""))逻辑说明:prev字段保存上一条记录的哈希,hash是本条记录内容的哈希。这样整份记录形成一条链,只要中间有一条被改,后面全部对不上。面试时讲这个设计,比讲“我有记录”有说服力得多。
参数说明:digest只留 200 字符是有意为之,完整输出放在单独文件里,记录文件保持轻量,方便快速检索。timeout=300是单条命令的上限,超过就中断,避免一条卡死的扫描拖住整个流程。实际用的时候把TRACE路径改成你自己的挂载目录。
留痕不是为了应付检查,是为了回答“你怎么知道这一步成功了”。没有记录,攻击链就是一段无法复现的叙述。
5. 五个真实翻车点:从现象到解决
5.1 靶场里打得通,被追问日志时答不上来
现象:靶场链路完整复现,面试讲得很顺,被问到“这一步在目标侧会产生什么日志”时卡住。
原因:只关注攻击侧的成功与否,没看过目标侧视角。容器靶场里默认没开日志采集,你根本不知道自己的操作留下了什么。
解决:在靶场里加一台日志节点,或者至少让对外服务把访问日志落到挂载目录。每做一步,回头看一眼日志里长什么样。这个动作做三次,你对“动作—日志”的对应关系就有直觉了。
5.2 内网段没做隔离,扫描直接越界
现象:从工作台直接连通内网数据服务,流程上没报错,事后才发现越界。
原因:网络编排里忘了加内网标记,容器之间通过宿主机路由互通。这个问题在小规模靶场里特别常见,因为容器网络默认是通的。
解决:编排文件里内网段必须加不出网开关,启动后立刻做三次连通性验证,把“不可达”这条结果也记进留痕文件。不可达是证据,不是失败。
5.3 全端口扫描把容器网络打满,结果全是误报
现象:扫描结果显示大量 filtered,换一台目标又是一堆 open,结果不可信。
原因:并发和速率设得太激进,容器网络栈处理不过来,丢包被识别成 filtered。另外容器环境的半连接扫描本身就不稳定。
解决:降速率、改用全连接扫描、先扫常用端口再决定要不要全端口。扫完看 filtered 占比,超过一半就重扫,别拿不可信的结果往下走。
5.4 权限维持手段加上去,清场清单却没写
现象:靶场跑通了,环境里留了一堆东西,下次重搭时状态对不上,之前的结果无法复现。
原因:进场前没写清场清单,只想着怎么进去,没想怎么出来。真实项目里这会导致环境残留,是明确的事故。
解决:进场前先写一张清场清单,列明会新增的账号、文件、配置项、计划任务,每加一项就勾一项。测试结束后按清单逐条清理,清完复扫一遍,和初始基线比对。
5.5 工具参数背得熟,讲不出为什么这么选
现象:面试时把工具参数报得很溜,被问“为什么用这个而不用那个”就答不上来。
原因:学习路径是从工具出发的,先学命令再找场景。正确的路径应该反过来,先有场景和约束,再选工具。
解决:每个动作都用“目标—约束—选择”三段式重新组织一遍。目标是拿到什么信息,约束是范围、时间、业务影响,选择才是工具和参数。三段式讲顺了,工具名反而可以不提。
6. 用检测视角反向准备:把攻击链翻译成一份复盘报告
准备到最后阶段,我会换一个视角:假设我是防守方,拿到自己这条攻击链的记录,能不能还原出全部动作。这个练习比再刷十道题有用。
做法很直接,把留痕文件按时间排开,逐条问三个问题:这条动作在哪个日志源里能看到、能看到什么字段、如果要做告警该用什么条件。下面这张表是我常用的对照,左边是动作类型,中间是日志来源,右边是可见特征。
| 动作类型 | 主要日志来源 | 可见特征 |
|---|---|---|
| 端口扫描 | 主机防火墙日志、服务访问日志 | 短时间大量连接请求、来源单一 |
| 服务识别 | 应用访问日志 | 异常路径请求、统一错误响应集中出现 |
| 认证尝试 | 认证服务日志 | 同一来源多次失败、时间间隔规律 |
| 横向连接 | 内网主机安全日志 | 非典型源目组合、非工作时段连接 |
| 配置变更 | 系统审计日志 | 计划任务、账号、服务配置的新增记录 |
有了这张表,面试时讲攻击链就可以多一层:我不是打通了就完事,我知道每一步在防守侧长什么样。这一层能明显拉开差距,因为它说明你理解的是对抗,不是单方面的工具使用。
我还会做一件小事:把整条链写成一份三页以内的复盘报告,结构是背景与范围、链路时间线、每步的证据引用、检测可见性、修复建议。报告里不放工具截图,只放时间戳和记录编号。这个习惯来自一次教训——早期我交的报告全是截图,委托方问“这一步和上一步的先后顺序怎么证明”,我翻遍截图也说不清。后来改成时间线加记录编号,同一个问题一句话就能答。
如果你正准备红队岗面试,我的建议是先把靶场和留痕跑通,再回头背题。题目会过时,可复现的流程不会。希望帮到你。
本文还有配套的精品资源,点击获取