第二届“Parloo”CTF应急响应挑战赛实战复盘:从Webshell追踪到内网渗透
1. 初识Webshell:攻击者的敲门砖
去年参加第二届Parloo杯CTF时,我遇到了一道典型的应急响应题目。攻击者通过上传Webshell成功入侵了服务器,这让我想起现实中80%的网站入侵都始于Webshell。Webshell本质上是一个藏在Web目录下的脚本文件,比如PHP、JSP或者ASPX,攻击者通过它就能在浏览器里执行系统命令。
当时题目给的Nginx日志里藏着关键线索。我花了半小时在/var/log/nginx目录下翻找,终于发现攻击者上传的a.php文件。这里有个实用技巧:用grep -r "eval(" /var/log/nginx快速搜索危险函数。日志显示攻击IP是192.168.1.100,但老手都会用代理,所以真正的突破口在文件上传时间戳——2025-05-05 00:04:40,这正是暴力破解开始的时间点。
2. 日志分析的三个黄金法则
分析Web日志时我总结了三步法:
- 异常状态码筛选:
cat access.log | grep -E "404|500"找异常请求 - 高频IP定位:
awk '{print $1}' access.log | sort | uniq -c | sort -nr - 时间轴关联:用
sed -n '/05\/May\/2025:00:00/,/05\/May\/2025:01:00/p' access.log锁定攻击时段
在比赛中发现攻击者还尝试了PHPMyAdmin爆破。通过docker日志找到关键记录:
docker logs web_app | grep -i "failed login"这揭示了第二个攻击IP:192.168.1.101。有趣的是,这两个IP其实属于同一团伙,一个负责扫描,一个负责渗透。
3. 恶意软件分析的实战技巧
PC01上的简历.exe是个经典钓鱼样本。用微步云沙箱分析时,发现它外联到88.173.90.103:8084。本地分析更直接:
netstat -ano | findstr "8084" strings resume.exe | findstr "http"样本的MD5是32位大写哈希值,这是取证的关键标识。更狠的是,攻击者在PC02创建了隐藏账户,用DiskGenius导出SAM数据库后,用猕猴桃(mimikatz)轻松破解:
impacket-secretsdump -sam SAM -system SYSTEM local4. 内网横向移动的常见套路
进入内网后,攻击者主要通过三种方式扩散:
- SSH爆破:在ssh_server发现parloo/parloo弱口令
- 恶意服务:
systemctl list-units --type=service查获rootset后门服务 - 计划任务:在PC02的Task Scheduler里发现恶意脚本
最绝的是攻击者修改了/usr/bin/id程序,这是典型的命令劫持。用以下命令快速检测:
ls -alh /usr/bin /bin | grep -v "root"5. 权限维持的六种隐蔽手段
比赛中发现的权限维持技术堪称教科书级别:
- 隐藏账户:Hack用户加上$符号
- 启动项:/etc/rc.local注入恶意命令
- 服务后门:parloohack_script.service
- SSH公钥:.ssh/authorized_keys被篡改
- CronJob:/etc/crontab添加反向shell
- 动态库劫持:LD_PRELOAD注入
取证时用lsof -i :9999发现了C2连接,IP是10.12.12.13:9999,用的竟然是Shiro反序列化漏洞。
6. 溯源分析的三个关键突破点
- 邮箱追踪:从钓鱼样本逆向得到n0k4u@outlook.com
- GitHub关联:通过邮箱反查发现ParlooSEc账号
- 浏览器历史:在PC02的Chrome里找到C2管理后台密码
有个骚操作是攻击者在Gitea的commit里藏了flag,用这个命令找到线索:
git log -p | grep -i "flag"7. 数据解密实战:自定义算法破解
遇到桌面上加密的txt文件时,先用已知明文"palu{"反推密钥。Python解密脚本如下:
def custom_decrypt(ciphertext, key): decrypted = [] key_bytes = [ord(c) for c in key] for i in range(0, len(ciphertext), 2): hex_byte = ciphertext[i:i+2] substituted = int(hex_byte, 16) xored = ((substituted & 0x0F) << 4) | ((substituted & 0xF0) >> 4) xor_key = key_bytes[(i//2) % len(key_bytes)] shifted = xored ^ xor_key original_char_code = shifted - ((i//2) % 5 + 1) decrypted.append(chr(original_char_code)) return ''.join(decrypted) print(custom_decrypt("c3a1c3c13e326020c3919093e1260525045e", "MySecretKey"))最终解密出flag:palu{Password-000}
8. 防御建议:从比赛中学到的实战经验
- Webshell防护:在nginx配置添加以下规则:
location ~* \.(php|jsp)$ { deny all; }- 日志监控:用Fail2Ban自动封禁爆破IP
- 权限控制:定期检查sudoers文件
- 文件完整性校验:
aide --check监控关键目录 - 网络隔离:重要服务限制内网访问
那次比赛最后在PC03的内网通聊天记录里找到终极flag。攻击者伪装成同事发送钓鱼文件,这提醒我们:技术防护再强,人也可能成为突破口。每次CTF都是一次攻防演练,而这些实战经验,正是防御真实攻击的最佳教材。
