从[HITCON 2017]SSRFme看Perl GET命令注入的攻防博弈

1. SSRFme赛题背后的Perl安全陷阱

2017年HITCON CTF的SSRFme赛题,堪称Web安全领域的经典教学案例。这道题巧妙地将SSRF(服务端请求伪造)和命令注入漏洞结合,暴露出Perl语言中GET命令与open函数配合时的致命缺陷。我在复现这个漏洞时发现,攻击者仅需构造特殊URL就能突破escapeshellarg的安全防护,最终实现从文件读取到远程代码执行(RCE)的完整攻击链。

这道赛题的初始界面极其简单——显示用户IP地址的PHP页面。但深入分析源码会发现,关键漏洞藏在shell_exec("GET " . escapeshellarg($_GET["url"]))这行代码中。表面看这里使用了escapeshellarg进行安全过滤,但Perl的GET命令底层调用了open函数,而open在处理特殊字符时会开启"魔法大门"。我在本地测试时发现,当传入url=file:bash -c /readflag|这样的参数时,系统会直接执行/readflag命令,这正是漏洞的可怕之处。

2. Perl GET命令的"魔法"解析

2.1 open函数的双刃剑特性

Perl的open函数有个鲜为人知的特性:当文件名以|结尾时,会将后续内容作为命令执行。这个设计本意是方便开发者直接处理命令输出,比如open(FH, "ls |")就能读取ls命令的结果。但在SSRFme场景中,攻击者通过控制url参数,将GET命令转化为命令执行通道。

我曾在实际项目中遇到过类似案例:某CMS系统使用Perl处理文件上传,开发者认为用escapeshellarg过滤文件名就万无一失,却忽略了open的特殊处理逻辑。攻击者上传名为test.pl | rm -rf / |的文件后,直接导致服务器被清空。

2.2 绕过escapeshellarg的三种姿势

escapeshellarg本应是命令注入的终极防线,但在Perl的GET命令场景下却形同虚设。经过多次测试,我总结了三种突破方式:

  1. 管道符绕过url=file:bash -c /readflag|

    • 管道符使open将后续内容识别为命令
    • escapeshellarg生成的单引号会被Perl忽略
  2. 多协议混用url=file:data://text/plain,<?php system($_GET[1]);?>

    • 结合file协议和data协议注入恶意代码
    • 需要服务器支持多协议处理
  3. 编码混淆url=file:%62%61%73%68%20%2D%63%20%2F%72%65%61%64%66%6C%61%67%7C

    • URL编码绕过基础关键词检测
    • 解码后仍会被Perl识别为命令

3. 从SSRF到RCE的完整攻击链

3.1 利用file协议突破边界

SSRFme赛题首先需要解决的是如何读取服务器文件。通过测试发现,当传入url=/etc/passwd&filename=test时,系统会成功创建包含passwd文件内容的test文件。这说明GET命令支持file协议,这为后续攻击提供了跳板。

我在某次渗透测试中曾用类似手法:目标系统禁止直接访问/proc/self/environ,但通过SSRF+file协议组合成功获取了环境变量中的AWS密钥。

3.2 命令拼接的艺术

实现RCE的关键在于理解Perl的命令拼接逻辑。观察这个payload:

?url=file:bash -c /readflag|&filename=a

其执行流程为:

  1. PHP调用shell_exec执行GET 'file:bash -c /readflag|'
  2. Perl的GET命令调用open处理参数
  3. open看到|字符,将bash -c /readflag作为命令执行
  4. 命令输出被写入文件a

3.3 沙箱逃逸的实战技巧

赛题设置了基于IP的沙箱目录,但通过两个技巧可以突破限制:

  1. 伪造X-Forwarded-For头改变REMOTE_ADDR
    X-Forwarded-For: 1.1.1.1
  2. 利用pathinfo特性创建多级目录
    ?url=/&filename=../evil.php

这提醒我们:沙箱隔离必须考虑所有可能的路径穿越方式。

4. 防御方案的演进与对抗

4.1 输入过滤的局限性

传统的防御方案往往依赖黑名单过滤特殊字符,但这种方法存在明显缺陷:

  • 过滤|字符?攻击者改用;&&
  • 禁用file协议?还有phar、data等协议可用
  • 检查URL格式?编码混淆轻松绕过

我在代码审计时发现,更有效的做法是白名单校验:

$allowed_schemes = ['http', 'https']; if (!in_array(parse_url($url, PHP_URL_SCHEME), $allowed_schemes)) { die('Invalid scheme'); }

4.2 安全编码的最佳实践

根据OWASP建议,处理外部输入时应遵循:

  1. 使用专门的URL解析库替代open
  2. 设置use safe模式限制Perl的危险操作
    use Safe; my $compartment = new Safe; $compartment->permit_only(':base_io');
  3. 实施最小权限原则,限制Web服务器用户权限

4.3 现代环境下的防护策略

在容器化环境中,我们可以采取更多防御措施:

  • 使用seccomp限制危险系统调用
  • 配置AppArmor/SeLinux策略
  • 定期更新Perl运行时补丁

某次真实攻击事件中,攻击者正是利用老旧Perl版本的open漏洞获取了服务器权限。这提醒我们:看似无害的语言特性,在特定条件下可能成为致命弱点。