第二届帕鲁杯的应急响应主线赛题,是知攻善防实验室出的。这个系列赛题在圈里口碑一直不错,原因很简单:它不搞花架子,模拟的就是一个真实被入侵的业务系统,选手要做的就是把攻防双方遗留的痕迹一点点拼起来。我今年完整跟完了这条主线,从信息收集到权限维持清理,整套流程走下来,最大的感受是,应急响应考题已经不再是"背命令"的考试,而是一场关于攻击者心理和系统细节的对抗。这篇文章就把我这次的完整思路、实操命令和踩过的坑整理出来,给后面要参加类似比赛的兄弟一个参考。
先说结论:这条主线题的核心不在"你能不能找到一个Webshell",而在"你能不能把攻击者的完整行动路径还原出来"。后面的章节我会从题目设计逻辑、实操排查主线、日志溯源、处置加固和备赛建议五个角度展开,全程用我实际操作的视角讲,能直接抄作业的地方我会把命令也放出来。
1. 赛题背后:应急响应到底考什么
1.1 从题目设计反推攻击链
知攻善防实验室这套题,题目设计往往是先有一条完整的攻击链,然后在攻击链上埋点。出题人站在攻击者角度想:如果我想长时间控制一台业务服务器,我会在哪里放一个不容易被发现的后门?我会怎么清理日志?我会把恶意进程伪装成什么?选手要做的,就是把这些埋点找出来,然后还原攻击者的路径。
这个思路非常贴近现实应急响应的工作状态。真实场景里你面对的从来不是一个标注好路径的"木马文件",而是一条安全设备告警、一段异常流量、一个可疑文件名,甚至只是一个业务卡顿的反馈。所有信息都是碎片,你要在碎片里拼出全貌。
这次赛题里还存在"明线"和"暗线"两层结构。明线是攻击者留下的Webshell文件、异常进程、反弹Shell连接这类容易被发现的痕迹;暗线则是权限维持脚本、内存马、日志清理行为,以及藏在正常业务代码里的恶意逻辑。如果只看明线,很容易出现"找到了三个后门就收工"的误判,实际上暗线里还有一个每五分钟执行一次的守护脚本,删了Webshell它会再落一个出来。这种设计意图很明显:考察选手是否具备完整的应急响应思维,而不是单一技能点。
1.2 考题的核心能力模型
结合这次赛题,我把应急响应需要的能力拆成五个维度:
| 能力项 | 考查方式 | 平时怎么练 |
|---|---|---|
| 日志分析 | 给原始access.log和auth.log,要求还原攻击IP、攻击时间 | 自己搭Web环境,模拟SQL注入和Webshell访问,再对着日志看特征 |
| 样本研判 | 给可疑文件,判断Webshell类型、连接密码、加密方式 | 收集公开的Webshell样本,分类分析常见混淆手法 |
| 系统排查 | 找异常进程、启动项、计划任务、SSH密钥 | 在虚拟机里手工植入后门,练习用系统命令排查 |
| 时间线重构 | 结合文件时间、日志时间、进程启动时间对齐攻击路径 | 把一次完整攻击的各个时间点整理成表格,练到条件反射 |
| 处置加固 | 证据保全前提下清除后门并修复漏洞 | 熟悉常见漏洞的修复配置,练习最小权限原则 |
这几个能力的优先级有讲究。对新手来说,日志分析是救命稻草,因为它最客观,文件可以被篡改,日志也可以被清理,但多个日志源交叉验证后很难完全抹掉痕迹。对熟练的人来说,系统排查才是效率关键,因为日志量太大时,通过进程和文件系统异常能快速锁定重点区域。知道自己的强项和弱项在哪,才能合理分配比赛时间。
2. 从告警到定位:完整的排查主线
2.1 拿到环境后的第一件事
很多人在拿到环境后第一件事是急着用find扫描整个磁盘找Webshell,这个习惯在真实场景里非常危险。你每跑一条命令、每打开一个文件,都可能改变系统状态——访问一个文件会更新它的atime,运行一个扫描器会创建临时文件,这些都会破坏现场。应急响应的第一原则是:先保全,后分析。
我拿到环境后的操作顺序是这样的:
- 先给系统做快照(如果是虚拟机),确保随时可以回滚到初始状态。
- 然后执行一组信息收集命令,把当前状态固化下来:当前时间、系统版本、登录用户、网络连接、进程列表、计划任务、最近登录记录。
- 所有输出都重定向到外部存储,或者用scp传到自己的机器上,绝不在被入侵系统里写文件。
这组基线信息非常重要。比如你发现系统时间是北京时间,但日志时间戳是UTC,后面分析时就要自动加上时区偏移,否则时间线会对不齐。又比如你先看了进程列表再去看日志,才能知道哪个进程启动时间跟攻击时间重合。
这里还要提醒一个细节:日志表面干净的服务器不代表没有失陷。攻击者很可能清理过日志,所以你要有意识地去寻找"被清理过的痕迹"——比如bash_history文件被清空但文件大小不为0,或者wtmp日志存在异常截断。这些线索本身就是入侵证据。
2.2 系统层快速梳理:用户、启动项、计划任务
做完基线快照,我开始从系统层排查,这个环节的目标是回答三个问题:攻击者留了哪些账号?开机自启了什么东西?定时任务里有没有异常?
账号排查我用这几个命令:
# 查看UID为0的超级用户 awk -F: '$3==0{print $1}' /etc/passwd # 查看最近登录记录 last -F /var/log/wtmp # 查看失败登录记录 lastb -F /var/log/btmp # 查看当前登录用户 who w这次赛题里我就在passwd文件里发现了一个叫syshelper的用户,UID是1002,home目录是/home/syshelper,但这个小细节很容易被忽略。真正让我确定它有问题的,是后续在它的home目录下发现了一个可疑的authorized_keys文件,里面有一串公钥。这意味着攻击者可能通过SSH密钥免密登录,即使你改了密码,他依然能进来。
启动项和计划任务也是重点区域,我通常这么查:
# 查看系统级计划任务 cat /etc/crontab ls -la /etc/cron.d/ # 查看当前用户的cron任务 crontab -l ls -la /var/spool/cron/ # 查看开机启动项 ls -la /etc/rc.d/rc.local systemctl list-unit-files --state=enabled ls -la /etc/systemd/system/这次在/etc/cron.d/目录下发现了一个名为sysupdate的文件,内容是一个shell脚本定时任务,每五分钟执行一次/usr/lib/systemd/systemd-update.sh。乍一看名字完全正常,但仔细看脚本内容,里面居然有一段curl命令从外部地址拉取资源。这就解释了为什么Webshell删了之后还会反复出现——系统里有个自动恢复机制。所以排查时一定要看计划任务的内容,不能只看文件名。
2.3 网络连接与进程排查:从可疑端口到可疑进程
系统层排查完了,下一步就是看进程和网络。这一环节最容易翻车,因为攻击者最擅长伪装进程名。
我的做法是先看网络连接,再看进程列表,然后交叉验证。这次赛题里,我用netstat看到一个ID为17432的进程持有外联连接:
netstat -antlp | grep ESTABLISHED ss -antlp | grep ESTABLISHED让我警觉的是这个连接的目的端口是8443,一个不常见的端口,而且进程名显示为php-fpm7。业务服务器上有php-fpm是正常的,但正常php-fpm不会主动外联。于是我顺着PID去/proc目录里挖:
# 查看进程启动目录 ls -la /proc/17432/cwd # 查看进程执行文件的真实路径 ls -la /proc/17432/exe # 查看进程打开的文件 ls -la /proc/17432/fd # 查看进程命令行参数 cat /proc/17432/cmdline | tr '\0' ' '这一挖问题就暴露了:进程名虽然是php-fpm7,但exe指向的是/tmp/.X11-unix/update,一个伪装成系统目录的隐藏路径。这种手法在真实攻击里非常常见——进程名挑一个业务系统里合法的名字,路径却放在临时目录或隐藏目录里。更隐蔽的还会用进程名加空格的方式,比如"php-fpm7 "后面多个空格,让人在ps的输出里看不清真实名字。
经验之谈:进程排查永远不要只信ps显示的进程名,一定要结合/proc里exe、cwd、cmdline、fd几个维度的信息综合判断。另外进程占用CPU排行也有参考价值,一个业务高峰期不存在的进程突然占满CPU,往往就是挖矿木马或者恶意脚本在跑。
3. 日志导向的溯源与定性
3.1 Web访问日志:从流量里找攻击动作
系统层的线索帮我定位到了具体恶意文件,但要还原攻击者的完整路径,还得回到日志上。Web访问日志是最重要的数据源之一,因为Web攻击往往是整个攻击链的入口。
拿到nginx的access.log后,我第一件事是统计攻击者的IP和访问频率:
# 统计访问量TOP20的IP awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20 # 查看POST请求,Webshell一般通过POST交互 grep "POST" access.log | tail -50 # 查看包含危险参数的请求 grep -E "(eval|base64|cmd|whoami|id)" access.log | tail -50通过这三个命令,我迅速锁定了两个高访问量IP,其中一个在特定时间段内密集发送POST /upload/avatar.php请求,UA信息是随机生成的,明显是自动化工具在尝试上传。顺着这些请求往下翻,我在日志里找到了一个访问shell.php的请求,状态码200,返回包长度异常大,这是典型的Webshell连接特征。
这里要重点提醒一下:不要只盯着GET和POST方法,还得看其他HTTP方法。攻击者可能用OPTIONS探测服务器支持的请求方法,用PUT直接上传文件,用DELETE清理痕迹。如果日志里出现大量异常方法请求,往往意味着攻击者正在做前期探测或者已经成功利用漏洞。
另外日志分析有个很关键的前提——确认日志本身的完整性。如果发现access.log中间有明显的几个小时空档,没有一条请求记录,那不是业务低谷,很可能是攻击者把那段日志删了。看到这种"异常平静",应该立即标记为可疑节点。
3.2 系统日志与登录行为:找出后门来源
Web日志告诉了我入口,但权限维持方式还得看系统日志。这次赛题里,/var/log/auth.log记录了大量来自同一个IP的SSH登录尝试,时间集中在凌晨两点到四点,有明显的暴力破解特征。更关键的是,在这些失败尝试之后,出现了一条成功的SSH登录记录,登录用户正是我之前在passwd里发现的syshelper。
系统日志的常规排查命令:
# 查看全部认证日志 cat /var/log/auth.log # 查看最后100条登录记录 tail -100 /var/log/auth.log # 查看所有用户的登录历史 lastlog # 查看sudo授权记录 grep "sudo" /var/log/auth.log | tail -30这个过程也能看出攻击者的大致手法:先通过Web漏洞拿到一个低权限Shell,然后提权创建syshelper用户,再往authorized_keys里写入公钥,实现持久化SSH登录。整个手法相当克制,没有大范围破坏,也没有加密勒索行为,目的就是长期控制服务器。
如果日志被清空了,也别慌。可以检查以下几个地方:bash_history有没有被清、当前会话有没有执行过命令、/proc里有没有已经退出进程的残留信息、临时目录里有没有脚本运行日志。这些地方往往能挖出攻击者没处理干净的信息碎片。
3.3 时间线对齐:把碎片拼成故事
把所有线索汇总后,我整理了一张时间线表,这是整个应急响应报告里最核心的部分:
| 时间 | 事件 | 证据来源 |
|---|---|---|
| 02:13 | 攻击者通过Web上传接口写入listener.php | nginx access.log中的POST记录 |
| 02:15 | 访问listener.php,触发执行 | access.log中的GET请求,状态码200 |
| 02:18 | 建立反弹Shell连接 | netstat异常外联记录 |
| 02:22 | 创建syshelper用户并写入SSH公钥 | auth.log成功登录记录、passwd文件 |
| 02:30 | 写入计划任务,实现权限维持 | /etc/cron.d/sysupdate文件 |
| 03:05 | 清理access.log和auth.log中早间日志 | 日志时间断档 |
时间线对齐这个环节特别容易出错,因为不同日志源的时间格式和时区可能不一致。我的建议是先把所有时间统一换算成同一时区,再把文件mtime、进程启动时间、日志时间放在一起比对。比如我发现Webshell文件的mtime是02:13,而access.log里对应的POST请求是02:12:58,基本吻合,说明文件就是那次请求写入的。这种多重印证能避免被单一来源欺骗。
4. 处置与加固:别捡了芝麻丢西瓜
4.1 后门清除:找到"所有"后门才算完
清除后门是这个环节最考验细心程度的地方,因为一个漏网的后门会让前面所有工作白费。这次赛题我踩过的坑是:第一次清除时只删了Webshell和计划任务,但没过多久就发现恶意脚本又回来了,原因是我漏掉了/etc/systemd/system/目录下的一个服务文件,攻击者注册了一个systemd服务,服务会自动拉起来迁移脚本。这个教训非常深刻。
我的清除清单是这样的:
- 删除所有Webshell文件,包括混淆变体和隐藏在图片文件里的马。
- 清除计划任务和systemd服务,包括cron.d目录下的所有可疑文件。
- 删除异常的SSH授权公钥和可疑系统用户。
- 清理恶意进程和对应的可执行文件,注意/proc里可能还有残留。
- 检查环境变量、LD_PRELOAD、shell启动文件(.bashrc、.profile)里有没有恶意加载。
清除时要记住一个原则:先找到所有后门,再一起清除。不要在发现第一个Webshell时立刻删除,因为攻击者可能设置了监控,一旦发现文件被删就会触发备用后门。我这次的处理方式是先做一份完整清单,确保所有恶意文件、恶意用户、计划任务、服务都被标记之后,再统一处置。
4.2 漏洞修补与最小权限:别让攻击者再进来
清完后门只是止血,真正的"治疗"是修复漏洞和最小化权限。这次攻击的入口是上传功能没有限制文件类型,导致PHP文件被当作图片上传并成功执行。对应的修复方案是:
- 上传目录禁止执行脚本,nginx里配置
location ~ \.php$规则排除上传目录。 - 校验上传文件的内容,不只检查扩展名,还要检查文件头(magic bytes)。
- 将上传目录设置为只读权限,业务运行账号改为非root用户。
PHP层面的加固也很重要,可以禁用危险函数和动态执行:
disable_functions = exec,system,passthru,shell_exec,popen,proc_open open_basedir = /var/www/html/:/tmp/这里我多说一句:很多人在修复漏洞时只盯着漏洞本身,忽略了权限最小化。如果Web服务是以root权限运行的,攻击者拿到WebShell就等于拿到服务器最高权限,那就不是修一个上传校验能解决的了。所以生产环境一定要给服务单独创建低权限用户,目录权限严格按需分配。这个思路就像你给家门换了把好锁,但更要紧的是不要把备用钥匙藏在门垫下面。
4.3 验证与回归:清除后必须自测
处置完成后不能直接收工,还要做一轮完整验证。我复盘赛题时专门做了这个环节:
- 用之前记录的攻击载荷重新发送一次,确认上传接口返回403,漏洞已修复。
- 全盘扫描Web目录、临时目录、上传目录,确认没有新的Webshell文件出现。
- 观察5到10分钟,确认没有新的可疑进程和网络外联。
- 检查计划任务和服务列表,确认恶意项没有自动恢复。
- 用
last和lastb确认没有新的成功登录记录。
自测这步很关键,因为有些恶意脚本设计成"定时复活"模式,删掉之后过几分钟又会重新生成。如果清除后没有等待足够长的观察期,很容易误判为处置完成。我通常会在清除后观察一个完整的计划任务周期,比如那个恶意计划任务是5分钟执行一次,我会至少等10分钟再确认,宁可多花时间也不能留尾巴。
5. 复盘总结与备赛建议
5.1 这次赛题里最容易失分的三个地方
结合我自己和同组几个选手的复盘,整理出三个高频失分点:
| 失分点 | 表现 | 对策 |
|---|---|---|
| 日志清理后不会找替代数据源 | 发现auth.log被清空后,直接卡住 | 检查进程残留、临时目录、bash_history上下文 |
| 被伪装进程名带偏 | 看到php-fpm就默认正常 | 必须用/proc目录交叉验证exe、cwd、fd |
| 报告缺少时间线 | 列出证据但无法还原时间线 | 所有日志时间统一时区,做多重印证 |
另外一个很容易被忽略的细节是文件系统的隐蔽痕迹。攻击者惯用的手法是在/tmp目录下创建隐藏目录,比如/tmp/.X11-unix、/tmp/...(三个点),或者给文件名字前面加空格,让ls的常规输出不好辨认。排查时我习惯加上ls -la查看隐藏文件,同时用find /tmp -type f -mtime -3这样的命令按时间筛选,比全盘扫描高效得多。
5.2 平时怎么练应急响应
最后说点备赛建议。很多人问应急响应怎么练,我的答案是:不要只刷题,要搭一个自己的靶场,亲手把恶意程序"放"进去,再亲手排查出来。
我的训练方法很简单:
- 在虚拟机里装一个标准Linux环境带Web服务,拍快照。
- 故意往系统里放几种不同类型的后门:PHP一句话、计划任务守护脚本、SSH公钥、systemd服务,再启动一个异常进程。
- 过几天后当作真实事件来排查,不回忆之前做了什么,全靠日志和系统状态找线索。
- 对比自己找到的恶意项和实际植入的恶意项,查漏补缺。
这个训练方法比刷题扎实得多,因为它强迫你学会"从结果反推过程"。练多了之后,你会建立起对正常系统的敏感度——知道正常系统里哪些文件不该存在、哪些进程不该运行、哪些用户不该出现。有了这个敏感度,比赛里找异常就是分分钟的事。
工具方面,我常用的就是系统自带命令配合少量开源工具。日志分析用grep/awk就够,进程分析用ps/lsof/netstat,Webshell查杀用特征扫描加人工确认,流量分析需要时用抓包工具。真正决定应急响应水平的不是工具多高级,而是你愿不愿意把基础命令嚼透、把系统细节摸透。
说句实在话,这次打完帕鲁杯应急响应主线,我最大的收获不是背熟了多少命令,而是把"正常系统长什么样"这件事刻在了脑子里。应急响应到最后拼的就是对底噪的熟悉度——哪里的文件该在什么位置、哪些进程该占多少资源、哪些日志该是什么节奏,这些正常基线越清晰,异常出现时你的直觉就越敏锐。这个思路放在比赛里是得分点,放在工作里是救命技能。如果下一届还出应急响应主线,我大概率还会去报名,毕竟这种把攻防浓缩进一场比赛的体验,比看十篇技术文章都过瘾。